diff --git a/.babelrc b/.babelrc deleted file mode 100644 index e8facd85b..000000000 --- a/.babelrc +++ /dev/null @@ -1,17 +0,0 @@ -{ - "presets": [ - ["es2017"], - ["es2016"], - ["es2015", { - "loose": true, - "spec": false - }] - ], - "plugins": [ - ["transform-runtime", { - "helpers": true, - "polyfill": true, - "regenerator": true - }] - ] -} diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..eac6cdd34 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,59 @@ +defaults: &defaults + working_directory: ~/repo + docker: + - image: circleci/node:10.2 + +version: 2.0 +jobs: + install: + <<: *defaults + working_directory: ~/repo + steps: + - checkout + - restore_cache: + keys: + - v1-dependencies-{{ checksum "package-lock.json" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + - run: npm install + - run: npm install bslint nyc codecov + - save_cache: + paths: + - node_modules + key: v1-dependencies-{{ checksum "package-lock.json" }} + - persist_to_workspace: + root: . + paths: . + + test: + <<: *defaults + steps: + - attach_workspace: + at: . + - run: + name: Tests with coverage + command: npm run test-ci + - run: + name: Submit coverage + command: ./node_modules/.bin/codecov + + lint: + <<: *defaults + steps: + - attach_workspace: + at: . + - run: + name: Lint + command: npm run lint-ci + +workflows: + version: 2 + test_and_lint: + jobs: + - install + - lint: + requires: + - install + - test: + requires: + - install diff --git a/.eslintfiles b/.eslintfiles index d91e3c883..16c5daa7b 100644 --- a/.eslintfiles +++ b/.eslintfiles @@ -5,12 +5,9 @@ bin/spvnode bin/wallet browser/server.js browser/wsproxy.js -examples/ +browser/src/app.js lib/ migrate/ scripts/ test/ -webpack/ -webpack.browser.js -webpack.compat.js -webpack.node.js +docs/examples diff --git a/.eslintrc.json b/.eslintrc.json index dc2f6a3e7..4db51ca11 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,8 +4,53 @@ "node": true }, "extends": "eslint:recommended", + "globals": { + "Atomics": "readable", + "BigInt": "readable", + "BigInt64Array": "readable", + "BigUint64Array": "readable", + "queueMicrotask": "readable", + "SharedArrayBuffer": "readable", + "TextEncoder": "readable", + "TextDecoder": "readable" + }, + "overrides": [ + { + "files": ["*.mjs"], + "parserOptions": { + "sourceType": "module" + } + }, + { + "files": ["*.cjs"], + "parserOptions": { + "sourceType": "script" + } + }, + { + "files": [ + "test/{,**/}*.{mjs,cjs,js}" + ], + "env": { + "mocha": true + }, + "globals": { + "register": "readable" + }, + "rules": { + "max-len": "off", + "prefer-arrow-callback": "off" + } + } + ], + "parser": "babel-eslint", "parserOptions": { - "ecmaVersion": 8 + "ecmaVersion": 10, + "ecmaFeatures": { + "globalReturn": true + }, + "requireConfigFile": false, + "sourceType": "script" }, "root": true, "rules": { @@ -27,11 +72,20 @@ }], "func-name-matching": "error", "indent": ["off", 2, { + "ArrayExpression": "off", "SwitchCase": 1, "CallExpression": { "arguments": "off" }, - "ArrayExpression": "off" + "FunctionDeclaration": { + "parameters": "off" + }, + "FunctionExpression": { + "parameters": "off" + }, + "MemberExpression": "off", + "ObjectExpression": "off", + "ImportDeclaration": "off" }], "handle-callback-err": "off", "linebreak-style": ["error", "unix"], diff --git a/.gitignore b/.gitignore index 84ce2ec2d..2c34f35af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,11 @@ -node_modules/ -docs/reference/ -docker_data/ -browser/bcoin* -npm-debug.log -.DS_Store +node_modules/ +.nyc_output +docs/reference/ +docker_data/ +browser/bcoin* +npm-debug.log +coverage/ +yarn.lock +webpack.*.js +.DS_Store +tmp \ No newline at end of file diff --git a/.npmignore b/.npmignore index c93e8534f..0d1166793 100644 --- a/.npmignore +++ b/.npmignore @@ -1,9 +1,18 @@ +.babel* +.bmocharc* +.bpkgignore +.editorconfig +.eslint* .git* -bench/ -docs/ -docker_data/ -test/ +.mocharc* +.yarnignore +yarn.lock node_modules/ -browser/bcoin* package-lock.json npm-debug.log +webpack.*.js +browser/bcoin* +bench/ +build/ +docs/ +test/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 60ff57b5f..1694cfbd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,357 @@ -# Bcoin Release Notes & Changelog +# Bcoin release notes & changelog + +## v2.0.0-dev + +### How to upgrade + +The way that block data is stored has changed for greater performance, +efficiency, reliability and portability. + +- Block and undo block data has been moved from LevelDB into flat files. +- The transaction and address indexes have been moved into separate + LevelDB databases. +- The transaction has been de-duplicated, and will reduce disk usage by + half for those running with `txindex` enabled. +- The `txindex` and `addrindex` can now be enabled after the initial + block download. +- The `addrindex` has been sorted to support querying for large sets + of results, and will no longer cause CPU and memory exhaustion issues. +- The `addrindex` will correctly distinguish between `p2pkh` and + `p2wpkh` addresses. + +To upgrade to the new disk layout it's necessary to move block data +from LevelDB (e.g. `~/.bcoin/chain`) to a new file based block +storage (e.g. `~./.bcoin/blocks`), and remove `txindex` and `addrindex` +data from the chain database, for those that have that feature enabled. + +To do this you can run: +``` +node ./migrate/chaindb4to6.js /path/to/bcoin/chain +``` + +The migration will take 1-3 hours, depending on hardware. The block data +will now be stored at `/path/to/bcoin/blocks`, after the data has been moved +the chain database will be compacted to free disk space. + +Alternatively, you can also sync the chain again, however the above +migration will be faster as additional network bandwidth won't be used +for downloading the blocks again. + +For those with `txindex` and `addrindex` enabled, the indexes will be +regenerated by rescanning the chain on next startup, this process can +take multiple hours (e.g. 8 hours) depending on hardware and the +index. Please take the potential downtime in re-indexing into account +before upgrading. + +### Wallet API changes + +#### HTTP + +- `PUT /wallet/:id` Creating a watch-only wallet now requires an `accountKey` + argument. This is to prevent bcoin from generating keys and addresses the + user can not spend from. +- `POST /wallet/:id/create` + - Now has a `sign` argument for optional signing of transactions. + - Now has a `template` option, that will skip templating inputs when + `sign = false`, but you can enable it if necessary. It does not have an + effect when `sign = true`. + - Exposes `blocks`, which can will be used if there is no `rate` option. + - Exposes `sort` (Default `true`), that can be used to disable BIP69 sorting. + +#### RPC + +- Bug fix addresses for the `getnewaddress` command with various networks. +- Deprecate the `ismine` and `iswatchonly` fields from the `validateaddress` + command and add `isscript`, `iswitness`, `ischange`, `witness_version` + and `witness_program` to partially match the v0.18.0 Bitcoin Core release + (26a2000b0177fd2668b7d82e5aa52829cf2bfdf6) +- Add wallet RPC `getaddressinfo` to return `ismine` and `iswatchonly` + with the correct values instead of their previous values which were + hardcoded to false. Also returns `address`, `scriptPubKey`, `isscript`, + `iswitness`, `witness_version` and `witness_program`. + (a28ffa272a3c4d90d0273d9aa223a23becc08e0e) + +### Node API changes + +#### HTTP + +Several CPU and memory exhaustion issues have been resolved with some +additional arguments for querying multiple sets of results for addresses +that have many transactions. + +- `GET /tx/address/:address` has several new arguments: `after`, `reverse` + and `limit`. The `after` argument is a txid, for querying additional results + after a previous result. The `reverse` argument will change the order that + results are returned, the default order is oldest to latest. The `limit` + argument can be used to give results back in smaller sets if necessary. +- `POST /tx/address` This has been deprecated, instead query for each address + individually with `GET /tx/address/:address` with the expectation that + there could be _many_ results that would additionally need to be queried + in a subsequent query using the `after` argument to request the next set. +- `POST /coin/address` and `GET /coin/address/:address` are deprecated as + coins can be generated using results from `/tx/address/:address` and + querying by only a range of the latest transactions to stay synchronized. + Coins could otherwise be removed from results at any point, and thus the + entire set of results would need to be queried every time to discover + which coins have been spent and are currently available. +- `GET /` has new fields `.indexes.{addr,tx}` for the status of indexers. + +### Network changes + +- Regtest params have been updated to correspond with other bitcoin + implementations for cross testing. Key prefixes have been updated and + segwit is always active (39684df6051b599da9ed1383cb7e3f8605806a74). The + regtest seeds have also been cleared so that the node will not attempt + to connect to itself. +- Testnet seeds have been updated (8b6eba165b090edfc1ff5dbc9c422758d60b6342) + +### Logging changes + +- Wallet and node HTTP and RPC is now more clearly identified. The wallet + HTTP server will log with the context `wallet-http` and the RPC as + `wallet-rpc`. The node HTTP will log with the context `node-http` and the + RPC as `node-rpc`. There was also a reduction in the duplicate logs in + the case of an error for the node RPC, and the addition of method logging + for the wallet RPC. +- There is now also additional logging during wallet rescans. + +### Configuration changes + +- Pool options now has a `--discover` option exposed, and the `--only` node + option will disable discovery of new nodes. +- The option for `coin-cache` has been removed, this setting was causing + issues during the sync with out-of-memory errors and was making performance + worse instead of better. +- The database location for indexes can be configured via the + `--index-prefix` option. Default locations are `prefix` + `/index` + (e.g. `~/.bcoin/testnet/index/tx` and `~/.bcoin/testnet/index/addr`). + +### Script changes + +- Script has been updated for policy flags. For example + `VERIFY_CONST_SCRIPTCODE` to disable `OP_CODESEPARATOR` in non-segwit + scripts and make a positive `findAndDelete` result invalid. +- Segwit scripts that terminate with a stack size not equal to one + now has as error of `CLEANSTACK` instead of `EVAL_FALSE`. +- `VERIFY_UPGRADABLE_NOPS` now does not apply to `OP_CHECKLOCKTIMEVERIFY` + and `OP_CHECKSEQUENCEVERIFY`. + +### Testing changes + +- Switched to use a security focused rewrite of `mocha` called + `bmocha`, for further details see: https://github.com/bcoin-org/bmocha +- Data has been updated to be in sync with other implementations + around policy-only script validation. +- Tests now cleanly close with all timers being cleared. +- Config file `wallet.conf` won't be read during test runs that was + causing issues with some testing environments. + +### Chain changes + +- The transaction index methods are now implemented at `node.txindex`: + - `getMeta(hash)` + - `getTX(hash)` + - `hasTX(hash)` + - `getSpentView(tx)` +- The address index method `getHashesByAddress` is now implemented + at `node.addrindex`: + - `getHashesByAddress(addr)` It now accepts `Address` instances + rather than `Address|String` and the results are now sorted in + order of appearance in the blockchain. + - `getHashesByAddress(addr, options)` A new options argument has + been added with the fields: + - `after` - A transaction hash for results to begin after. + - `limit` - The total number of results to return at maximum. + - `reverse` - Will give results in order of latest to oldest. +- The following methods require `node.addrindex.getHashesByAddress` + in conjunction with `node.txindex.getTX` and `node.txindex.getMeta` + respectively, and now includes a new options argument as described + above for `getHashesByAddress`: + - `node.getMetaByAddress(addr, options)` + - `node.getTXByAddress(addr, options)` +- The following method has been deprecated: + - `getCoinsByAddress(addr)` + +### Other changes + +- A new module for storing block data in files. +- Use of `buffer-map` for storing hashes + (see https://github.com/bcoin-org/bcoin/issues/533). +- Use of `bsert` for assertions. +- `SIGINT` handling will close the full node, spvnode and wallet. +- Using the bcoin library in a REPL now has auto-completion by pressing tab. +- Various documentation updates. +- Mempool fix to add non-standard (non-segwit) to reject cache. +- A lockfile is now included and all dependencies integrity verified + with sha512 hash and not the vulnerable sha1 hash. +- Updates to dependencies including `bcrypto` to version > 3. +- Various small fixes to run bcoin in a browser. + +## v1.0.0 + +### Migration + +The latest bcoin release contains almost a total rewrite of the wallet. A +migration is required. + +Bcoin has also been modularized quite a bit. Users installing globally from npm +should be sure to run: + +``` bash +$ npm update -g bcoin +``` + +For users who cloned the respository themselves, it might be wise to simply +remove the `node_modules` directory and do a fresh `npm install`. + +``` bash +$ cd bcoin/ +$ git pull origin master +$ rm -rf node_modules/ +$ npm install +$ npm test +``` + +--- + +Once upgrading is complete, a migration script must be executed. The migration +script resides in `./migrate/latest` as an executable file. + +A bcoin __prefix directory__ must be passed as the first argument. + +Example: + +``` bash +$ ./migrate/latest ~/.bcoin +``` + +What this does: + +- Renames `chain.ldb` to `chain`. +- Renames `spvchain.ldb` to `spvchain`. +- Renames `walletdb.ldb` to `wallet`. +- Runs a migration (`chaindb3to4.js`) on `chain`. +- Runs a migration (`chaindb3to4.js`) on `spvchain`. +- Runs a migration (`walletdb6to7.js`) on `wallet`. + +The chain migration should be minor, and only taxing if you have +`--index-address` enabled. + +If you have a testnet, regtest, or simnet directory, the migration must also be +run on these, e.g. + +``` bash +$ ./migrate/latest ~/.bcoin/testnet +``` + +Also note that the `--prefix` argument now __always__ creates a fully-fledged +prefix directory. For example `--prefix ~/.bcoin_whatever --network testnet` +will create a directory structure of `~/.bcoin_whatever/testnet/` instead of +`~/.bcoin_whatever`. Please update your directory structure accordingly. + +### Configuration changes + +Wallet and Node are now separated and use different persistent configuration files. +Some configuration options have moved to `wallet.conf`, which is stored in the prefix +directory next to the existing `bcoin.conf`. + +All configuration options in `wallet.conf` will have the `wallet-` +prefix added implicitly. The prefix is still needed when using CLI/ENV configuration +methods. The wallet now has it's own HTTP server, so options such as +`wallet-http-host` must also be specified along with `http-host` +if you require this setting (required for Docker networking). + +Wallet-specific settings such as `api-key` and `wallet-auth` +have moved to `wallet.conf`. If using CLI/ENV options, these are prefixed with `wallet-`. +Various configuration method examples are shown below. +Note some config options (eg. `network`) are automatically passed to wallet plugin +if specified through CLI/ENV, but should be specified in `wallet.conf` for `bwallet-cli`. + +Example using wallet.conf: +``` +network: testnet +wallet-auth: true +api-key: hunter2 +http-host: 0.0.0.0 +``` + +Example using CLI options: +`./bin/node --network=testnet --http-host=0.0.0.0 --wallet-http-host=0.0.0.0 --wallet-api-key=hunter2 --wallet-wallet-auth=true` + +Example using ENV: +`BCOIN_NETWORK=simnet BCOIN_HTTP_HOST=0.0.0.0 BCOIN_WALLET_HTTP_HOST=0.0.0.0 BCOIN_WALLET_API_KEY=hunter2 BCOIN_WALLET_WALLET_AUTH=true ./bin/node` + +### Bcoin client changes + +The `bcoin cli` interface has been deprecated and is now replaced with a +separate tool: `bcoin-cli`. `bcoin wallet` has also been deprecated and is +replaced with `bwallet-cli`. Both `bcoin-cli` and `bwallet-cli` can be +installed with the `bclient` package in npm. + +### Wallet API changes + +The main wallet API changes have to do with changes in the structure of the +HTTP server itself. The bcoin wallet HTTP server will now always listen on it's +own port. Standard ports are: + +``` +main: 8334 +testnet: 18334 +regtest: 48334 +simnet: 18558 +``` + +The default account object is no longer present on the wallet. To retrieve it, +request `/wallet/:id/account/default` instead. More minor changes to the JSON +output include the removal of `id` and `wid` properties on a number of objects. +Please update your code accordingly. + +For socket.io events, all `wallet ` prefixes have been removed. For example, +`wallet join` should be replaced with simply `join`. All wallet-related events +(`tx`, `confirmed`, etc) now receive the wallet ID as the first argument. The +`tx` event will receive `[id, details]` instead of `[details]`. + +The `_admin` endpoints have been moved up a level. Instead of requesting +`/wallet/_admin/backup` (for example), request `/backup` instead. If +`--wallet-auth` is enabled, a new flag `--admin-token` is accepted. Admin token +is a 64 character hex string and allows access to all admin routes. For +example, `GET /backup?token=xxx...`. It also allows access to any wallet on the +HTTP or websocket layer (websockets who use the admin token can join a wallet +of `*`, which notifies them of events on _all_ wallets). + +### Notable changes + +- __wallet/http__ - A `conflict` event will be correctly emitted for _all_ + double spends. +- __wallet/http__ - Account balances are now indexed and will appear on account + objects via the API. +- __bcoin__ - Bcoin has been rewritten using class syntax. Objects will not be + inheritable with legacy prototypal inheritance. +- __pkg__ - Bcoin is once again buildable with browserify. +- __pkg__ - Numerous modules have been moved out to separate dependencies. For + example, the `lib/crypto` module has been moved to the `bcrypto` package (and + also heavily optimized even further). +- __bcoin__ - The main `bcoin` module has been rewritten in terms of what it + exposes and how. Conventions have changed (`Address` instead of `address`). + Due to the use of class syntax, something like `bcoin.Address(str)` is no + longer possible. Please take a look at `lib/bcoin.js` to get a better idea of + what has changed. +- __net__ - Support for BIP151 and BIP150 has been dropped. + +## v1.0.0-beta.15 + +### Notable changes ## v1.0.0-beta.14 -### Notable Changes +### Notable changes - __pkg__ - Ignored `bcoin*` files in npmignore have been removed. This fixes the npm install. ## v1.0.0-beta.13 -### Notable Changes +### Notable changes - __config__ - Options using megabyte units are now calculated properly again (6182df044228f9215938e7d314435f3f2640acca, @@ -127,7 +469,7 @@ Becomes this: ## v1.0.0-beta.12 -### Notable Changes +### Notable changes - __networks__ - Fixed simnet wpkh prefix. - __http__ - `wallet join` without wallet auth has been fixed for responses. @@ -135,14 +477,14 @@ Becomes this: ## v1.0.0-beta.11 -### Notable Changes +### Notable changes - __networks__ - Simnet params have been fixed. - __cli__ - Chain reset call has been fixed. ## v1.0.0-beta.10 -### Notable Changes +### Notable changes - __wallet/http__ - Create wallet route modified (`POST /wallet/:id?` changed to `PUT /wallet/:id`). @@ -178,7 +520,7 @@ Becomes this: ## v1.0.0-beta.9 -### Notable Changes +### Notable changes - __mempool__ - Trimming now removes dependency chains by cumulative fee rate. - __mempool__ - Cumulative descendant fees are now updated properly when @@ -190,14 +532,14 @@ Becomes this: ## v1.0.0-beta.8 -### Notable Changes +### Notable changes - __mempool__ - Fixed critical fee estimator bug causing throwing in the mempool. ## v1.0.0-beta.7 -### Notable Changes +### Notable changes - __http__ - Always display spent coins in tx routes (e.g. `/tx/[txid]`). - __mempool__ - An on-disk mempool is now exposed via `--persistent-mempool` @@ -209,20 +551,20 @@ Becomes this: ## v1.0.0-beta.6 -### Notable Changes +### Notable changes - __http__ - Better bitcoind compatability for JSON-RPC. ## v1.0.0-beta.5 -### Notable Changes +### Notable changes - __miner__ - Better fee rate comparisons. - __deps__ - Upgrade deps, fix build on arm and windows. ## v1.0.0-beta.4 -### Notable Changes +### Notable changes - __miner__ - Optimized TX sorting. - __rpc__ - Improved getblocktemplate to provide more @@ -230,21 +572,21 @@ Becomes this: ## v1.0.0-beta.3 -### Notable Changes +### Notable changes - __miner__ - Improved fee rate sorting. - __rpc__ - Fix incompatibilities in rpc api (getblocktemplate & submitblock). ## v1.0.0-beta.2 -### Notable Changes +### Notable changes - __pool__ - Increase max header chain failures to 500 (prevents the initial sync from reverting to getblocks so frequently). ## v1.0.0-beta.1 -### Notable Changes +### Notable changes - __wsproxy__: Fixed proof of work handling in websocket proxy (43c491b). - __chain__: Optimized MTP and network target calculations (1e07d1b). @@ -256,6 +598,6 @@ Becomes this: ## v1.0.0-beta -### Notable Changes +### Notable changes - Initial tagged release. diff --git a/Makefile b/Makefile deleted file mode 100644 index caee01cc2..000000000 --- a/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -all: - @npm run webpack - -browser: - @npm run webpack-browser - -compat: - @npm run webpack-compat - -node: - @npm run webpack-node - -clean: - @npm run clean - -docs: - @npm run docs - -lint: - @npm run lint - -test: - @npm test - -.PHONY: all browser compat node clean docs lint test diff --git a/README.md b/README.md index c457e2a09..35c89ecb2 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ details. **Fcoin** is an alternative implementation of the flo protocol, written in node.js. +Bcoin, the parent project, is well tested and aware of all known consensus rules. It is currently +used in production as the consensus backend and wallet system for +[purse.io][purse]. + ## Uses - Full Node @@ -31,7 +35,7 @@ node.js. - Layer 2 Backend (lightning) - General Purpose Bitcoin Library -Try it in the browser: http://bcoin.io/browser.html +Try it in the browser: [https://bcoin.io/browser/](https://bcoin.io/browser/) ## Install @@ -42,13 +46,14 @@ $ npm install $ ./bin/fcoin ``` -See the [Beginner's Guide][guide] for more in-depth installation instructions. +See the [Getting started][guide] guide for more in-depth installation +instructions, including verifying releases. ## Documentation -- API Docs: http://bcoin.io/docs/ -- REST Docs: http://bcoin.io/api-docs/index.html -- Docs: [docs/](docs/README.md) +- General docs: [docs/](docs/README.md) +- Wallet and node API docs: https://bcoin.io/api-docs/ +- Library API docs: https://bcoin.io/docs/ ## Support @@ -74,7 +79,12 @@ all code is your original work. `` See LICENSE for more info. [purse]: https://purse.io -[guide]: https://github.com/bcoin-org/bcoin/blob/master/docs/Beginner's-Guide.md +[guide]: docs/getting-started.md [freenode]: https://freenode.net/ [irc]: irc://irc.freenode.net/bcoin -[changelog]: https://github.com/bcoin-org/bcoin/blob/master/CHANGELOG.md +[changelog]: CHANGELOG.md + +[coverage-status-img]: https://codecov.io/gh/bcoin-org/bcoin/badge.svg?branch=master +[coverage-status-url]: https://codecov.io/gh/bcoin-org/bcoin?branch=master +[circleci-status-img]: https://circleci.com/gh/bcoin-org/bcoin/tree/master.svg?style=shield +[circleci-status-url]: https://circleci.com/gh/bcoin-org/bcoin/tree/master diff --git a/bench/bech32.js b/bench/bech32.js index 3b19c307a..002f81830 100644 --- a/bench/bech32.js +++ b/bench/bech32.js @@ -1,7 +1,7 @@ 'use strict'; const Address = require('../lib/primitives/address'); -const random = require('../lib/crypto/random'); +const random = require('bcrypto/lib/random'); const bench = require('./bench'); const addrs = []; diff --git a/bench/blockstore.js b/bench/blockstore.js new file mode 100644 index 000000000..817633b04 --- /dev/null +++ b/bench/blockstore.js @@ -0,0 +1,476 @@ +/*! + * bench/blockstore.js - benchmark blockstore for bcoin + * + * This can be run to benchmark the performance of the blockstore + * module for writing, reading and pruning block data. Results are + * written to stdout as JSON or formated bench results. + * + * Usage: + * node ./blockstore.js [--maxfile=] [--total=] + * [--location=] [--store=] + * [--output=] [--unsafe] + * + * Options: + * - `maxfile` The maximum file size (applies to "file" store). + * - `total` The total number of block bytes to write. + * - `location` The location to store block data. + * - `store` This can be "file" or "level". + * - `output` This can be "json", "bench" or "benchjson". + * - `unsafe` This will allocate block data directly from memory + * instead of random, it is faster. + * + * Copyright (c) 2019, Braydon Fuller (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +process.title = 'blockstore-bench'; + +const {isAbsolute} = require('path'); +const {mkdirp} = require('bfile'); +const random = require('bcrypto/lib/random'); +const {BufferMap} = require('buffer-map'); + +const { + FileBlockStore, + LevelBlockStore +} = require('../lib/blockstore'); + +const config = { + 'maxfile': { + value: true, + parse: a => parseInt(a), + valid: a => Number.isSafeInteger(a), + fallback: 128 * 1024 * 1024 + }, + 'total': { + value: true, + parse: a => parseInt(a), + valid: a => Number.isSafeInteger(a), + fallback: 3 * 1024 * 1024 * 1024 + }, + 'location': { + value: true, + valid: a => isAbsolute(a), + fallback: '/tmp/bcoin-bench-blockstore' + }, + 'store': { + value: true, + valid: a => (a === 'file' || a === 'level'), + fallback: 'file' + }, + 'output': { + value: true, + valid: a => (a === 'json' || a === 'bench' || a === 'benchjson'), + fallback: 'bench' + }, + 'unsafe': { + value: false, + valid: a => (a === true || a === false), + fallback: false + } +}; + +/** + * These block sizes were generated from bitcoin mainnet blocks by putting + * sizes into bins of 256 ^ (2 * n) as the upper bound and calculating + * the percentage of each and then distributing to roughly match the + * percentage of the following: + * + * |-------------|------------| + * | percentage | bytes | + * |-------------|------------| + * | 23.4055 | 1048576 | + * | 15.5338 | 256 | + * | 12.2182 | 262144 | + * | 8.4079 | 524288 | + * | 7.1289 | 131072 | + * | 6.9197 | 65536 | + * | 6.7073 | 2097152 | + * | 4.6753 | 32768 | + * | 3.9695 | 4096 | + * | 3.3885 | 16384 | + * | 2.6526 | 8192 | + * | 2.0048 | 512 | + * | 1.587 | 1024 | + * | 1.3976 | 2048 | + * | 0.0032 | 4194304 | + * |-------------|------------| + */ + +const distribution = [ + 1048576, 256, 256, 524288, 262144, 256, 131072, 256, 524288, 256, 131072, + 1048576, 262144, 1048576, 2097152, 256, 1048576, 65536, 256, 262144, 8192, + 32768, 32768, 256, 1048576, 524288, 2097152, 1024, 1048576, 1048576, 131072, + 131072, 262144, 512, 1048576, 1048576, 1024, 1048576, 1048576, 262144, 2048, + 262144, 256, 1048576, 131072, 4096, 524288, 65536, 4096, 65536, 131072, + 2097152, 2097152, 2097152, 256, 524288, 4096, 262144, 65536, 65536, 262144, + 16384, 1048576, 32768, 262144, 1048576, 256, 131072, 1048576, 1048576, + 1048576, 8192, 1048576, 256, 16384, 1048576, 256, 256, 524288, 256, 32768, + 16384, 32768, 1048576, 512, 4096, 1048576, 1048576, 524288, 65536, 2097152, + 512, 262144, 8192, 524288, 131072, 65536, 16384, 2048, 262144, 1048576, + 1048576, 256, 524288, 262144, 4194304, 262144, 2097152 +]; + +(async () => { + let settings = null; + try { + settings = processArgs(process.argv, config); + } catch (err) { + console.log(err.message); + process.exit(1); + } + + await mkdirp(settings.location); + + let store = null; + let output = null; + + if (settings.store === 'file') { + store = new FileBlockStore({ + location: settings.location, + maxFileLength: settings.maxfile + }); + } else if (settings.store === 'level') { + store = new LevelBlockStore({ + location: settings.location + }); + } + + if (settings.output === 'bench') { + output = new BenchOutput(); + } else if (settings.output === 'benchjson') { + output = new BenchJSONOutput(); + } else if (settings.output === 'json') { + output = new JSONOutput(); + } + + await store.open(); + + const hashes = []; + const lengths = new BufferMap(); + + output.start(); + + // 1. Write data to the block store + let written = 0; + + async function write() { + for (const length of distribution) { + const hash = random.randomBytes(32); + let raw = null; + if (settings.unsafe) { + raw = Buffer.allocUnsafe(length); + } else { + raw = random.randomBytes(length); + } + + const start = process.hrtime(); + await store.write(hash, raw); + const elapsed = process.hrtime(start); + + hashes.push(hash); + lengths.set(hash, length); + written += length; + + output.result('write', start, elapsed, length); + + if (written >= settings.total) + break; + } + } + + while (written < settings.total) + await write(); + + // 2. Read data from the block store + for (const hash of hashes) { + const start = process.hrtime(); + const raw = await store.read(hash); + const elapsed = process.hrtime(start); + + output.result('read', start, elapsed, raw.length); + } + + // 3. Read data not in the order it was written (random) + for (let i = 0; i < hashes.length; i++) { + const rand = random.randomInt() / 0xffffffff * (hashes.length - 1) | 0; + const hash = hashes[rand]; + + const start = process.hrtime(); + const raw = await store.read(hash); + const elapsed = process.hrtime(start); + + output.result('randomread', start, elapsed, raw.length); + } + + // 4. Prune data from the block store + for (const hash of hashes) { + const start = process.hrtime(); + await store.prune(hash); + const elapsed = process.hrtime(start); + const length = lengths.get(hash); + + output.result('prune', start, elapsed, length); + } + + output.end(); + + await store.close(); +})().catch((err) => { + console.error(err); + process.exit(1); +}); + +class JSONOutput { + constructor() { + this.time = process.hrtime(); + this.index = 0; + } + + start() { + process.stdout.write('['); + } + + result(type, start, elapsed, length) { + if (this.index > 0) + process.stdout.write(','); + + const since = [start[0] - this.time[0], start[1] - this.time[1]]; + const smicro = hrToMicro(since); + const emicro = hrToMicro(elapsed); + + process.stdout.write(`{"type":"${type}","start":${smicro},`); + process.stdout.write(`"elapsed":${emicro},"length":${length},`); + process.stdout.write(`"index":${this.index}}`); + + this.index += 1; + } + + end() { + process.stdout.write(']'); + } +} + +class BenchOutput { + constructor() { + this.time = process.hrtime(); + this.index = 0; + this.results = {}; + this.interval = null; + this.stdout = process.stdout; + } + + start() { + this.stdout.write('Starting benchmark...\n'); + this.interval = setInterval(() => { + this.stdout.write(`Operation count=${this.index}\n`); + }, 5000); + } + + result(type, start, elapsed, length) { + const micro = hrToMicro(elapsed); + + if (!this.results[type]) + this.results[type] = {}; + + if (!this.results[type][length]) + this.results[type][length] = []; + + this.results[type][length].push(micro); + + this.index += 1; + } + + end() { + clearInterval(this.interval); + + this.stdout.write('Benchmark finished.\n'); + + function format(value) { + if (typeof value === 'number') + value = value.toFixed(2); + + if (typeof value !== 'string') + value = value.toString(); + + while (value.length < 15) + value = `${value} `; + + return value; + } + + function title(value) { + if (typeof value !== 'string') + value = value.toString(); + + while (value.length < 85) + value = ` ${value} `; + + if (value.length > 85) + value = value.slice(0, 85); + + return value; + } + + for (const type in this.results) { + this.stdout.write('\n'); + this.stdout.write(`${title(type)}\n`); + this.stdout.write(`${'='.repeat(85)}\n`); + this.stdout.write(`${format('length')}`); + this.stdout.write(`${format('operations')}`); + this.stdout.write(`${format('min')}`); + this.stdout.write(`${format('max')}`); + this.stdout.write(`${format('average')}`); + this.stdout.write(`${format('median')}`); + this.stdout.write('\n'); + this.stdout.write(`${'-'.repeat(85)}\n`); + + for (const length in this.results[type]) { + const cal = calculate(this.results[type][length]); + + this.stdout.write(`${format(length)}`); + this.stdout.write(`${format(cal.operations.toString())}`); + this.stdout.write(`${format(cal.min)}`); + this.stdout.write(`${format(cal.max)}`); + this.stdout.write(`${format(cal.average)}`); + this.stdout.write(`${format(cal.median)}`); + this.stdout.write('\n'); + } + this.stdout.write('\n'); + } + this.stdout.write('\n'); + } +} + +class BenchJSONOutput { + constructor() { + this.time = null; + this.results = {}; + this.stdout = process.stdout; + } + + start() { + this.time = process.hrtime(); + } + + result(type, start, elapsed, length) { + const micro = hrToMicro(elapsed); + + if (!this.results[type]) + this.results[type] = {}; + + if (!this.results[type][length]) + this.results[type][length] = []; + + this.results[type][length].push(micro); + } + + end() { + const report = { + summary: [], + time: hrToMicro(process.hrtime(this.time)), + elapsed: 0 + }; + + for (const type in this.results) { + for (const length in this.results[type]) { + const cal = calculate(this.results[type][length]); + + report.elapsed += cal.total; + + report.summary.push({ + type: type, + length: length, + operations: cal.operations, + min: cal.min, + max: cal.max, + average: cal.average, + median: cal.median + }); + } + } + + this.stdout.write(JSON.stringify(report, null, 2)); + this.stdout.write('\n'); + } +} + +function hrToMicro(time) { + return (time[0] * 1000000) + (time[1] / 1000); +} + +function calculate(times) { + times.sort((a, b) => a - b); + + let min = Infinity; + let max = 0; + + let total = 0; + + for (const micro of times) { + if (micro < min) + min = micro; + + if (micro > max) + max = micro; + + total += micro; + } + + const average = total / times.length; + const median = times[times.length / 2 | 0]; + const operations = times.length; + + return { + total, + operations, + min, + max, + average, + median + }; +} + +function processArgs(argv, config) { + const args = {}; + + for (const key in config) + args[key] = config[key].fallback; + + for (let i = 2; i < process.argv.length; i++) { + const arg = process.argv[i]; + const match = arg.match(/^(\-){1,2}([a-z]+)(\=)?(.*)?$/); + + if (!match) { + throw new Error(`Unexpected argument: ${arg}.`); + } else { + const key = match[2]; + let value = match[4]; + + if (!config[key]) + throw new Error(`Invalid argument: ${arg}.`); + + if (config[key].value && !value) { + value = process.argv[i + 1]; + i++; + } else if (!config[key].value && !value) { + value = true; + } else if (!config[key].value && value) { + throw new Error(`Unexpected value: ${key}=${value}`); + } + + if (config[key].parse) + value = config[key].parse(value); + + if (value) + args[key] = value; + + if (!config[key].valid(args[key])) + throw new Error(`Invalid value: ${key}=${value}`); + } + } + + return args; +} diff --git a/bench/buffer.js b/bench/buffer.js index 847a0e94e..4cb462f02 100644 --- a/bench/buffer.js +++ b/bench/buffer.js @@ -1,7 +1,7 @@ 'use strict'; -const BufferWriter = require('../lib/utils/writer'); -const StaticWriter = require('../lib/utils/staticwriter'); +const BufferWriter = require('bufio').BufferWriter; +const StaticWriter = require('bufio').StaticWriter; const common = require('../test/util/common'); const bench = require('./bench'); @@ -14,7 +14,7 @@ const tx5 = common.readTX('tx5'); tx.refresh(); const {size} = tx.getWitnessSizes(); const bw = new StaticWriter(size); - tx.toWitnessWriter(bw); + tx.toWriter(bw); bw.render(); } end(10000); @@ -26,7 +26,7 @@ const tx5 = common.readTX('tx5'); for (let i = 0; i < 10000; i++) { tx.refresh(); const bw = new BufferWriter(); - tx.toWitnessWriter(bw); + tx.toWriter(bw); bw.render(); } end(10000); diff --git a/bench/chacha.js b/bench/chacha.js deleted file mode 100644 index 00249d33d..000000000 --- a/bench/chacha.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; - -const ChaCha20 = require('../lib/crypto/chacha20'); -const Poly1305 = require('../lib/crypto/poly1305'); -const digest = require('../lib/crypto/digest'); -const bench = require('./bench'); - -console.log('note: rate measured in kb/s'); - -const chacha = new ChaCha20(); -const poly = new Poly1305(); -const key = Buffer.alloc(32, 0x02); -const iv = Buffer.from('0102030405060708', 'hex'); -const chunk = Buffer.allocUnsafe(32); -const data = Buffer.allocUnsafe(32); - -for (let i = 0; i < 32; i++) - chunk[i] = i; - -for (let i = 0; i < 32; i++) - data[i] = i & 0xff; - -chacha.init(key, iv, 0); -poly.init(key); - -{ - const end = bench('encrypt'); - for (let i = 0; i < 1000000; i++) - chacha.encrypt(chunk); - end(1000000 * 32 / 1024); -} - -{ - const end = bench('update'); - for (let i = 0; i < 1000000; i++) - poly.update(data); - end(1000000 * 32 / 1024); -} - -{ - const end = bench('finish'); - for (let i = 0; i < 1000000; i++) { - poly.init(key); - poly.update(data); - poly.finish(); - } - end(1000000 * 32 / 1024); -} - -// For reference: -{ - const end = bench('sha256'); - for (let i = 0; i < 1000000; i++) - digest.hash256(data); - end(1000000 * 32 / 1024); -} diff --git a/bench/coins.js b/bench/coins.js index fea14577f..a3ee6025f 100644 --- a/bench/coins.js +++ b/bench/coins.js @@ -1,8 +1,8 @@ 'use strict'; const CoinView = require('../lib/coins/coinview'); -const BufferReader = require('../lib/utils/reader'); -const StaticWriter = require('../lib/utils/writer'); +const BufferReader = require('bufio').BufferReader; +const StaticWriter = require('bufio').StaticWriter; const common = require('../test/util/common'); const bench = require('./bench'); diff --git a/bench/hexstring.js b/bench/hexstring.js new file mode 100644 index 000000000..c57e1319c --- /dev/null +++ b/bench/hexstring.js @@ -0,0 +1,17 @@ +'use strict'; + +const random = require('bcrypto/lib/random'); +const util = require('../lib/utils/util'); +const bench = require('./bench'); + +const hashes = []; + +for (let i = 0; i < 100000; i++) + hashes.push(random.randomBytes(32)); + +{ + const end = bench('hexstring'); + for (let i = 0; i < hashes.length; i++) + util.fromRev(util.revHex(hashes[i])); + end(100000); +} diff --git a/bench/merkle.js b/bench/merkle.js index 85f6d6a09..75cdc70c1 100644 --- a/bench/merkle.js +++ b/bench/merkle.js @@ -1,8 +1,9 @@ 'use strict'; const assert = require('assert'); -const merkle = require('../lib/crypto/merkle'); -const random = require('../lib/crypto/random'); +const merkle = require('bcrypto/lib/merkle'); +const random = require('bcrypto/lib/random'); +const SHA256 = require('bcrypto/lib/sha256'); const bench = require('./bench'); const leaves = []; @@ -13,7 +14,7 @@ for (let i = 0; i < 3000; i++) { const end = bench('tree'); for (let i = 0; i < 1000; i++) { - const [n, m] = merkle.createTree(leaves.slice()); + const [n, m] = merkle.createTree(SHA256, leaves.slice()); assert(n); assert(!m); } diff --git a/bench/script.js b/bench/script.js index e138b43ca..5d64b10f1 100644 --- a/bench/script.js +++ b/bench/script.js @@ -1,6 +1,6 @@ 'use strict'; -const random = require('../lib/crypto/random'); +const random = require('bcrypto/lib/random'); const Script = require('../lib/script/script'); const bench = require('./bench'); diff --git a/bench/tx.js b/bench/tx.js index b6baa7cef..3b1933400 100644 --- a/bench/tx.js +++ b/bench/tx.js @@ -1,11 +1,11 @@ 'use strict'; +const random = require('bcrypto/lib/random'); const Address = require('../lib/primitives/address'); const TX = require('../lib/primitives/tx'); const Script = require('../lib/script/script'); const MTX = require('../lib/primitives/mtx'); -const encoding = require('../lib/utils/encoding'); -const random = require('../lib/crypto/random'); +const consensus = require('../lib/protocol/consensus'); const common = require('../test/util/common'); const bench = require('./bench'); @@ -84,7 +84,7 @@ const tx10 = common.readTX('tx10'); const end = bench('input hashes'); for (let i = 0; i < 10000; i++) - tx.getInputHashes(null, 'hex'); + tx.getInputHashes(); end(10000); } @@ -94,7 +94,7 @@ const tx10 = common.readTX('tx10'); const end = bench('output hashes'); for (let i = 0; i < 10000; i++) - tx.getOutputHashes('hex'); + tx.getOutputHashes(); end(10000); } @@ -104,7 +104,7 @@ const tx10 = common.readTX('tx10'); const end = bench('all hashes'); for (let i = 0; i < 10000; i++) - tx.getHashes(null, 'hex'); + tx.getHashes(); end(10000); } @@ -156,7 +156,7 @@ const mtx = new MTX(); for (let i = 0; i < 100; i++) { mtx.addInput({ prevout: { - hash: encoding.NULL_HASH, + hash: consensus.ZERO_HASH, index: 0 }, script: new Script() @@ -176,7 +176,7 @@ const tx2 = mtx.toTX(); const end = bench('input hashes'); for (let i = 0; i < 10000; i++) - tx2.getInputHashes(null, 'hex'); + tx2.getInputHashes(); end(10000); } @@ -185,7 +185,7 @@ const tx2 = mtx.toTX(); const end = bench('output hashes'); for (let i = 0; i < 10000; i++) - tx2.getOutputHashes('hex'); + tx2.getOutputHashes(); end(10000); } @@ -194,7 +194,7 @@ const tx2 = mtx.toTX(); const end = bench('all hashes'); for (let i = 0; i < 10000; i++) - tx2.getHashes(null, 'hex'); + tx2.getHashes(); end(10000); } diff --git a/bench/walletdb.js b/bench/walletdb.js index 226024c86..74a0fb0f9 100644 --- a/bench/walletdb.js +++ b/bench/walletdb.js @@ -1,13 +1,13 @@ 'use strict'; const bench = require('./bench'); -const random = require('../lib/crypto/random'); +const random = require('bcrypto/lib/random'); const WalletDB = require('../lib/wallet/walletdb'); const MTX = require('../lib/primitives/mtx'); const Outpoint = require('../lib/primitives/outpoint'); function dummy() { - const hash = random.randomBytes(32).toString('hex'); + const hash = random.randomBytes(32); return new Outpoint(hash, 0); } @@ -37,7 +37,7 @@ const walletdb = new WalletDB({ end(1000); for (const addr of result) - addrs.push(addr.receive.getAddress()); + addrs.push(addr.receiveAddress()); } // Keys diff --git a/bin/cli b/bin/cli index 73a1657ff..f61658fb1 100755 --- a/bin/cli +++ b/bin/cli @@ -2,796 +2,6 @@ 'use strict'; -const Config = require('../lib/node/config'); -const util = require('../lib/utils/util'); -const Client = require('../lib/http/client'); -const Wallet = require('../lib/http/wallet'); - -const ANTIREPLAY = '' - + '6a2e426974636f696e3a204120506565722d746f2d5065657' - + '220456c656374726f6e696320436173682053797374656d'; - -function CLI() { - this.config = new Config('fcoin'); - - this.config.load({ - argv: true, - env: true - }); - - this.config.open('fcoin.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 (util.isBase58(hash) || util.isBech32(hash)) { - 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 (util.isBase58(hash) || util.isBech32(hash)) { - 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) - }); - } 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); -}; - -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) - }; - } - - 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); -}; - -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.client.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 (util.isBase58(key)) { - await this.wallet.importPrivate(account, key, passphrase); - this.log('Imported private key.'); - return; - } - - if (util.isHex(key)) { - await this.wallet.importPublic(account, key); - this.log('Imported public key.'); - return; - } - - throw new Error('Bad key for import.'); -}; - -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); - } - - 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); -}; - -CLI.prototype.handleWallet = async function handleWallet() { - this.wallet = new Wallet({ - uri: this.config.str(['url', 'uri']), - apiKey: this.config.str('api-key'), - network: this.config.str('network'), - 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; - 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('Other Options:'); - this.log(' --passphrase [passphrase]: For signing & account creation.'); - this.log(' --account [account-name]: Account name.'); - break; - } -}; - -CLI.prototype.handleNode = async function handleNode() { - this.client = new Client({ - uri: this.config.str(['url', 'uri']), - apiKey: this.config.str('api-key'), - network: this.config.str('network') - }); - - 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 'rescan': - await this.rescan(); - 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(' $ rescan [height]: Rescan for transactions.'); - 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; - } -}; - -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'; - await this.handleNode(); - break; - } - await this.handleWallet(); - break; - default: - await this.handleNode(); - break; - } -}; - -CLI.prototype.destroy = function destroy() { - if (this.wallet) - this.wallet.client.destroy(); - - if (this.client) - this.client.destroy(); - - return Promise.resolve(); -}; - -(async () => { - const cli = new CLI(); - await cli.open(); - await cli.destroy(); - process.exit(0); -})().catch((err) => { - console.error(err.stack); - process.exit(1); -}); +console.error('%s%s', + 'Warning: The `fcoin cli` interface is deprecated.\n', + 'Please use `fcoin-cli` and `fwallet-cli` instead.'); diff --git a/bin/fcoin-cli b/bin/fcoin-cli new file mode 100755 index 000000000..26e59876c --- /dev/null +++ b/bin/fcoin-cli @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +'use strict'; + +require('@oipwg/fclient/bin/fcoin-cli'); diff --git a/bin/fwallet b/bin/fwallet new file mode 100755 index 000000000..dd044242f --- /dev/null +++ b/bin/fwallet @@ -0,0 +1,55 @@ +#!/bin/bash + +rl=0 +daemon=0 + +if ! type perl > /dev/null 2>& 1; then + if uname | grep -i 'darwin' > /dev/null; then + echo 'Bcoin requires perl to start on OSX.' >& 2 + exit 1 + fi + rl=1 +fi + +if test $rl -eq 1; then + file=$(readlink -f "$0") +else + # Have to do it this way + # because OSX isn't a real OS + file=$(perl -MCwd -e "print Cwd::realpath('$0')") +fi + +dir=$(dirname "$file") + +if test x"$1" = x'cli'; then + shift + exec "${dir}/cli" "wallet" "$@" + exit 1 +fi + +for arg in "$@"; do + case "$arg" in + --daemon) + daemon=1 + ;; + esac +done + +if test $daemon -eq 1; then + # And yet again, OSX doesn't support something. + if ! type setsid > /dev/null 2>& 1; then + ( + "${dir}/wallet" "$@" > /dev/null 2>& 1 & + echo "$!" + ) + exit 0 + fi + ( + setsid "${dir}/wallet" "$@" > /dev/null 2>& 1 & + echo "$!" + ) + exit 0 +else + exec "${dir}/wallet" "$@" + exit 1 +fi diff --git a/bin/fwallet-cli b/bin/fwallet-cli new file mode 100755 index 000000000..c4c285220 --- /dev/null +++ b/bin/fwallet-cli @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +'use strict'; + +require('@oipwg/fclient/bin/fwallet-cli'); diff --git a/bin/node b/bin/node index d9d1551fa..6395d9bec 100755 --- a/bin/node +++ b/bin/node @@ -6,7 +6,7 @@ process.title = 'fcoin'; if (process.argv.indexOf('--help') !== -1 || process.argv.indexOf('-h') !== -1) { - console.error('See the bcoin wiki at: https://github.com/bcoin-org/bcoin/wiki.'); + console.error('See the bcoin docs at: https://github.com/bcoin-org/bcoin/tree/master/docs.'); process.exit(1); throw new Error('Could not exit.'); } @@ -22,14 +22,14 @@ if (process.argv.indexOf('--version') !== -1 const FullNode = require('../lib/node/fullnode'); const node = new FullNode({ - config: true, + file: true, argv: true, env: true, logFile: true, logConsole: true, logLevel: 'debug', - db: 'leveldb', - persistent: true, + checkpoints: true, + memory: false, workers: true, listen: true, loader: require diff --git a/bin/spvnode b/bin/spvnode index b9499c57f..25cd46e39 100755 --- a/bin/spvnode +++ b/bin/spvnode @@ -6,17 +6,17 @@ process.title = 'fcoin'; const assert = require('assert'); const SPVNode = require('../lib/node/spvnode'); -const util = require('../lib/utils/util'); const Outpoint = require('../lib/primitives/outpoint'); -const node = SPVNode({ - config: true, +const node = new SPVNode({ + file: true, argv: true, env: true, logFile: true, logConsole: true, logLevel: 'debug', db: 'leveldb', + memory: false, persistent: true, workers: true, listen: true, @@ -24,7 +24,7 @@ const node = SPVNode({ }); // Temporary hack -if (!node.has('walletdb')) { +if (!node.config.bool('no-wallet') && !node.has('walletdb')) { const plugin = require('../lib/wallet/plugin'); node.use(plugin); } @@ -48,7 +48,7 @@ process.on('SIGINT', async () => { node.on('block', (block) => { assert(block.txs.length >= 1); if (block.txs.length > 1) - util.log(block.txs[1]); + console.log(block.txs[1]); }); } diff --git a/bin/wallet b/bin/wallet index 13a91a21e..e27cd392d 100755 --- a/bin/wallet +++ b/bin/wallet @@ -2,24 +2,50 @@ 'use strict'; -process.title = 'bcoin-wallet'; +process.title = 'fwallet'; -const server = require('../lib/wallet/server'); +if (process.argv.indexOf('--help') !== -1 + || process.argv.indexOf('-h') !== -1) { + console.error('See the bcoin docs at: https://github.com/bcoin-org/bcoin/tree/master/docs.'); + process.exit(1); + throw new Error('Could not exit.'); +} -const wdb = server.create({ - config: true, +if (process.argv.indexOf('--version') !== -1 + || process.argv.indexOf('-v') !== -1) { + const pkg = require('../package.json'); + console.log(pkg.version); + process.exit(0); + throw new Error('Could not exit.'); +} + +const Node = require('../lib/wallet/node'); + +const node = new Node({ + file: true, argv: true, env: true, logFile: true, logConsole: true, logLevel: 'debug', - db: 'leveldb' + memory: false, + workers: true, + listen: true, + loader: require }); process.on('unhandledRejection', (err, promise) => { throw err; }); -wdb.open().catch((err) => { - throw err; +process.on('SIGINT', async () => { + await node.close(); +}); + +(async () => { + await node.ensure(); + await node.open(); +})().catch((err) => { + console.error(err.stack); + process.exit(1); }); diff --git a/browser/index.html b/browser/index.html index 3e147a008..e01ce17be 100644 --- a/browser/index.html +++ b/browser/index.html @@ -1,106 +1,110 @@ - - -bcoin - - - - -

Bcoin, the browser full node

-Welcome. Your machine is currently validating the blockchain. -The blocks and wallet are stored on your local disk with indexed DB. You are -connecting to the actual bitcoin P2P network via a websocket->tcp proxy. -Enjoy. (See the bcoin repo for -more bitcoin magic). -
-
Chain State:
-
Last 20 Blocks/TXs:
-
-
-
-
- -
-
-
- - - -
- -
- - + + + bcoin + + + + + +

Bcoin, the browser full node

+ + Welcome. Your machine is currently validating the blockchain. The blocks + and wallet are stored on your local disk with indexed DB. You are + connecting to the actual bitcoin P2P network via a websocket->tcp + proxy. Enjoy. (See the + bcoin repo + for more bitcoin magic). + +
+
Chain State:
+
Last 20 Blocks/TXs:
+
+
+
+
+ +
+
+
+ + + +
+ +
+ diff --git a/browser/index.js b/browser/index.js deleted file mode 100644 index 16bed1888..000000000 --- a/browser/index.js +++ /dev/null @@ -1,261 +0,0 @@ -;(function() { - -'use strict'; - -var util = bcoin.util; -var body = document.getElementsByTagName('body')[0]; -var log = document.getElementById('log'); -var wdiv = document.getElementById('wallet'); -var tdiv = document.getElementById('tx'); -var floating = document.getElementById('floating'); -var send = document.getElementById('send'); -var newaddr = document.getElementById('newaddr'); -var chainState = document.getElementById('state'); -var rpc = document.getElementById('rpc'); -var cmd = document.getElementById('cmd'); -var items = []; -var scrollback = 0; -var logger, node, wdb; - -window.onunhandledrejection = function(event) { - throw event.reason; -}; - -body.onmouseup = function() { - floating.style.display = 'none'; -}; - -floating.onmouseup = function(ev) { - ev.stopPropagation(); - return false; -}; - -function show(obj) { - floating.innerHTML = escape(util.inspectify(obj, false)); - floating.style.display = 'block'; -} - -logger = new bcoin.logger({ level: 'debug', console: true }); -logger.writeConsole = function(level, module, args) { - var name = bcoin.logger.levelsByVal[level]; - var msg = util.format(args, false); - if (++scrollback > 1000) { - log.innerHTML = ''; - scrollback = 1; - } - log.innerHTML += '' + util.now() + ' '; - if (name === 'error') { - log.innerHTML += ''; - log.innerHTML += '['; - log.innerHTML += name - log.innerHTML += '] '; - if (module) - log.innerHTML += '(' + module + ') '; - log.innerHTML += ''; - } else { - log.innerHTML += '['; - log.innerHTML += name - log.innerHTML += '] '; - if (module) - log.innerHTML += '(' + module + ') '; - } - log.innerHTML += escape(msg) + '\n'; - log.scrollTop = log.scrollHeight; -}; - -rpc.onsubmit = function(ev) { - var text = cmd.value || ''; - var argv = text.trim().split(/\s+/); - var method = argv.shift(); - var params = []; - var i, arg, param; - - cmd.value = ''; - - for (i = 0; i < argv.length; i++) { - arg = argv[i]; - try { - param = JSON.parse(arg); - } catch (e) { - param = arg; - } - params.push(param); - } - - node.rpc.execute({ method: method, params: params }).then(show, show); - - ev.preventDefault(); - ev.stopPropagation(); - - return false; -}; - -send.onsubmit = function(ev) { - var value = document.getElementById('amount').value; - var address = document.getElementById('address').value; - var tx, options; - - options = { - outputs: [{ - address: address, - value: bcoin.amount.value(value) - }] - }; - - wdb.primary.createTX(options).then(function(mtx) { - tx = mtx; - return wdb.primary.sign(tx); - }).then(function() { - return node.sendTX(tx); - }).then(function() { - show(tx); - }); - - ev.preventDefault(); - ev.stopPropagation(); - - return false; -}; - -newaddr.onmouseup = function() { - wdb.primary.createReceive().then(function() { - formatWallet(wdb.primary); - }); -}; - -function kb(size) { - size /= 1000; - return size.toFixed(2) + 'kb'; -} - -function create(html) { - var el = document.createElement('div'); - el.innerHTML = html; - return el.firstChild; -} - -function escape(html, encode) { - return html - .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); -} - -function addItem(item, entry) { - var height = entry ? entry.height : -1; - var el; - - if (items.length === 20) { - el = items.shift(); - tdiv.removeChild(el); - el.onmouseup = null; - } - - el = create('' + item.rhash() + ' (' + height - + ' - ' + kb(item.getSize()) + ')'); - tdiv.appendChild(el); - - setMouseup(el, item); - - items.push(el); - - chainState.innerHTML = '' - + 'tx=' + node.chain.db.state.tx - + ' coin=' + node.chain.db.state.coin - + ' value=' + bcoin.amount.btc(node.chain.db.state.value); -} - -function setMouseup(el, obj) { - el.onmouseup = function(ev) { - show(obj); - ev.stopPropagation(); - return false; - }; -} - -function formatWallet(wallet) { - var html = ''; - var json = wallet.master.toJSON(true); - var i, tx, el; - - html += 'Wallet
'; - - if (wallet.account.witness) { - html += 'Current Address (p2wpkh): ' - + wallet.getAddress() - + '
'; - html += 'Current Address (p2wpkh behind p2sh): ' - + wallet.getNestedAddress() - + '
'; - } else { - html += 'Current Address: ' + wallet.getAddress() + '
'; - } - - html += 'Extended Private Key: ' + json.key.xprivkey + '
'; - html += 'Mnemonic: ' + json.mnemonic.phrase + '
'; - - wallet.getBalance().then(function(balance) { - html += 'Confirmed Balance: ' - + bcoin.amount.btc(balance.confirmed) - + '
'; - - html += 'Unconfirmed Balance: ' - + bcoin.amount.btc(balance.unconfirmed) - + '
'; - - return wallet.getHistory(); - }).then(function(txs) { - return wallet.toDetails(txs); - }).then(function(txs) { - html += 'TXs:\n'; - wdiv.innerHTML = html; - - for (i = 0; i < txs.length; i++) { - tx = txs[i]; - - el = create( - '' - + tx.hash + ''); - - wdiv.appendChild(el); - setMouseup(el, tx.toJSON()); - } - }); -} - -node = new bcoin.fullnode({ - hash: true, - query: true, - prune: true, - network: 'main', - db: 'leveldb', - coinCache: 30000000, - logConsole: true, - workers: true, - workerFile: '/bcoin-worker.js', - logger: logger -}); - -wdb = node.use(bcoin.wallet.plugin); - -node.chain.on('block', addItem); -node.mempool.on('tx', addItem); - -node.open().then(function() { - return node.connect(); -}).then(function() { - node.startSync(); - - wdb.primary.on('balance', function() { - formatWallet(wdb.primary); - }); - - formatWallet(wdb.primary); -}).catch(function(err) { - throw err; -}); - -})(); diff --git a/browser/server.js b/browser/server.js index 2c8aa4b9d..f268c4557 100644 --- a/browser/server.js +++ b/browser/server.js @@ -1,25 +1,25 @@ 'use strict'; -const fs = require('../lib/utils/fs'); -const HTTPBase = require('../lib/http/base'); +const bweb = require('bweb'); +const fs = require('bfile'); const WSProxy = require('./wsproxy'); const index = fs.readFileSync(`${__dirname}/index.html`); -const indexjs = fs.readFileSync(`${__dirname}/index.js`); -const debug = fs.readFileSync(`${__dirname}/debug.html`); -const bcoin = fs.readFileSync(`${__dirname}/bcoin.js`); -const worker = fs.readFileSync(`${__dirname}/bcoin-worker.js`); +const app = fs.readFileSync(`${__dirname}/app.js`); +const worker = fs.readFileSync(`${__dirname}/worker.js`); const proxy = new WSProxy({ pow: process.argv.indexOf('--pow') !== -1, ports: [7312, 17312, 17412, 28333, 28901] }); -const server = new HTTPBase({ +const server = bweb.server({ port: Number(process.argv[2]) || 8080, sockets: false }); +server.use(server.router()); + proxy.on('error', (err) => { console.error(err.stack); }); @@ -28,30 +28,18 @@ server.on('error', (err) => { console.error(err.stack); }); -server.get('/favicon.ico', (req, res) => { - res.send(404, '', 'txt'); -}); - server.get('/', (req, res) => { res.send(200, index, 'html'); }); -server.get('/index.js', (req, res) => { - res.send(200, indexjs, 'js'); -}); - -server.get('/debug', (req, res) => { - res.send(200, debug, 'html'); -}); - -server.get('/bcoin.js', (req, res) => { - res.send(200, bcoin, 'js'); +server.get('/app.js', (req, res) => { + res.send(200, app, 'js'); }); -server.get('/bcoin-worker.js', (req, res) => { +server.get('/worker.js', (req, res) => { res.send(200, worker, 'js'); }); -proxy.attach(server.server); +proxy.attach(server.http); server.open(); diff --git a/browser/src/app.js b/browser/src/app.js new file mode 100644 index 000000000..894785266 --- /dev/null +++ b/browser/src/app.js @@ -0,0 +1,292 @@ +/* eslint-env browser */ +'use strict'; + +const Logger = require('blgr'); +const FullNode = require('../../lib/node/fullnode'); +const Amount = require('../../lib/btc/amount'); +const plugin = require('../../lib/wallet/plugin'); +const util = require('../../lib/utils/util'); +const ProxySocket = require('./proxysocket'); + +const body = document.getElementsByTagName('body')[0]; +const log = document.getElementById('log'); +const wdiv = document.getElementById('wallet'); +const tdiv = document.getElementById('tx'); +const floating = document.getElementById('floating'); +const send = document.getElementById('send'); +const newaddr = document.getElementById('newaddr'); +const chainState = document.getElementById('state'); +const rpc = document.getElementById('rpc'); +const cmd = document.getElementById('cmd'); +const items = []; + +let scrollback = 0; + +const logger = new Logger({ + level: 'debug', + console: true +}); + +logger.writeConsole = function writeConsole(level, module, args) { + const name = Logger.levelsByVal[level]; + const msg = this.fmt(args, false); + + if (++scrollback > 1000) { + log.innerHTML = ''; + scrollback = 1; + } + + const now = Math.floor(Date.now() / 1000); + + log.innerHTML += `${now} `; + + if (name === 'error') { + log.innerHTML += `[${name}] `; + if (module) + log.innerHTML += `(${module}) `; + log.innerHTML += ''; + } else { + log.innerHTML += `[${name}] `; + if (module) + log.innerHTML += `(${module}) `; + } + + log.innerHTML += escape(msg) + '\n'; + log.scrollTop = log.scrollHeight; +}; + +const node = new FullNode({ + hash: true, + query: true, + prune: true, + network: 'main', + memory: false, + logConsole: true, + workers: true, + workerFile: '/worker.js', + createSocket: (port, host) => { + const proto = global.location.protocol === 'https:' ? 'wss' : 'ws'; + const hostname = global.location.host; + return ProxySocket.connect(`${proto}://${hostname}`, port, host); + }, + logger: logger, + plugins: [plugin] +}); + +const {wdb} = node.require('walletdb'); +wdb.options.witness = true; + +window.onunhandledrejection = function onunhandledrejection(event) { + throw event.reason; +}; + +body.onmouseup = function onmouseup() { + floating.style.display = 'none'; +}; + +floating.onmouseup = function onmouseup(ev) { + ev.stopPropagation(); + return false; +}; + +function show(obj) { + if (obj instanceof Error) { + floating.innerHTML = obj.stack; + floating.style.display = 'block'; + return; + } + floating.innerHTML = escape(JSON.stringify(obj, null, 2)); + floating.style.display = 'block'; +} + +rpc.onsubmit = function onsubmit(ev) { + const text = cmd.value || ''; + const argv = text.trim().split(/\s+/); + const method = argv.shift(); + const params = []; + + cmd.value = ''; + + for (const arg of argv) { + let param; + try { + param = JSON.parse(arg); + } catch (e) { + param = arg; + } + params.push(param); + } + + (async () => { + try { + const result = await node.rpc.execute({ method, params }); + show(result); + } catch (e) { + show(e); + } + })(); + + ev.preventDefault(); + ev.stopPropagation(); + + return false; +}; + +send.onsubmit = function onsubmit(ev) { + const value = document.getElementById('amount').value; + const address = document.getElementById('address').value; + + const options = { + outputs: [{ + address: address, + value: Amount.value(value) + }] + }; + + (async () => { + try { + const mtx = await wdb.primary.createTX(options); + await wdb.primary.sign(mtx); + await node.relay(mtx.toTX()); + show(mtx); + } catch (e) { + show(e); + } + })(); + + ev.preventDefault(); + ev.stopPropagation(); + + return false; +}; + +newaddr.onmouseup = function onmouseup() { + (async () => { + try { + await wdb.primary.createReceive(); + formatWallet(wdb.primary); + } catch (e) { + show(e); + } + })(); +}; + +function kb(size) { + size /= 1000; + return size.toFixed(2) + 'kb'; +} + +function create(html) { + const el = document.createElement('div'); + el.innerHTML = html; + return el.firstChild; +} + +function escape(html, encode) { + return html + .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function addItem(item, entry) { + const height = entry ? entry.height : -1; + + if (items.length === 20) { + const el = items.shift(); + tdiv.removeChild(el); + el.onmouseup = null; + } + + const el = create('' + + `` + + `${item.rhash()} (${height} - ${kb(item.getSize())})` + + '' + ); + + tdiv.appendChild(el); + + setMouseup(el, item); + + items.push(el); + + chainState.innerHTML = '' + + `tx=${node.chain.db.state.tx} ` + + `coin=${node.chain.db.state.coin} ` + + `value=${Amount.btc(node.chain.db.state.value)}`; +} + +function setMouseup(el, obj) { + el.onmouseup = function onmouseup(ev) { + show(obj); + ev.stopPropagation(); + return false; + }; +} + +async function formatWallet(wallet) { + try { + await _formatWallet(wallet); + } catch (e) { + show(e); + } +} + +async function _formatWallet(wallet) { + const {key, mnemonic} = wallet.master.toJSON(node.network, true); + const account = await wallet.getAccount('default'); + const receive = account.receiveAddress(); + const nested = account.nestedAddress(); + const raddr = receive.toString(node.network); + const naddr = nested ? nested.toString(node.network) : null; + + let html = ''; + + html += 'Wallet
'; + + if (naddr) { + html += `Current Address (p2wpkh): ${raddr}
`; + html += `Current Address (p2wpkh behind p2sh): ${naddr}
`; + } else { + html += `Current Address: ${raddr}
`; + } + + html += `Extended Private Key: ${key.xprivkey}
`; + html += `Mnemonic: ${mnemonic.phrase}
`; + + const balance = await wallet.getBalance(); + + html += `Confirmed Balance: ${Amount.btc(balance.confirmed)}
`; + html += `Unconfirmed Balance: ${Amount.btc(balance.unconfirmed)}
`; + + const txs = await wallet.getHistory(); + const det = await wallet.toDetails(txs); + + html += 'TXs:\n'; + wdiv.innerHTML = html; + + for (const tx of det) { + const hash = util.revHex(tx.hash); + const el = create( + `${hash}`); + wdiv.appendChild(el); + setMouseup(el, tx.toJSON()); + } +} + +node.chain.on('block', addItem); +node.mempool.on('tx', addItem); + +(async () => { + await node.open(); + await node.connect(); + node.startSync(); + wdb.primary.on('balance', () => { + formatWallet(wdb.primary); + }); + formatWallet(wdb.primary); +})().catch((err) => { + throw err; +}); diff --git a/browser/src/proxysocket.js b/browser/src/proxysocket.js new file mode 100644 index 000000000..80f071773 --- /dev/null +++ b/browser/src/proxysocket.js @@ -0,0 +1,154 @@ +/*! + * proxysocket.js - wsproxy socket for bcoin + * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const assert = require('assert'); +const EventEmitter = require('events'); +const bsock = require('bsock'); + +class ProxySocket extends EventEmitter { + constructor(uri) { + super(); + + this.socket = bsock.socket(); + this.socket.reconnection = false; + this.socket.connect(uri); + + this.sendBuffer = []; + this.recvBuffer = []; + this.paused = false; + this.bytesWritten = 0; + this.bytesRead = 0; + this.remoteAddress = null; + this.remotePort = 0; + + this.closed = false; + + this.init(); + } + + init() { + this.socket.on('error', (err) => { + console.error(err); + }); + + this.socket.bind('tcp connect', (addr, port) => { + if (this.closed) + return; + this.remoteAddress = addr; + this.remotePort = port; + this.emit('connect'); + }); + + 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 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.on('disconnect', () => { + if (this.closed) + return; + this.closed = true; + this.emit('close'); + }); + } + + connect(port, host) { + this.remoteAddress = host; + this.remotePort = port; + + if (this.closed) { + this.sendBuffer.length = 0; + return; + } + + this.socket.fire('tcp connect', port, host); + + for (const chunk of this.sendBuffer) + this.write(chunk); + + this.sendBuffer.length = 0; + } + + setKeepAlive(enable, delay) { + this.socket.fire('tcp keep alive', enable, delay); + } + + setNoDelay(enable) { + this.socket.fire('tcp no delay', enable); + } + + setTimeout(timeout, callback) { + this.socket.fire('tcp set timeout', timeout); + if (callback) + this.on('timeout', callback); + } + + write(data, callback) { + this.bytesWritten += data.length; + + this.socket.fire('tcp data', data.toString('hex')); + + if (callback) + callback(); + + return true; + } + + pause() { + this.paused = true; + } + + resume() { + const recv = this.recvBuffer; + + 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 b92121174..3b219dd58 100644 --- a/browser/wsproxy.js +++ b/browser/wsproxy.js @@ -2,250 +2,203 @@ const assert = require('assert'); const net = require('net'); -const EventEmitter = require('events').EventEmitter; -const IOServer = require('socket.io'); -const util = require('../lib/utils/util'); -const digest = require('../lib/crypto/digest'); -const IP = require('../lib/utils/ip'); -const BufferWriter = require('../lib/utils/writer'); - -const TARGET = Buffer.from( - '0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - 'hex'); - -function WSProxy(options) { - if (!(this instanceof WSProxy)) - return new WSProxy(options); - - EventEmitter.call(this); - - if (!options) - options = {}; - - this.options = options; - this.target = options.target || TARGET; - this.pow = options.pow === true; - this.ports = new Set(); - this.io = new IOServer(); - this.sockets = new WeakMap(); - - if (options.ports) { - for (const port of options.ports) - this.ports.add(port); - } +const EventEmitter = require('events'); +const bsock = require('bsock'); +const IP = require('binet'); - this.init(); -} +class WSProxy extends EventEmitter { + constructor(options) { + super(); -Object.setPrototypeOf(WSProxy.prototype, EventEmitter.prototype); + if (!options) + options = {}; -WSProxy.prototype.init = function init() { - this.io.on('error', (err) => { - this.emit('error', err); - }); + this.options = options; + this.ports = new Set(); + this.io = bsock.server(); + this.sockets = new WeakMap(); - this.io.on('connection', (ws) => { - this.handleSocket(ws); - }); -}; + if (options.ports) { + for (const port of options.ports) + this.ports.add(port); + } -WSProxy.prototype.handleSocket = function handleSocket(ws) { - const state = new SocketState(this, ws); + this.init(); + } - // Use a weak map to avoid - // mutating the websocket object. - this.sockets.set(ws, state); + init() { + this.io.on('error', (err) => { + this.emit('error', err); + }); - ws.emit('info', state.toInfo()); + this.io.on('socket', (ws) => { + this.handleSocket(ws); + }); + } - ws.on('error', (err) => { - this.emit('error', err); - }); + handleSocket(ws) { + const state = new SocketState(this, ws); - ws.on('tcp connect', (port, host, nonce) => { - this.handleConnect(ws, port, host, nonce); - }); -}; + // Use a weak map to avoid + // mutating the websocket object. + this.sockets.set(ws, state); -WSProxy.prototype.handleConnect = function handleConnect(ws, port, host, nonce) { - const state = this.sockets.get(ws); - assert(state); + ws.on('error', (err) => { + this.emit('error', err); + }); - if (state.socket) { - this.log('Client is trying to reconnect (%s).', state.host); - return; + ws.bind('tcp connect', (port, host) => { + this.handleConnect(ws, port, host); + }); } - if (!util.isU16(port) - || typeof host !== 'string' - || host.length === 0) { - this.log('Client gave bad arguments (%s).', state.host); - ws.emit('tcp close'); - ws.disconnect(); - return; - } + handleConnect(ws, port, host) { + const state = this.sockets.get(ws); + assert(state); - if (this.pow) { - if (!util.isU32(nonce)) { - this.log('Client did not solve proof of work (%s).', state.host); - ws.emit('tcp close'); - ws.disconnect(); + if (state.socket) { + this.log('Client is trying to reconnect (%s).', state.host); return; } - const bw = new BufferWriter(); - bw.writeU32(nonce); - bw.writeBytes(state.snonce); - bw.writeU32(port); - bw.writeString(host, 'ascii'); + 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 pow = bw.render(); + 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 (digest.hash256(pow).compare(this.target) > 0) { - this.log('Client did not solve proof of work (%s).', state.host); - ws.emit('tcp close'); - ws.disconnect(); + 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; } - } - 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.emit('tcp error', { - message: 'EHOSTUNREACH', - code: 'EHOSTUNREACH' - }); - ws.disconnect(); - 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; + } - if (!IP.isRoutable(raw) || IP.isOnion(raw)) { - this.log( - 'Client is trying to connect to a bad ip: %s (%s).', - addr, state.host); - ws.emit('tcp error', { - message: 'ENETUNREACH', - code: 'ENETUNREACH' + 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); }); - ws.disconnect(); - return; - } - if (!this.ports.has(port)) { - this.log('Client is connecting to non-whitelist port (%s).', state.host); - ws.emit('tcp error', { - message: 'ENETUNREACH', - code: 'ENETUNREACH' + socket.on('data', (data) => { + ws.fire('tcp data', data.toString('hex')); }); - ws.disconnect(); - 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.emit('tcp error', { - message: 'ENETUNREACH', - code: 'ENETUNREACH' - }); - ws.disconnect(); - return; - } + 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(); + }); - socket.on('connect', () => { - ws.emit('tcp connect', socket.remoteAddress, socket.remotePort); - }); + ws.bind('tcp data', (data) => { + if (typeof data !== 'string') + return; + socket.write(Buffer.from(data, 'hex')); + }); - socket.on('data', (data) => { - ws.emit('tcp data', data.toString('hex')); - }); + ws.bind('tcp keep alive', (enable, delay) => { + socket.setKeepAlive(enable, delay); + }); - socket.on('error', (err) => { - ws.emit('tcp error', { - message: err.message, - code: err.code || null + ws.bind('tcp no delay', (enable) => { + socket.setNoDelay(enable); }); - }); - socket.on('timeout', () => { - ws.emit('tcp timeout'); - }); + ws.bind('tcp set timeout', (timeout) => { + socket.setTimeout(timeout); + }); - socket.on('close', () => { - this.log('Closing %s (%s).', state.remoteHost, state.host); - ws.emit('tcp close'); - ws.disconnect(); - }); + ws.bind('tcp pause', () => { + socket.pause(); + }); - ws.on('tcp data', (data) => { - if (typeof data !== 'string') - return; - socket.write(Buffer.from(data, 'hex')); - }); - - ws.on('tcp keep alive', (enable, delay) => { - socket.setKeepAlive(enable, delay); - }); - - ws.on('tcp no delay', (enable) => { - socket.setNoDelay(enable); - }); - - ws.on('tcp set timeout', (timeout) => { - socket.setTimeout(timeout); - }); - - ws.on('tcp pause', () => { - socket.pause(); - }); - - ws.on('tcp resume', () => { - socket.resume(); - }); - - ws.on('disconnect', () => { - socket.destroy(); - }); -}; - -WSProxy.prototype.log = function log(...args) { - process.stdout.write('wsproxy: '); - console.log(...args); -}; - -WSProxy.prototype.attach = function attach(server) { - this.io.attach(server); -}; - -function SocketState(server, socket) { - this.pow = server.pow; - this.target = server.target; - this.snonce = util.nonce(); - this.socket = null; - this.host = IP.normalize(socket.conn.remoteAddress); - this.remoteHost = null; + ws.bind('tcp resume', () => { + socket.resume(); + }); + + ws.on('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') - }; -}; - -SocketState.prototype.connect = function connect(port, host) { - this.socket = net.connect(port, host); - this.remoteHost = IP.toHostname(host, port); - return this.socket; -}; +class SocketState { + constructor(server, socket) { + this.socket = null; + this.host = socket.host; + this.remoteHost = null; + } + + connect(port, host) { + this.socket = net.connect(port, host); + this.remoteHost = IP.toHostname(host, port); + return this.socket; + } +} module.exports = WSProxy; diff --git a/docs/Beginner's-Guide.md b/docs/Beginner's-Guide.md deleted file mode 100644 index 90d197357..000000000 --- a/docs/Beginner's-Guide.md +++ /dev/null @@ -1,147 +0,0 @@ -## Introduction - -Bcoin is an _alternative_ implementation of the bitcoin protocol, written in node.js. It is a full node which can be used for full blockchain validation and is aware of all known consensus rules. - -## Requirements - -- Linux, OSX, or Windows (\*) (\*\*) -- node.js >=v5.0.0 -- npm >=v4.0.0 -- python2 (for node-gyp) -- gcc/g++ (for leveldb and secp256k1) -- git (optional, see below) - -(\*): Note that bcoin works best with unix-like OSes, and has not yet been thoroughly tested on windows. - -(\*\*): The BSDs and Solaris have also not been tested yet, but should work in theory. - -## Build & Install - -Bcoin is meant to be installed via npm, but for the security conscious, it may be better to clone from github. All tagged commits for release should be signed by @chjj's [PGP key][keybase] (`B4B1F62DBAC084E333F3A04A8962AB9DE6666BBD`). Signed copies of node.js are available from [nodejs.org][node], or from your respective OS's package repositories. - -### Installing via NPM - -``` bash -$ npm install -g bcoin --production -``` - -### Installing via Git - -``` bash -$ curl https://keybase.io/chjj/pgp_keys.asc | gpg --import -$ git clone git://github.com/bcoin-org/bcoin.git -$ cd bcoin -$ git tag -... -v1.0.0-alpha # latest version -$ git tag -v v1.0.0-alpha # verify signature -$ git checkout v1.0.0-alpha -$ npm install -g --production -``` - -### Troubleshooting - -If the build fails compilation for `bcoin-native` or `secp256k1-node` __validation will be slow__ (a block verification which should take 1 second on consumer grade hardware may take up to 15 seconds). Bcoin will throw a warning on boot if it detects a build failure. If you run into this issue, please post an issue on the repo. - -## Starting up your first bcoin node - -If bcoin is installed globally, `$ bcoin` should be in your PATH. If not, the bcoin bootstrap script resides in `/path/to/bcoin/bin/bcoin`. - -``` bash -$ bcoin -``` - -Will run a bcoin node as the foreground process, displaying all debug logs. - -To run as a daemon: - -``` bash -$ bcoin --daemon -``` - -This will start up a full node, complete with: a blockchain, mempool, miner, p2p server, wallet server, and an HTTP REST+RPC server. - -All logs will be written to `~/.bcoin/debug.log` by default. - -By default, the http server will only listen on `127.0.0.1:8332`. No auth will be required if an API key was not passed in. If you listen on any other host, auth will be required and an API key will be auto-generated if one was not passed in. - -## Listening Externally - -To listen publicly on the HTTP server, `--http-host=0.0.0.0` (ipv4) or `--http-host=::` (ipv4 and ipv6) can be passed. Additionally this: `--http-port=1337` can set the port. - -To advertise your node on the P2P network `--public-host=[your-public-ip]` and `--public-port=[your-public-port]` may be passed. - -## Using an API Key - -If listening publicly on the HTTP server, an API key is required. One will be generated and reported in the logs automatically if no key was chosen. An api key can be chosen with the `--api-key` option. - -Example: - -``` bash -$ bcoin --http-host=0.0.0.0 --api-key hunter2 --daemon -``` - -API keys are used with HTTP Basic Auth: - -``` bash -$ curl http://x:hunter2@localhost:8332/ -``` - -Bcoin CLI is the prepackaged tool for hitting both the REST and RPC api. - -``` bash -$ bcoin cli info --api-key hunter2 -$ bcoin rpc getblockchaininfo --api-key hunter2 -``` - -## Using Tor/SOCKS - -Bcoin has native support for SOCKS proxies, and will accept a `--proxy` option in the format of `--proxy=[user]:[pass]@host:port`. - -Passing the `--onion` option tells bcoin that the SOCKS proxy is a Tor socks proxy, and will enable Tor resolution for DNS lookups, as well as try to connect to `.onion` addresses found on the P2P network. - -``` bash -$ bcoin --proxy joe:hunter2@127.0.0.1:9050 --onion -``` - -### Running bcoin as a tor hidden service - -Your hidden service must first be configured with `tor`. Once you have the `.onion` address, it can be passed into `--public-host` in the form of `--public-host foo.onion`. - -## Target Nodes - -It's often desirable to run behind several trusted bitcoin nodes. To select permanent nodes to connect to, the `--nodes` option is available: - -``` bash -$ bcoin --nodes foo.example.com:8333,1.2.3.4:8333,5.6.7.8:8333 -``` - -If chosen, bcoin will _always_ try to connect to these nodes as outbound peers. They are top priority and whitelisted (not susceptible to permanent bans, only disconnections). - -To _only_ connect to these nodes. `--max-outbound` could be set to 3: - -``` bash -$ bcoin --nodes foo.example.com,1.2.3.4,5.6.7.8 --max-outbound 3 -``` - -## Disabling Listening - -To avoid accepting connections on the P2P network altogether, `--listen=false` can be passed to bcoin. - -### Selfish Mode - -Bcoin also supports a "selfish" mode. In this mode, bcoin still has full blockchain and mempool validation, but network services are disabled: it will not relay transactions or serve blocks to anyone. - -``` bash -$ bcoin --selfish --listen=false -``` - -Note: Selfish mode is not recommended. We encourage you to _help_ the network by relaying transactions and blocks. At the same time, selfish mode does have its uses if you do not have the bandwidth to spare, or if you're absolutely worried about potential DoS attacks. - -## Further Configuration - -See [Configuration][configuration]. - -[keybase]: https://keybase.io/chjj#show-public -[node]: https://nodejs.org/dist/v7.5.0/ -[configuration]: Configuration.md diff --git a/docs/CLI.md b/docs/CLI.md deleted file mode 100644 index 38d497fd0..000000000 --- a/docs/CLI.md +++ /dev/null @@ -1,235 +0,0 @@ -Bcoin ships with bcoin-cli as its default HTTP client for command line access. - -## Configuration - -Examples: - -``` bash -$ export BCOIN_API_KEY=hunter2 -$ export BCOIN_NETWORK=main -$ export BCOIN_URI=http://localhost:8332 -$ bcoin cli info -``` - -``` bash -$ bcoin cli info --api-key=hunter2 --uri=http://localhost -``` - -``` bash -$ echo 'api-key: hunter2' > ~/cli.conf -$ bcoin cli info --config=~/cli.conf -``` - -## Examples - -``` bash -$ export BCOIN_API_KEY=your-api-key - -# View the genesis block -$ bcoin cli block 0 - -# View the mempool -$ bcoin cli mempool - -# View primary wallet -$ bcoin wallet get - -# View transaction history -$ bcoin wallet history - -# Send a transaction -$ bcoin wallet send [address] 0.01 - -# View balance -$ bcoin wallet balance - -# Derive new address -$ bcoin wallet address - -# Create a new account -$ bcoin wallet account create foo - -# Send from account -$ bcoin wallet send [address] 0.01 --account=foo -``` - -RPC examples: - -``` bash -$ bcoin rpc getblockchaininfo -$ bcoin rpc getwalletinfo -$ bcoin rpc getpeerinfo -$ bcoin rpc getbalance -$ bcoin rpc listtransactions -$ bcoin rpc sendtoaddress [address] 0.01 -``` - -## Commands - -bcoin-cli commands are split into 3 categories: cli, rpc, and wallet. - -### Top-level Commands - -- `info`: Get server info. -- `wallets`: List all wallets. -- `wallet create [id]`: Create wallet. -- `broadcast [tx-hex]`: Broadcast transaction. -- `mempool`: Get mempool snapshot. -- `tx [hash/address]`: View transactions. -- `coin [hash+index/address]`: View coins. -- `block [hash/height]`: View block. -- `rescan [height]`: Rescan for transactions. -- `reset [height/hash]`: Reset chain to desired block. -- `resend`: Resend pending transactions. -- `backup [path]`: Backup the wallet db. -- `wallet [command]`: Execute wallet command. -- `rpc [command] [args]`: Execute RPC command. - -### Wallet Commands - -- `listen`: Listen for events. -- `get`: View wallet. -- `master`: View wallet master key. -- `shared add [xpubkey]`: Add key to wallet. -- `shared remove [xpubkey]`: Remove key from wallet. -- `balance`: Get wallet balance. -- `history`: View TX history. -- `pending`: View pending TXs. -- `coins`: View wallet coins. -- `account list`: List account names. -- `account create [account-name]`: Create account. -- `account get [account-name]`: Get account details. -- `address`: Derive new receiving address. -- `change`: Derive new change address. -- `nested`: Derive new nested address. -- `retoken`: Create new api key. -- `send [address] [value]`: Send transaction. -- `mktx [address] [value]`: Create transaction. -- `sign [tx-hex]`: Sign transaction. -- `zap [age?]`: Zap pending wallet TXs. -- `tx [hash]`: View transaction details. -- `blocks`: List wallet blocks. -- `block [height]`: View wallet block. -- `view [tx-hex]`: Parse and view transaction. -- `import [wif|hex]`: Import private or public key. -- `watch [address]`: Import an address. -- `key [address]`: Get wallet key by address. -- `dump [address]`: Get wallet key WIF by address. -- `lock`: Lock wallet. -- `unlock [passphrase] [timeout?]`: Unlock wallet. -- `resend`: Resend pending transactions. - -### RPC Commands - -Bcoin implements nearly all bitcoind calls along with some custom calls. - -- `stop` -- `help` -- `getblockchaininfo` -- `getbestblockhash` -- `getblockcount` -- `getblock` -- `getblockhash` -- `getblockheader` -- `getchaintips` -- `getdifficulty` -- `getmempoolancestors` -- `getmempooldescendants` -- `getmempoolentry` -- `getmempoolinfo` -- `getrawmempool` -- `gettxout` -- `gettxoutsetinfo` -- `verifychain` -- `invalidateblock` -- `reconsiderblock` -- `getnetworkhashps` -- `getmininginfo` -- `prioritisetransaction` -- `getwork` -- `getworklp` -- `getblocktemplate` -- `submitblock` -- `setgenerate` -- `getgenerate` -- `generate` -- `generatetoaddress` -- `estimatefee` -- `estimatepriority` -- `estimatesmartfee` -- `estimatesmartpriority` -- `getinfo` -- `validateaddress` -- `createmultisig` -- `createwitnessaddress` -- `verifymessage` -- `signmessagewithprivkey` -- `setmocktime` -- `getconnectioncount` -- `ping` -- `getpeerinfo` -- `addnode` -- `disconnectnode` -- `getaddednodeinfo` -- `getnettotals` -- `getnetworkinfo` -- `setban` -- `listbanned` -- `clearbanned` -- `getrawtransaction` -- `createrawtransaction` -- `decoderawtransaction` -- `decodescript` -- `sendrawtransaction` -- `signrawtransaction` -- `gettxoutproof` -- `verifytxoutproof` -- `fundrawtransaction` -- `resendwallettransactions` -- `abandontransaction` -- `addmultisigaddress` -- `addwitnessaddress` -- `backupwallet` -- `dumpprivkey` -- `dumpwallet` -- `encryptwallet` -- `getaccountaddress` -- `getaccount` -- `getaddressesbyaccount` -- `getbalance` -- `getnewaddress` -- `getrawchangeaddress` -- `getreceivedbyaccount` -- `getreceivedbyaddress` -- `gettransaction` -- `getunconfirmedbalance` -- `getwalletinfo` -- `importprivkey` -- `importwallet` -- `importaddress` -- `importprunedfunds` -- `importpubkey` -- `keypoolrefill` -- `listaccounts` -- `listaddressgroupings` -- `listlockunspent` -- `listreceivedbyaccount` -- `listreceivedbyaddress` -- `listsinceblock` -- `listtransactions` -- `listunspent` -- `lockunspent` -- `move` -- `sendfrom` -- `sendmany` -- `sendtoaddress` -- `setaccount` -- `settxfee` -- `signmessage` -- `walletlock` -- `walletpassphrasechange` -- `walletpassphrase` -- `removeprunedfunds` -- `getmemory` -- `selectwallet` -- `setloglevel` \ No newline at end of file diff --git a/docs/Configuration.md b/docs/Configuration.md deleted file mode 100644 index b094e1d0f..000000000 --- a/docs/Configuration.md +++ /dev/null @@ -1,98 +0,0 @@ -By default, the mainnet bcoin config file will reside in ~/.bcoin/bcoin.conf. - -All bcoin configuration options work in the config file, CLI arguments, and -process environment (with a `BCOIN_` prefix). - -## Datadir/Prefix - -Bcoin's datadir is determined by the `prefix` option. - -Example: - -``` bash -$ bcoin --prefix ~/.bcoin_spv --spv -``` - -Will create a datadir of `~/.bcoin_spv`, containing a chain database, wallet database and log file. - -## Common Options - -- `config`: Points to a custom config file, not in the prefix directory. -- `network`: Which network's chainparams to use for the node (main, testnet, regtest, or segnet4) (default: main). -- `workers`: Whether to use a worker process pool for transaction verification (default: true). -- `workers-size`: Number of worker processes to spawn for transaction verification. By default, the worker pool will be sized based on the number of CPUs/cores in the machine. -- `workers-timeout`: Worker process execution timeout in milliseconds (default: 120000). -- `sigcache-size`: Max number of items in signature cache. - -## Node Options - -- `prefix`: The data directory (stores databases, logs, and configs) (default=~/.bcoin). -- `db`: Which database backend to use (default=leveldb). -- `max-files`: Max open files for leveldb. Higher generally means more disk page cache benefits, but also more memory usage (default: 64). -- `cache-size`: Size (in MB) of leveldb cache and write buffer (default: 32mb). - -## Logger Options - -- `log-level`: `error`, `warning`, `info`, `debug`, or `spam` (default: debug). -- `log-console`: `true` or `false` - whether to actually write to stdout/stderr - if foregrounded (default: true). -- `log-file`: Whether to use a log file (default: true). - -## Chain Options - -Note that certain chain options affect the format and indexing of the chain database and must be passed in consistently each time. - -- `prune`: Prune from the last 288 blocks (default: false). -- `checkpoints`: Use checkpoints and getheaders for the initial sync (default: true). -- `coin-cache`: The size (in MB) of the in-memory UTXO cache. By default, there is no UTXO cache enabled. To get a good number of cache hits per block, the coin cache has to be fairly large (60-100mb recommended at least). -- `index-tx`: Index transactions (enables transaction endpoints in REST api) (default: false). -- `index-address`: Index transactions and utxos by address (default: false). - -## Mempool Options - -- `mempool-size`: Max mempool size in MB (default: 100). -- `replace-by-fee`: Allow replace-by-fee transactions (default: false). -- `persistent-mempool`: Save mempool to disk and read into memory on boot (default: false). - -## Pool Options - -- `selfish`: Enable "selfish" mode (no relaying of txes or blocks) (default: false). -- `compact`: Enable compact block relay (default: true). -- `bip37`: Enable serving of bip37 merkleblocks (default: false). -- `bip151`: Enable bip151 peer-to-peer encryption (default: false). -- `listen`: Accept incoming connections (default: true). -- `max-outbound`: Max number of outbound connections (default: 8). -- `max-inbound`: Max number of inbound connections (default: 30). -- `seeds`: Custom list of DNS seeds (comma-separated). -- `host`: Host to listen on (default: 0.0.0.0). -- `port`: Port to listen on (default: 8333). -- `public-host`: Public host to advertise on network. -- `public-port`: Public port to advertise on network. -- `bip150`: Enable bip150 peer auth (default: false). -- `identity-key`: BIP150 identity key (32 byte hex string). -- `auth-peers`: Path to `authorized-peers` file for BIP150. -- `known-peers`: Path to `known-peers` file for BIP150. -- `nodes`: List of target nodes to connect to (comma-separated). - -## Miner Options - -- `coinbase-flags`: Coinbase flags (default: mined by bcoin). -- `coinbase-address`: List of payout addresses, randomly selected during block creation (comma-separated). -- `max-block-weight`: Max block weight to mine (default: 4000000). -- `reserved-block-weight`: Amount of space reserved for coinbase (default: 4000). -- `reserved-block-sigops`: Amount of sigops reserved for coinbase (default: 400). - -## HTTP - -- `http-host`: HTTP host to listen on (default: 127.0.0.1). -- `http-port`: HTTP port to listen on (default: 8332). -- `ssl-cert`: Path to SSL cert. -- `ssl-key`: Path to SSL key. -- `service-key`: Service key (used for accessing wallet system only). -- `api-key`: API key (used for accessing all node APIs). -- `wallet-auth`: Enable token auth for wallets (default: false). -- `no-auth`: Disable auth for API server and wallets (default: false). - -## Sample Config File - -See https://github.com/bcoin-org/bcoin/blob/master/etc/sample.conf. diff --git a/docs/Examples/client-api.js b/docs/Examples/client-api.js deleted file mode 100644 index 2dbf1d0e2..000000000 --- a/docs/Examples/client-api.js +++ /dev/null @@ -1,120 +0,0 @@ -'use strict'; - -const bcoin = require('../..'); -const encoding = bcoin.encoding; -const co = bcoin.co; -const Outpoint = bcoin.outpoint; -const MTX = bcoin.mtx; -const HTTP = bcoin.http; -const FullNode = bcoin.fullnode; -const plugin = bcoin.wallet.plugin; - -const node = new FullNode({ - network: 'regtest', - apiKey: 'foo', - walletAuth: true, - db: 'memory' -}); - -node.use(plugin); - -const wallet = new HTTP.Wallet({ - network: 'regtest', - apiKey: 'foo' -}); - -async function fundWallet(wdb, addr) { - // Coinbase - const mtx = new MTX(); - mtx.addOutpoint(new Outpoint(encoding.NULL_HASH, 0)); - mtx.addOutput(addr, 50460); - mtx.addOutput(addr, 50460); - mtx.addOutput(addr, 50460); - mtx.addOutput(addr, 50460); - - const tx = mtx.toTX(); - - wallet.once('balance', (balance) => { - console.log('New Balance:'); - console.log(balance); - }); - - wallet.once('address', (receive) => { - console.log('New Receiving Address:'); - console.log(receive); - }); - - wallet.once('tx', (details) => { - console.log('New Wallet TX:'); - console.log(details); - }); - - await wdb.addTX(tx); - await co.timeout(300); -} - -async function sendTX(addr, value) { - const options = { - rate: 10000, - outputs: [{ - value: value, - address: addr - }] - }; - - const tx = await wallet.send(options); - - return tx.hash; -} - -async function callNodeApi() { - const info = await wallet.client.getInfo(); - - console.log('Server Info:'); - console.log(info); - - const json = await wallet.client.rpc.execute('getblocktemplate', []); - - console.log('Block Template (RPC):'); - console.log(json); -} - -(async () => { - const wdb = node.require('walletdb'); - - await node.open(); - - const w = await wallet.create({ id: 'test' }); - - console.log('Wallet:'); - console.log(w); - - // Fund default account. - await fundWallet(wdb, w.account.receiveAddress); - - const balance = await wallet.getBalance(); - - console.log('Balance:'); - console.log(balance); - - const acct = await wallet.createAccount('foo'); - - console.log('Account:'); - console.log(acct); - - // Send to our new account. - const hash = await sendTX(acct.receiveAddress, 10000); - - console.log('Sent TX:'); - console.log(hash); - - const tx = await wallet.getTX(hash); - - console.log('Sent TX details:'); - console.log(tx); - - await callNodeApi(); -})().catch((err) => { - console.error(err.stack); - process.exit(1); -}); diff --git a/docs/Examples/connect-to-the-p2p-network.js b/docs/Examples/connect-to-the-p2p-network.js deleted file mode 100644 index 8024fa66d..000000000 --- a/docs/Examples/connect-to-the-p2p-network.js +++ /dev/null @@ -1,98 +0,0 @@ -'use strict'; -const bcoin = require('../..').set('main'); -const Chain = bcoin.chain; -const Mempool = bcoin.mempool; -const Pool = bcoin.pool; - -// Create a blockchain and store it in leveldb. -// `db` also accepts `rocksdb` and `lmdb`. -const prefix = process.env.HOME + '/my-bcoin-environment'; -const chain = new Chain({ - db: 'leveldb', - location: prefix + '/chain', - network: 'main' -}); - -const mempool = new Mempool({ chain: chain }); - -// Create a network pool of peers with a limit of 8 peers. -const pool = new Pool({ - chain: chain, - mempool: mempool, - maxPeers: 8 -}); - -// Open the pool (implicitly opens mempool and chain). -(async function() { - await pool.open(); - - // Connect, start retrieving and relaying txs - await pool.connect(); - - // Start the blockchain sync. - pool.startSync(); - - // Watch the action - chain.on('block', (block) => { - console.log('Connected block to blockchain:'); - console.log(block); - }); - - mempool.on('tx', (tx) => { - console.log('Added tx to mempool:'); - console.log(tx); - }); - - pool.on('tx', (tx) => { - console.log('Saw transaction:'); - console.log(tx.rhash); - }); -})(); - -// Start up a testnet sync in-memory -// while we're at it (because we can). - -const tchain = new Chain({ - network: 'testnet', - db: 'memory' -}); - -const tmempool = new Mempool({ - network: 'testnet', - chain: tchain -}); - -const tpool = new Pool({ - network: 'testnet', - chain: tchain, - mempool: tmempool, - size: 8 -}); - -(async function() { - await tpool.open(); - - // Connect, start retrieving and relaying txs - await tpool.connect(); - - // Start the blockchain sync. - tpool.startSync(); - - tchain.on('block', (block) => { - console.log('Added testnet block:'); - console.log(block); - }); - - tmempool.on('tx', (tx) => { - console.log('Added testnet tx to mempool:'); - console.log(tx); - }); - - tpool.on('tx', (tx) => { - console.log('Saw testnet transaction:'); - console.log(tx); - }); -})().catch((err) => { - console.error(err.stack); - process.exit(1); -}); diff --git a/docs/Examples/fullnode-and-wallet.js b/docs/Examples/fullnode-and-wallet.js deleted file mode 100644 index 50d64198f..000000000 --- a/docs/Examples/fullnode-and-wallet.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; -const bcoin = require('../..').set('main'); -const walletPlugin = bcoin.wallet.plugin; - -const node = bcoin.fullnode({ - checkpoints: true, - // Primary wallet passphrase - passsphrase: 'node', - logLevel: 'info' -}); - -node.use(walletPlugin); - -// We get a lot of errors sometimes, -// usually from peers hanging up on us. -// Just ignore them for now. -node.on('error', (err) => { - ; -}); - -// New Address we'll be sending to. -const newReceiving = 'AddressHere'; - -// Start the node -(async () => { - await node.open(); - - const options = { - id: 'mywallet', - passphrase: 'foo', - witness: false, - type: 'pubkeyhash' - }; - - const walletdb = node.require('walletdb'); - - await walletdb.open(); - const wallet = await walletdb.create(options); - - console.log('Created wallet with address: %s', wallet.getAddress('base58')); - - await node.connect(); - - // Start syncing the blockchain - node.startSync(); - - // Wait for balance and send it to a new address. - wallet.once('balance', async (balance) => { - // Create a transaction, fill - // it with coins, and sign it. - const options = { - subtractFee: true, - outputs: [{ - address: newReceiving, - value: balance.total - }] - }; - - const tx = await wallet.createTX(options); - const stx = await wallet.sign(tx, 'foo'); - - console.log('sending tx:'); - console.log(stx); - - await node.sendTX(stx); - console.log('tx sent!'); - }); - - node.chain.on('block', (block) => { - ; - }); - - node.mempool.on('tx', (tx) => { - ; - }); - - node.chain.on('full', () => { - node.mempool.getHistory().then(console.log); - }); -})().catch((err) => { - console.error(err.stack); - process.exit(1); -}); diff --git a/docs/Examples/get-tx-from-chain.js b/docs/Examples/get-tx-from-chain.js deleted file mode 100644 index 39c1646fc..000000000 --- a/docs/Examples/get-tx-from-chain.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -const path = require('path'); -const bcoin = require('../..'); -const Chain = bcoin.chain; -const Logger = bcoin.logger; -const util = bcoin.util; - -const HOME = process.env.HOME; - -// Setup logger to see what's Bcoin doing. -const logger = new Logger({ - level: 'debug' -}); - -// Create chain for testnet, specify chain directory -const chain = new Chain({ - logger: logger, - network: 'testnet', - db: 'leveldb', - prefix: path.join(HOME, '.bcoin/testnet'), - indexTX: true, - indexAddress: true -}); - -(async () => { - await logger.open(); - await chain.open(); - - console.log('Current height:', chain.height); - - const entry = await chain.getEntry(50000); - console.log('Block at 50k:', entry); - - // eslint-disable-next-line max-len - const txhash = '4dd628123dcde4f2fb3a8b8a18b806721b56007e32497ebe76cde598ce1652af'; - const txmeta = await chain.db.getMeta(util.revHex(txhash)); - const tx = txmeta.tx; - const coinview = await chain.db.getSpentView(tx); - - console.log(`Tx with hash ${txhash}:`, txmeta); - console.log(`Tx input: ${tx.getInputValue(coinview)},` + - ` output: ${tx.getOutputValue()}, fee: ${tx.getFee(coinview)}`); - - // eslint-disable-next-line max-len - const bhash = '00000000077eacdd2c803a742195ba430a6d9545e43128ba55ec3c80beea6c0c'; - const block = await chain.db.getBlock(util.revHex(bhash)); - console.log(`Block with hash ${bhash}:`, block); -})().catch((err) => { - console.error(err.stack); - process.exit(1); -}); diff --git a/docs/Examples/miner-configs.js b/docs/Examples/miner-configs.js deleted file mode 100644 index b6ad820b4..000000000 --- a/docs/Examples/miner-configs.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict'; - -const bcoin = require('../..'); -const KeyRing = bcoin.keyring; -const WorkerPool = bcoin.workerpool; -const Chain = bcoin.chain; -const Miner = bcoin.miner; - -const key = KeyRing.generate('regtest'); - -const workers = new WorkerPool({ - enabled: true -}); - -const chain = new Chain({ - network: 'regtest', - workers: workers -}); - -const miner = new Miner({ - chain: chain, - addresses: [key.getAddress()], - coinbaseFlags: 'my-miner', - workers: workers -}); - -(async () => { - await miner.open(); - - const tmpl = await miner.createBlock(); - - console.log('Block template:'); - console.log(tmpl); - - const job = await miner.createJob(); - const block = await job.mineAsync(); - - console.log('Mined block:'); - console.log(block); - console.log(block.txs[0]); - - await chain.add(block); - - console.log('New tip:'); - console.log(chain.tip); -})().catch((err) => { - console.error(err.stack); - process.exit(1); -}); diff --git a/docs/Examples/spv-sync-wallet.js b/docs/Examples/spv-sync-wallet.js deleted file mode 100644 index 75f5992d5..000000000 --- a/docs/Examples/spv-sync-wallet.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; - -const bcoin = require('../..'); -const Chain = bcoin.chain; -const Pool = bcoin.pool; -const WalletDB = bcoin.walletdb; - -bcoin.set('testnet'); - -// SPV chains only store the chain headers. -const chain = Chain({ - db: 'leveldb', - location: process.env.HOME + '/spvchain', - spv: true -}); - -const pool = new Pool({ - chain: chain, - spv: true, - maxPeers: 8 -}); - -const walletdb = new WalletDB({ db: 'memory' }); - -(async () => { - await pool.open(); - await walletdb.open(); - - const wallet = await walletdb.create(); - - console.log('Created wallet with address %s', wallet.getAddress('base58')); - - // Add our address to the spv filter. - pool.watchAddress(wallet.getAddress()); - - // Connect, start retrieving and relaying txs - await pool.connect(); - - // Start the blockchain sync. - pool.startSync(); - - pool.on('tx', async (tx) => { - console.log('received TX'); - - await walletdb.addTX(tx); - console.log('Transaction added to walletDB'); - }); - - wallet.on('balance', (balance) => { - console.log('Balance updated.'); - console.log(bcoin.amount.btc(balance.unconfirmed)); - }); -})().catch((err) => { - console.error(err.stack); - process.exit(1); -}); diff --git a/docs/README.md b/docs/README.md index 997bee266..266ddeae1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,51 +1,44 @@ -Welcome to the bcoin docs! +# Documentation -## Getting Started -- [Getting Started][getting-started] -- [Configuration][configuration] -- [Wallet System][wallet-system] -- [Design][design] -- [Guides][guides] +## Table of contents -## Running -- [Bcoin CLI][cli] -- [Running in the Browser][browser] -- [REST and RPC API][rest-rpc] +- [Getting started](getting-started.md) +- [Configuration](configuration.md) +- [Wallet system](wallet-system.md) +- [Design](design.md) +- [Node and wallet CLI](cli.md) -## Code Examples -- [Simple Fullnode][example-simple-fullnode] -- [Connect to Peer][example-connect-peer] -- [Connecting to the P2P Network][example-p2p] -- [Creating a Blockchain and Mempool][example-blockchain] -- [Wallet with Dummy TX][example-wallet-dummy] -- [Fullnode Object][example-fullnode-wallet] -- [SPV Sync][example-spv] -- [Plugin Example][example-peers-plugin] -- [Client API Usage][example-client-api] -- [Miner with WorkerPool][example-miner-configs] -- [Create and Sign TX][example-tx-create-sign] -- [Get Transaction from Chain][example-tx-from-chain] +## External links +- [Guides](https://bcoin.io/guides.html) +- [Running in the browser](https://bcoin.io/guides/browser.html) +- [Node and wallet REST and RPC API](https://bcoin.io/api-docs/index.html) -[getting-started]: Beginner's-Guide.md -[configuration]: Configuration.md -[design]: Design.md -[wallet-system]: Wallet-System.md -[guides]: http://bcoin.io/guides.html +## Library examples -[cli]: CLI.md -[browser]: Running-in-the-browser.md -[rest-rpc]: http://bcoin.io/api-docs/index.html#introduction +These code examples are designed to demonstrate how to integrate bcoin modules +with minimal configuration. -[example-p2p]: Examples/connect-to-the-p2p-network.js -[example-blockchain]: Examples/create-a-blockchain-and-mempool.js -[example-fullnode-wallet]: Examples/fullnode-and-wallet.js -[example-spv]: Examples/spv-sync-wallet.js -[example-wallet-dummy]: Examples/wallet.js -[example-peers-plugin]: Examples/peers-plugin.js -[example-client-api]: Examples/client-api.js -[example-miner-configs]: Examples/miner-configs.js -[example-connect-peer]: Examples/connect-to-peer.js -[example-simple-fullnode]: Examples/fullnode.js -[example-tx-create-sign]: Examples/create-sign-tx.js -[example-tx-from-chain]: Examples/get-tx-from-chain.js +- [Simple fullnode](examples/fullnode.js) - Creates a `FullNode` object and + connects to `testnet`. +- [Connect to peer](examples/connect-to-peer.js) - Connects to a user-defined + peer in `regtest` mode. +- [Connecting to the P2P network](examples/connect-to-the-p2p-network.js) - + Creates `chain`, `pool`, and `mempool` objects for both main and + testnet networks. +- [Creating a blockchain and mempool](examples/create-a-blockchain-and-mempool.js) - + Mines a block from the mempool to the chain. +- [Wallet with dummy TX](examples/wallet.js) - Adds a "dummy" transaction to + the wallet and `tx` event is handled. +- [SPV sync](examples/spv-sync-wallet.js) - A transaction matching the SPV + node's bloom filter is broadcast by a minimal full node to the SPV node. +- [Plugin example](examples/peers-plugin.js) - Demonstrates the `plugin` + feature of bcoin's `node` object. +- [Client API usage](examples/client-api.js) - Demonstrates usage of the node + and wallet API. +- [Create and sign TX](examples/create-sign-tx.js) - Demonstrates how to use + `mtx` and `keyring` modules to sign a transaction. +- [Get transaction from chain](examples/get-tx-from-chain.js) - Connects to + live testnet network and syncs the first 1000 blocks with tx indexing active. +- [Create watch only wallet](examples/watch-only-wallet.js) - Imports an `xpub` + into a new watch-only wallet that can derive addresses. diff --git a/docs/Running-in-the-browser.md b/docs/Running-in-the-browser.md deleted file mode 100644 index 0e3a525d6..000000000 --- a/docs/Running-in-the-browser.md +++ /dev/null @@ -1,16 +0,0 @@ -Because bcoin is written in node.js, it is capable of being browserified. - -## Running a full node in the browser - -``` bash -$ cd ~/bcoin -$ make # Browserify bcoin -$ 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. \ No newline at end of file diff --git a/docs/Wallet-System.md b/docs/Wallet-System.md deleted file mode 100644 index 0655c0852..000000000 --- a/docs/Wallet-System.md +++ /dev/null @@ -1,22 +0,0 @@ -Wallet REST API: [REST-RPC-API](REST-RPC-API.md) - -## Notes on wallet system - -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. diff --git a/docs/cli.md b/docs/cli.md new file mode 100644 index 000000000..6adde7469 --- /dev/null +++ b/docs/cli.md @@ -0,0 +1,73 @@ +# Node and wallet CLI + +Bcoin ships with [bclient](https://github.com/bcoin-org/bclient) as its +default client to the [API](https://bcoin.io/api-docs) for command +line access. + +## Configuration + +Using environment variables: +```bash +$ export BCOIN_API_KEY=hunter2 +$ export BCOIN_NETWORK=testnet +$ bcoin --daemon +$ bcoin-cli info +``` + +With command-line arguments: + +```bash +$ bcoin-cli --network=testnet --api-key=hunter2 info +``` + +You can also use `~/.bcoin/bcoin.conf` for configuration options, +see [Configuration](configuration.md) for the full details. + +## Examples + +Common node commands: + +```bash +# View the genesis block +$ bcoin-cli block 0 + +# View the mempool +$ bcoin-cli mempool + +# Execute an RPC command to list network peers +$ bcoin-cli rpc getpeerinfo +``` + +Common wallet commands: + +```bash +# View primary wallet +$ bwallet-cli get + +# View transaction history +$ bwallet-cli history + +# Send a transaction +$ bwallet-cli send
0.01 + +# View balance +$ bwallet-cli balance + +# Derive new address +$ bwallet-cli address + +# Create a new account +$ bwallet-cli account create foo + +# Send from account +$ bwallet-cli send
0.01 --account=foo +``` + +Get more help: + +```bash +$ bcoin-cli help +$ bcoin-cli rpc help +$ bwallet-cli help +$ bwallet-cli rpc help +``` diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 000000000..bd9c8344a --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,190 @@ +# Configuration + +By default, the mainnet bcoin config files will reside in `~/.bcoin/bcoin.conf` +and `~/.bcoin/wallet.conf`. Any parameter passed to bcoin at startup will have +precedence over the config file. Even if you are just running `bclient` without +bcoin installed (to access a remote server, for example) the configuration +files would still reside in `~/.bcoin/` + +For example: + +``` bash +bcoin --network=regtest --api-key=menace --daemon +``` + +...will read the config file at `~/.bcoin/regtest/bcoin.conf` and ignore any +`network` or `api-key` parameters listed in that file. + +All bcoin configuration options work in the config file, CLI arguments, +process environment, and in the constructor parameters when instantiating +new `node` objects in JavaScript. Each method has slightly different +formatting. Note specifically the usage of hyphens and capital letters. + +See the examples below: + +| config file | CLI parameter | environment variable | JS object constructor | +|---|---|---|---| +| `network: testnet` | `--network=testnet` | `BCOIN_NETWORK=testnet` | `{network: 'testnet'}` | +| `log-level: debug` | `--log-level=debug` | `BCOIN_LOG_LEVEL=debug` | `{logLevel: 'debug'}` | +| `max-outbound: 8` | `--max-outbound=8` | `BCOIN_MAX_OUTBOUND=8` | `{maxOutbound: 8}`| + +## Prefix + +Bcoin's data directory is determined by the `prefix` option. + +Example: + +``` bash +$ bcoin --prefix ~/.bcoin_spv --spv +``` + +Will create a datadir of `~/.bcoin_spv`, containing a chain database, wallet +database and log file. + +## Common options + +- `config`: Points to a custom config file, not in the prefix directory. +- `network`: Which network's chainparams to use for the node (main, testnet, + regtest, or simnet) (default: main). +- `workers`: Whether to use a worker process pool for transaction + verification (default: true). +- `workers-size`: Number of worker processes to spawn for transaction + verification. By default, the worker pool will be sized based on the number + of CPUs/cores in the machine. +- `workers-timeout`: Worker process execution timeout in + milliseconds (default: 120000). +- `sigcache-size`: Max number of items in signature cache. + +## Node options + +- `prefix`: The data directory (stores databases, logs, and configs) + (default=~/.bcoin). +- `db`: Which database backend to use (default=leveldb). +- `max-files`: Max open files for leveldb. Higher generally means more disk + page cache benefits, but also more memory usage (default: 64). +- `cache-size`: Size (in MB) of leveldb cache and write buffer + (default: 32mb). +- `spv`: Enable Simplified Payments Verification (SPV) mode + +*Note: The `spv` and `daemon` options can not be entered in `bcoin.conf`. +They will only work when passed as a launch argument:* +``` +bcoin --spv --daemon +``` + +## Logger options + +- `log-level`: `error`, `warning`, `info`, `debug`, or `spam` (default: debug). +- `log-console`: `true` or `false` - whether to actually write to stdout/stderr + if foregrounded (default: true). +- `log-file`: Whether to use a log file (default: true). + +## Chain options + +Note that certain chain options affect the format and indexing of the chain +database and must be passed in consistently. + +- `prune`: Prune from the last 288 blocks (default: false). +- `checkpoints`: Use checkpoints and getheaders for the initial + sync (default: true). +- `index-tx`: Index transactions (enables transaction endpoints in REST api) + (default: false). +- `index-address`: Index transactions and utxos by address (default: false). + +## Mempool options + +- `mempool-size`: Max mempool size in MB (default: 100). +- `replace-by-fee`: Allow replace-by-fee transactions (default: false). +- `persistent-mempool`: Save mempool to disk and read into memory on boot + (default: false). + +## Pool options + +- `selfish`: Enable "selfish" mode (no relaying of txes or blocks) + (default: false). +- `compact`: Enable compact block relay (default: true). +- `bip37`: Enable serving of bip37 merkleblocks (default: false). +- `listen`: Accept incoming connections (default: true). +- `max-outbound`: Max number of outbound connections (default: 8). +- `max-inbound`: Max number of inbound connections (default: 8). +- `seeds`: Custom list of DNS seeds (comma-separated). +- `host`: Host to listen on (default: 0.0.0.0). +- `port`: Port to listen on (default: 8333). +- `public-host`: Public host to advertise on network. +- `public-port`: Public port to advertise on network. +- `nodes`: List of target nodes to connect to (comma-separated). +- `only`: List of nodes to ONLY connect to (no other nodes or dns seeds will + be contacted). + +## Miner options + +- `coinbase-flags`: Coinbase flags (default: mined by bcoin). +- `coinbase-address`: List of payout addresses, randomly selected during block + creation (comma-separated). +- `max-block-weight`: Max block weight to mine (default: 4000000). +- `reserved-block-weight`: Amount of space reserved for coinbase + (default: 4000). +- `reserved-block-sigops`: Amount of sigops reserved for coinbase + (default: 400). + +## HTTP + +- `http-host`: HTTP host to listen on (default: 127.0.0.1). +- `http-port`: HTTP port to listen on (default: 8332 for mainnet). +- `ssl-cert`: Path to SSL cert. +- `ssl-key`: Path to SSL key. +- `service-key`: Service key (used for accessing wallet system only). +- `api-key`: API key (used for accessing all node APIs, may be different than + API key for wallet server). +- `cors`: Enable "Cross-Origin Resource Sharing" HTTP headers (default: false). + +Note: For security `cors` should not be used with `no-auth`. \ +If enabled you should also enable `wallet-auth` and set `api-key`. + +## Wallet options + +These options must be saved in `wallet.conf`. They can also be passed as +environment variables or command-line variables if they are preceeded with +a `wallet-` prefix. (See [CHANGELOG.md](https://github.com/bcoin-org/bcoin/blob/master/CHANGELOG.md#configuration-changes)) + +For example, to run a bcoin and wallet node on a remote server that you can +access from a local machine, you would launch bcoin with the command: + +`bcoin --network=testnet --http-host=0.0.0.0 --wallet-http-host=0.0.0.0 +--wallet-api-key=hunter2 --wallet-wallet-auth=true` + +### Bcoin client: + +- `node-host`: Location of bcoin node HTTP server (default: localhost). +- `node-port`: Port of bcoin node HTTP server (defaults to RPC port + of network). +- `node-ssl`: Whether to use SSL (default: false). +- `node-api-key`: API-key for bcoin HTTP server. + +### Wallet database: + +- `memory`: Keep database in memory rather than write to disk. +- `max-files`: Max open files for leveldb. +- `cache-size`: Size (in MB) of leveldb cache and write buffer. +- `witness`: Make SegWit enabled wallets. +- `checkpoints`: Trust hard-coded blockchain checkpoints. + +### Wallet http server: + +- `ssl`: Whether to use SSL (default: false). +- `ssl-key`: Path to SSL key. +- `ssl-cert`: Path to SSL cert. +- `http-host`: HTTP host to listen on (default: 127.0.0.1). +- `http-port`: HTTP port to listen on (default: 8334 for mainnet). +- `api-key`: API key (used for accessing all wallet APIs, may be different + than API key for node server). +- `cors`: Enable "Cross-Origin Resource Sharing" HTTP headers (default: false). +- `no-auth`: Disable auth for API server and wallets (default: false). +- `wallet-auth`: Enable token auth for wallets (default: false). +- `admin-token`: Token required if `wallet-auth` is enabled: restricts access + to [all wallet admin routes.](https://bcoin.io/api-docs/#wallet-admin-commands) + +## Sample config files + +- Node https://github.com/bcoin-org/bcoin/blob/master/etc/sample.conf +- Wallet https://github.com/bcoin-org/bcoin/blob/master/etc/sample.wallet.conf diff --git a/docs/Design.md b/docs/design.md similarity index 77% rename from docs/Design.md rename to docs/design.md index ed02fe3f3..0206917da 100644 --- a/docs/Design.md +++ b/docs/design.md @@ -1,4 +1,4 @@ -## Notes on Design +# Design Bcoin is thoroughly event driven. It has a fullnode object, but Bcoin was specifically designed so the mempool, blockchain, p2p pool, and wallet database @@ -20,47 +20,48 @@ http client -> tx -> http server -> mempool ``` Not only does the loose coupling make testing easier, it ensures people can -utilize bcoin for many use cases. +utilize bcoin for many use cases. Learn more about specific events and +event emitters at https://bcoin.io/guides/events.html -### Performance +## Performance -Non-javscript people reading this may think using javascript isn't a wise +Non-javscript people reading this may think using JavaScript isn't a wise decision. -#### Javascript +### JavaScript -Javascript is inherently slow due to how dynamic it is, but modern JITs have +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 +### Concurrency -Bcoin runs in node.js, so the javascript code is limited to one thread. We +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 +you are allowed to share memory between threads using the transferable 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 +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 +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 +### Benefits -The real feature of javascript is that your code will run almost anywhere. With +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 visiting a webpage. @@ -68,4 +69,4 @@ visiting a webpage. [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 \ No newline at end of file +[elliptic]: https://github.com/indutny/elliptic diff --git a/docs/examples/client-api.js b/docs/examples/client-api.js new file mode 100644 index 000000000..4bbba4295 --- /dev/null +++ b/docs/examples/client-api.js @@ -0,0 +1,144 @@ +'use strict'; + +const bcoin = require('../..'); +const client = require('bclient'); +const plugin = bcoin.wallet.plugin; +const network = bcoin.Network.get('regtest'); + +const node = new bcoin.FullNode({ + network: 'regtest', + memory: true +}); + +node.use(plugin); + +const walletClient = new client.WalletClient({ + port: network.walletPort +}); + +const nodeClient = new client.NodeClient({ + port: network.rpcPort +}); + +async function fundWallet(wdb, addr) { + // Coinbase + const mtx = new bcoin.MTX(); + mtx.addOutpoint(new bcoin.Outpoint(bcoin.consensus.ZERO_HASH, 0)); + mtx.addOutput(addr, 50460); + mtx.addOutput(addr, 50460); + mtx.addOutput(addr, 50460); + mtx.addOutput(addr, 50460); + + const tx = mtx.toTX(); + + walletClient.bind('balance', (walletID, balance) => { + console.log('New Balance:'); + console.log(walletID, balance); + }); + + walletClient.bind('address', (walletID, receive) => { + console.log('New Receiving Address:'); + console.log(walletID, receive); + }); + + walletClient.bind('tx', (walletID, details) => { + console.log('New Wallet TX:'); + console.log(walletID, details); + }); + + await wdb.addTX(tx); + await new Promise(r => setTimeout(r, 300)); +} + +async function sendTX(addr, value) { + const options = { + rate: 10000, + outputs: [{ + value: value, + address: addr + }] + }; + + // API call: walletClient.send('test', options) + const tx = await walletClient.request('POST', '/wallet/test/send', options); + + return tx.hash; +} + +async function callNodeApi() { + // API call: nodeClient.getInfo() + const info = await nodeClient.request('GET', '/'); + + console.log('Server Info:'); + console.log(info); + + const json = await nodeClient.execute( + 'getblocktemplate', + [{rules: ['segwit']}] + ); + + console.log('Block Template (RPC):'); + console.log(json); +} + +(async () => { + const wdb = node.require('walletdb').wdb; + + await node.open(); + + // API call: walletClient.createWallet('test') + const testWallet = await walletClient.request('PUT', '/wallet/test'); + + console.log('Wallet:'); + console.log(testWallet); + + // open socket to listen for events + await walletClient.open(); + + // subscribe to events from all wallets + walletClient.all(); + + // Fund default account. + // API call: walletClient.createAddress('test', 'default') + const receive = await walletClient.request( + 'POST', + '/wallet/test/address', + {account: 'default'} + ); + await fundWallet(wdb, receive.address); + + // API call: walletClient.getBalance('test', 'default') + const balance = await walletClient.request( + 'GET', + '/wallet/test/balance', + {account: 'default'} + ); + + console.log('Balance:'); + console.log(balance); + + // API call: walletClient.createAccount('test', 'foo') + const acct = await walletClient.request('PUT', '/wallet/test/account/foo'); + + console.log('Account:'); + console.log(acct); + + // Send to our new account. + const hash = await sendTX(acct.receiveAddress, 10000); + + console.log('Sent TX:'); + console.log(hash); + + // API call: walletClient.getTX('test', hash) + const tx = await walletClient.request('GET', `/wallet/test/tx/${hash}`); + + console.log('Sent TX details:'); + console.log(tx); + + await callNodeApi(); + await walletClient.close(); + await node.close(); +})().catch((err) => { + console.error(err.stack); + process.exit(1); +}); diff --git a/docs/Examples/connect-to-peer.js b/docs/examples/connect-to-peer.js similarity index 57% rename from docs/Examples/connect-to-peer.js rename to docs/examples/connect-to-peer.js index 321cd141b..9fbcad0b3 100644 --- a/docs/Examples/connect-to-peer.js +++ b/docs/examples/connect-to-peer.js @@ -1,22 +1,25 @@ 'use strict'; -// Usage: $ node ./docs/Examples/connect-to-peer.js [ip]:[port] +/* + * Usage: + * Run another Bitcoin node on local regtest network, for example + * $ ../../bin/bcoin --network=regtest + * Execute this script with the other node's address and port + * $ node connect-to-peer.js 127.0.0.1:48444 + */ const bcoin = require('../..'); -const Peer = bcoin.peer; -const NetAddress = bcoin.netaddress; -const Network = bcoin.network; -const network = Network.get('testnet'); +const network = bcoin.Network.get('regtest'); -const peer = Peer.fromOptions({ - network: 'testnet', +const peer = bcoin.Peer.fromOptions({ + network: 'regtest', agent: 'my-subversion', hasWitness: () => { return false; } }); -const addr = NetAddress.fromHostname(process.argv[2], 'testnet'); +const addr = bcoin.net.NetAddress.fromHostname(process.argv[2], 'regtest'); console.log(`Connecting to ${addr.hostname}`); diff --git a/docs/examples/connect-to-the-p2p-network.js b/docs/examples/connect-to-the-p2p-network.js new file mode 100644 index 000000000..c78d48dc4 --- /dev/null +++ b/docs/examples/connect-to-the-p2p-network.js @@ -0,0 +1,117 @@ +'use strict'; + +const bcoin = require('../..').set('main'); + +const Logger = require('blgr'); + +// Setup logger to see what's Bcoin doing. +const logger = new Logger({ + level: 'info' +}); + +// Create a blockchain and store it in memory. +const chain = new bcoin.Chain({ + memory: true, + network: 'main', + logger: logger +}); + +const mempool = new bcoin.Mempool({ + chain: chain, + logger: logger +}); + +// Create a network pool of peers with a limit of 8 peers. +const pool = new bcoin.Pool({ + chain: chain, + mempool: mempool, + maxPeers: 8, + logger: logger +}); + +(async function() { + await logger.open(); + await chain.open(); + + await pool.open(); + + // Connect, start retrieving and relaying txs + await pool.connect(); + + // Start the blockchain sync. + pool.startSync(); + + // Watch the action + const color = '\x1b[31m'; + chain.on('block', (block) => { + console.log(color, 'Added mainnet block:'); + console.log(block.rhash()); + }); + + mempool.on('tx', (tx) => { + console.log(color, 'Added mainnet tx to mempool:'); + console.log(tx.rhash); + }); + + pool.on('tx', (tx) => { + console.log(color, 'Saw mainnet transaction:'); + console.log(tx.rhash); + }); +})().catch((err) => { + console.error(err.stack); + process.exit(1); +});; + +// Start up a testnet sync in-memory +// while we're at it (because we can). + +const tchain = new bcoin.Chain({ + memory: true, + network: 'testnet', + logger: logger +}); + +const tmempool = new bcoin.Mempool({ + network: 'testnet', + chain: tchain, + logger: logger +}); + +const tpool = new bcoin.Pool({ + network: 'testnet', + chain: tchain, + mempool: tmempool, + size: 8, + logger: logger +}); + +(async function() { + await tchain.open(); + + await tpool.open(); + + // Connect, start retrieving and relaying txs + await tpool.connect(); + + // Start the blockchain sync. + tpool.startSync(); + + const color = '\x1b[32m'; + tchain.on('block', (block) => { + console.log(color, 'Added testnet block:'); + console.log(block.rhash()); + }); + + tmempool.on('tx', (tx) => { + console.log(color, 'Added testnet tx to mempool:'); + console.log(tx.rhash); + }); + + tpool.on('tx', (tx) => { + console.log(color, 'Saw testnet transaction:'); + console.log(tx.rhash); + }); +})().catch((err) => { + console.error(err.stack); + process.exit(1); +}); diff --git a/docs/Examples/create-a-blockchain-and-mempool.js b/docs/examples/create-a-blockchain-and-mempool.js similarity index 71% rename from docs/Examples/create-a-blockchain-and-mempool.js rename to docs/examples/create-a-blockchain-and-mempool.js index 1c3d42802..60b804623 100644 --- a/docs/Examples/create-a-blockchain-and-mempool.js +++ b/docs/examples/create-a-blockchain-and-mempool.js @@ -1,8 +1,6 @@ 'use strict'; + const bcoin = require('../..'); -const Chain = bcoin.chain; -const Mempool = bcoin.mempool; -const Miner = bcoin.miner; // Default network (so we can avoid passing // the `network` option into every object below.) @@ -10,9 +8,9 @@ bcoin.set('regtest'); // Start up a blockchain, mempool, and miner using in-memory // databases (stored in a red-black tree instead of on-disk). -const chain = new Chain({ db: 'memory' }); -const mempool = new Mempool({ chain: chain }); -const miner = new Miner({ +const chain = new bcoin.Chain({ network: 'regtest', memory: true }); +const mempool = new bcoin.Mempool({ chain: chain }); +const miner = new bcoin.Miner({ chain: chain, mempool: mempool, @@ -21,8 +19,11 @@ const miner = new Miner({ }); (async () => { + // Open the chain + await chain.open(); + // Open the miner (initialize the databases, etc). - // Miner will implicitly call `open` on chain and mempool. + // Miner will implicitly call `open` on mempool. await miner.open(); // Create a Cpu miner job @@ -32,7 +33,7 @@ const miner = new Miner({ const block = await job.mineAsync(); // Add the block to the chain - console.log('Adding %s to the blockchain.', block.rhash); + console.log('Adding %s to the blockchain.', block.rhash()); console.log(block); await chain.add(block); console.log('Added block!'); diff --git a/docs/Examples/create-sign-tx.js b/docs/examples/create-sign-tx.js similarity index 85% rename from docs/Examples/create-sign-tx.js rename to docs/examples/create-sign-tx.js index 6661626c2..e345d4788 100644 --- a/docs/Examples/create-sign-tx.js +++ b/docs/examples/create-sign-tx.js @@ -7,13 +7,13 @@ const assert = require('assert'); (async () => { const master = bcoin.hd.generate(); - const key = master.derivePath('m/44/0/0/0/0'); - const keyring = new bcoin.keyring(key.privateKey); - const cb = new bcoin.mtx(); + const key = master.derivePath('m/44\'/0\'/0\'/0/0'); + const keyring = new bcoin.wallet.WalletKey(key.privateKey); + const cb = new bcoin.MTX(); cb.addInput({ - prevout: new bcoin.outpoint(), - script: new bcoin.script(), + prevout: new bcoin.Outpoint(), + script: new bcoin.Script(), sequence: 0xffffffff }); @@ -29,11 +29,11 @@ const assert = require('assert'); // 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. - const coin = bcoin.coin.fromTX(cb, 0, -1); + const coin = bcoin.Coin.fromTX(cb, 0, -1); coins.push(coin); // Create our redeeming transaction. - const mtx = new bcoin.mtx(); + const mtx = new bcoin.MTX(); // Send 10,000 satoshis to ourself. mtx.addOutput({ diff --git a/docs/Examples/fullnode.js b/docs/examples/fullnode.js similarity index 85% rename from docs/Examples/fullnode.js rename to docs/examples/fullnode.js index c63ec67ed..416d2e931 100644 --- a/docs/Examples/fullnode.js +++ b/docs/examples/fullnode.js @@ -1,11 +1,10 @@ 'use strict'; const bcoin = require('../..'); -const FullNode = bcoin.fullnode; -const node = new FullNode({ +const node = new bcoin.FullNode({ + memory: true, network: 'testnet', - db: 'memory', workers: true }); diff --git a/docs/examples/get-tx-from-chain.js b/docs/examples/get-tx-from-chain.js new file mode 100644 index 000000000..e88e17dfc --- /dev/null +++ b/docs/examples/get-tx-from-chain.js @@ -0,0 +1,81 @@ +'use strict'; + +const bcoin = require('../..'); +const fs = require('bfile'); + +// Create chain for testnet, stored in memory by default. +// To store the chain on disk at the `prefix` location, +// set `memory: false`. +const chain = new bcoin.Chain({ + network: 'testnet', + indexTX: true, + indexAddress: true, + db: 'leveldb', + prefix: '/tmp/bcoin-testnet-example', + memory: true +}); + +// Create a network pool of peers with a limit of 8 peers. +const pool = new bcoin.Pool({ + chain: chain, + maxPeers: 8 +}); + +(async () => { + // Ensure the directory exists if we are writing to disk + if (!chain.options.memory) + await fs.mkdirp(chain.options.prefix); + + await chain.open(); + + // Connect the blockchain to the network + await pool.open(); + await pool.connect(); + pool.startSync(); + + // Monitor blockchain height and react when we hit the target + chain.on('connect', async (entry, block) => { + const height = entry.height; + console.log( + `Height: ${chain.height} ` + + `Block: ${entry.rhash()} ` + + `TXs: ${block.txs.length}` + ); + + if (height === 1000) { + const entry = await chain.getEntry(1000); + console.log('Block at height 1000:\n', entry); + + // testnet tx at height 500 + const txhash = + 'fc407d7a3b819daa5cf1ecc2c2a4b103c3782104d1425d170993bd534779a0da'; + const txhashBuffer = Buffer.from(txhash, 'hex').reverse(); + + const txmeta = await chain.db.getMeta(txhashBuffer); + const tx = txmeta.tx; + const coinview = await chain.db.getSpentView(tx); + + console.log(`Tx with hash ${txhash}:\n`, txmeta); + console.log( + `\n Input value: ${tx.getInputValue(coinview)}` + + `\n Output value: ${tx.getOutputValue()}` + + `\n Fee: ${tx.getFee(coinview)}` + ); + + // testnet block at height 800 + const hash = + Buffer.from( + '000000004df86f64cca38c6587df348e0c6849ebee628b3f840f552c707cc862', + 'hex' + ); + // chainDB indexes blocks by the REVERSE (little endian) hash + const block = await chain.getBlock(hash.reverse()); + console.log(`Block with hash ${hash.toString('hex')}:`, block); + + process.exit(1); + } + }); +})().catch((err) => { + console.error(err.stack); + process.exit(1); +}); diff --git a/docs/Examples/peers-plugin.js b/docs/examples/peers-plugin.js similarity index 72% rename from docs/Examples/peers-plugin.js rename to docs/examples/peers-plugin.js index fb7881825..4b1f15fc0 100644 --- a/docs/Examples/peers-plugin.js +++ b/docs/examples/peers-plugin.js @@ -1,7 +1,6 @@ 'use strict'; const bcoin = require('../..'); -const FullNode = bcoin.fullnode; function MyPlugin(node) { this.node = node; @@ -27,9 +26,9 @@ MyPlugin.prototype.sayPeers = function sayPeers() { console.log('Number of peers: %d', this.node.pool.peers.size()); }; -const node = new FullNode({ +const node = new bcoin.FullNode({ + memory: true, network: 'testnet', - db: 'memory', workers: true }); @@ -44,15 +43,7 @@ node.use(MyPlugin); plugin.sayPeers(); - node.on('connect', (entry, block) => { - console.log('%s (%d) added to chain.', entry.rhash(), entry.height); - }); - - node.on('tx', (tx) => { - console.log('%s added to mempool.', tx.txid()); - }); - - node.startSync(); + await node.close(); })().catch((err) => { console.error(err.stack); process.exit(1); diff --git a/docs/examples/spv-sync-wallet.js b/docs/examples/spv-sync-wallet.js new file mode 100644 index 000000000..e986a0fd7 --- /dev/null +++ b/docs/examples/spv-sync-wallet.js @@ -0,0 +1,84 @@ +'use strict'; + +const bcoin = require('../..'); + +bcoin.set('regtest'); + +// SPV chains only store the chain headers. +const chain = new bcoin.Chain({ + spv: true +}); + +const pool = new bcoin.Pool({ + chain: chain, + maxOutbound: 1 +}); + +const walletdb = new bcoin.wallet.WalletDB({ memory: true }); + +// Full node will provide tx data to SPV node +const full = {}; +full.chain = new bcoin.Chain(); +full.pool = new bcoin.Pool({ + chain: full.chain, + port: 44444, + bip37: true, + listen: true +}); + +(async () => { + await pool.open(); + await walletdb.open(); + await chain.open(); + await pool.connect(); + + await full.pool.open(); + await full.chain.open(); + await full.pool.connect(); + + const wallet = await walletdb.create(); + const walletAddress = await wallet.receiveAddress(); + console.log('Created wallet with address %s', walletAddress); + + // Add our address to the SPV filter. + pool.watchAddress(walletAddress); + + // Start the blockchain sync. + pool.startSync(); + + // Get ready to receive transactions! + pool.on('tx', (tx) => { + console.log('Received TX:\n', tx); + + walletdb.addTX(tx); + console.log('TX added to wallet DB!'); + }); + + wallet.on('balance', (balance) => { + console.log('Balance updated:\n', balance.toJSON()); + }); + + // Connect the SPV node to the full node server + const netAddr = await pool.hosts.addNode('127.0.0.1:44444'); + const peer = pool.createOutbound(netAddr); + pool.peers.add(peer); + + full.pool.on('peer open', async () => { + console.log('SPV node peers:\n', pool.peers); + console.log('Full node peers:\n', full.pool.peers); + + // Create a dummy transaction and send it from full to SPV node + const mtx = new bcoin.MTX(); + mtx.addOutpoint(new bcoin.Outpoint(bcoin.consensus.ZERO_HASH, 0)); + mtx.addOutput(walletAddress, 12000); + const tx = mtx.toTX(); + + // Give the node a few seconds to process connection before sending + console.log('Waiting for transaction...'); + await new Promise(r => setTimeout(r, 3000)); + await full.pool.broadcast(tx); + }); +})().catch((err) => { + console.error(err.stack); + process.exit(1); +}); diff --git a/docs/Examples/wallet.js b/docs/examples/wallet.js similarity index 54% rename from docs/Examples/wallet.js rename to docs/examples/wallet.js index fc066e14e..5edb5e7cb 100644 --- a/docs/Examples/wallet.js +++ b/docs/examples/wallet.js @@ -1,19 +1,16 @@ 'use strict'; const bcoin = require('../..'); -const random = bcoin.crypto.random; -const WalletDB = bcoin.walletdb; -const MTX = bcoin.mtx; -const Outpoint = bcoin.outpoint; +const random = require('bcrypto/lib/random'); function dummy() { - const hash = random.randomBytes(32).toString('hex'); - return new Outpoint(hash, 0); + const hash = random.randomBytes(32); + return new bcoin.Outpoint(hash, 0); } -const walletdb = new WalletDB({ +const walletdb = new bcoin.wallet.WalletDB({ network: 'testnet', - db: 'memory' + memory: true }); (async () => { @@ -31,18 +28,17 @@ const walletdb = new WalletDB({ console.log('Created account'); console.log(acct); - const mtx = new MTX(); + const mtx = new bcoin.MTX(); mtx.addOutpoint(dummy()); - mtx.addOutput(acct.getReceive(), 50460); + mtx.addOutput(acct.receiveAddress(), 50460); const tx = mtx.toTX(); - await walletdb.addTX(tx); - - const wtx = await wallet.getTX(tx.hash('hex')); + wallet.on('tx', (tx) => { + console.log('Received transaciton:\n', tx); + }); - console.log('Added transaction'); - console.log(wtx); + await walletdb.addTX(tx); })().catch((err) => { console.error(err.stack); process.exit(1); diff --git a/docs/examples/watch-only-wallet.js b/docs/examples/watch-only-wallet.js new file mode 100644 index 000000000..926d965cd --- /dev/null +++ b/docs/examples/watch-only-wallet.js @@ -0,0 +1,70 @@ +'use strict'; + +const bcoin = require('../..'); + +(async () => { + // use well known test mnemonic + const phrase = [ + 'abandon', 'abandon', 'abandon', 'abandon', + 'abandon', 'abandon', 'abandon', 'abandon', + 'abandon', 'abandon', 'abandon', 'about' + ].join(' '); + + const network = bcoin.Network.get('regtest'); + + const mnemonic = bcoin.Mnemonic.fromPhrase(phrase); + + // m' + const priv = bcoin.HDPrivateKey.fromMnemonic(mnemonic); + + // m'/44' + const bip44Key = priv.derive(44, true); + + // m'/44'/0' + const bitcoinKey = bip44Key.derive(0, true); + + // m'/44'/0'/0' + const accountKey = bitcoinKey.derive(0, true); + + // account extended public key + // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Serialization_format + const xpub = accountKey.xpubkey(network.type); + + console.log('xpub to import:\n', xpub); + + // recommended to use hardware wallet to derive keys + // see github.com/bcoin-org/bledger + + // create watch only wallet + // the wallet will generate lookahead + // addresses from the account extended public key + // and can find spendable coins in the blockchain state + const wdb = new bcoin.wallet.WalletDB({ + network: 'regtest', + memory: true + }); + + await wdb.open(); + + // new wallet still generates a master private key, but it will not be used + const wallet = await wdb.create({ + name: 'my-watch-only-wallet', + accountKey: xpub, + watchOnly: true + }); + + // xpub account key placed at Account 0. Address 0 is already derived. + const acct0 = await wallet.getAccount(0); + + // create new receive addresses through the deterministic chain + const key1 = await wallet.createReceive(0); + const addr1 = key1.getAddress('string', 'regtest'); + + const key2 = await wallet.createReceive(0); + const addr2 = key2.getAddress('string', 'regtest'); + + console.log('Wallet:\n', wallet); + console.log('Account:\n', acct0); + console.log('Address 1:\n', addr1); + console.log('Address 2:\n', addr2); +})(); diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 000000000..69c2503fb --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,218 @@ +# Getting started + +## Introduction + +Bcoin is an _alternative_ implementation of the bitcoin protocol, written in +node.js. It is a full node which can be used for full blockchain validation +and is aware of all known consensus rules. + +## Requirements + +- Linux, OSX, or Windows (\*) (\*\*) +- node.js >=v8.14.0 +- npm >=v6.4.1 +- python2 (for node-gyp) +- gcc/g++ (for leveldb and secp256k1) +- git (optional, see below) + +(\*): Note that bcoin works best with unix-like OSes, and has not yet been +thoroughly tested on windows. + +(\*\*): The BSDs and Solaris have also not been tested yet, but should work +in theory. + +## Build & install + +Bcoin is meant to be installed via git for security purposes, as there +are security issues when installing via npm. All tagged commits for +release should be signed by @chjj's [PGP key][keybase] +(`B4B1F62DBAC084E333F3A04A8962AB9DE6666BBD`). Signed copies of node.js +are available from [nodejs.org][node], or from your respective OS's +package repositories. + +### Installing via Git + +``` bash +$ curl https://keybase.io/chjj/pgp_keys.asc | gpg --import +$ git clone git://github.com/bcoin-org/bcoin.git +$ cd bcoin +``` + +For a specific release: +``` +$ git tag +$ git tag -v # verify signature +$ git checkout +``` + +Install dependencies: +``` +$ npm install +$ npm install -g # link globally +``` +**Note:** Dependencies are checked for integrity using `package-lock.json`. +However `npm` _will not_ make these checks with `npm install -g` and it +will link your installation globally so that `bcoin` is in your +path _(e.g. $ bcoin)_. + +### Installing via Docker + +Check [bcoin-docker](https://github.com/bcoin-org/bcoin-docker) + +### Installing on Windows + +Install OpenSSL v1.0.2L 64-Bit: + +https://slproweb.com/download/Win64OpenSSL-1_0_2L.exe + +As Administrator, open `cmd.exe` and run: + +```console +C:\Users\bcoin\bcoin>npm install --global --production windows-build-tools +``` + +to install `VCBuild.exe` and `Python 2.7.x` both required by `node-gyp` +for building native modules. + +Then continue [Installing via Git](#installing-via-git) + +Note that you need a shell that supports bash scripts, like Git Bash to launch +bcoin. + +### Troubleshooting + +If the build fails compilation for `bcoin-native` or `secp256k1-node` +__validation will be slow__ (a block verification which should take 1 second +on consumer grade hardware may take up to 15 seconds). Bcoin will throw a +warning on boot if it detects a build failure. If you run into this issue, +please post an issue on the repo. + +## Starting up your first bcoin node + +If bcoin is installed globally, `$ bcoin` should be in your PATH. If not, +the bcoin bootstrap script resides in `/path/to/bcoin/bin/bcoin`. + +``` bash +$ bcoin +``` + +Will run a bcoin node as the foreground process, displaying all debug logs. + +To run as a daemon: + +``` bash +$ bcoin --daemon +``` + +This will start up a full node, complete with: a blockchain, mempool, miner, +p2p server, wallet server, and an HTTP REST+RPC server. + +All logs will be written to `~/.bcoin/debug.log` by default. + +By default, the http server will only listen on `127.0.0.1:8332`. No auth +will be required if an API key was not passed in. If you listen on any other +host, auth will be required and an API key will be auto-generated if one was +not passed in. + +## Listening externally + +To listen publicly on the HTTP server, `--http-host=0.0.0.0` (ipv4) or +`--http-host=::` (ipv4 and ipv6) can be passed. Additionally this: +`--http-port=1337` can set the port. + +To advertise your node on the P2P network `--public-host=[your-public-ip]` +and `--public-port=[your-public-port]` may be passed. + +## Using an API Key + +If listening publicly on the HTTP server, an API key is required. One will +be randomly generated if no key was chosen, but not explicitly reported to +the user. An API key can be chosen with the `--api-key` option. + +Example: + +``` bash +$ bcoin --http-host=0.0.0.0 --api-key hunter2 --daemon +``` + +API keys are used with HTTP Basic Auth: + +``` bash +$ curl http://x:hunter2@localhost:8332/ +``` + +[bclient](https://github.com/bcoin-org/bclient) is the prepackaged tool for +querying both the REST and RPC APIs. If bcoin is installed globally, both +`bcoin-cli` and `bwallet-cli` should be on your path. + +``` bash +$ bcoin-cli info --api-key hunter2 +$ bcoin-cli rpc getblockchaininfo --api-key hunter2 +$ bwallet-cli balance +``` + +## Using Tor/SOCKS + +Bcoin has native support for SOCKS proxies, and will accept a `--proxy` option +in the format of `--proxy=[user]:[pass]@host:port`. + +Passing the `--onion` option tells bcoin that the SOCKS proxy is a Tor socks +proxy, and will enable Tor resolution for DNS lookups, as well as try to +connect to `.onion` addresses found on the P2P network. + +``` bash +$ bcoin --proxy joe:hunter2@127.0.0.1:9050 --onion +``` + +### Running bcoin as a tor hidden service + +Your hidden service must first be configured with `tor`. Once you have the +`.onion` address, it can be passed into `--public-host` in the form +of `--public-host foo.onion`. + +## Target nodes + +It's often desirable to run behind several trusted bitcoin nodes. To select +permanent nodes to connect to, the `--nodes` option is available: + +``` bash +$ bcoin --nodes foo.example.com:8333,1.2.3.4:8333,5.6.7.8:8333 +``` + +If chosen, bcoin will _always_ try to connect to these nodes as outbound +peers. They are top priority and whitelisted (not susceptible to permanent +bans, only disconnections). + +To _only_ connect to these nodes, use `--only` + +``` bash +$ bcoin --only foo.example.com,1.2.3.4,5.6.7.8 +``` + +## Disabling listening + +To avoid accepting connections on the P2P network altogether, +`--listen=false` can be passed to bcoin. + +### Selfish mode + +Bcoin also supports a "selfish" mode. In this mode, bcoin still has full +blockchain and mempool validation, but network services are disabled: it +will not relay transactions or serve blocks to anyone. + +``` bash +$ bcoin --selfish --listen=false +``` + +Note: Selfish mode is not recommended. We encourage you to _help_ the network +by relaying transactions and blocks. At the same time, selfish mode does have +its uses if you do not have the bandwidth to spare, or if you're absolutely +worried about potential DoS attacks. + +## Further configuration + +See [Configuration][configuration]. + +[keybase]: https://keybase.io/chjj#show-public +[node]: https://nodejs.org +[configuration]: configuration.md diff --git a/docs/wallet-system.md b/docs/wallet-system.md new file mode 100644 index 000000000..8e90caa9f --- /dev/null +++ b/docs/wallet-system.md @@ -0,0 +1,53 @@ +# Wallet system + +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 are based on 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 wallets, with many accounts, with many +addresses for each account. Bcoin should theoretically be able to scale to +hundreds of thousands of wallets/accounts/addresses. + +## Deviation from BIP44 + +**It's important to backup the wallet database.** There are several deviations +from [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) +that break the determinsitic algorithm, and therefore it's recommended to +backup the wallet database any time an account is created, as there are several +possible configurations of an account. + +There is a command available via the wallet RPC called `backupwallet` that can +clone the database to a new destination. There are also the RPC calls +`dumpwallet` and `dumpprivkey` for exporting private keys. After shutting down +the wallet process, it's also possible to copy the LevelDB database from the +default location at `~/.bcoin/wallet` for main net or +`~/.bcoin//wallet` for others. Copying LevelDB while the process is +running can result in a corrupted copy. LevelDB is also prone to +[database corruption](https://en.wikipedia.org/wiki/LevelDB#Bugs_and_reliability). + +Each account can be of a different type. You could have a pubkeyhash account, +a multisig account, and a witness pubkeyhash account all in the same wallet. +Accounts can be configured with or without Segregated Witness and both base58 +(nested-in-P2SH) and bech32 (native) P2WPKH addresses can be derived from the +same account. + +Bcoin adds a third branch to each account for nested SegWit addresses. +Branch `0` and `1` are for `receive` and `change` addresses respectively +(which is BIP44 standard) but branch `2` is used by bcoin to derive +[nested SegWit addresses.](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#P2WPKH_nested_in_BIP16_P2SH) + +Accounts in a bcoin wallet can be configured for multisig and import xpubs +from cosigners. Externally-generated Extended Private Keys (`xpriv`) and non-HD +single address private keys can all be imported into a bcoin wallet. Balances +of those addresses can be watched as well spent from (in the case of a private +key). + +Unlike [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki), +bcoin does not limit account depth and a new account can be created +after an empty account. This can create issues with deterministic account +discovery from the master node (seed) as there are `2 ^ 31 - 1` _(worst case)_ +possible accounts to search. diff --git a/etc/authorized-peers b/etc/authorized-peers deleted file mode 100644 index 0039fff50..000000000 --- a/etc/authorized-peers +++ /dev/null @@ -1 +0,0 @@ -02b9f7499c4166e76d3a64326bbbe92dc02f0e07dc184f94995da61683c311cb0f diff --git a/etc/known-peers b/etc/known-peers deleted file mode 100644 index 695f51b9b..000000000 --- a/etc/known-peers +++ /dev/null @@ -1 +0,0 @@ -node.bcoin.io,52.39.113.206 02b9f7499c4166e76d3a64326bbbe92dc02f0e07dc184f94995da61683c311cb0f diff --git a/etc/sample.conf b/etc/sample.conf index 6999ef9b5..3dfe2db85 100644 --- a/etc/sample.conf +++ b/etc/sample.conf @@ -37,7 +37,6 @@ log-file: true prune: false checkpoints: true -coin-cache: 0 entry-cache: 5000 index-tx: false index-address: false @@ -60,7 +59,6 @@ persistent-mempool: false selfish: false compact: true bip37: false -bip151: true listen: true max-outbound: 8 max-inbound: 30 @@ -81,10 +79,6 @@ host: :: # public-host: 1.2.3.4 # public-port: 8444 -# BIP151 AuthDB and Identity Key -bip150: false -identity-key: 74b4147957813b62cc8987f2b711ddb31f8cb46dcbf71502033da66053c8780a - # Always try to connect to these nodes. # nodes: 127.0.0.1,127.0.0.2 @@ -113,11 +107,4 @@ http-host: :: # ssl-key: @/ssl/priv.key api-key: bikeshed # no-auth: false - -# -# Wallet -# - -wallet-witness: false -wallet-checkpoints: true -wallet-auth: false +# cors: false diff --git a/etc/sample.wallet.conf b/etc/sample.wallet.conf new file mode 100644 index 000000000..ca4192cba --- /dev/null +++ b/etc/sample.wallet.conf @@ -0,0 +1,28 @@ +# Sample bcoin wallet config file (~/.bcoin/wallet.conf) + +# +# Options +# + +# network: main + +# +# HTTP +# + +http-host: :: +# http-port: 8334 +# ssl: true +# ssl-cert: @/ssl/cert.crt +# ssl-key: @/ssl/priv.key +api-key: bikeshed +# no-auth: false +# cors: false + +# +# Wallet +# + +witness: false +checkpoints: true +wallet-auth: false diff --git a/lib/bcoin-browser.js b/lib/bcoin-browser.js index 1bd05f0f8..63ce0cd0c 100644 --- a/lib/bcoin-browser.js +++ b/lib/bcoin-browser.js @@ -15,111 +15,6 @@ * * @exports bcoin * @type {Object} - * - * @property {Function} bn - See {@url https://github.com/indutny/bn.js}. - * @property {Object} elliptic - See {@url https://github.com/indutny/elliptic}. - * - * @property {Object} bip70 - See {@link module:bip70}. - * - * @property {Object} blockchain - See {@link module:blockchain}. - * @property {Function} chain - See {@link module:blockchain.Chain}. - * @property {Function} chaindb - See {@link module:blockchain.ChainDB}. - * @property {Function} chainentry - See {@link module:blockchain.ChainEntry}. - * - * @property {Object} btc - * @property {Function} amount - * @property {Function} uri - * - * @property {Object} coins - * @property {Function} coinview - * - * @property {Object} crypto - * @property {Object} secp256k1 - * @property {Object} schnorr - * - * @property {Object} db - * @property {Object} ldb - * - * @property {Object} hd - * - * @property {Object} http - * @property {Object} rpc - * - * @property {Object} txmempool - * @property {Object} fees - * @property {Object} mempool - * @property {Object} mempoolentry - * - * @property {Object} mining - * @property {Object} miner - * @property {Object} minerblock - * - * @property {Object} net - * @property {Object} bip150 - * @property {Object} bip151 - * @property {Object} bip152 - * @property {Object} dns - * @property {Object} packets - * @property {Object} peer - * @property {Object} pool - * @property {Object} tcp - * - * @property {Object} node - * @property {Object} config - * @property {Object} fullnode - * @property {Object} logger - * @property {Object} spvnode - * - * @property {Object} primitives - * @property {Object} address - * @property {Object} block - * @property {Object} coin - * @property {Object} headers - * @property {Object} input - * @property {Object} invitem - * @property {Object} keyring - * @property {Object} merkleblock - * @property {Object} mtx - * @property {Object} netaddress - * @property {Object} outpoint - * @property {Object} output - * @property {Object} tx - * - * @property {Object} protocol - * @property {Object} consensus - * @property {Object} errors - * @property {Object} network - * @property {Object} networks - * @property {Object} policy - * @property {Object} timedata - * - * @property {Object} txscript - * @property {Object} opcodes - * @property {Object} program - * @property {Object} script - * @property {Object} sigcache - * @property {Object} stack - * @property {Object} witness - * - * @property {Object} utils - * @property {Object} base32 - * @property {Object} base58 - * @property {Object} bloom - * @property {Object} co - * @property {Object} encoding - * @property {Object} lock - * @property {Object} reader - * @property {Object} staticwriter - * @property {Object} util - * @property {Object} writer - * - * @property {Object} wallet - * @property {Object} path - * @property {Object} walletkey - * @property {Object} walletdb - * - * @property {Object} workers - * @property {Object} workerpool */ const bcoin = exports; @@ -130,150 +25,107 @@ const bcoin = exports; */ bcoin.set = function set(network) { - bcoin.network.set(network); + bcoin.Network.set(network); return bcoin; }; -/** - * Cache all necessary modules. - */ - -bcoin.cache = function cache() { - ; -}; - /* * Expose */ -// Horrible BIP -bcoin.bip70 = require('./bip70'); - // Blockchain bcoin.blockchain = require('./blockchain'); -bcoin.chain = require('./blockchain/chain'); -bcoin.chaindb = require('./blockchain/chaindb'); -bcoin.chainentry = require('./blockchain/chainentry'); +bcoin.Chain = require('./blockchain/chain'); +bcoin.ChainEntry = require('./blockchain/chainentry'); // BTC bcoin.btc = require('./btc'); -bcoin.amount = require('./btc/amount'); -bcoin.uri = require('./btc/uri'); +bcoin.Amount = require('./btc/amount'); +bcoin.URI = require('./btc/uri'); // Coins bcoin.coins = require('./coins'); -bcoin.coinview = require('./coins/coinview'); - -// Crypto -bcoin.crypto = require('./crypto'); -bcoin.bn = require('./crypto/bn'); -bcoin.secp256k1 = require('./crypto/secp256k1'); -bcoin.schnorr = require('./crypto/schnorr'); - -// DB -bcoin.db = require('./db'); -bcoin.ldb = require('./db/ldb'); +bcoin.Coins = require('./coins/coins'); +bcoin.CoinEntry = require('./coins/coinentry'); +bcoin.CoinView = require('./coins/coinview'); // HD bcoin.hd = require('./hd'); +bcoin.HDPrivateKey = require('./hd/private'); +bcoin.HDPublicKey = require('./hd/public'); +bcoin.Mnemonic = require('./hd/mnemonic'); -// HTTP -bcoin.http = require('./http'); -bcoin.rpc = require('./http/rpc'); +// Index +bcoin.indexer = require('./indexer'); +bcoin.Indexer = require('./indexer/indexer'); +bcoin.TXIndexer = require('./indexer/txindexer'); +bcoin.AddrIndexer = require('./indexer/addrindexer'); // Mempool -bcoin.txmempool = require('./mempool'); -bcoin.fees = require('./mempool/fees'); -bcoin.mempool = require('./mempool/mempool'); -bcoin.mempoolentry = require('./mempool/mempoolentry'); +bcoin.mempool = require('./mempool'); +bcoin.Fees = require('./mempool/fees'); +bcoin.Mempool = require('./mempool/mempool'); +bcoin.MempoolEntry = require('./mempool/mempoolentry'); // Miner bcoin.mining = require('./mining'); -bcoin.miner = require('./mining/miner'); -bcoin.template = require('./mining/template'); +bcoin.Miner = require('./mining/miner'); // Net bcoin.net = require('./net'); -bcoin.bip150 = require('./net/bip150'); -bcoin.bip151 = require('./net/bip151'); -bcoin.bip152 = require('./net/bip152'); -bcoin.dns = require('./net/dns'); bcoin.packets = require('./net/packets'); -bcoin.peer = require('./net/peer'); -bcoin.pool = require('./net/pool'); -bcoin.tcp = require('./net/tcp'); +bcoin.Peer = require('./net/peer'); +bcoin.Pool = require('./net/pool'); // Node bcoin.node = require('./node'); -bcoin.config = require('./node/config'); -bcoin.fullnode = require('./node/fullnode'); -bcoin.logger = require('./node/logger'); -bcoin.spvnode = require('./node/spvnode'); +bcoin.Node = require('./node/node'); +bcoin.FullNode = require('./node/fullnode'); +bcoin.SPVNode = require('./node/spvnode'); // Primitives bcoin.primitives = require('./primitives'); -bcoin.address = require('./primitives/address'); -bcoin.block = require('./primitives/block'); -bcoin.coin = require('./primitives/coin'); -bcoin.headers = require('./primitives/headers'); -bcoin.input = require('./primitives/input'); -bcoin.invitem = require('./primitives/invitem'); -bcoin.keyring = require('./primitives/keyring'); -bcoin.merkleblock = require('./primitives/merkleblock'); -bcoin.mtx = require('./primitives/mtx'); -bcoin.netaddress = require('./primitives/netaddress'); -bcoin.outpoint = require('./primitives/outpoint'); -bcoin.output = require('./primitives/output'); -bcoin.tx = require('./primitives/tx'); +bcoin.Address = require('./primitives/address'); +bcoin.Block = require('./primitives/block'); +bcoin.Coin = require('./primitives/coin'); +bcoin.Headers = require('./primitives/headers'); +bcoin.Input = require('./primitives/input'); +bcoin.InvItem = require('./primitives/invitem'); +bcoin.KeyRing = require('./primitives/keyring'); +bcoin.MerkleBlock = require('./primitives/merkleblock'); +bcoin.MTX = require('./primitives/mtx'); +bcoin.Outpoint = require('./primitives/outpoint'); +bcoin.Output = require('./primitives/output'); +bcoin.TX = require('./primitives/tx'); // Protocol bcoin.protocol = require('./protocol'); bcoin.consensus = require('./protocol/consensus'); -bcoin.errors = require('./protocol/errors'); -bcoin.network = require('./protocol/network'); +bcoin.Network = require('./protocol/network'); bcoin.networks = require('./protocol/networks'); bcoin.policy = require('./protocol/policy'); -bcoin.timedata = require('./protocol/timedata'); // Script -bcoin.txscript = require('./script'); -bcoin.opcode = require('./script/opcode'); -bcoin.program = require('./script/program'); -bcoin.script = require('./script/script'); -bcoin.scriptnum = require('./script/scriptnum'); -bcoin.sigcache = require('./script/sigcache'); -bcoin.stack = require('./script/stack'); -bcoin.witness = require('./script/witness'); +bcoin.script = require('./script'); +bcoin.Opcode = require('./script/opcode'); +bcoin.Program = require('./script/program'); +bcoin.Script = require('./script/script'); +bcoin.ScriptNum = require('./script/scriptnum'); +bcoin.SigCache = require('./script/sigcache'); +bcoin.Stack = require('./script/stack'); +bcoin.Witness = require('./script/witness'); // Utils bcoin.utils = require('./utils'); -bcoin.base32 = require('./utils/base32'); -bcoin.base58 = require('./utils/base58'); -bcoin.bloom = require('./utils/bloom'); -bcoin.co = require('./utils/co'); -bcoin.encoding = require('./utils/encoding'); -bcoin.int64 = require('./utils/int64'); -bcoin.lock = require('./utils/lock'); -bcoin.reader = require('./utils/reader'); -bcoin.staticwriter = require('./utils/staticwriter'); bcoin.util = require('./utils/util'); -bcoin.writer = require('./utils/writer'); // Wallet bcoin.wallet = require('./wallet'); -bcoin.path = require('./wallet/path'); -bcoin.walletkey = require('./wallet/walletkey'); -bcoin.walletdb = require('./wallet/walletdb'); +bcoin.WalletDB = require('./wallet/walletdb'); // Workers bcoin.workers = require('./workers'); -bcoin.workerpool = require('./workers/workerpool'); +bcoin.WorkerPool = require('./workers/workerpool'); // Package Info bcoin.pkg = require('./pkg'); - -/* - * Expose Globally - */ - -global.bcoin = bcoin; diff --git a/lib/bcoin.js b/lib/bcoin.js index 92dc153f5..4e26fc486 100644 --- a/lib/bcoin.js +++ b/lib/bcoin.js @@ -17,111 +17,6 @@ * * @exports bcoin * @type {Object} - * - * @property {Function} bn - See {@url https://github.com/indutny/bn.js}. - * @property {Object} elliptic - See {@url https://github.com/indutny/elliptic}. - * - * @property {Object} bip70 - See {@link module:bip70}. - * - * @property {Object} blockchain - See {@link module:blockchain}. - * @property {Function} chain - See {@link module:blockchain.Chain}. - * @property {Function} chaindb - See {@link module:blockchain.ChainDB}. - * @property {Function} chainentry - See {@link module:blockchain.ChainEntry}. - * - * @property {Object} btc - * @property {Function} amount - * @property {Function} uri - * - * @property {Object} coins - * @property {Function} coinview - * - * @property {Object} crypto - * @property {Object} secp256k1 - * @property {Object} schnorr - * - * @property {Object} db - * @property {Object} ldb - * - * @property {Object} hd - * - * @property {Object} http - * @property {Object} rpc - * - * @property {Object} txmempool - * @property {Object} fees - * @property {Object} mempool - * @property {Object} mempoolentry - * - * @property {Object} mining - * @property {Object} miner - * @property {Object} minerblock - * - * @property {Object} net - * @property {Object} bip150 - * @property {Object} bip151 - * @property {Object} bip152 - * @property {Object} dns - * @property {Object} packets - * @property {Object} peer - * @property {Object} pool - * @property {Object} tcp - * - * @property {Object} node - * @property {Object} config - * @property {Object} fullnode - * @property {Object} logger - * @property {Object} spvnode - * - * @property {Object} primitives - * @property {Object} address - * @property {Object} block - * @property {Object} coin - * @property {Object} headers - * @property {Object} input - * @property {Object} invitem - * @property {Object} keyring - * @property {Object} merkleblock - * @property {Object} mtx - * @property {Object} netaddress - * @property {Object} outpoint - * @property {Object} output - * @property {Object} tx - * - * @property {Object} protocol - * @property {Object} consensus - * @property {Object} errors - * @property {Object} network - * @property {Object} networks - * @property {Object} policy - * @property {Object} timedata - * - * @property {Object} txscript - * @property {Object} opcodes - * @property {Object} program - * @property {Object} script - * @property {Object} sigcache - * @property {Object} stack - * @property {Object} witness - * - * @property {Object} utils - * @property {Object} base32 - * @property {Object} base58 - * @property {Object} bloom - * @property {Object} co - * @property {Object} encoding - * @property {Object} lock - * @property {Object} reader - * @property {Object} staticwriter - * @property {Object} util - * @property {Object} writer - * - * @property {Object} wallet - * @property {Object} path - * @property {Object} walletkey - * @property {Object} walletdb - * - * @property {Object} workers - * @property {Object} workerpool */ const bcoin = exports; @@ -133,8 +28,9 @@ const bcoin = exports; */ bcoin.define = function define(name, path) { - let cache; + let cache = null; Object.defineProperty(bcoin, name, { + enumerable: true, get() { if (!cache) cache = require(path); @@ -149,162 +45,110 @@ bcoin.define = function define(name, path) { */ bcoin.set = function set(network) { - bcoin.network.set(network); + bcoin.Network.set(network); return bcoin; }; -/** - * Cache all necessary modules. - */ - -bcoin.cache = function cache() { - bcoin.bip70; - bcoin.blockchain; - bcoin.btc; - bcoin.coins; - bcoin.crypto; - bcoin.db; - bcoin.hd; - bcoin.http; - bcoin.txmempool; - bcoin.mining; - bcoin.net; - bcoin.node; - bcoin.primitives; - bcoin.protocol; - bcoin.txscript; - bcoin.utils; - bcoin.wallet; - bcoin.workers; - bcoin.pkg; -}; - /* * Expose */ -// Horrible BIP -bcoin.define('bip70', './bip70'); - // Blockchain bcoin.define('blockchain', './blockchain'); -bcoin.define('chain', './blockchain/chain'); -bcoin.define('chaindb', './blockchain/chaindb'); -bcoin.define('chainentry', './blockchain/chainentry'); +bcoin.define('blockstore', './blockstore'); +bcoin.define('Chain', './blockchain/chain'); +bcoin.define('ChainEntry', './blockchain/chainentry'); // BTC bcoin.define('btc', './btc'); -bcoin.define('amount', './btc/amount'); -bcoin.define('uri', './btc/uri'); +bcoin.define('Amount', './btc/amount'); +bcoin.define('URI', './btc/uri'); // Coins bcoin.define('coins', './coins'); -bcoin.define('coinview', './coins/coinview'); - -// Crypto -bcoin.define('crypto', './crypto'); -bcoin.define('bn', './crypto/bn'); -bcoin.define('secp256k1', './crypto/secp256k1'); -bcoin.define('schnorr', './crypto/schnorr'); - -// DB -bcoin.define('db', './db'); -bcoin.define('ldb', './db/ldb'); +bcoin.define('Coins', './coins/coins'); +bcoin.define('CoinEntry', './coins/coinentry'); +bcoin.define('CoinView', './coins/coinview'); // HD bcoin.define('hd', './hd'); +bcoin.define('HDPrivateKey', './hd/private'); +bcoin.define('HDPublicKey', './hd/public'); +bcoin.define('Mnemonic', './hd/mnemonic'); -// HTTP -bcoin.define('http', './http'); -bcoin.define('rpc', './http/rpc'); +// Index +bcoin.define('indexer', './indexer'); +bcoin.define('Indexer', './indexer/indexer'); +bcoin.define('TXIndexer', './indexer/txindexer'); +bcoin.define('AddrIndexer', './indexer/addrindexer'); // Mempool -bcoin.define('txmempool', './mempool'); -bcoin.define('fees', './mempool/fees'); -bcoin.define('mempool', './mempool/mempool'); -bcoin.define('mempoolentry', './mempool/mempoolentry'); +bcoin.define('mempool', './mempool'); +bcoin.define('Fees', './mempool/fees'); +bcoin.define('Mempool', './mempool/mempool'); +bcoin.define('MempoolEntry', './mempool/mempoolentry'); // Miner bcoin.define('mining', './mining'); -bcoin.define('miner', './mining/miner'); -bcoin.define('template', './mining/template'); +bcoin.define('Miner', './mining/miner'); // Net bcoin.define('net', './net'); -bcoin.define('bip150', './net/bip150'); -bcoin.define('bip151', './net/bip151'); -bcoin.define('bip152', './net/bip152'); -bcoin.define('dns', './net/dns'); bcoin.define('packets', './net/packets'); -bcoin.define('peer', './net/peer'); -bcoin.define('pool', './net/pool'); -bcoin.define('tcp', './net/tcp'); +bcoin.define('Peer', './net/peer'); +bcoin.define('Pool', './net/pool'); // Node bcoin.define('node', './node'); -bcoin.define('config', './node/config'); -bcoin.define('fullnode', './node/fullnode'); -bcoin.define('logger', './node/logger'); -bcoin.define('spvnode', './node/spvnode'); +bcoin.define('Node', './node/node'); +bcoin.define('FullNode', './node/fullnode'); +bcoin.define('SPVNode', './node/spvnode'); // Primitives bcoin.define('primitives', './primitives'); -bcoin.define('address', './primitives/address'); -bcoin.define('block', './primitives/block'); -bcoin.define('coin', './primitives/coin'); -bcoin.define('headers', './primitives/headers'); -bcoin.define('input', './primitives/input'); -bcoin.define('invitem', './primitives/invitem'); -bcoin.define('keyring', './primitives/keyring'); -bcoin.define('merkleblock', './primitives/merkleblock'); -bcoin.define('mtx', './primitives/mtx'); -bcoin.define('netaddress', './primitives/netaddress'); -bcoin.define('outpoint', './primitives/outpoint'); -bcoin.define('output', './primitives/output'); -bcoin.define('tx', './primitives/tx'); +bcoin.define('Address', './primitives/address'); +bcoin.define('Block', './primitives/block'); +bcoin.define('Coin', './primitives/coin'); +bcoin.define('Headers', './primitives/headers'); +bcoin.define('Input', './primitives/input'); +bcoin.define('InvItem', './primitives/invitem'); +bcoin.define('KeyRing', './primitives/keyring'); +bcoin.define('MerkleBlock', './primitives/merkleblock'); +bcoin.define('MTX', './primitives/mtx'); +bcoin.define('Outpoint', './primitives/outpoint'); +bcoin.define('Output', './primitives/output'); +bcoin.define('TX', './primitives/tx'); // Protocol bcoin.define('protocol', './protocol'); bcoin.define('consensus', './protocol/consensus'); -bcoin.define('errors', './protocol/errors'); -bcoin.define('network', './protocol/network'); +bcoin.define('Network', './protocol/network'); bcoin.define('networks', './protocol/networks'); bcoin.define('policy', './protocol/policy'); -bcoin.define('timedata', './protocol/timedata'); // Script -bcoin.define('txscript', './script'); -bcoin.define('opcode', './script/opcode'); -bcoin.define('program', './script/program'); -bcoin.define('script', './script/script'); -bcoin.define('scriptnum', './script/scriptnum'); -bcoin.define('sigcache', './script/sigcache'); -bcoin.define('stack', './script/stack'); -bcoin.define('witness', './script/witness'); +bcoin.define('script', './script'); +bcoin.define('Opcode', './script/opcode'); +bcoin.define('Program', './script/program'); +bcoin.define('Script', './script/script'); +bcoin.define('ScriptNum', './script/scriptnum'); +bcoin.define('SigCache', './script/sigcache'); +bcoin.define('Stack', './script/stack'); +bcoin.define('Witness', './script/witness'); // Utils bcoin.define('utils', './utils'); -bcoin.define('base32', './utils/base32'); -bcoin.define('base58', './utils/base58'); -bcoin.define('bloom', './utils/bloom'); -bcoin.define('co', './utils/co'); -bcoin.define('encoding', './utils/encoding'); -bcoin.define('int64', './utils/int64'); -bcoin.define('lock', './utils/lock'); -bcoin.define('reader', './utils/reader'); -bcoin.define('staticwriter', './utils/staticwriter'); bcoin.define('util', './utils/util'); -bcoin.define('writer', './utils/writer'); // Wallet bcoin.define('wallet', './wallet'); -bcoin.define('path', './wallet/path'); -bcoin.define('walletkey', './wallet/walletkey'); -bcoin.define('walletdb', './wallet/walletdb'); +bcoin.define('Path', './wallet/path'); +bcoin.define('WalletKey', './wallet/walletkey'); +bcoin.define('WalletDB', './wallet/walletdb'); // Workers bcoin.define('workers', './workers'); -bcoin.define('workerpool', './workers/workerpool'); +bcoin.define('WorkerPool', './workers/workerpool'); // Package Info bcoin.define('pkg', './pkg'); diff --git a/lib/bip70/certs.js b/lib/bip70/certs.js deleted file mode 100644 index d0c0d12a4..000000000 --- a/lib/bip70/certs.js +++ /dev/null @@ -1,154 +0,0 @@ -'use strict'; - -module.exports = [ - 'ebd41040e4bb3ec742c9e381d31ef2a41a48b6685c96e7cef3c1df6cd4331c99', - 'ca42dd41745fd0b81eb902362cf9d8bf719da1bd1b1efc946f5b4c99f42c1b9e', - 'eb04cf5eb1f39afa762f2bb120f296cba520c1b97db1589565b81cb9a17b7244', - '6dc47172e01cbcb0bf62580d895fe2b8ac9ad4f873801e0c10b9c837d21eb177', - '16af57a9f676b0ab126095aa5ebadef22ab31119d644ac95cd4b93dbf3f26aeb', - '8c7209279ac04e275e16d07fd3b775e80154b5968046e31f52dd25766324e9a7', - '687fa451382278fff0c8b11f8d43d576671c6eb2bceab413fb83d965d06d2ff2', - '0791ca0749b20782aad3c7d7bd0cdfc9485835843eb2d7996009ce43ab6c6927', - '8095210805db4bbc355e4428d8fd6ec2cde3ab5fb97a9942988eb8f4dcd06016', - '73c176434f1bc6d5adf45b0e76e727287c8de57616c1e6e6141a2b2cbc7d8e4c', - 'af8b6762a1e528228161a95d5c559ee266278f75d79e830189a503506abd6b4c', - 'ff856a2d251dcd88d36656f450126798cfabaade40799c722de4d2b5db36a73a', - 'ca2d82a08677072f8ab6764ff035676cfe3e5e325e012172df3f92096db79b85', - 'a0459b9f63b22559f5fa5d4c6db3f9f72ff19342033578f073bf1d1b46cbb912', - 'a0234f3bc8527ca5628eec81ad5d69895da5680dc91d1cb8477f33f878b95b0b', - '69fac9bd55fb0ac78d53bbee5cf1d597989fd0aaab20a25151bdf1733ee7d122', - 'd8e0febc1db2e38d00940f37d27d41344d993e734b99d5656d9778d4d8143624', - 'd7a7a0fb5d7e2731d771e9484ebcdef71d5f0c3e0a2948782bc83ee0ea699ef4', - 'bd81ce3b4f6591d11a67b5fc7a47fdef25521bf9aa4e18b9e3df2e34a7803be8', - '3f06e55681d496f5be169eb5389f9f2b8ff61e1708df6881724849cd5d27cb69', - 'a45ede3bbbf09c8ae15c72efc07268d693a21c996fd51e67ca079460fd6d8873', - '85a0dd7dd720adb7ff05f83d542b209dc7ff4528f7d677b18389fea5e5c49e86', - '18f1fc7f205df8adddeb7fe007dd57e3af375a9c4d8d73546bf4f1fed1e18d35', - 'e75e72ed9f560eec6eb4800073a43fc3ad19195a392282017895974a99026b6c', - '7908b40314c138100b518d0735807ffbfcf8518a0095337105ba386b153dd927', - '6ea54741d004667eed1b4816634aa3a79e6e4b96950f8279dafc8d9bd8812137', - '0c258a12a5674aef25f28ba7dcfaeceea348e541e6f5cc4ee63b71b361606ac3', - 'ef3cb417fc8ebf6f97876c9e4ece39de1ea5fe649141d1028b7d11c0b2298ced', - 'cecddc905099d8dadfc5b1d209b737cbe2c18cfb2c10c0ff0bcf0d3286fc1aa2', - 'c3846bf24b9e93ca64274c0ec67c1ecc5e024ffcacd2d74019350e81fe546ae4', - '1465fa205397b876faa6f0a9958e5590e40fcc7faa4fb7c2c8677521fb5fb658', - 'c766a9bef2d4071c863a31aa4920e813b2d198608cb7b7cfe21143b836df09ea', - '7600295eefe85b9e1fd624db76062aaaae59818a54d2774cd4c0b2c01131e1b3', - '21db20123660bb2ed418205da11ee7a85a65e2bc6e55b5af7e7899c8a266d92e', - '3e9099b5015e8f486c00bcea9d111ee721faba355a89bcf1df69561e3dc6325c', - '4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161', - '7431e5f4c3c1ce4690774f0b61e05440883ba9a01ed00ba6abd7806ed3b118cf', - '0f993c8aef97baaf5687140ed59ad1821bb4afacf0aa9a58b5d57a338a3afbcb', - '0687260331a72403d909f105e69bcf0d32e1bd2493ffc6d9206d11bcd6770739', - '767c955a76412c89af688e90a1c70f556cfd6b6025dbea10416d7eb6831f8c40', - '62dd0be9b9f50a163ea0f8e75c053b1eca57ea55c8688f647c6881f2c8357b95', - 'be6c4da2bbb9ba59b6f3939768374246c3c005993fa98f020d1dedbed48a81d5', - '37d51006c512eaab626421f1ec8c92013fc5f82ae98ee533eb4619b8deb4d06c', - '8d722f81a9c113c0791df136a2966db26c950a971db46b4199f4ea54b78bfb9f', - '9acfab7e43c8d880d06b262a94deeee4b4659989c3d0caf19baf6405e41ab7df', - 'f1c1b50ae5a20dd8030ec9f6bc24823dd367b5255759b4e71b61fce9f7375d73', - '4200f5043ac8590ebb527d209ed1503029fbcbd41ca1b506ec27f15ade7dac69', - '0c2cd63df7806fa399ede809116b575bf87989f06518f9808c860503178baf66', - '15f0ba00a3ac7af3ac884c072b1011a077bd77c097f40164b2f8598abd83860c', - 'a71272aeaaa3cfe8727f7fb39f0fb3d1e5426e9060b06ee6f13e9a3c5833cd43', - '1793927a0614549789adce2f8f34f7f0b66d0f3ae3a3b84d21ec15dbba4fadc7', - 'b9bea7860a962ea3611dab97ab6da3e21c1068b97d55575ed0e11279c11c8932', - 'a22dba681e97376e2d397d728aae3a9b6296b9fdba60bc2e11f647f2c675fb37', - '41c923866ab4cad6b7ad578081582e020797a6cbdf4fff78ce8396b38937d7f5', - '327a3d761abadea034eb998406275cb1a4776efdae2fdf6d0168ea1c4f5567d0', - 'e3b6a2db2ed7ce48842f7ac53241c7b71d54144bfb40c11f3f1d0b42f5eea12d', - 'b6191a50d0c3977f7da99bcdaac86a227daeb9679ec70ba3b0c9d92271c170d3', - '960adf0063e96356750c2965dd0a0867da0b9cbd6e77714aeafb2349ab393da3', - 'c0a6f4dc63a24bfdcf54ef2a6a082a0a72de35803e2ff5ff527ae5d87206dfd5', - 'e4c73430d7a5b50925df43370a0d216e9a79b9d6db8373a0c69eb1cc31c7c52a', - '0f4e9cdd264b025550d170806340214fe94434c9b02f697ec710fc5feafb5e38', - '35ae5bddd8f7ae635cffba5682a8f00b95f48462c7108ee9a0e5292b074aafb2', - 'eaa962c4fa4a6bafebe415196d351ccd888d4f53f3fa8ae6d7c466a94e6042bb', - 'e28393773da845a679f2080cc7fb44a3b7a1c3792cb7eb7729fdcb6a8d99aea7', - '2d47437de17951215a12f3c58e51c729a58026ef1fcc0a5fb3d9dc012f600d19', - 'b478b812250df878635c2aa7ec7d155eaa625ee82916e2cd294361886cd1fbd4', - 'a4310d50af18a6447190372a86afaf8b951ffb431d837f1e5688b45971ed1557', - '4b03f45807ad70f21bfc2cae71c9fde4604c064cf5ffb686bae5dbaad7fdd34c', - '5edb7ac43b82a06a8761e8d7be4979ebf2611f7dd79bf91c1c6b566a219ed766', - '2399561127a57125de8cefea610ddf2fa078b5c8067f4e828290bfb860e84b3c', - '69ddd7ea90bb57c93e135dc85ea6fcd5480b603239bdc454fc758b2a26cf7f79', - '6c61dac3a2def031506be036d2a6fe401994fbd13df9c8d466599274c446ec98', - '668c83947da63b724bece1743c31a0e6aed0db8ec5b31be377bb784f91b6716f', - 'ecc3e9c3407503bee091aa952f41348ff88baa863b2264befac807901574e939', - 'f9e67d336c51002ac054c632022d66dda2e7e3fff10ad061ed31d8bbb410cfb2', - 'bf0feefb9e3a581ad5f9e9db7589985743d261085c4d314f6f5d7259aa421612', - '03950fb49a531f3e1991942398dfa9e0ea32d7ba1cdd9bc85db57ed9400b434a', - '3c5f81fea5fab82c64bfa2eaecafcde8e077fc8620a7cae537163df36edbf378', - 'cbb522d7b7f127ad6a0113865bdf1cd4102e7d0759af635a7cf4720dc963c53b', - '04048028bf1f2864d48f9ad4d83294366a828856553f3b14303f90147f5d40ef', - '2530cc8e98321502bad96f9b1fba1b099e2d299e0f4548bb914f363bc0d4531f', - '063e4afac491dfd332f3089b8542e94617d893d7fe944e10a7937ee29d9693c0', - '136335439334a7698016a0d324de72284e079d7b5220bb8fbd747816eebebaca', - '45140b3247eb9cc8c5b4f0d7b53091f73292089e6e5a63e2749dd3aca9198eda', - '2ce1cb0bf9d2f9e102993fbe215152c3b2dd0cabde1c68e5319b839154dbb7f5', - '568d6905a2c88708a4b3025190edcfedb1974a606a13c6e5290fcb2ae63edab5', - '0376ab1d54c5f9803ce4b2e201a0ee7eef7b57b636e8a93c9b8d4860c96f5fa7', - '0a81ec5a929777f145904af38d5d509f66b5e2c58fcdb531058b0e17f3f0b41b', - '70a73f7f376b60074248904534b11482d5bf0e698ecc498df52577ebf2e93b9a', - 'bd71fdf6da97e4cf62d1647add2581b07d79adf8397eb4ecba9c5e8488821423', - '5c58468d55f58e497e743982d2b50010b6d165374acf83a7d4a32db768c4408e', - 'fcbfe2886206f72b27593c8b070297e12d769ed10ed7930705a8098effc14d17', - '8c4edfd04348f322969e7e29a4cd4dca004655061c16e1b076422ef342ad630e', - 'bfd88fe1101c41ae3e801bf8be56350ee9bad1a6b9bd515edc5c6d5b8711ac44', - '513b2cecb810d4cde5dd85391adfc6c2dd60d87bb736d2b521484aa47a0ebef6', - '88497f01602f3154246ae28c4d5aef10f1d87ebb76626f4ae0b7f95ba7968799', - 'bc104f15a48be709dca542a7e1d4b9df6f054527e802eaa92d595444258afe71', - '55926084ec963a64b96e2abe01ce0ba86a64fbfebcc7aab5afc155b37fd76066', - 'c1b48299aba5208fe9630ace55ca68a03eda5a519c8802a0d3a673be8f8e557d', - 'e17890ee09a3fbf4f48b9c414a17d637b7a50647e9bc752322727fcc1742a911', - 'c7ba6567de93a798ae1faa791e712d378fae1f93c4397fea441bb7cbe6fd5995', - '9a114025197c5bb95d94e63d55cd43790847b646b23cdf11ada4a00eff15fb48', - 'edf7ebbca27a2a384d387b7d4010c666e2edb4843e4c29b4ae1d5b9332e6b24d', - 'fd73dad31c644ff1b43bef0ccdda96710b9cd9875eca7e31707af3e96d522bbd', - '3e84ba4342908516e77573c0992f0979ca084e4685681ff195ccba8a229b8a76', - '978cd966f2faa07ba7aa9500d9c02e9d77f2cdada6ad6ba74af4b91c66593c50', - '49e7a442acf0ea6287050054b52564b650e4f49e42e348d6aa38e039e957b1c1', - 'eec5496b988ce98625b934092eec2908bed0b0f316c2d4730c84eaf1f3d34881', - '3cfc3c14d1f684ff17e38c43ca440c00b967ec933e8bfe064ca1d72c90f2adb0', - '1c01c6f4dbb2fefc22558b2bca32563f49844acfc32b7be4b0ff599f9e8c7af7', - 'f09b122c7114f4a09bd4ea4f4a99d558b46e4c25cd81140d29c05613914c3841', - 'd95fea3ca4eedce74cd76e75fc6d1ff62c441f0fa8bc77f034b19e5db258015d', - 'f96f23f4c3e79c077a46988d5af5900676a0f039cb645dd17549b216c82440ce', - 'e23d4a036d7b70e9f595b1422079d2b91edfbb1fb651a0633eaa8a9dc5f80703', - '9a6ec012e1a7da9dbe34194d478ad7c0db1822fb071df12981496ed104384113', - '59769007f7685d0fcd50872f9f95d5755a5b2b457d81f3692b610a98672f0e1b', - 'dd6936fe21f8f077c123a1a521c12224f72255b73e03a7260693e8a24b0fa389', - 'b0bfd52bb0d7d9bd92bf5d4dc13da255c02c542f378365ea893911f55e55f23c', - '91e2f5788d5810eba7ba58737de1548a8ecacd014598bc0b143e041b17052552', - 'f356bea244b7a91eb35d53ca9ad7864ace018e2d35d5f8f96ddf68a6f41aa474', - '8a866fd1b276b57e578e921c65828a2bed58e9f2f288054134b7f1f4bfc9cc74', - '8fe4fb0af93a4d0d67db0bebb23e37c71bf325dcbcdd240ea04daf58b47e1840', - '88ef81de202eb018452e43f864725cea5fbd1fc2d9d205730709c5d8b8690f46', - '7d05ebb682339f8c9451ee094eebfefa7953a114edb2f44949452fab7d2fc185', - '7e37cb8b4c47090cab36551ba6f45db840680fba166a952db100717f43053fc2', - 'cb3ccbb76031e5e0138f8dd39a23f9de47ffc35e43c1144cea27d46a5ab1cb5f', - '31ad6648f8104138c738f39ea4320133393e3a18cc02296ef97c2ac9ef6731d0', - '552f7bdcf1a7af9e6ce672017f4f12abf77240c78e761ac203d1d9d20ac89988', - '4b22d5a6aec99f3cdb79aa5ec06838479cd5ecba7164f7f22dc1d65f63d85708', - 'd6f034bd94aa233f0297eca4245b283973e447aa590f310c77f48fdf83112254', - '52f0e1c4e58ec629291b60317f074671b85d7ea80d5b07273463534b32b40234', - 'e793c9b02fd8aa13e21c31228accb08119643b749c898964b1746d46c3d4cbd2', - '4ff460d54b9c86dabfbcfc5712e0400d2bed3fbc4d4fbdaa86e06adcd2a9ad7a', - 'bec94911c2955676db6c0a550986d76e3ba005667c442c9762b4fbb773de228c', - '179fbc148a3dd00fd24ea13458cc43bfa7f59c8182d783a513f6ebec100c8924', - '3c4fb0b95ab8b30032f432b86f535fe172c185d0fd39865837cf36187fa6f428', - '4d2491414cfe956746ec4cefa6cf6f72e28a1329432f9d8a907ac4cb5dadc15a', - '5d56499be4d2e08bcfcad08a3e38723d50503bde706948e42f55603019e528ae', - '30d0895a9a448a262091635522d1f52010b5867acae12c78ef958fd4f4389f2f', - '43df5774b03e7fef5fe40d931a7bedf1bb2e6b42738c4e6d3841103d3aa7f339', - '02ed0eb28c14da45165c566791700d6451d7fb56f0b2ab1d3b8eb070e56edff5', - '5cc3d78e4e1d5e45547a04e6873e64f90cf9536d1ccc2ef800f355c4c5fd70fd', - '49351b903444c185ccdc5c693d24d8555cb208d6a8141307699f4af063199d78', - '8de78655e1be7f7847800b93f694d21d368cc06e033e7fab04bb5eb99da6b700', - '2a99f5bc1174b73cbb1d620884e01c34e51ccb3978da125f0e33268883bf4158', - '6b9c08e86eb0f767cfad65cd98b62149e5494a67f5845e7bd1ed019f27b86bd6', - 'd487a56f83b07482e85e963394c1ecc2c9e51d0903ee946b02c301581ed99e16', - '8b45da1c06f791eb0cabf26be588f5fb23165c2e614bf885562d0dce50b29b02', - 'a1339d33281a0b56e557d3d32b1ce7f9367eb094bd5fa72a7e5004c8ded7cafe', - 'b676f2eddae8775cd36cb0f63cd1d4603961f49e6265ba013a2f0307b6d0b804' -]; diff --git a/lib/bip70/index.js b/lib/bip70/index.js deleted file mode 100644 index 64bd80f0b..000000000 --- a/lib/bip70/index.js +++ /dev/null @@ -1,19 +0,0 @@ -/*! - * bip70/index.js - bip70 for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module bip70 - */ - -exports.certs = require('./certs'); -exports.PaymentACK = require('./paymentack'); -exports.PaymentDetails = require('./paymentdetails'); -exports.Payment = require('./payment'); -exports.PaymentRequest = require('./paymentrequest'); -exports.pk = require('./pk'); -exports.x509 = require('./x509'); diff --git a/lib/bip70/payment.js b/lib/bip70/payment.js deleted file mode 100644 index d5ab654b9..000000000 --- a/lib/bip70/payment.js +++ /dev/null @@ -1,179 +0,0 @@ -/*! - * payment.js - bip70 payment for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const Output = require('../primitives/output'); -const TX = require('../primitives/tx'); -const Script = require('../script/script'); -const ProtoReader = require('../utils/protoreader'); -const ProtoWriter = require('../utils/protowriter'); -const PaymentDetails = require('./paymentdetails'); - -/** - * Represents a BIP70 payment. - * @alias module:bip70.Payment - * @constructor - * @param {Object?} options - * @property {Buffer} merchantData - * @property {TX[]} transactions - * @property {Output[]} refundTo - * @property {String|null} memo - */ - -function Payment(options) { - if (!(this instanceof Payment)) - return new Payment(options); - - this.merchantData = null; - this.transactions = []; - this.refundTo = []; - this.memo = null; - - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options. - * @private - * @param {Object} options - * @returns {Payment} - */ - -Payment.prototype.fromOptions = function fromOptions(options) { - if (options.merchantData) - this.setData(options.merchantData); - - if (options.transactions) { - assert(Array.isArray(options.transactions)); - for (const item of options.transactions) { - const tx = new TX(item); - this.transactions.push(tx); - } - } - - if (options.refundTo) { - assert(Array.isArray(options.refundTo)); - for (const item of options.refundTo) { - const output = new Output(item); - this.refundTo.push(output); - } - } - - if (options.memo != null) { - assert(typeof options.memo === 'string'); - this.memo = options.memo; - } - - return this; -}; - -/** - * Instantiate payment from options. - * @param {Object} options - * @returns {Payment} - */ - -Payment.fromOptions = function fromOptions(options) { - return new Payment().fromOptions(options); -}; - -/** - * Set payment details. - * @method - * @alias Payment#setData - * @param {Object} data - * @param {String?} enc - */ - -Payment.prototype.setData = PaymentDetails.prototype.setData; - -/** - * Get payment details. - * @method - * @alias Payment#getData - * @param {String?} enc - * @returns {String|Object|null} - */ - -Payment.prototype.getData = PaymentDetails.prototype.getData; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @returns {Payment} - */ - -Payment.prototype.fromRaw = function fromRaw(data) { - const br = new ProtoReader(data); - - this.merchantData = br.readFieldBytes(1, true); - - while (br.nextTag() === 2) { - const tx = TX.fromRaw(br.readFieldBytes(2)); - this.transactions.push(tx); - } - - while (br.nextTag() === 3) { - const op = new ProtoReader(br.readFieldBytes(3)); - const output = new Output(); - output.value = op.readFieldU64(1, true); - output.script = Script.fromRaw(op.readFieldBytes(2, true)); - this.refundTo.push(output); - } - - this.memo = br.readFieldString(4, true); - - return this; -}; - -/** - * Instantiate payment from serialized data. - * @param {Buffer} data - * @returns {Payment} - */ - -Payment.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new Payment().fromRaw(data); -}; - -/** - * Serialize the payment (protobuf). - * @returns {Buffer} - */ - -Payment.prototype.toRaw = function toRaw() { - const bw = new ProtoWriter(); - - if (this.merchantData) - bw.writeFieldBytes(1, this.merchantData); - - for (const tx of this.transactions) - bw.writeFieldBytes(2, tx.toRaw()); - - for (const output of this.refundTo) { - const op = new ProtoWriter(); - op.writeFieldU64(1, output.value); - op.writeFieldBytes(2, output.script.toRaw()); - bw.writeFieldBytes(3, op.render()); - } - - if (this.memo != null) - bw.writeFieldString(4, this.memo); - - return bw.render(); -}; - -/* - * Expose - */ - -module.exports = Payment; diff --git a/lib/bip70/paymentack.js b/lib/bip70/paymentack.js deleted file mode 100644 index b755ee848..000000000 --- a/lib/bip70/paymentack.js +++ /dev/null @@ -1,111 +0,0 @@ -/*! - * paymentack.js - bip70 paymentack for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const ProtoReader = require('../utils/protoreader'); -const ProtoWriter = require('../utils/protowriter'); -const Payment = require('./payment'); - -/** - * Represents a BIP70 payment ack. - * @alias module:bip70.PaymentACK - * @constructor - * @param {Object?} options - * @property {Payment} payment - * @property {String|null} memo - */ - -function PaymentACK(options) { - if (!(this instanceof PaymentACK)) - return new PaymentACK(options); - - this.payment = new Payment(); - this.memo = null; - - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options. - * @private - * @param {Object} options - * @returns {PaymentACK} - */ - -PaymentACK.prototype.fromOptions = function fromOptions(options) { - if (options.payment) - this.payment.fromOptions(options.payment); - - if (options.memo != null) { - assert(typeof options.memo === 'string'); - this.memo = options.memo; - } - - return this; -}; - -/** - * Instantiate payment ack from options. - * @param {Object} options - * @returns {PaymentACK} - */ - -PaymentACK.fromOptions = function fromOptions(options) { - return new PaymentACK().fromOptions(options); -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @returns {PaymentACK} - */ - -PaymentACK.prototype.fromRaw = function fromRaw(data) { - const br = new ProtoReader(data); - - this.payment.fromRaw(br.readFieldBytes(1)); - this.memo = br.readFieldString(2, true); - - return this; -}; - -/** - * Instantiate payment ack from serialized data. - * @param {Buffer} data - * @returns {PaymentACK} - */ - -PaymentACK.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new PaymentACK().fromRaw(data); -}; - -/** - * Serialize the payment ack (protobuf). - * @returns {Buffer} - */ - -PaymentACK.prototype.toRaw = function toRaw() { - const bw = new ProtoWriter(); - - bw.writeFieldBytes(1, this.payment.toRaw()); - - if (this.memo != null) - bw.writeFieldString(2, this.memo); - - return bw.render(); -}; - -/* - * Expose - */ - -module.exports = PaymentACK; diff --git a/lib/bip70/paymentdetails.js b/lib/bip70/paymentdetails.js deleted file mode 100644 index c33d8c421..000000000 --- a/lib/bip70/paymentdetails.js +++ /dev/null @@ -1,242 +0,0 @@ -/*! - * paymentdetails.js - bip70 paymentdetails for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const util = require('../utils/util'); -const Output = require('../primitives/output'); -const ProtoReader = require('../utils/protoreader'); -const ProtoWriter = require('../utils/protowriter'); - -/** - * Represents BIP70 payment details. - * @alias module:bip70.PaymentDetails - * @constructor - * @param {Object?} options - * @property {String|null} network - * @property {Output[]} outputs - * @property {Number} time - * @property {Number} expires - * @property {String|null} memo - * @property {String|null} paymentUrl - * @property {Buffer|null} merchantData - */ - -function PaymentDetails(options) { - if (!(this instanceof PaymentDetails)) - return new PaymentDetails(options); - - this.network = null; - this.outputs = []; - this.time = util.now(); - this.expires = -1; - this.memo = null; - this.paymentUrl = null; - this.merchantData = null; - - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options. - * @private - * @param {Object} options - * @returns {PaymentDetails} - */ - -PaymentDetails.prototype.fromOptions = function fromOptions(options) { - if (options.network != null) { - assert(typeof options.network === 'string'); - this.network = options.network; - } - - if (options.outputs) { - assert(Array.isArray(options.outputs)); - for (const item of options.outputs) { - const output = new Output(item); - this.outputs.push(output); - } - } - - if (options.time != null) { - assert(util.isInt(options.time)); - this.time = options.time; - } - - if (options.expires != null) { - assert(util.isInt(options.expires)); - this.expires = options.expires; - } - - if (options.memo != null) { - assert(typeof options.memo === 'string'); - this.memo = options.memo; - } - - if (options.paymentUrl != null) { - assert(typeof options.paymentUrl === 'string'); - this.paymentUrl = options.paymentUrl; - } - - if (options.merchantData) - this.setData(options.merchantData); - - return this; -}; - -/** - * Instantiate payment details from options. - * @param {Object} options - * @returns {PaymentDetails} - */ - -PaymentDetails.fromOptions = function fromOptions(options) { - return new PaymentDetails().fromOptions(options); -}; - -/** - * Test whether the payment is expired. - * @returns {Boolean} - */ - -PaymentDetails.prototype.isExpired = function isExpired() { - if (this.expires === -1) - return false; - return util.now() > this.expires; -}; - -/** - * Set payment details. - * @param {Object} data - * @param {String?} enc - */ - -PaymentDetails.prototype.setData = function setData(data, enc) { - if (data == null || Buffer.isBuffer(data)) { - this.merchantData = data; - return; - } - - if (typeof data !== 'string') { - assert(!enc || enc === 'json'); - this.merchantData = Buffer.from(JSON.stringify(data), 'utf8'); - return; - } - - this.merchantData = Buffer.from(data, enc); -}; - -/** - * Get payment details. - * @param {String?} enc - * @returns {String|Object|null} - */ - -PaymentDetails.prototype.getData = function getData(enc) { - let data = this.merchantData; - - if (!data) - return null; - - if (!enc) - return data; - - if (enc === 'json') { - data = data.toString('utf8'); - try { - data = JSON.parse(data); - } catch (e) { - return null; - } - return data; - } - - return data.toString(enc); -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @returns {PaymentDetails} - */ - -PaymentDetails.prototype.fromRaw = function fromRaw(data) { - const br = new ProtoReader(data); - - this.network = br.readFieldString(1, true); - - while (br.nextTag() === 2) { - const op = new ProtoReader(br.readFieldBytes(2)); - const output = new Output(); - output.value = op.readFieldU64(1, true); - output.script.fromRaw(op.readFieldBytes(2, true)); - this.outputs.push(output); - } - - this.time = br.readFieldU64(3); - this.expires = br.readFieldU64(4, true); - this.memo = br.readFieldString(5, true); - this.paymentUrl = br.readFieldString(6, true); - this.merchantData = br.readFieldBytes(7, true); - - return this; -}; - -/** - * Instantiate payment details from serialized data. - * @param {Buffer} data - * @returns {PaymentDetails} - */ - -PaymentDetails.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new PaymentDetails().fromRaw(data); -}; - -/** - * Serialize the payment details (protobuf). - * @returns {Buffer} - */ - -PaymentDetails.prototype.toRaw = function toRaw() { - const bw = new ProtoWriter(); - - if (this.network != null) - bw.writeFieldString(1, this.network); - - for (const output of this.outputs) { - const op = new ProtoWriter(); - op.writeFieldU64(1, output.value); - op.writeFieldBytes(2, output.script.toRaw()); - bw.writeFieldBytes(2, op.render()); - } - - bw.writeFieldU64(3, this.time); - - if (this.expires !== -1) - bw.writeFieldU64(4, this.expires); - - if (this.memo != null) - bw.writeFieldString(5, this.memo); - - if (this.paymentUrl != null) - bw.writeFieldString(6, this.paymentUrl); - - if (this.merchantData) - bw.writeFieldString(7, this.merchantData); - - return bw.render(); -}; - -/* - * Expose - */ - -module.exports = PaymentDetails; diff --git a/lib/bip70/paymentrequest.js b/lib/bip70/paymentrequest.js deleted file mode 100644 index 6fb17555f..000000000 --- a/lib/bip70/paymentrequest.js +++ /dev/null @@ -1,351 +0,0 @@ -/*! - * paymentrequest.js - bip70 paymentrequest for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const util = require('../utils/util'); -const digest = require('../crypto/digest'); -const x509 = require('./x509'); -const PEM = require('../utils/pem'); -const ProtoReader = require('../utils/protoreader'); -const ProtoWriter = require('../utils/protowriter'); -const PaymentDetails = require('./paymentdetails'); - -/** - * Represents a BIP70 payment request. - * @alias module:bip70.PaymentRequest - * @constructor - * @param {Object?} options - * @property {Number} version - * @property {String|null} pkiType - * @property {Buffer|null} pkiData - * @property {PaymentDetails} paymentDetails - * @property {Buffer|null} signature - */ - -function PaymentRequest(options) { - if (!(this instanceof PaymentRequest)) - return new PaymentRequest(options); - - this.version = -1; - this.pkiType = null; - this.pkiData = null; - this.paymentDetails = new PaymentDetails(); - this.signature = null; - - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options. - * @private - * @param {Object} options - * @returns {PaymentRequest} - */ - -PaymentRequest.prototype.fromOptions = function fromOptions(options) { - if (options.version != null) { - assert(util.isInt(options.version)); - this.version = options.version; - } - - if (options.pkiType != null) { - assert(typeof options.pkiType === 'string'); - this.pkiType = options.pkiType; - } - - if (options.pkiData) { - assert(Buffer.isBuffer(options.pkiData)); - this.pkiData = options.pkiData; - } - - if (options.paymentDetails) - this.paymentDetails.fromOptions(options.paymentDetails); - - if (options.signature) { - assert(Buffer.isBuffer(options.signature)); - this.signature = options.signature; - } - - if (options.chain) - this.setChain(options.chain); - - return this; -}; - -/** - * Instantiate payment request from options. - * @param {Object} options - * @returns {PaymentRequest} - */ - -PaymentRequest.fromOptions = function fromOptions(options) { - return new PaymentRequest().fromOptions(options); -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @returns {PaymentRequest} - */ - -PaymentRequest.prototype.fromRaw = function fromRaw(data) { - const br = new ProtoReader(data); - - this.version = br.readFieldU32(1, true); - this.pkiType = br.readFieldString(2, true); - this.pkiData = br.readFieldBytes(3, true); - this.paymentDetails.fromRaw(br.readFieldBytes(4)); - this.signature = br.readFieldBytes(5, true); - - return this; -}; - -/** - * Instantiate payment request from serialized data. - * @param {Buffer} data - * @returns {PaymentRequest} - */ - -PaymentRequest.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new PaymentRequest().fromRaw(data); -}; - -/** - * Serialize the payment request (protobuf). - * @returns {Buffer} - */ - -PaymentRequest.prototype.toRaw = function toRaw() { - const bw = new ProtoWriter(); - - if (this.version !== -1) - bw.writeFieldU32(1, this.version); - - if (this.pkiType != null) - bw.writeFieldString(2, this.pkiType); - - if (this.pkiData) - bw.writeFieldBytes(3, this.pkiData); - - bw.writeFieldBytes(4, this.paymentDetails.toRaw()); - - if (this.signature) - bw.writeFieldBytes(5, this.signature); - - return bw.render(); -}; - -/** - * Get payment request signature algorithm. - * @returns {Object|null} - */ - -PaymentRequest.prototype.getAlgorithm = function getAlgorithm() { - if (!this.pkiType) - throw new Error('No PKI type available.'); - - const parts = this.pkiType.split('+'); - - if (parts.length !== 2) - throw new Error('Could not parse PKI algorithm.'); - - if (parts[0] !== 'x509') - throw new Error(`Unknown PKI type: ${parts[0]}.`); - - if (parts[1] !== 'sha1' && parts[1] !== 'sha256') - throw new Error(`Unknown hash algorithm: ${parts[1]}.`); - - return new Algorithm(parts[0], parts[1]); -}; - -/** - * Serialize payment request for sighash. - * @returns {Buffer} - */ - -PaymentRequest.prototype.signatureData = function signatureData() { - const signature = this.signature; - - this.signature = Buffer.alloc(0); - - const data = this.toRaw(); - - this.signature = signature; - - return data; -}; - -/** - * Get signature hash. - * @returns {Hash} - */ - -PaymentRequest.prototype.signatureHash = function signatureHash() { - const alg = this.getAlgorithm(); - return digest.hash(alg.hash, this.signatureData()); -}; - -/** - * Set x509 certificate chain. - * @param {Buffer[]} chain - */ - -PaymentRequest.prototype.setChain = function setChain(chain) { - const bw = new ProtoWriter(); - - assert(Array.isArray(chain), 'Chain must be an array.'); - - for (let cert of chain) { - if (typeof cert === 'string') { - const pem = PEM.decode(cert); - assert(pem.type === 'certificate', 'Bad certificate PEM.'); - cert = pem.data; - } - assert(Buffer.isBuffer(cert), 'Certificates must be PEM or DER.'); - bw.writeFieldBytes(1, cert); - } - - this.pkiData = bw.render(); -}; - -/** - * Get x509 certificate chain. - * @returns {Buffer[]} - */ - -PaymentRequest.prototype.getChain = function getChain() { - const chain = []; - - if (!this.pkiData) - return chain; - - const br = new ProtoReader(this.pkiData); - - while (br.nextTag() === 1) - chain.push(br.readFieldBytes(1)); - - return chain; -}; - -/** - * Sign payment request (chain must be set). - * @param {Buffer} key - * @param {Buffer[]?} certs - */ - -PaymentRequest.prototype.sign = function sign(key, certs) { - if (certs) - this.setChain(certs); - - if (!this.pkiType) - this.pkiType = 'x509+sha256'; - - const alg = this.getAlgorithm(); - const msg = this.signatureData(); - const chain = this.getChain(); - - this.signature = x509.signSubject(alg.hash, msg, key, chain); -}; - -/** - * Verify payment request signature. - * @returns {Boolean} - */ - -PaymentRequest.prototype.verify = function verify() { - if (!this.pkiType || this.pkiType === 'none') - return false; - - if (!this.signature) - return false; - - let alg; - try { - alg = this.getAlgorithm(); - } catch (e) { - return false; - } - - const msg = this.signatureData(); - const sig = this.signature; - const chain = this.getChain(); - - try { - return x509.verifySubject(alg.hash, msg, sig, chain); - } catch (e) { - return false; - } -}; - -/** - * Verify x509 certificate chain. - * @returns {Boolean} - */ - -PaymentRequest.prototype.verifyChain = function verifyChain() { - if (!this.pkiType || this.pkiType === 'none') - return false; - - try { - return x509.verifyChain(this.getChain()); - } catch (e) { - return false; - } -}; - -/** - * Get root certificate authority. - * @returns {Object|null} - */ - -PaymentRequest.prototype.getCA = function getCA() { - if (!this.pkiType || this.pkiType === 'none') - throw new Error('No CA found (pkiType).'); - - const chain = this.getChain(); - - if (chain.length === 0) - throw new Error('No CA found (chain).'); - - const root = x509.parse(chain[chain.length - 1]); - - return new CA(root); -}; - -/** - * Algorithm - * @constructor - * @ignore - */ - -function Algorithm(key, hash) { - this.key = key; - this.hash = hash; -} - -/** - * CA - * @constructor - * @ignore - */ - -function CA(root) { - this.name = x509.getCAName(root); - this.trusted = x509.isTrusted(root); - this.cert = root; -} - -/* - * Expose - */ - -module.exports = PaymentRequest; diff --git a/lib/bip70/pk.js b/lib/bip70/pk.js deleted file mode 100644 index cfd5c6c09..000000000 --- a/lib/bip70/pk.js +++ /dev/null @@ -1,53 +0,0 @@ -/*! - * pk.js - public key algorithms for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module bip70/pk - */ - -const rsa = require('../crypto/rsa'); -const ecdsa = require('../crypto/ecdsa'); - -/** - * Verify signature with public key. - * @param {String} hash - Hash algorithm. - * @param {Buffer} msg - * @param {Buffer} sig - * @param {Object} key - * @returns {Boolean} - */ - -exports.verify = function verify(hash, msg, sig, key) { - switch (key.alg) { - case 'rsa': - return rsa.verify(hash, msg, sig, key.data); - case 'ecdsa': - return ecdsa.verify(key.curve, hash, msg, sig, key.data); - default: - throw new Error('Unsupported algorithm.'); - } -}; - -/** - * Sign message with private key. - * @param {String} hash - Hash algorithm. - * @param {Buffer} msg - * @param {Object} key - * @returns {Buffer} - */ - -exports.sign = function sign(hash, msg, key) { - switch (key.alg) { - case 'rsa': - return rsa.sign(hash, msg, key.data); - case 'ecdsa': - return ecdsa.sign(key.curve, hash, msg, key.data); - default: - throw new Error('Unsupported algorithm.'); - } -}; diff --git a/lib/bip70/x509.js b/lib/bip70/x509.js deleted file mode 100644 index 87bf3507f..000000000 --- a/lib/bip70/x509.js +++ /dev/null @@ -1,472 +0,0 @@ -/*! - * x509.js - x509 handling for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const ASN1 = require('../utils/asn1'); -const PEM = require('../utils/pem'); -const util = require('../utils/util'); -const digest = require('../crypto/digest'); -const pk = require('./pk'); -const certs = require('./certs'); - -/** - * @exports bip70/x509 - */ - -const x509 = exports; - -/** - * Map of trusted root certs. - * @type {Set} - */ - -x509.trusted = new Set(); - -/** - * Whether to allow untrusted root - * certs during verification. - * @type {Boolean} - */ - -x509.allowUntrusted = false; - -/** - * OID to algorithm map for PKI. - * @const {Object} - * @see https://www.ietf.org/rfc/rfc2459.txt - * @see https://tools.ietf.org/html/rfc3279 - * @see http://oid-info.com/get/1.2.840.10040.4 - * @see http://oid-info.com/get/1.2.840.113549.1.1 - * @see http://oid-info.com/get/1.2.840.10045.4.3 - */ - -x509.oid = { - '1.2.840.10040.4.1' : { key: 'dsa', hash: null }, - '1.2.840.10040.4.2' : { key: 'dsa', hash: null }, - '1.2.840.10040.4.3' : { key: 'dsa', hash: 'sha1' }, - '1.2.840.113549.1.1.1' : { key: 'rsa', hash: null }, - '1.2.840.113549.1.1.2' : { key: 'rsa', hash: 'md2' }, - '1.2.840.113549.1.1.3' : { key: 'rsa', hash: 'md4' }, - '1.2.840.113549.1.1.4' : { key: 'rsa', hash: 'md5' }, - '1.2.840.113549.1.1.5' : { key: 'rsa', hash: 'sha1' }, - '1.2.840.113549.1.1.11': { key: 'rsa', hash: 'sha256' }, - '1.2.840.113549.1.1.12': { key: 'rsa', hash: 'sha384' }, - '1.2.840.113549.1.1.13': { key: 'rsa', hash: 'sha512' }, - '1.2.840.113549.1.1.14': { key: 'rsa', hash: 'sha224' }, - '1.2.840.10045.2.1' : { key: 'ecdsa', hash: null }, - '1.2.840.10045.4.1' : { key: 'ecdsa', hash: 'sha1' }, - '1.2.840.10045.4.3.1' : { key: 'ecdsa', hash: 'sha224' }, - '1.2.840.10045.4.3.2' : { key: 'ecdsa', hash: 'sha256' }, - '1.2.840.10045.4.3.3' : { key: 'ecdsa', hash: 'sha384' }, - '1.2.840.10045.4.3.4' : { key: 'ecdsa', hash: 'sha512' } -}; - -/** - * OID to curve name map for ECDSA. - * @type {Object} - */ - -x509.curves = { - '1.3.132.0.33': 'p224', - '1.2.840.10045.3.1.7': 'p256', - '1.3.132.0.34': 'p384', - '1.3.132.0.35': 'p521' -}; - -/** - * Retrieve cert value by OID. - * @param {Object} cert - * @param {String} oid - * @returns {String} - */ - -x509.getSubjectOID = function getSubjectOID(cert, oid) { - const subject = cert.tbs.subject; - - for (const entry of subject) { - if (entry.type === oid) - return entry.value; - } - - return null; -}; - -/** - * Try to retrieve CA name by checking - * for a few different OIDs. - * @param {Object} cert - * @returns {String} - */ - -x509.getCAName = function getCAName(cert) { - // This seems to work the best in practice - // for getting a human-readable and - // descriptive name for the CA. - // See: - // http://oid-info.com/get/2.5.4 - // Precedence: - // (3) commonName - // (11) organizationUnitName - // (10) organizationName - return x509.getSubjectOID(cert, '2.5.4.3') - || x509.getSubjectOID(cert, '2.5.4.11') - || x509.getSubjectOID(cert, '2.5.4.10') - || 'Unknown'; -}; - -/** - * Test whether a cert is trusted by hashing - * and looking it up in the trusted map. - * @param {Object} cert - * @returns {Buffer} - */ - -x509.isTrusted = function isTrusted(cert) { - const fingerprint = digest.sha256(cert.raw); - const hash = fingerprint.toString('hex'); - return x509.trusted.has(hash); -}; - -/** - * Add root certificates to the trusted map. - * @param {Buffer[]} certs - */ - -x509.setTrust = function setTrust(certs) { - assert(Array.isArray(certs), 'Certs must be an array.'); - - for (let cert of certs) { - if (typeof cert === 'string') { - const pem = PEM.decode(cert); - assert(pem.type === 'certificate', 'Must add certificates to trust.'); - cert = pem.data; - } - - assert(Buffer.isBuffer(cert), 'Certificates must be PEM or DER.'); - - cert = x509.parse(cert); - - const hash = digest.sha256(cert.raw); - const fingerprint = hash.toString('hex'); - - x509.trusted.add(fingerprint); - } -}; - -/** - * Add root certificate fingerprints to the trusted map. - * @param {Hash[]} hashes - */ - -x509.setFingerprints = function setFingerprints(hashes) { - assert(Array.isArray(hashes), 'Certs must be an array.'); - - for (let hash of hashes) { - if (typeof hash === 'string') - hash = Buffer.from(hash, 'hex'); - - assert(Buffer.isBuffer(hash), 'Fingerprint must be a buffer.'); - assert(hash.length === 32, 'Fingerprint must be a sha256 hash.'); - - hash = hash.toString('hex'); - x509.trusted.add(hash); - } -}; - -/** - * Retrieve key algorithm from cert. - * @param {Object} cert - * @returns {Object} - */ - -x509.getKeyAlgorithm = function getKeyAlgorithm(cert) { - const oid = cert.tbs.pubkey.alg.alg; - const alg = x509.oid[oid]; - - if (!alg) - throw new Error(`Unknown key algorithm: ${oid}.`); - - return alg; -}; - -/** - * Retrieve signature algorithm from cert. - * @param {Object} cert - * @returns {Object} - */ - -x509.getSigAlgorithm = function getSigAlgorithm(cert) { - const oid = cert.sigAlg.alg; - const alg = x509.oid[oid]; - - if (!alg || !alg.hash) - throw new Error(`Unknown signature algorithm: ${oid}.`); - - return alg; -}; - -/** - * Lookup curve based on key parameters. - * @param {Buffer} params - * @returns {Object} - */ - -x509.getCurve = function getCurve(params) { - let oid; - - try { - oid = ASN1.parseOID(params); - } catch (e) { - throw new Error('Could not parse curve OID.'); - } - - const curve = x509.curves[oid]; - - if (!curve) - throw new Error(`Unknown ECDSA curve: ${oid}.`); - - return curve; -}; - -/** - * Parse a DER formatted cert. - * @param {Buffer} der - * @returns {Object|null} - */ - -x509.parse = function parse(der) { - try { - return ASN1.parseCert(der); - } catch (e) { - throw new Error('Could not parse DER certificate.'); - } -}; - -/** - * Get cert public key. - * @param {Object} cert - * @returns {Object|null} - */ - -x509.getPublicKey = function getPublicKey(cert) { - const alg = x509.getKeyAlgorithm(cert); - const key = cert.tbs.pubkey.pubkey; - const params = cert.tbs.pubkey.alg.params; - let curve = null; - - if (alg.key === 'ecdsa') { - if (!params) - throw new Error('No curve selected for ECDSA (cert).'); - - curve = x509.getCurve(params); - } - - return { - alg: alg.key, - data: key, - params: params, - curve: curve - }; -}; - -/** - * Verify cert expiration time. - * @param {Object} cert - * @returns {Boolean} - */ - -x509.verifyTime = function verifyTime(cert) { - const time = cert.tbs.validity; - const now = util.now(); - return now > time.notBefore && now < time.notAfter; -}; - -/** - * Get signature key info from cert chain. - * @param {Buffer} key - * @param {Buffer[]} chain - * @returns {Object} - */ - -x509.getSigningKey = function getSigningKey(key, chain) { - assert(chain.length !== 0, 'No chain available.'); - - if (typeof key === 'string') { - let curve = null; - - key = PEM.decode(key); - - if (key.alg === 'ecdsa') { - if (!key.params) - throw new Error('No curve selected for ECDSA (key).'); - - curve = x509.getCurve(key.params); - } - - key = { - alg: key.alg, - data: key.data, - params: key.params, - curve: curve - }; - } else { - const cert = x509.parse(chain[0]); - const pub = x509.getPublicKey(cert); - - key = { - alg: pub.alg, - data: key, - params: pub.params, - curve: pub.curve - }; - } - - return key; -}; - -/** - * Sign a hash with the chain signing key. - * @param {String} hash - * @param {Buffer} msg - * @param {Buffer} key - * @param {Buffer[]} chain - * @returns {Buffer} - */ - -x509.signSubject = function signSubject(hash, msg, key, chain) { - const priv = x509.getSigningKey(key, chain); - return pk.sign(hash, msg, priv); -}; - -/** - * Get chain verification key. - * @param {Buffer[]} chain - * @returns {Object|null} - */ - -x509.getVerifyKey = function getVerifyKey(chain) { - if (chain.length === 0) - throw new Error('No verify key available (cert chain).'); - - const cert = x509.parse(chain[0]); - - return x509.getPublicKey(cert); -}; - -/** - * Verify a sighash against chain verification key. - * @param {String} hash - * @param {Buffer} msg - * @param {Buffer} sig - * @param {Buffer[]} chain - * @returns {Boolean} - */ - -x509.verifySubject = function verifySubject(hash, msg, sig, chain) { - const key = x509.getVerifyKey(chain); - return pk.verify(hash, msg, sig, key); -}; - -/** - * Parse certificate chain. - * @param {Buffer[]} chain - * @returns {Object[]} - */ - -x509.parseChain = function parseChain(chain) { - const certs = []; - - for (const item of chain) { - const cert = x509.parse(item); - certs.push(cert); - } - - return certs; -}; - -/** - * Verify all expiration times in a certificate chain. - * @param {Object[]} chain - * @returns {Boolean} - */ - -x509.verifyTimes = function verifyTimes(chain) { - for (const cert of chain) { - if (!x509.verifyTime(cert)) - return false; - } - - return true; -}; - -/** - * Verify that at least one parent - * cert in the chain is trusted. - * @param {Object[]} chain - * @returns {Boolean} - */ - -x509.verifyTrust = function verifyTrust(chain) { - // If trust hasn't been - // setup, just return. - if (x509.allowUntrusted) - return true; - - // Make sure we trust one - // of the certs in the chain. - for (const cert of chain) { - // If any certificate in the chain - // is trusted, assume we also trust - // the parent. - if (x509.isTrusted(cert)) - return true; - } - - // No trusted certs present. - return false; -}; - -/** - * Verify certificate chain. - * @param {Object[]} certs - */ - -x509.verifyChain = function verifyChain(certs) { - const chain = x509.parseChain(certs); - - // Parse certificates and - // check validity time. - if (!x509.verifyTimes(chain)) - throw new Error('Invalid certificate times.'); - - // Verify signatures. - for (let i = 1; i < chain.length; i++) { - const child = chain[i - 1]; - const parent = chain[i]; - const alg = x509.getSigAlgorithm(child); - const key = x509.getPublicKey(parent); - const msg = child.tbs.raw; - const sig = child.sig; - - if (!pk.verify(alg.hash, msg, sig, key)) - throw new Error(`${alg.key} verification failed for chain.`); - } - - // Make sure we trust one - // of the certs in the chain. - if (!x509.verifyTrust(chain)) - throw new Error('Certificate chain is untrusted.'); - - return true; -}; - -/* - * Load trusted certs. - */ - -x509.setFingerprints(certs); diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index f2b65a818..5ac953dd9 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -7,3013 +7,2926 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const path = require('path'); -const AsyncObject = require('../utils/asyncobject'); +const AsyncEmitter = require('bevent'); +const Logger = require('blgr'); +const {Lock} = require('bmutex'); +const LRU = require('blru'); +const {BufferMap} = require('buffer-map'); +const BN = require('bcrypto/lib/bn.js'); const Network = require('../protocol/network'); -const Logger = require('../node/logger'); const ChainDB = require('./chaindb'); const common = require('./common'); const consensus = require('../protocol/consensus'); const util = require('../utils/util'); -const Lock = require('../utils/lock'); -const LRU = require('../utils/lru'); const ChainEntry = require('./chainentry'); const CoinView = require('../coins/coinview'); const Script = require('../script/script'); const {VerifyError} = require('../protocol/errors'); -const co = require('../utils/co'); const thresholdStates = common.thresholdStates; /** - * Represents a blockchain. + * Blockchain * @alias module:blockchain.Chain - * @constructor - * @param {Object} options - * @param {String?} options.name - Database name. - * @param {String?} options.location - Database file location. - * @param {String?} options.db - Database backend (`"leveldb"` by default). - * @param {Number?} options.maxOrphans - * @param {Boolean?} options.spv - * @property {Boolean} loaded - * @property {ChainDB} db - Note that Chain `options` will be passed - * to the instantiated ChainDB. - * @property {Lock} locker - * @property {Object} invalid + * @property {ChainDB} db * @property {ChainEntry?} tip * @property {Number} height * @property {DeploymentState} state - * @property {Object} orphan - Orphan map. - * @emits Chain#open - * @emits Chain#error - * @emits Chain#block - * @emits Chain#competitor - * @emits Chain#resolved - * @emits Chain#checkpoint - * @emits Chain#fork - * @emits Chain#reorganize - * @emits Chain#invalid - * @emits Chain#exists - * @emits Chain#purge - * @emits Chain#connect - * @emits Chain#reconnect - * @emits Chain#disconnect */ -function Chain(options) { - if (!(this instanceof Chain)) - return new Chain(options); +class Chain extends AsyncEmitter { + /** + * Create a blockchain. + * @constructor + * @param {Object} options + */ - AsyncObject.call(this); + constructor(options) { + super(); - this.options = new ChainOptions(options); + this.opened = false; + this.options = new ChainOptions(options); - this.network = this.options.network; - this.logger = this.options.logger.context('chain'); - this.workers = this.options.workers; + this.network = this.options.network; + this.logger = this.options.logger.context('chain'); + this.blocks = this.options.blocks; + this.workers = this.options.workers; - this.db = new ChainDB(this.options); + this.db = new ChainDB(this.options); - this.locker = new Lock(true); - this.invalid = new LRU(100); - this.state = new DeploymentState(); + this.locker = new Lock(true, BufferMap); + this.invalid = new LRU(100, null, BufferMap); + this.state = new DeploymentState(); - this.tip = new ChainEntry(); - this.height = -1; - this.synced = false; + this.tip = new ChainEntry(); + this.height = -1; + this.synced = false; - this.orphanMap = new Map(); - this.orphanPrev = new Map(); -} + this.orphanMap = new BufferMap(); + this.orphanPrev = new BufferMap(); + } -Object.setPrototypeOf(Chain.prototype, AsyncObject.prototype); + /** + * Open the chain, wait for the database to load. + * @returns {Promise} + */ -/** - * Size of set to pick median time from. - * @const {Number} - * @default - */ + async open() { + assert(!this.opened, 'Chain is already open.'); + this.opened = true; -Chain.MEDIAN_TIMESPAN = 11; + this.logger.info('Chain is loading.'); -/** - * Open the chain, wait for the database to load. - * @alias Chain#open - * @returns {Promise} - */ + if (this.options.checkpoints) + this.logger.info('Checkpoints are enabled.'); -Chain.prototype._open = async function _open() { - this.logger.info('Chain is loading.'); + if (this.options.bip91) + this.logger.warning('BIP91 enabled. Segsignal will be enforced.'); - if (this.options.checkpoints) - this.logger.info('Checkpoints are enabled.'); + if (this.options.bip148) + this.logger.warning('BIP148 enabled. UASF will be enforced.'); - if (this.options.coinCache) - this.logger.info('Coin cache is enabled.'); + await this.db.open(); - if (this.options.bip91) - this.logger.warning('BIP91 enabled. Segsignal will be enforced.'); + const tip = await this.db.getTip(); - if (this.options.bip148) - this.logger.warning('BIP148 enabled. UASF will be enforced.'); + assert(tip); - await this.db.open(); + this.tip = tip; + this.height = tip.height; - const tip = await this.db.getTip(); + this.logger.info('Chain Height: %d', tip.height); - assert(tip); + this.logger.memory(); - this.tip = tip; - this.height = tip.height; + const state = await this.getDeploymentState(); - this.logger.info('Chain Height: %d', tip.height); + this.setDeploymentState(state); - this.logger.memory(); + this.logger.memory(); - const state = await this.getDeploymentState(); + this.emit('tip', tip); - this.setDeploymentState(state); + this.maybeSync(); + } - this.logger.memory(); + /** + * Close the chain, wait for the database to close. + * @returns {Promise} + */ - this.emit('tip', tip); + async close() { + assert(this.opened, 'Chain is not open.'); + this.opened = false; + return this.db.close(); + } - this.maybeSync(); -}; + /** + * Perform all necessary contextual verification on a block. + * @private + * @param {Block} block + * @param {ChainEntry} prev + * @param {Number} flags + * @returns {Promise} - Returns {@link ContextResult}. + */ -/** - * Close the chain, wait for the database to close. - * @alias Chain#close - * @returns {Promise} - */ + async verifyContext(block, prev, flags) { + // Initial non-contextual verification. + const state = await this.verify(block, prev, flags); -Chain.prototype._close = function _close() { - return this.db.close(); -}; + // Skip everything if we're in SPV mode. + if (this.options.spv) { + const view = new CoinView(); + return [view, state]; + } -/** - * Perform all necessary contextual verification on a block. - * @private - * @param {Block} block - * @param {ChainEntry} prev - * @param {Number} flags - * @returns {Promise} - Returns {@link ContextResult}. - */ + // Skip everything if we're using checkpoints. + if (this.isHistorical(prev)) { + const view = await this.updateInputs(block, prev); + return [view, state]; + } -Chain.prototype.verifyContext = async function verifyContext(block, prev, flags) { - // Initial non-contextual verification. - const state = await this.verify(block, prev, flags); + // BIP30 - Verify there are no duplicate txids. + // Note that BIP34 made it impossible to create + // duplicate txids. + if (!state.hasBIP34()) + await this.verifyDuplicates(block, prev); - // Skip everything if we're in SPV mode. - if (this.options.spv) { - const view = new CoinView(); - return [view, state]; - } + // Verify scripts, spend and add coins. + const view = await this.verifyInputs(block, prev, state); - // Skip everything if we're using checkpoints. - if (this.isHistorical(prev)) { - const view = await this.updateInputs(block, prev); return [view, state]; } - // BIP30 - Verify there are no duplicate txids. - // Note that BIP34 made it impossible to create - // duplicate txids. - if (!state.hasBIP34()) - await this.verifyDuplicates(block, prev); - - // Verify scripts, spend and add coins. - const view = await this.verifyInputs(block, prev, state); - - return [view, state]; -}; + /** + * Perform all necessary contextual verification + * on a block, without POW check. + * @param {Block} block + * @returns {Promise} + */ -/** - * Perform all necessary contextual verification - * on a block, without POW check. - * @param {Block} block - * @returns {Promise} - */ - -Chain.prototype.verifyBlock = async function verifyBlock(block) { - const unlock = await this.locker.lock(); - try { - return await this._verifyBlock(block); - } finally { - unlock(); + async verifyBlock(block) { + const unlock = await this.locker.lock(); + try { + return await this._verifyBlock(block); + } finally { + unlock(); + } } -}; - -/** - * Perform all necessary contextual verification - * on a block, without POW check (no lock). - * @private - * @param {Block} block - * @returns {Promise} - */ - -Chain.prototype._verifyBlock = async function _verifyBlock(block) { - const flags = common.flags.DEFAULT_FLAGS & ~common.flags.VERIFY_POW; - return await this.verifyContext(block, this.tip, flags); -}; - -/** - * Test whether the hash is in the main chain. - * @param {Hash} hash - * @returns {Promise} - Returns Boolean. - */ - -Chain.prototype.isMainHash = function isMainHash(hash) { - return this.db.isMainHash(hash); -}; - -/** - * Test whether the entry is in the main chain. - * @param {ChainEntry} entry - * @returns {Promise} - Returns Boolean. - */ - -Chain.prototype.isMainChain = function isMainChain(entry) { - return this.db.isMainChain(entry); -}; - -/** - * Get ancestor by `height`. - * @param {ChainEntry} entry - * @param {Number} height - * @returns {Promise} - Returns ChainEntry. - */ -Chain.prototype.getAncestor = function getAncestor(entry, height) { - return this.db.getAncestor(entry, height); -}; + /** + * Perform all necessary contextual verification + * on a block, without POW check (no lock). + * @private + * @param {Block} block + * @returns {Promise} + */ -/** - * Get previous entry. - * @param {ChainEntry} entry - * @returns {Promise} - Returns ChainEntry. - */ + async _verifyBlock(block) { + const flags = common.flags.DEFAULT_FLAGS & ~common.flags.VERIFY_POW; + return this.verifyContext(block, this.tip, flags); + } -Chain.prototype.getPrevious = function getPrevious(entry) { - return this.db.getPrevious(entry); -}; + /** + * Test whether the hash is in the main chain. + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ -/** - * Get previous cached entry. - * @param {ChainEntry} entry - * @returns {ChainEntry|null} - */ + isMainHash(hash) { + return this.db.isMainHash(hash); + } -Chain.prototype.getPrevCache = function getPrevCache(entry) { - return this.db.getPrevCache(entry); -}; + /** + * Test whether the entry is in the main chain. + * @param {ChainEntry} entry + * @returns {Promise} - Returns Boolean. + */ -/** - * Get next entry. - * @param {ChainEntry} entry - * @returns {Promise} - Returns ChainEntry. - */ - -Chain.prototype.getNext = function getNext(entry) { - return this.db.getNext(entry); -}; + isMainChain(entry) { + return this.db.isMainChain(entry); + } -/** - * Get next entry. - * @param {ChainEntry} entry - * @returns {Promise} - Returns ChainEntry. - */ + /** + * Get ancestor by `height`. + * @param {ChainEntry} entry + * @param {Number} height + * @returns {Promise} - Returns ChainEntry. + */ -Chain.prototype.getNextEntry = function getNextEntry(entry) { - return this.db.getNextEntry(entry); -}; + getAncestor(entry, height) { + return this.db.getAncestor(entry, height); + } -/** - * Calculate median time past. - * @param {ChainEntry} prev - * @param {Number?} time - * @returns {Promise} - Returns Number. - */ + /** + * Get previous entry. + * @param {ChainEntry} entry + * @returns {Promise} - Returns ChainEntry. + */ -Chain.prototype.getMedianTime = async function getMedianTime(prev, time) { - let timespan = Chain.MEDIAN_TIMESPAN; + getPrevious(entry) { + return this.db.getPrevious(entry); + } - const median = []; + /** + * Get previous cached entry. + * @param {ChainEntry} entry + * @returns {ChainEntry|null} + */ - // In case we ever want to check - // the MTP of the _current_ block - // (necessary for BIP148). - if (time != null) { - median.push(time); - timespan -= 1; + getPrevCache(entry) { + return this.db.getPrevCache(entry); } - let entry = prev; + /** + * Get next entry. + * @param {ChainEntry} entry + * @returns {Promise} - Returns ChainEntry. + */ - for (let i = 0; i < timespan && entry; i++) { - median.push(entry.time); + getNext(entry) { + return this.db.getNext(entry); + } - const cache = this.getPrevCache(entry); + /** + * Get next entry. + * @param {ChainEntry} entry + * @returns {Promise} - Returns ChainEntry. + */ - if (cache) - entry = cache; - else - entry = await this.getPrevious(entry); + getNextEntry(entry) { + return this.db.getNextEntry(entry); } - median.sort(cmp); + /** + * Calculate median time past. + * @param {ChainEntry} prev + * @param {Number?} time + * @returns {Promise} - Returns Number. + */ - return median[median.length >>> 1]; -}; + async getMedianTime(prev, time) { + let timespan = consensus.MEDIAN_TIMESPAN; -/** - * Test whether the entry is potentially - * an ancestor of a checkpoint. - * @param {ChainEntry} prev - * @returns {Boolean} - */ + const median = []; -Chain.prototype.isHistorical = function isHistorical(prev) { - if (this.options.checkpoints) { - if (prev.height + 1 <= this.network.lastCheckpoint) - return true; - } - return false; -}; - -/** - * Contextual verification for a block, including - * version deployments (IsSuperMajority), versionbits, - * coinbase height, finality checks. - * @private - * @param {Block} block - * @param {ChainEntry} prev - * @param {Number} flags - * @returns {Promise} - Returns {@link DeploymentState}. - */ + // In case we ever want to check + // the MTP of the _current_ block + // (necessary for BIP148). + if (time != null) { + median.push(time); + timespan -= 1; + } -Chain.prototype.verify = async function verify(block, prev, flags) { - assert(typeof flags === 'number'); + let entry = prev; - // Extra sanity check. - if (block.prevBlock !== prev.hash) - throw new VerifyError(block, 'invalid', 'bad-prevblk', 0); + for (let i = 0; i < timespan && entry; i++) { + median.push(entry.time); - // Verify a checkpoint if there is one. - const hash = block.hash('hex'); - if (!this.verifyCheckpoint(prev, hash)) { - throw new VerifyError(block, - 'checkpoint', - 'checkpoint mismatch', - 100); - } + const cache = this.getPrevCache(entry); - // Skip everything when using checkpoints. - // We can do this safely because every - // block in between each checkpoint was - // validated outside in the header chain. - if (this.isHistorical(prev)) { - if (this.options.spv) - return this.state; + if (cache) + entry = cache; + else + entry = await this.getPrevious(entry); + } - // Once segwit is active, we will still - // need to check for block mutability. - if (!block.hasWitness() && !block.getCommitmentHash()) - return new DeploymentState(); + median.sort(cmp); - flags &= ~common.flags.VERIFY_BODY; + return median[median.length >>> 1]; } - // Non-contextual checks. - if (flags & common.flags.VERIFY_BODY) { - const [valid, reason, score] = block.checkBody(); + /** + * Test whether the entry is potentially + * an ancestor of a checkpoint. + * @param {ChainEntry} prev + * @returns {Boolean} + */ - if (!valid) - throw new VerifyError(block, 'invalid', reason, score, true); + isHistorical(prev) { + if (this.options.checkpoints) { + if (prev.height + 1 <= this.network.lastCheckpoint) + return true; + } + return false; } - // Ensure the POW is what we expect. - const bits = await this.getTarget(block.time, prev); + /** + * Contextual verification for a block, including + * version deployments (IsSuperMajority), versionbits, + * coinbase height, finality checks. + * @private + * @param {Block} block + * @param {ChainEntry} prev + * @param {Number} flags + * @returns {Promise} - Returns {@link DeploymentState}. + */ - // console.log(block.bits, bits); - - if (block.bits !== bits) { - throw new VerifyError(block, - 'invalid', - 'bad-diffbits', - 100); - } + async verify(block, prev, flags) { + assert(typeof flags === 'number'); - // Skip all blocks in spv mode once - // we've verified the network target. - if (this.options.spv) - return this.state; + // Extra sanity check. + if (!block.prevBlock.equals(prev.hash)) + throw new VerifyError(block, 'invalid', 'bad-prevblk', 0); - // Ensure the timestamp is correct. - const mtp = await this.getMedianTime(prev); + // Verify a checkpoint if there is one. + const hash = block.hash(); + if (!this.verifyCheckpoint(prev, hash)) { + throw new VerifyError(block, + 'checkpoint', + 'checkpoint mismatch', + 100); + } - if (block.time <= mtp) { - throw new VerifyError(block, - 'invalid', - 'time-too-old', - 0); - } + // Skip everything when using checkpoints. + // We can do this safely because every + // block in between each checkpoint was + // validated outside in the header chain. + if (this.isHistorical(prev)) { + if (this.options.spv) + return this.state; + + // Check merkle root. + if (flags & common.flags.VERIFY_BODY) { + assert(typeof block.createMerkleRoot === 'function'); + + const root = block.createMerkleRoot(); + + if (!root || !block.merkleRoot.equals(root)) { + throw new VerifyError(block, + 'invalid', + 'bad-txnmrklroot', + 100, + true); + } - // Check timestamp against adj-time+2hours. - // If this fails we may be able to accept - // the block later. - if (block.time > this.network.now() + 2 * 60 * 60) { - throw new VerifyError(block, - 'invalid', - 'time-too-new', - 0, - true); - } + flags &= ~common.flags.VERIFY_BODY; + } - // Calculate height of current block. - const height = prev.height + 1; + // Once segwit is active, we will still + // need to check for block mutability. + if (!block.hasWitness() && !block.getCommitmentHash()) + return new DeploymentState(); + } - // Only allow version 2 blocks (coinbase height) - // once the majority of blocks are using it. - if (block.version < 2 && height >= this.network.block.bip34height) - throw new VerifyError(block, 'obsolete', 'bad-version', 0); + // Non-contextual checks. + if (flags & common.flags.VERIFY_BODY) { + const [valid, reason, score] = block.checkBody(); - // Only allow version 3 blocks (sig validation) - // once the majority of blocks are using it. - if (block.version < 3 && height >= this.network.block.bip66height) - throw new VerifyError(block, 'obsolete', 'bad-version', 0); + if (!valid) + throw new VerifyError(block, 'invalid', reason, score, true); + } - // Only allow version 4 blocks (checklocktimeverify) - // once the majority of blocks are using it. - if (block.version < 4 && height >= this.network.block.bip65height) - throw new VerifyError(block, 'obsolete', 'bad-version', 0); + // Ensure the POW is what we expect. + const bits = await this.getTarget(block.time, prev); - // Get the new deployment state. - const state = await this.getDeployments(block.time, prev); + if (block.bits !== bits) { + throw new VerifyError(block, + 'invalid', + 'bad-diffbits', + 100); + } - // Enforce BIP91/BIP148. - if (state.hasBIP91() || state.hasBIP148()) { - const {segwit} = this.network.deployments; - if (!consensus.hasBit(block.version, segwit.bit)) - throw new VerifyError(block, 'invalid', 'bad-no-segwit', 0); - } + // Skip all blocks in spv mode once + // we've verified the network target. + if (this.options.spv) + return this.state; - // Get timestamp for tx.isFinal(). - const time = state.hasMTP() ? mtp : block.time; + // Ensure the timestamp is correct. + const mtp = await this.getMedianTime(prev); - // Transactions must be finalized with - // regards to nSequence and nLockTime. - for (const tx of block.txs) { - if (!tx.isFinal(height, time)) { + if (block.time <= mtp) { throw new VerifyError(block, 'invalid', - 'bad-txns-nonfinal', - 10); + 'time-too-old', + 0); } - } - // Make sure the height contained - // in the coinbase is correct. - if (state.hasBIP34()) { - if (block.getCoinbaseHeight() !== height) { + // Check timestamp against adj-time+2hours. + // If this fails we may be able to accept + // the block later. + if (block.time > this.network.now() + 2 * 60 * 60) { throw new VerifyError(block, 'invalid', - 'bad-cb-height', - 100); + 'time-too-new', + 0, + true); } - } - // Check the commitment hash for segwit. - let commit = null; - if (state.hasWitness()) { - commit = block.getCommitmentHash(); - if (commit) { - // These are totally malleable. Someone - // may have even accidentally sent us - // the non-witness version of the block. - // We don't want to consider this block - // "invalid" if either of these checks - // fail. - if (!block.getWitnessNonce()) { + // Calculate height of current block. + const height = prev.height + 1; + + // Only allow version 2 blocks (coinbase height) + // once the majority of blocks are using it. + if (block.version < 2 && height >= this.network.block.bip34height) + throw new VerifyError(block, 'obsolete', 'bad-version', 0); + + // Only allow version 3 blocks (sig validation) + // once the majority of blocks are using it. + if (block.version < 3 && height >= this.network.block.bip66height) + throw new VerifyError(block, 'obsolete', 'bad-version', 0); + + // Only allow version 4 blocks (checklocktimeverify) + // once the majority of blocks are using it. + if (block.version < 4 && height >= this.network.block.bip65height) + throw new VerifyError(block, 'obsolete', 'bad-version', 0); + + // Get the new deployment state. + const state = await this.getDeployments(block.time, prev); + + // Enforce BIP91/BIP148. + if (state.hasBIP91() || state.hasBIP148()) { + const {segwit} = this.network.deployments; + if (!consensus.hasBit(block.version, segwit.bit)) + throw new VerifyError(block, 'invalid', 'bad-no-segwit', 0); + } + + // Get timestamp for tx.isFinal(). + const time = state.hasMTP() ? mtp : block.time; + + // Transactions must be finalized with + // regards to nSequence and nLockTime. + for (const tx of block.txs) { + if (!tx.isFinal(height, time)) { throw new VerifyError(block, 'invalid', - 'bad-witness-nonce-size', - 100, - true); + 'bad-txns-nonfinal', + 10); } + } - if (!commit.equals(block.createCommitmentHash())) { + // Make sure the height contained + // in the coinbase is correct. + if (state.hasBIP34()) { + if (block.getCoinbaseHeight() !== height) { throw new VerifyError(block, 'invalid', - 'bad-witness-merkle-match', - 100, - true); + 'bad-cb-height', + 100); } } - } - // Blocks that do not commit to - // witness data cannot contain it. - // @TODO: Testnet had an issue syncing with this, look into why this is happening, and fix it properly - // if (!commit) { - // if (block.hasWitness()) { - // throw new VerifyError(block, - // 'invalid', - // 'unexpected-witness', - // 100, - // true); - // } - // } - - // Check block weight (different from block size - // check in non-contextual verification). - if (block.getWeight() > consensus.MAX_BLOCK_WEIGHT) { - throw new VerifyError(block, - 'invalid', - 'bad-blk-weight', - 100); - } - - return state; -}; - -/** - * Check all deployments on a chain, ranging from p2sh to segwit. - * @param {Number} time - * @param {ChainEntry} prev - * @returns {Promise} - Returns {@link DeploymentState}. - */ - -Chain.prototype.getDeployments = async function getDeployments(time, prev) { - const deployments = this.network.deployments; - const height = prev.height + 1; - const state = new DeploymentState(); - - // For some reason bitcoind has p2sh in the - // mandatory flags by default, when in reality - // it wasn't activated until march 30th 2012. - // The first p2sh output and redeem script - // appeared on march 7th 2012, only it did - // not have a signature. See: - // 6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192 - // 9c08a4d78931342b37fd5f72900fb9983087e6f46c4a097d8a1f52c74e28eaf6 - if (time >= consensus.BIP16_TIME) - state.flags |= Script.flags.VERIFY_P2SH; + // Check the commitment hash for segwit. + let commit = null; + if (state.hasWitness()) { + commit = block.getCommitmentHash(); + if (commit) { + // These are totally malleable. Someone + // may have even accidentally sent us + // the non-witness version of the block. + // We don't want to consider this block + // "invalid" if either of these checks + // fail. + if (!block.getWitnessNonce()) { + throw new VerifyError(block, + 'invalid', + 'bad-witness-nonce-size', + 100, + true); + } - // Coinbase heights are now enforced (bip34). - if (height >= this.network.block.bip34height) - state.bip34 = true; + if (!commit.equals(block.createCommitmentHash())) { + throw new VerifyError(block, + 'invalid', + 'bad-witness-merkle-match', + 100, + true); + } + } + } - // Signature validation is now enforced (bip66). - if (height >= this.network.block.bip66height) - state.flags |= Script.flags.VERIFY_DERSIG; + // Blocks that do not commit to + // witness data cannot contain it. + if (!commit) { + if (block.hasWitness()) { + throw new VerifyError(block, + 'invalid', + 'unexpected-witness', + 100, + true); + } + } - // CHECKLOCKTIMEVERIFY is now usable (bip65). - if (height >= this.network.block.bip65height) - state.flags |= Script.flags.VERIFY_CHECKLOCKTIMEVERIFY; + // Check block weight (different from block size + // check in non-contextual verification). + if (block.getWeight() > consensus.MAX_BLOCK_WEIGHT) { + throw new VerifyError(block, + 'invalid', + 'bad-blk-weight', + 100); + } - // CHECKSEQUENCEVERIFY and median time - // past locktimes are now usable (bip9 & bip113). - if (await this.isActive(prev, deployments.csv)) { - state.flags |= Script.flags.VERIFY_CHECKSEQUENCEVERIFY; - state.lockFlags |= common.lockFlags.VERIFY_SEQUENCE; - state.lockFlags |= common.lockFlags.MEDIAN_TIME_PAST; + return state; } - // Check the state of the segwit deployment. - const witness = await this.getState(prev, deployments.segwit); - - // Segregrated witness (bip141) is now usable - // along with SCRIPT_VERIFY_NULLDUMMY (bip147). - if (witness === thresholdStates.ACTIVE) { - state.flags |= Script.flags.VERIFY_WITNESS; - state.flags |= Script.flags.VERIFY_NULLDUMMY; - } + /** + * Check all deployments on a chain, ranging from p2sh to segwit. + * @param {Number} time + * @param {ChainEntry} prev + * @returns {Promise} - Returns {@link DeploymentState}. + */ - // Segsignal is now enforced (bip91). - if (this.options.bip91) { - if (witness === thresholdStates.STARTED) { - if (await this.isActive(prev, deployments.segsignal)) - state.bip91 = true; + async getDeployments(time, prev) { + const deployments = this.network.deployments; + const height = prev.height + 1; + const state = new DeploymentState(); + + // For some reason bitcoind has p2sh in the + // mandatory flags by default, when in reality + // it wasn't activated until march 30th 2012. + // The first p2sh output and redeem script + // appeared on march 7th 2012, only it did + // not have a signature. See: + // 6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192 + // 9c08a4d78931342b37fd5f72900fb9983087e6f46c4a097d8a1f52c74e28eaf6 + if (time >= consensus.BIP16_TIME) + state.flags |= Script.flags.VERIFY_P2SH; + + // Coinbase heights are now enforced (bip34). + if (height >= this.network.block.bip34height) + state.bip34 = true; + + // Signature validation is now enforced (bip66). + if (height >= this.network.block.bip66height) + state.flags |= Script.flags.VERIFY_DERSIG; + + // CHECKLOCKTIMEVERIFY is now usable (bip65). + if (height >= this.network.block.bip65height) + state.flags |= Script.flags.VERIFY_CHECKLOCKTIMEVERIFY; + + // CHECKSEQUENCEVERIFY and median time + // past locktimes are now usable (bip9 & bip113). + if (await this.isActive(prev, deployments.csv)) { + state.flags |= Script.flags.VERIFY_CHECKSEQUENCEVERIFY; + state.lockFlags |= common.lockFlags.VERIFY_SEQUENCE; + state.lockFlags |= common.lockFlags.MEDIAN_TIME_PAST; } - } - // UASF is now enforced (bip148) (mainnet-only). - if (this.options.bip148 && this.network === Network.main) { - if (witness !== thresholdStates.LOCKED_IN - && witness !== thresholdStates.ACTIVE) { - // The BIP148 MTP check is nonsensical in - // that it includes the _current_ entry's - // timestamp. This requires some hackery, - // since bcoin only operates on the sane - // assumption that deployment checks should - // only ever examine the values of the - // previous block (necessary for mining). - const mtp = await this.getMedianTime(prev, time); - if (mtp >= 1501545600 && mtp <= 1510704000) - state.bip148 = true; + // Check the state of the segwit deployment. + const witness = await this.getState(prev, deployments.segwit); + + // Segregrated witness (bip141) is now usable + // along with SCRIPT_VERIFY_NULLDUMMY (bip147). + if (witness === thresholdStates.ACTIVE) { + state.flags |= Script.flags.VERIFY_WITNESS; + state.flags |= Script.flags.VERIFY_NULLDUMMY; } - } - return state; -}; + // Segsignal is now enforced (bip91). + if (this.options.bip91) { + if (witness === thresholdStates.STARTED) { + if (await this.isActive(prev, deployments.segsignal)) + state.bip91 = true; + } + } -/** - * Set a new deployment state. - * @param {DeploymentState} state - */ + // UASF is now enforced (bip148) (mainnet-only). + if (this.options.bip148 && this.network === Network.main) { + if (witness !== thresholdStates.LOCKED_IN + && witness !== thresholdStates.ACTIVE) { + // The BIP148 MTP check is nonsensical in + // that it includes the _current_ entry's + // timestamp. This requires some hackery, + // since bcoin only operates on the sane + // assumption that deployment checks should + // only ever examine the values of the + // previous block (necessary for mining). + const mtp = await this.getMedianTime(prev, time); + if (mtp >= 1501545600 && mtp <= 1510704000) + state.bip148 = true; + } + } -Chain.prototype.setDeploymentState = function setDeploymentState(state) { - if (this.options.checkpoints && this.height < this.network.lastCheckpoint) { - this.state = state; - return; + return state; } - if (!this.state.hasP2SH() && state.hasP2SH()) - this.logger.warning('P2SH has been activated.'); + /** + * Set a new deployment state. + * @param {DeploymentState} state + */ - if (!this.state.hasBIP34() && state.hasBIP34()) - this.logger.warning('BIP34 has been activated.'); + setDeploymentState(state) { + if (this.options.checkpoints && this.height < this.network.lastCheckpoint) { + this.state = state; + return; + } - if (!this.state.hasBIP66() && state.hasBIP66()) - this.logger.warning('BIP66 has been activated.'); + if (!this.state.hasP2SH() && state.hasP2SH()) + this.logger.warning('P2SH has been activated.'); - if (!this.state.hasCLTV() && state.hasCLTV()) - this.logger.warning('BIP65 has been activated.'); + if (!this.state.hasBIP34() && state.hasBIP34()) + this.logger.warning('BIP34 has been activated.'); - if (!this.state.hasCSV() && state.hasCSV()) - this.logger.warning('CSV has been activated.'); + if (!this.state.hasBIP66() && state.hasBIP66()) + this.logger.warning('BIP66 has been activated.'); - if (!this.state.hasWitness() && state.hasWitness()) - this.logger.warning('Segwit has been activated.'); + if (!this.state.hasCLTV() && state.hasCLTV()) + this.logger.warning('BIP65 has been activated.'); - if (!this.state.hasBIP91() && state.hasBIP91()) - this.logger.warning('BIP91 has been activated.'); + if (!this.state.hasCSV() && state.hasCSV()) + this.logger.warning('CSV has been activated.'); - if (!this.state.hasBIP148() && state.hasBIP148()) - this.logger.warning('BIP148 has been activated.'); + if (!this.state.hasWitness() && state.hasWitness()) + this.logger.warning('Segwit has been activated.'); - this.state = state; -}; + if (!this.state.hasBIP91() && state.hasBIP91()) + this.logger.warning('BIP91 has been activated.'); -/** - * Determine whether to check block for duplicate txids in blockchain - * history (BIP30). If we're on a chain that has bip34 activated, we - * can skip this. - * @private - * @see https://github.com/bitcoin/bips/blob/master/bip-0030.mediawiki - * @param {Block} block - * @param {ChainEntry} prev - * @returns {Promise} - */ + if (!this.state.hasBIP148() && state.hasBIP148()) + this.logger.warning('BIP148 has been activated.'); -Chain.prototype.verifyDuplicates = async function verifyDuplicates(block, prev) { - for (const tx of block.txs) { - if (!await this.hasCoins(tx)) - continue; - - const height = prev.height + 1; - const hash = this.network.bip30[height]; - - // Blocks 91842 and 91880 created duplicate - // txids by using the same exact output script - // and extraNonce. - if (!hash || block.hash('hex') !== hash) - throw new VerifyError(block, 'invalid', 'bad-txns-BIP30', 100); + this.state = state; } -}; -/** - * Spend and update inputs (checkpoints only). - * @private - * @param {Block} block - * @param {ChainEntry} prev - * @returns {Promise} - Returns {@link CoinView}. - */ + /** + * Determine whether to check block for duplicate txids in blockchain + * history (BIP30). If we're on a chain that has bip34 activated, we + * can skip this. + * @private + * @see https://github.com/bitcoin/bips/blob/master/bip-0030.mediawiki + * @param {Block} block + * @param {ChainEntry} prev + * @returns {Promise} + */ + + async verifyDuplicates(block, prev) { + for (const tx of block.txs) { + if (!await this.hasCoins(tx)) + continue; + + const height = prev.height + 1; + const hash = this.network.bip30[height]; + + // Blocks 91842 and 91880 created duplicate + // txids by using the same exact output script + // and extraNonce. + if (!hash || !block.hash().equals(hash)) + throw new VerifyError(block, 'invalid', 'bad-txns-BIP30', 100); + } + } -Chain.prototype.updateInputs = async function updateInputs(block, prev) { - const view = new CoinView(); - const height = prev.height + 1; - const cb = block.txs[0]; + /** + * Spend and update inputs (checkpoints only). + * @private + * @param {Block} block + * @param {ChainEntry} prev + * @returns {Promise} - Returns {@link CoinView}. + */ - view.addTX(cb, height); + async updateInputs(block, prev) { + const view = new CoinView(); + const height = prev.height + 1; + const cb = block.txs[0]; - for (let i = 1; i < block.txs.length; i++) { - const tx = block.txs[i]; + view.addTX(cb, height); - assert(await view.spendInputs(this.db, tx), - 'BUG: Spent inputs in historical data!'); + for (let i = 1; i < block.txs.length; i++) { + const tx = block.txs[i]; - view.addTX(tx, height); - } + assert(await view.spendInputs(this.db, tx), + 'BUG: Spent inputs in historical data!'); - return view; -}; + view.addTX(tx, height); + } -/** - * Check block transactions for all things pertaining - * to inputs. This function is important because it is - * what actually fills the coins into the block. This - * function will check the block reward, the sigops, - * the tx values, and execute and verify the scripts (it - * will attempt to do this on the worker pool). If - * `checkpoints` is enabled, it will skip verification - * for historical data. - * @private - * @see TX#verifyInputs - * @see TX#verify - * @param {Block} block - * @param {ChainEntry} prev - * @param {DeploymentState} state - * @returns {Promise} - Returns {@link CoinView}. - */ + return view; + } + + /** + * Check block transactions for all things pertaining + * to inputs. This function is important because it is + * what actually fills the coins into the block. This + * function will check the block reward, the sigops, + * the tx values, and execute and verify the scripts (it + * will attempt to do this on the worker pool). If + * `checkpoints` is enabled, it will skip verification + * for historical data. + * @private + * @see TX#verifyInputs + * @see TX#verify + * @param {Block} block + * @param {ChainEntry} prev + * @param {DeploymentState} state + * @returns {Promise} - Returns {@link CoinView}. + */ + + async verifyInputs(block, prev, state) { + const view = new CoinView(); + const height = prev.height + 1; + const interval = this.network.halvingInterval; + + let sigops = 0; + let reward = 0; + + // Check all transactions + for (let i = 0; i < block.txs.length; i++) { + const tx = block.txs[i]; + + // Ensure tx is not double spending an output. + if (i > 0) { + if (!await view.spendInputs(this.db, tx)) { + throw new VerifyError(block, + 'invalid', + 'bad-txns-inputs-missingorspent', + 100); + } + } -Chain.prototype.verifyInputs = async function verifyInputs(block, prev, state) { - const view = new CoinView(); - const height = prev.height + 1; - const interval = this.network.halvingInterval; + // Verify sequence locks. + if (i > 0 && tx.version >= 2) { + const valid = await this.verifyLocks(prev, tx, view, state.lockFlags); - let sigops = 0; - let reward = 0; + if (!valid) { + throw new VerifyError(block, + 'invalid', + 'bad-txns-nonfinal', + 100); + } + } - // Check all transactions - for (let i = 0; i < block.txs.length; i++) { - const tx = block.txs[i]; + // Count sigops (legacy + scripthash? + witness?) + sigops += tx.getSigopsCost(view, state.flags); - // Ensure tx is not double spending an output. - if (i > 0) { - if (!await view.spendInputs(this.db, tx)) { + if (sigops > consensus.MAX_BLOCK_SIGOPS_COST) { throw new VerifyError(block, 'invalid', - 'bad-txns-inputs-missingorspent', + 'bad-blk-sigops', 100); } - } - // Verify sequence locks. - if (i > 0 && tx.version >= 2) { - const valid = await this.verifyLocks(prev, tx, view, state.lockFlags); + // Contextual sanity checks. + if (i > 0) { + const [fee, reason, score] = tx.checkInputs(view, height); - if (!valid) { - throw new VerifyError(block, - 'invalid', - 'bad-txns-nonfinal', - 100); + if (fee === -1) { + throw new VerifyError(block, + 'invalid', + reason, + score); + } + + reward += fee; + + if (reward > consensus.MAX_MONEY) { + throw new VerifyError(block, + 'invalid', + 'bad-txns-accumulated-fee-outofrange', + 100); + } } + + // Add new coins. + view.addTX(tx, height); } - // Count sigops (legacy + scripthash? + witness?) - sigops += tx.getSigopsCost(view, state.flags); + // Make sure the miner isn't trying to conjure more coins. + reward += consensus.getReward(height, interval); - if (sigops > consensus.MAX_BLOCK_SIGOPS_COST) { + if (block.getClaimed() > reward) { throw new VerifyError(block, 'invalid', - 'bad-blk-sigops', + 'bad-cb-amount', 100); } - // Contextual sanity checks. - if (i > 0) { - const [fee, reason, score] = tx.checkInputs(view, height); - - if (fee === -1) { - throw new VerifyError(block, - 'invalid', - reason, - score); - } + // Push onto verification queue. + const jobs = []; + for (let i = 1; i < block.txs.length; i++) { + const tx = block.txs[i]; + jobs.push(tx.verifyAsync(view, state.flags, this.workers)); + } - reward += fee; + // Verify all txs in parallel. + const results = await Promise.all(jobs); - if (reward > consensus.MAX_MONEY) { + for (const result of results) { + if (!result) { throw new VerifyError(block, 'invalid', - 'bad-cb-amount', + 'mandatory-script-verify-flag-failed', 100); } } - // Add new coins. - view.addTX(tx, height); + return view; } - // Make sure the miner isn't trying to conjure more coins. - reward += consensus.getReward(height, interval); + /** + * Find the block at which a fork occurred. + * @private + * @param {ChainEntry} fork - The current chain. + * @param {ChainEntry} longer - The competing chain. + * @returns {Promise} + */ - if (block.getClaimed() > reward) { - throw new VerifyError(block, - 'invalid', - 'bad-cb-amount', - 100); - } - - // Push onto verification queue. - const jobs = []; - for (let i = 1; i < block.txs.length; i++) { - const tx = block.txs[i]; - jobs.push(tx.verifyAsync(view, state.flags, this.workers)); - } - - // Verify all txs in parallel. - if (!await co.every(jobs)) { - throw new VerifyError(block, - 'invalid', - 'mandatory-script-verify-flag-failed', - 100); - } + async findFork(fork, longer) { + while (!fork.hash.equals(longer.hash)) { + while (longer.height > fork.height) { + longer = await this.getPrevious(longer); + if (!longer) + throw new Error('No previous entry for new tip.'); + } - return view; -}; + if (fork.hash.equals(longer.hash)) + return fork; -/** - * Find the block at which a fork ocurred. - * @private - * @param {ChainEntry} fork - The current chain. - * @param {ChainEntry} longer - The competing chain. - * @returns {Promise} - */ + fork = await this.getPrevious(fork); -Chain.prototype.findFork = async function findFork(fork, longer) { - while (fork.hash !== longer.hash) { - while (longer.height > fork.height) { - longer = await this.getPrevious(longer); - if (!longer) - throw new Error('No previous entry for new tip.'); + if (!fork) + throw new Error('No previous entry for old tip.'); } - if (fork.hash === longer.hash) - return fork; - - fork = await this.getPrevious(fork); - - if (!fork) - throw new Error('No previous entry for old tip.'); + return fork; } - return fork; -}; - -/** - * Reorganize the blockchain (connect and disconnect inputs). - * Called when a competing chain with a higher chainwork - * is received. - * @private - * @param {ChainEntry} competitor - The competing chain's tip. - * @returns {Promise} - */ + /** + * Reorganize the blockchain (connect and disconnect inputs). + * Called when a competing chain with a higher chainwork + * is received. + * @private + * @param {ChainEntry} competitor - The competing chain's tip. + * @returns {Promise} + */ -Chain.prototype.reorganize = async function reorganize(competitor) { - const tip = this.tip; - const fork = await this.findFork(tip, competitor); + async reorganize(competitor) { + const tip = this.tip; + const fork = await this.findFork(tip, competitor); - assert(fork, 'No free space or data corruption.'); - - // Blocks to disconnect. - const disconnect = []; - let entry = tip; - while (entry.hash !== fork.hash) { - disconnect.push(entry); - entry = await this.getPrevious(entry); - assert(entry); - } + assert(fork, 'No free space or data corruption.'); - // Blocks to connect. - const connect = []; - entry = competitor; - while (entry.hash !== fork.hash) { - connect.push(entry); - entry = await this.getPrevious(entry); - assert(entry); - } + // Blocks to disconnect. + const disconnect = []; + let entry = tip; + while (!entry.hash.equals(fork.hash)) { + disconnect.push(entry); + entry = await this.getPrevious(entry); + assert(entry); + } - // Disconnect blocks/txs. - for (let i = 0; i < disconnect.length; i++) { - const entry = disconnect[i]; - await this.disconnect(entry); - } + // Blocks to connect. + const connect = []; + entry = competitor; + while (!entry.hash.equals(fork.hash)) { + connect.push(entry); + entry = await this.getPrevious(entry); + assert(entry); + } - // Connect blocks/txs. - // We don't want to connect the new tip here. - // That will be done outside in setBestChain. - for (let i = connect.length - 1; i >= 1; i--) { - const entry = connect[i]; - await this.reconnect(entry); - } + // Disconnect blocks/txs. + for (let i = 0; i < disconnect.length; i++) { + const entry = disconnect[i]; + await this.disconnect(entry); + } - this.logger.warning( - 'Chain reorganization: old=%s(%d) new=%s(%d)', - tip.rhash(), - tip.height, - competitor.rhash(), - competitor.height - ); + // Connect blocks/txs. + // We don't want to connect the new tip here. + // That will be done outside in setBestChain. + for (let i = connect.length - 1; i >= 1; i--) { + const entry = connect[i]; + await this.reconnect(entry); + } - await this.fire('reorganize', tip, competitor); -}; + this.logger.warning( + 'Chain reorganization: old=%h(%d) new=%h(%d)', + tip.hash, + tip.height, + competitor.hash, + competitor.height + ); + + await this.emitAsync('reorganize', tip, competitor); + } + + /** + * Reorganize the blockchain for SPV. This + * will reset the chain to the fork block. + * @private + * @param {ChainEntry} competitor - The competing chain's tip. + * @returns {Promise} + */ + + async reorganizeSPV(competitor) { + const tip = this.tip; + const fork = await this.findFork(tip, competitor); + + assert(fork, 'No free space or data corruption.'); + + // Buffer disconnected blocks. + const disconnect = []; + let entry = tip; + while (!entry.hash.equals(fork.hash)) { + disconnect.push(entry); + entry = await this.getPrevious(entry); + assert(entry); + } -/** - * Reorganize the blockchain for SPV. This - * will reset the chain to the fork block. - * @private - * @param {ChainEntry} competitor - The competing chain's tip. - * @returns {Promise} - */ + // Reset the main chain back + // to the fork block, causing + // us to redownload the blocks + // on the new main chain. + await this._reset(fork.hash, true); + + // Emit disconnection events now that + // the chain has successfully reset. + for (const entry of disconnect) { + const headers = entry.toHeaders(); + const view = new CoinView(); + await this.emitAsync('disconnect', entry, headers, view); + } -Chain.prototype.reorganizeSPV = async function reorganizeSPV(competitor) { - const tip = this.tip; - const fork = await this.findFork(tip, competitor); + this.logger.warning( + 'SPV reorganization: old=%h(%d) new=%h(%d)', + tip.hash, + tip.height, + competitor.hash, + competitor.height + ); - assert(fork, 'No free space or data corruption.'); + this.logger.warning( + 'Chain replay from height %d necessary.', + fork.height); - // Buffer disconnected blocks. - const disconnect = []; - let entry = tip; - while (entry.hash !== fork.hash) { - disconnect.push(entry); - entry = await this.getPrevious(entry); - assert(entry); + return this.emitAsync('reorganize', tip, competitor); } - // Reset the main chain back - // to the fork block, causing - // us to redownload the blocks - // on the new main chain. - await this._reset(fork.hash, true); + /** + * Disconnect an entry from the chain (updates the tip). + * @param {ChainEntry} entry + * @returns {Promise} + */ - // Emit disconnection events now that - // the chain has successfully reset. - for (const entry of disconnect) { - const headers = entry.toHeaders(); - const view = new CoinView(); - await this.fire('disconnect', entry, headers, view); - } + async disconnect(entry) { + let block = await this.getBlock(entry.hash); - this.logger.warning( - 'SPV reorganization: old=%s(%d) new=%s(%d)', - tip.rhash(), - tip.height, - competitor.rhash(), - competitor.height - ); + if (!block) { + if (!this.options.spv) + throw new Error('Block not found.'); + block = entry.toHeaders(); + } - this.logger.warning( - 'Chain replay from height %d necessary.', - fork.height); + const prev = await this.getPrevious(entry); + const view = await this.db.disconnect(entry, block); - await this.fire('reorganize', tip, competitor); -}; + assert(prev); -/** - * Disconnect an entry from the chain (updates the tip). - * @param {ChainEntry} entry - * @returns {Promise} - */ + this.tip = prev; + this.height = prev.height; -Chain.prototype.disconnect = async function disconnect(entry) { - let block = await this.getBlock(entry.hash); + this.emit('tip', prev); - if (!block) { - if (!this.options.spv) - throw new Error('Block not found.'); - block = entry.toHeaders(); + return this.emitAsync('disconnect', entry, block, view); } - const prev = await this.getPrevious(entry); - const view = await this.db.disconnect(entry, block); + /** + * Reconnect an entry to the chain (updates the tip). + * This will do contextual-verification on the block + * (necessary because we cannot validate the inputs + * in alternate chains when they come in). + * @param {ChainEntry} entry + * @param {Number} flags + * @returns {Promise} + */ - assert(prev); + async reconnect(entry) { + const flags = common.flags.VERIFY_NONE; - this.tip = prev; - this.height = prev.height; + let block = await this.getBlock(entry.hash); - this.emit('tip', prev); - - await this.fire('disconnect', entry, block, view); -}; + if (!block) { + if (!this.options.spv) + throw new Error('Block not found.'); + block = entry.toHeaders(); + } -/** - * Reconnect an entry to the chain (updates the tip). - * This will do contextual-verification on the block - * (necessary because we cannot validate the inputs - * in alternate chains when they come in). - * @param {ChainEntry} entry - * @param {Number} flags - * @returns {Promise} - */ + const prev = await this.getPrevious(entry); + assert(prev); -Chain.prototype.reconnect = async function reconnect(entry) { - const flags = common.flags.VERIFY_NONE; + let view, state; + try { + [view, state] = await this.verifyContext(block, prev, flags); + } catch (err) { + if (err.type === 'VerifyError') { + if (!err.malleated) + this.setInvalid(entry.hash); + this.logger.warning( + 'Tried to reconnect invalid block: %h (%d).', + entry.hash, entry.height); + } + throw err; + } - let block = await this.getBlock(entry.hash); + await this.db.reconnect(entry, block, view); - if (!block) { - if (!this.options.spv) - throw new Error('Block not found.'); - block = entry.toHeaders(); - } + this.tip = entry; + this.height = entry.height; + this.setDeploymentState(state); - const prev = await this.getPrevious(entry); - assert(prev); + this.emit('tip', entry); + this.emit('reconnect', entry, block); - let view, state; - try { - [view, state] = await this.verifyContext(block, prev, flags); - } catch (err) { - if (err.type === 'VerifyError') { - if (!err.malleated) - this.setInvalid(entry.hash); - this.logger.warning( - 'Tried to reconnect invalid block: %s (%d).', - entry.rhash(), entry.height); - } - throw err; + return this.emitAsync('connect', entry, block, view); } - await this.db.reconnect(entry, block, view); + /** + * Set the best chain. This is called on every valid block + * that comes in. It may add and connect the block (main chain), + * save the block without connection (alternate chain), or + * reorganize the chain (a higher fork). + * @private + * @param {ChainEntry} entry + * @param {Block} block + * @param {ChainEntry} prev + * @param {Number} flags + * @returns {Promise} + */ - this.tip = entry; - this.height = entry.height; - this.setDeploymentState(state); + async setBestChain(entry, block, prev, flags) { + // A higher fork has arrived. + // Time to reorganize the chain. + if (!entry.prevBlock.equals(this.tip.hash)) { + this.logger.warning('WARNING: Reorganizing chain.'); - this.emit('tip', entry); - this.emit('reconnect', entry, block); - - await this.fire('connect', entry, block, view); -}; - -/** - * Set the best chain. This is called on every valid block - * that comes in. It may add and connect the block (main chain), - * save the block without connection (alternate chain), or - * reorganize the chain (a higher fork). - * @private - * @param {ChainEntry} entry - * @param {Block} block - * @param {ChainEntry} prev - * @param {Number} flags - * @returns {Promise} - */ + // In spv-mode, we reset the + // chain and redownload the blocks. + if (this.options.spv) + return this.reorganizeSPV(entry); -Chain.prototype.setBestChain = async function setBestChain(entry, block, prev, flags) { - // A higher fork has arrived. - // Time to reorganize the chain. - if (entry.prevBlock !== this.tip.hash) { - this.logger.warning('WARNING: Reorganizing chain.'); - - // In spv-mode, we reset the - // chain and redownload the blocks. - if (this.options.spv) { - await this.reorganizeSPV(entry); - return; + await this.reorganize(entry); } - await this.reorganize(entry); - } - - // Warn of unknown versionbits. - if (entry.hasUnknown(this.network)) { - this.logger.warning( - 'Unknown version bits in block %d: %s.', - entry.height, util.hex32(entry.version)); - } - - // Otherwise, everything is in order. - // Do "contextual" verification on our block - // now that we're certain its previous - // block is in the chain. - let view, state; - try { - [view, state] = await this.verifyContext(block, prev, flags); - } catch (err) { - if (err.type === 'VerifyError') { - if (!err.malleated) - this.setInvalid(entry.hash); + // Warn of unknown versionbits. + if (entry.hasUnknown(this.network)) { this.logger.warning( - 'Tried to connect invalid block: %s (%d).', - entry.rhash(), entry.height); + 'Unknown version bits in block %d: %s.', + entry.height, entry.version.toString(16)); } - throw err; - } - // Save block and connect inputs. - await this.db.save(entry, block, view); - - // Expose the new state. - this.tip = entry; - this.height = entry.height; - this.setDeploymentState(state); - - this.emit('tip', entry); - this.emit('block', block, entry); - - await this.fire('connect', entry, block, view); -}; - -/** - * Save block on an alternate chain. - * @private - * @param {ChainEntry} entry - * @param {Block} block - * @param {ChainEntry} prev - * @param {Number} flags - * @returns {Promise} - */ - -Chain.prototype.saveAlternate = async function saveAlternate(entry, block, prev, flags) { - try { - // Do as much verification - // as we can before saving. - await this.verify(block, prev, flags); - } catch (err) { - if (err.type === 'VerifyError') { - if (!err.malleated) - this.setInvalid(entry.hash); - this.logger.warning( - 'Invalid block on alternate chain: %s (%d).', - entry.rhash(), entry.height); + // Otherwise, everything is in order. + // Do "contextual" verification on our block + // now that we're certain its previous + // block is in the chain. + let view, state; + try { + [view, state] = await this.verifyContext(block, prev, flags); + } catch (err) { + if (err.type === 'VerifyError') { + if (!err.malleated) + this.setInvalid(entry.hash); + this.logger.warning( + 'Tried to connect invalid block: %h (%d).', + entry.hash, entry.height); + } + throw err; } - throw err; - } - - // Warn of unknown versionbits. - if (entry.hasUnknown(this.network)) { - this.logger.warning( - 'Unknown version bits in block %d: %s.', - entry.height, util.hex32(entry.version)); - } - - await this.db.save(entry, block); - - this.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' - + ' chainwork-diff=%s', - entry.height, - this.tip.height, - entry.height, - this.tip.rhash(), - entry.rhash(), - this.tip.chainwork.toString(), - entry.chainwork.toString(), - this.tip.chainwork.sub(entry.chainwork).toString()); - - // Emit as a "competitor" block. - this.emit('competitor', block, entry); -}; - -/** - * Reset the chain to the desired block. This - * is useful for replaying the blockchain download - * for SPV. - * @param {Hash|Number} block - * @returns {Promise} - */ - -Chain.prototype.reset = async function reset(block) { - const unlock = await this.locker.lock(); - try { - return await this._reset(block, false); - } finally { - unlock(); - } -}; - -/** - * Reset the chain to the desired block without a lock. - * @private - * @param {Hash|Number} block - * @returns {Promise} - */ - -Chain.prototype._reset = async function _reset(block, silent) { - const tip = await this.db.reset(block); - - // Reset state. - this.tip = tip; - this.height = tip.height; - this.synced = false; - - const state = await this.getDeploymentState(); - - this.setDeploymentState(state); - - this.emit('tip', tip); - - if (!silent) - await this.fire('reset', tip); - // Reset the orphan map completely. There may - // have been some orphans on a forked chain we - // no longer need. - this.purgeOrphans(); + // Save block and connect inputs. + await this.db.save(entry, block, view); - this.maybeSync(); -}; + // Expose the new state. + this.tip = entry; + this.height = entry.height; + this.setDeploymentState(state); -/** - * Reset the chain to a height or hash. Useful for replaying - * the blockchain download for SPV. - * @param {Hash|Number} block - hash/height - * @returns {Promise} - */ + this.emit('tip', entry); + this.emit('block', block, entry); -Chain.prototype.replay = async function replay(block) { - const unlock = await this.locker.lock(); - try { - return await this._replay(block, true); - } finally { - unlock(); + return this.emitAsync('connect', entry, block, view); } -}; -/** - * Reset the chain without a lock. - * @private - * @param {Hash|Number} block - hash/height - * @param {Boolean?} silent - * @returns {Promise} - */ + /** + * Save block on an alternate chain. + * @private + * @param {ChainEntry} entry + * @param {Block} block + * @param {ChainEntry} prev + * @param {Number} flags + * @returns {Promise} + */ -Chain.prototype._replay = async function _replay(block, silent) { - const entry = await this.getEntry(block); + async saveAlternate(entry, block, prev, flags) { + // Do not accept forked chain older than the + // last checkpoint. + if (this.options.checkpoints) { + if (prev.height + 1 < this.network.lastCheckpoint) + throw new VerifyError(block, + 'checkpoint', + 'bad-fork-prior-to-checkpoint', + 100); + } - if (!entry) - throw new Error('Block not found.'); + try { + // Do as much verification + // as we can before saving. + await this.verify(block, prev, flags); + } catch (err) { + if (err.type === 'VerifyError') { + if (!err.malleated) + this.setInvalid(entry.hash); + this.logger.warning( + 'Invalid block on alternate chain: %h (%d).', + entry.hash, entry.height); + } + throw err; + } - if (!await this.isMainChain(entry)) - throw new Error('Cannot reset on alternate chain.'); + // Warn of unknown versionbits. + if (entry.hasUnknown(this.network)) { + this.logger.warning( + 'Unknown version bits in block %d: %s.', + entry.height, entry.version.toString(16)); + } - if (entry.isGenesis()) { - await this._reset(entry.hash, silent); - return; + await this.db.save(entry, block); + + this.logger.warning('Heads up: Competing chain at height %d:' + + ' tip-height=%d competitor-height=%d' + + ' tip-hash=%h competitor-hash=%h' + + ' tip-chainwork=%s competitor-chainwork=%s' + + ' chainwork-diff=%s', + entry.height, + this.tip.height, + entry.height, + this.tip.hash, + entry.hash, + this.tip.chainwork.toString(), + entry.chainwork.toString(), + this.tip.chainwork.sub(entry.chainwork).toString()); + + // Emit as a "competitor" block. + this.emit('competitor', block, entry); + } + + /** + * Reset the chain to the desired block. This + * is useful for replaying the blockchain download + * for SPV. + * @param {Hash|Number} block + * @returns {Promise} + */ + + async reset(block) { + const unlock = await this.locker.lock(); + try { + return await this._reset(block, false); + } finally { + unlock(); + } } - await this._reset(entry.prevBlock, silent); -}; + /** + * Reset the chain to the desired block without a lock. + * @private + * @param {Hash|Number} block + * @returns {Promise} + */ -/** - * Invalidate block. - * @param {Hash} hash - * @returns {Promise} - */ + async _reset(block, silent) { + const tip = await this.db.reset(block); -Chain.prototype.invalidate = async function invalidate(hash) { - const unlock = await this.locker.lock(); - try { - return await this._invalidate(hash); - } finally { - unlock(); - } -}; + // Reset state. + this.tip = tip; + this.height = tip.height; + this.synced = false; -/** - * Invalidate block (no lock). - * @param {Hash} hash - * @returns {Promise} - */ + const state = await this.getDeploymentState(); -Chain.prototype._invalidate = async function _invalidate(hash) { - await this._replay(hash, false); - this.setInvalid(hash); -}; + this.setDeploymentState(state); -/** - * Retroactively prune the database. - * @returns {Promise} - */ + this.emit('tip', tip); -Chain.prototype.prune = async function prune() { - const unlock = await this.locker.lock(); - try { - return await this.db.prune(); - } finally { - unlock(); - } -}; + if (!silent) + await this.emitAsync('reset', tip); -/** - * Scan the blockchain for transactions containing specified address hashes. - * @param {Hash} start - Block hash to start at. - * @param {Bloom} filter - Bloom filter containing tx and address hashes. - * @param {Function} iter - Iterator. - * @returns {Promise} - */ + // Reset the orphan map completely. There may + // have been some orphans on a forked chain we + // no longer need. + this.purgeOrphans(); -Chain.prototype.scan = async function scan(start, filter, iter) { - const unlock = await this.locker.lock(); - try { - return await this.db.scan(start, filter, iter); - } finally { - unlock(); + this.maybeSync(); } -}; -/** - * Add a block to the chain, perform all necessary verification. - * @param {Block} block - * @param {Number?} flags - * @param {Number?} id - * @returns {Promise} - */ + /** + * Reset the chain to a height or hash. Useful for replaying + * the blockchain download for SPV. + * @param {Hash|Number} block - hash/height + * @returns {Promise} + */ -Chain.prototype.add = async function add(block, flags, id) { - const hash = block.hash('hex'); - const unlock = await this.locker.lock(hash); - try { - return await this._add(block, flags, id); - } finally { - unlock(); + async replay(block) { + const unlock = await this.locker.lock(); + try { + return await this._replay(block, true); + } finally { + unlock(); + } } -}; -/** - * Add a block to the chain without a lock. - * @private - * @param {Block} block - * @param {Number?} flags - * @param {Number?} id - * @returns {Promise} - */ + /** + * Reset the chain without a lock. + * @private + * @param {Hash|Number} block - hash/height + * @param {Boolean?} silent + * @returns {Promise} + */ -Chain.prototype._add = async function _add(block, flags, id) { - const hash = block.hash('hex'); + async _replay(block, silent) { + const entry = await this.getEntry(block); - if (flags == null) - flags = common.flags.DEFAULT_FLAGS; + if (!entry) + throw new Error('Block not found.'); - if (id == null) - id = -1; + if (!await this.isMainChain(entry)) + throw new Error('Cannot reset on alternate chain.'); - // Special case for genesis block. - if (hash === this.network.genesis.hash) { - this.logger.debug('Saw genesis block: %s.', block.rhash()); - throw new VerifyError(block, 'duplicate', 'duplicate', 0); - } + if (entry.isGenesis()) { + await this._reset(entry.hash, silent); + return; + } - // Do we already have this block in the queue? - if (this.hasPending(hash)) { - this.logger.debug('Already have pending block: %s.', block.rhash()); - throw new VerifyError(block, 'duplicate', 'duplicate', 0); + await this._reset(entry.prevBlock, silent); } - // If the block is already known to be - // an orphan, ignore it. - if (this.hasOrphan(hash)) { - this.logger.debug('Already have orphan block: %s.', block.rhash()); - throw new VerifyError(block, 'duplicate', 'duplicate', 0); - } + /** + * Invalidate block. + * @param {Hash} hash + * @returns {Promise} + */ - // Do not revalidate known invalid blocks. - if (this.hasInvalid(block)) { - this.logger.debug('Invalid ancestors for block: %s.', block.rhash()); - throw new VerifyError(block, 'duplicate', 'duplicate', 100); + async invalidate(hash) { + const unlock = await this.locker.lock(); + try { + return await this._invalidate(hash); + } finally { + unlock(); + } } - // Check the POW before doing anything. - if (flags & common.flags.VERIFY_POW) { - if (!block.verifyPOW()) - throw new VerifyError(block, 'invalid', 'high-hash', 50); - } + /** + * Invalidate block (no lock). + * @param {Hash} hash + * @returns {Promise} + */ - // Do we already have this block? - if (await this.hasEntry(hash)) { - this.logger.debug('Already have block: %s.', block.rhash()); - throw new VerifyError(block, 'duplicate', 'duplicate', 0); + async _invalidate(hash) { + await this._replay(hash, false); + this.setInvalid(hash); } - // Find the previous block entry. - const prev = await this.getEntry(block.prevBlock); + /** + * Retroactively prune the database. + * @returns {Promise} + */ - // If previous block wasn't ever seen, - // add it current to orphans and return. - if (!prev) { - this.storeOrphan(block, flags, id); - return null; + async prune() { + const unlock = await this.locker.lock(); + try { + return await this.db.prune(); + } finally { + unlock(); + } } - // Connect the block. - const entry = await this.connect(prev, block, flags); - - // Handle any orphans. - if (this.hasNextOrphan(hash)) - await this.handleOrphans(entry); - - return entry; -}; + /** + * Scan the blockchain for transactions containing specified address hashes. + * @param {Hash} start - Block hash to start at. + * @param {Bloom} filter - Bloom filter containing tx and address hashes. + * @param {Function} iter - Iterator. + * @returns {Promise} + */ -/** - * Connect block to chain. - * @private - * @param {ChainEntry} prev - * @param {Block} block - * @param {Number} flags - * @returns {Promise} - */ - -Chain.prototype.connect = async function connect(prev, block, flags) { - const start = util.hrtime(); - - // Sanity check. - assert(block.prevBlock === prev.hash); - - // Explanation: we try to keep as much data - // off the javascript heap as possible. Blocks - // in the future may be 8mb or 20mb, who knows. - // In fullnode-mode we store the blocks in - // "compact" form (the headers plus the raw - // Buffer object) until they're ready to be - // fully validated here. They are deserialized, - // validated, and connected. Hopefully the - // deserialized blocks get cleaned up by the - // GC quickly. - if (block.isMemory()) { + async scan(start, filter, iter) { + const unlock = await this.locker.lock(); try { - block = block.toBlock(); - } catch (e) { - this.logger.error(e); - throw new VerifyError(block, - 'malformed', - 'error parsing message', - 10, - true); + return await this.db.scan(start, filter, iter); + } finally { + unlock(); } } - // Create a new chain entry. - const entry = ChainEntry.fromBlock(block, prev); + /** + * Add a block to the chain, perform all necessary verification. + * @param {Block} block + * @param {Number?} flags + * @param {Number?} id + * @returns {Promise} + */ - // The block is on a alternate chain if the - // chainwork is less than or equal to - // our tip's. Add the block but do _not_ - // connect the inputs. - if (entry.chainwork.lte(this.tip.chainwork)) { - // Save block to an alternate chain. - await this.saveAlternate(entry, block, prev, flags); - } else { - // Attempt to add block to the chain index. - await this.setBestChain(entry, block, prev, flags); + async add(block, flags, id) { + const hash = block.hash(); + const unlock = await this.locker.lock(hash); + try { + return await this._add(block, flags, id); + } finally { + unlock(); + } } - // Keep track of stats. - this.logStatus(start, block, entry); + /** + * Add a block to the chain without a lock. + * @private + * @param {Block} block + * @param {Number?} flags + * @param {Number?} id + * @returns {Promise} + */ - // Check sync state. - this.maybeSync(); + async _add(block, flags, id) { + const hash = block.hash(); - return entry; -}; + if (flags == null) + flags = common.flags.DEFAULT_FLAGS; -/** - * Handle orphans. - * @private - * @param {ChainEntry} entry - * @returns {Promise} - */ - -Chain.prototype.handleOrphans = async function handleOrphans(entry) { - let orphan = this.resolveOrphan(entry.hash); + if (id == null) + id = -1; - while (orphan) { - const {block, flags, id} = orphan; - - try { - entry = await this.connect(entry, block, flags); - } catch (err) { - if (err.type === 'VerifyError') { - this.logger.warning( - 'Could not resolve orphan block %s: %s.', - block.rhash(), err.message); + // Special case for genesis block. + if (hash.equals(this.network.genesis.hash)) { + this.logger.debug('Saw genesis block: %h.', block.hash()); + throw new VerifyError(block, 'duplicate', 'duplicate', 0); + } - this.emit('bad orphan', err, id); + // Do we already have this block in the queue? + if (this.hasPending(hash)) { + this.logger.debug('Already have pending block: %h.', block.hash()); + throw new VerifyError(block, 'duplicate', 'duplicate', 0); + } - break; - } - throw err; + // If the block is already known to be + // an orphan, ignore it. + if (this.hasOrphan(hash)) { + this.logger.debug('Already have orphan block: %h.', block.hash()); + throw new VerifyError(block, 'duplicate', 'duplicate', 0); } - this.logger.debug( - 'Orphan block was resolved: %s (%d).', - block.rhash(), entry.height); + // Do not revalidate known invalid blocks. + if (this.hasInvalid(block)) { + this.logger.debug('Invalid ancestors for block: %h.', block.hash()); + throw new VerifyError(block, 'duplicate', 'duplicate', 100); + } - this.emit('resolved', block, entry); + // Check the POW before doing anything. + if (flags & common.flags.VERIFY_POW) { + if (!block.verifyPOW()) + throw new VerifyError(block, 'invalid', 'high-hash', 50); + } - orphan = this.resolveOrphan(entry.hash); - } -}; + // Do we already have this block? + if (await this.hasEntry(hash)) { + this.logger.debug('Already have block: %h.', block.hash()); + throw new VerifyError(block, 'duplicate', 'duplicate', 0); + } -/** - * Test whether the chain has reached its slow height. - * @private - * @returns {Boolean} - */ + // Find the previous block entry. + const prev = await this.getEntry(block.prevBlock); -Chain.prototype.isSlow = function isSlow() { - if (this.options.spv) - return false; + // If previous block wasn't ever seen, + // add it current to orphans and return. + if (!prev) { + this.storeOrphan(block, flags, id); + return null; + } - if (this.synced) - return true; + // Connect the block. + const entry = await this.connect(prev, block, flags); + + // Handle any orphans. + if (this.hasNextOrphan(hash)) + await this.handleOrphans(entry); + + return entry; + } + + /** + * Connect block to chain. + * @private + * @param {ChainEntry} prev + * @param {Block} block + * @param {Number} flags + * @returns {Promise} + */ + + async connect(prev, block, flags) { + const start = util.bench(); + + // Sanity check. + assert(block.prevBlock.equals(prev.hash)); + + // Explanation: we try to keep as much data + // off the javascript heap as possible. Blocks + // in the future may be 8mb or 20mb, who knows. + // In fullnode-mode we store the blocks in + // "compact" form (the headers plus the raw + // Buffer object) until they're ready to be + // fully validated here. They are deserialized, + // validated, and connected. Hopefully the + // deserialized blocks get cleaned up by the + // GC quickly. + if (block.isMemory()) { + try { + block = block.toBlock(); + } catch (e) { + this.logger.error(e); + throw new VerifyError(block, + 'malformed', + 'error parsing message', + 10, + true); + } + } - if (this.height === 1 || this.height % 20 === 0) - return true; + // Create a new chain entry. + const entry = ChainEntry.fromBlock(block, prev); - if (this.height >= this.network.block.slowHeight) - return true; + // The block is on a alternate chain if the + // chainwork is less than or equal to + // our tip's. Add the block but do _not_ + // connect the inputs. + if (entry.chainwork.lte(this.tip.chainwork)) { + // Save block to an alternate chain. + await this.saveAlternate(entry, block, prev, flags); + } else { + // Attempt to add block to the chain index. + await this.setBestChain(entry, block, prev, flags); + } - return false; -}; + // Keep track of stats. + this.logStatus(start, block, entry); -/** - * Calculate the time difference from - * start time and log block. - * @private - * @param {Array} start - * @param {Block} block - * @param {ChainEntry} entry - */ + // Check sync state. + this.maybeSync(); -Chain.prototype.logStatus = function logStatus(start, block, entry) { - if (!this.isSlow()) - return; + return entry; + } - // Report memory for debugging. - this.logger.memory(); + /** + * Handle orphans. + * @private + * @param {ChainEntry} entry + * @returns {Promise} + */ - const elapsed = util.hrtime(start); + async handleOrphans(entry) { + let orphan = this.resolveOrphan(entry.hash); - this.logger.info( - 'Block %s (%d) added to chain (size=%d txs=%d time=%d).', - entry.rhash(), - entry.height, - block.getSize(), - block.txs.length, - elapsed); + while (orphan) { + const {block, flags, id} = orphan; - if (this.db.coinCache.capacity > 0) { - this.logger.debug('Coin Cache: size=%dmb, items=%d.', - util.mb(this.db.coinCache.size), this.db.coinCache.items); - } -}; + try { + entry = await this.connect(entry, block, flags); + } catch (err) { + if (err.type === 'VerifyError') { + this.logger.warning( + 'Could not resolve orphan block %h: %s.', + block.hash(), err.message); -/** - * Verify a block hash and height against the checkpoints. - * @private - * @param {ChainEntry} prev - * @param {Hash} hash - * @returns {Boolean} - */ + this.emit('bad orphan', err, id); -Chain.prototype.verifyCheckpoint = function verifyCheckpoint(prev, hash) { - if (!this.options.checkpoints) - return true; + break; + } + throw err; + } - const height = prev.height + 1; - const checkpoint = this.network.checkpointMap[height]; + this.logger.debug( + 'Orphan block was resolved: %h (%d).', + block.hash(), entry.height); - if (!checkpoint) - return true; + this.emit('resolved', block, entry); - if (hash === checkpoint) { - this.logger.debug('Hit checkpoint block %s (%d).', - util.revHex(hash), height); - this.emit('checkpoint', hash, height); - return true; + orphan = this.resolveOrphan(entry.hash); + } } - // Someone is either mining on top of - // an old block for no reason, or the - // consensus protocol is broken and - // there was a 20k+ block reorg. - this.logger.warning( - 'Checkpoint mismatch at height %d: expected=%s received=%s', - height, - util.revHex(checkpoint), - util.revHex(hash) - ); - - this.purgeOrphans(); - - return false; -}; + /** + * Test whether the chain has reached its slow height. + * @private + * @returns {Boolean} + */ -/** - * Store an orphan. - * @private - * @param {Block} block - * @param {Number?} flags - * @param {Number?} id - */ + isSlow() { + if (this.options.spv) + return false; -Chain.prototype.storeOrphan = function storeOrphan(block, flags, id) { - const height = block.getCoinbaseHeight(); - const orphan = this.orphanPrev.get(block.prevBlock); + if (this.synced) + return true; - // The orphan chain forked. - if (orphan) { - assert(orphan.block.hash('hex') !== block.hash('hex')); - assert(orphan.block.prevBlock === block.prevBlock); + if (this.height === 1 || this.height % 20 === 0) + return true; - this.logger.warning( - 'Removing forked orphan block: %s (%d).', - orphan.block.rhash(), height); + if (this.height >= this.network.block.slowHeight) + return true; - this.removeOrphan(orphan); + return false; } - this.limitOrphans(); - this.addOrphan(new Orphan(block, flags, id)); - - this.logger.debug( - 'Storing orphan block: %s (%d).', - block.rhash(), height); + /** + * Calculate the time difference from + * start time and log block. + * @private + * @param {Array} start + * @param {Block} block + * @param {ChainEntry} entry + */ - this.emit('orphan', block); -}; - -/** - * Add an orphan. - * @private - * @param {Orphan} orphan - * @returns {Orphan} - */ + logStatus(start, block, entry) { + if (!this.isSlow()) + return; -Chain.prototype.addOrphan = function addOrphan(orphan) { - const block = orphan.block; - const hash = block.hash('hex'); + // Report memory for debugging. + this.logger.memory(); - assert(!this.orphanMap.has(hash)); - assert(!this.orphanPrev.has(block.prevBlock)); - assert(this.orphanMap.size >= 0); + const elapsed = util.bench(start); - this.orphanMap.set(hash, orphan); - this.orphanPrev.set(block.prevBlock, orphan); + this.logger.info( + 'Block %h (%d) added to chain (size=%d txs=%d time=%d).', + entry.hash, + entry.height, + block.getSize(), + block.txs.length, + elapsed); + } - return orphan; -}; + /** + * Verify a block hash and height against the checkpoints. + * @private + * @param {ChainEntry} prev + * @param {Hash} hash + * @returns {Boolean} + */ -/** - * Remove an orphan. - * @private - * @param {Orphan} orphan - * @returns {Orphan} - */ + verifyCheckpoint(prev, hash) { + if (!this.options.checkpoints) + return true; -Chain.prototype.removeOrphan = function removeOrphan(orphan) { - const block = orphan.block; - const hash = block.hash('hex'); + const height = prev.height + 1; + const checkpoint = this.network.checkpointMap[height]; - assert(this.orphanMap.has(hash)); - assert(this.orphanPrev.has(block.prevBlock)); - assert(this.orphanMap.size > 0); + if (!checkpoint) + return true; - this.orphanMap.delete(hash); - this.orphanPrev.delete(block.prevBlock); + if (hash.equals(checkpoint)) { + this.logger.debug('Hit checkpoint block %h (%d).', + hash, height); + this.emit('checkpoint', hash, height); + return true; + } - return orphan; -}; + // Someone is either mining on top of + // an old block for no reason, or the + // consensus protocol is broken and + // there was a 20k+ block reorg. + this.logger.warning( + 'Checkpoint mismatch at height %d: expected=%h received=%h', + height, + checkpoint, + hash + ); -/** - * Test whether a hash would resolve the next orphan. - * @private - * @param {Hash} hash - Previous block hash. - * @returns {Boolean} - */ + this.purgeOrphans(); -Chain.prototype.hasNextOrphan = function hasNextOrphan(hash) { - return this.orphanPrev.has(hash); -}; + return false; + } -/** - * Resolve an orphan. - * @private - * @param {Hash} hash - Previous block hash. - * @returns {Orphan} - */ + /** + * Store an orphan. + * @private + * @param {Block} block + * @param {Number?} flags + * @param {Number?} id + */ -Chain.prototype.resolveOrphan = function resolveOrphan(hash) { - const orphan = this.orphanPrev.get(hash); + storeOrphan(block, flags, id) { + const height = block.getCoinbaseHeight(); + const orphan = this.orphanPrev.get(block.prevBlock); - if (!orphan) - return null; + // The orphan chain forked. + if (orphan) { + assert(!orphan.block.hash().equals(block.hash())); + assert(orphan.block.prevBlock.equals(block.prevBlock)); - return this.removeOrphan(orphan); -}; + this.logger.warning( + 'Removing forked orphan block: %h (%d).', + orphan.block.hash(), height); -/** - * Purge any waiting orphans. - */ + this.removeOrphan(orphan); + } -Chain.prototype.purgeOrphans = function purgeOrphans() { - const count = this.orphanMap.size; + this.limitOrphans(); + this.addOrphan(new Orphan(block, flags, id)); - if (count === 0) - return; + this.logger.debug( + 'Storing orphan block: %h (%d).', + block.hash(), height); - this.orphanMap.clear(); - this.orphanPrev.clear(); + this.emit('orphan', block); + } - this.logger.debug('Purged %d orphans.', count); -}; + /** + * Add an orphan. + * @private + * @param {Orphan} orphan + * @returns {Orphan} + */ -/** - * Prune orphans, only keep the orphan with the highest - * coinbase height (likely to be the peer's tip). - */ + addOrphan(orphan) { + const block = orphan.block; + const hash = block.hash(); -Chain.prototype.limitOrphans = function limitOrphans() { - const now = util.now(); + assert(!this.orphanMap.has(hash)); + assert(!this.orphanPrev.has(block.prevBlock)); + assert(this.orphanMap.size >= 0); - let oldest = null; - for (const orphan of this.orphanMap.values()) { - if (now < orphan.time + 60 * 60) { - if (!oldest || orphan.time < oldest.time) - oldest = orphan; - continue; - } + this.orphanMap.set(hash, orphan); + this.orphanPrev.set(block.prevBlock, orphan); - this.removeOrphan(orphan); + return orphan; } - if (this.orphanMap.size < this.options.maxOrphans) - return; + /** + * Remove an orphan. + * @private + * @param {Orphan} orphan + * @returns {Orphan} + */ - if (!oldest) - return; + removeOrphan(orphan) { + const block = orphan.block; + const hash = block.hash(); - this.removeOrphan(oldest); -}; + assert(this.orphanMap.has(hash)); + assert(this.orphanPrev.has(block.prevBlock)); + assert(this.orphanMap.size > 0); -/** - * Test whether an invalid block hash has been seen. - * @private - * @param {Block} block - * @returns {Boolean} - */ + this.orphanMap.delete(hash); + this.orphanPrev.delete(block.prevBlock); -Chain.prototype.hasInvalid = function hasInvalid(block) { - const hash = block.hash('hex'); + return orphan; + } - if (this.invalid.has(hash)) - return true; + /** + * Test whether a hash would resolve the next orphan. + * @private + * @param {Hash} hash - Previous block hash. + * @returns {Boolean} + */ - if (this.invalid.has(block.prevBlock)) { - this.setInvalid(hash); - return true; + hasNextOrphan(hash) { + return this.orphanPrev.has(hash); } - return false; -}; - -/** - * Mark a block as invalid. - * @private - * @param {Hash} hash - */ + /** + * Resolve an orphan. + * @private + * @param {Hash} hash - Previous block hash. + * @returns {Orphan} + */ -Chain.prototype.setInvalid = function setInvalid(hash) { - this.invalid.set(hash, true); -}; + resolveOrphan(hash) { + const orphan = this.orphanPrev.get(hash); -/** - * Forget an invalid block hash. - * @private - * @param {Hash} hash - */ + if (!orphan) + return null; -Chain.prototype.removeInvalid = function removeInvalid(hash) { - this.invalid.remove(hash); -}; + return this.removeOrphan(orphan); + } -/** - * Test the chain to see if it contains - * a block, or has recently seen a block. - * @param {Hash} hash - * @returns {Promise} - Returns Boolean. - */ + /** + * Purge any waiting orphans. + */ -Chain.prototype.has = async function has(hash) { - if (this.hasOrphan(hash)) - return true; + purgeOrphans() { + const count = this.orphanMap.size; - if (this.locker.has(hash)) - return true; - - if (this.invalid.has(hash)) - return true; + if (count === 0) + return; - return await this.hasEntry(hash); -}; + this.orphanMap.clear(); + this.orphanPrev.clear(); -/** - * Find the corresponding block entry by hash or height. - * @param {Hash|Number} hash/height - * @returns {Promise} - Returns {@link ChainEntry}. - */ + this.logger.debug('Purged %d orphans.', count); + } -Chain.prototype.getEntry = function getEntry(hash) { - return this.db.getEntry(hash); -}; + /** + * Prune orphans, only keep the orphan with the highest + * coinbase height (likely to be the peer's tip). + */ -/** - * Retrieve a chain entry by height. - * @param {Number} height - * @returns {Promise} - Returns {@link ChainEntry}. - */ + limitOrphans() { + const now = util.now(); -Chain.prototype.getEntryByHeight = function getEntryByHeight(height) { - return this.db.getEntryByHeight(height); -}; + let oldest = null; + for (const orphan of this.orphanMap.values()) { + if (now < orphan.time + 60 * 60) { + if (!oldest || orphan.time < oldest.time) + oldest = orphan; + continue; + } -/** - * Retrieve a chain entry by hash. - * @param {Hash} hash - * @returns {Promise} - Returns {@link ChainEntry}. - */ + this.removeOrphan(orphan); + } -Chain.prototype.getEntryByHash = function getEntryByHash(hash) { - return this.db.getEntryByHash(hash); -}; + if (this.orphanMap.size < this.options.maxOrphans) + return; -/** - * Get the hash of a block by height. Note that this - * will only return hashes in the main chain. - * @param {Number} height - * @returns {Promise} - Returns {@link Hash}. - */ + if (!oldest) + return; -Chain.prototype.getHash = function getHash(height) { - return this.db.getHash(height); -}; + this.removeOrphan(oldest); + } -/** - * Get the height of a block by hash. - * @param {Hash} hash - * @returns {Promise} - Returns Number. - */ + /** + * Test whether an invalid block hash has been seen. + * @private + * @param {Block} block + * @returns {Boolean} + */ -Chain.prototype.getHeight = function getHeight(hash) { - return this.db.getHeight(hash); -}; + hasInvalid(block) { + const hash = block.hash(); -/** - * Test the chain to see if it contains a block. - * @param {Hash} hash - * @returns {Promise} - Returns Boolean. - */ + if (this.invalid.has(hash)) + return true; -Chain.prototype.hasEntry = function hasEntry(hash) { - return this.db.hasEntry(hash); -}; + if (this.invalid.has(block.prevBlock)) { + this.setInvalid(hash); + return true; + } -/** - * Get the _next_ block hash (does not work by height). - * @param {Hash} hash - * @returns {Promise} - Returns {@link Hash}. - */ + return false; + } -Chain.prototype.getNextHash = function getNextHash(hash) { - return this.db.getNextHash(hash); -}; + /** + * Mark a block as invalid. + * @private + * @param {Hash} hash + */ -/** - * Check whether coins are still unspent. Necessary for bip30. - * @see https://bitcointalk.org/index.php?topic=67738.0 - * @param {TX} tx - * @returns {Promise} - Returns Boolean. - */ + setInvalid(hash) { + this.invalid.set(hash, true); + } -Chain.prototype.hasCoins = function hasCoins(tx) { - return this.db.hasCoins(tx); -}; + /** + * Forget an invalid block hash. + * @private + * @param {Hash} hash + */ -/** - * Get all tip hashes. - * @returns {Promise} - Returns {@link Hash}[]. - */ + removeInvalid(hash) { + this.invalid.remove(hash); + } -Chain.prototype.getTips = function getTips() { - return this.db.getTips(); -}; + /** + * Test the chain to see if it contains + * a block, or has recently seen a block. + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ -/** - * Get a coin (unspents only). - * @private - * @param {Outpoint} prevout - * @returns {Promise} - Returns {@link CoinEntry}. - */ + async has(hash) { + if (this.hasOrphan(hash)) + return true; -Chain.prototype.readCoin = function readCoin(prevout) { - return this.db.readCoin(prevout); -}; + if (this.locker.has(hash)) + return true; -/** - * Get a coin (unspents only). - * @param {Hash} hash - * @param {Number} index - * @returns {Promise} - Returns {@link Coin}. - */ + if (this.invalid.has(hash)) + return true; -Chain.prototype.getCoin = function getCoin(hash, index) { - return this.db.getCoin(hash, index); -}; + return this.hasEntry(hash); + } -/** - * Retrieve a block from the database (not filled with coins). - * @param {Hash} hash - * @returns {Promise} - Returns {@link Block}. - */ + /** + * Find the corresponding block entry by hash or height. + * @param {Hash|Number} hash/height + * @returns {Promise} - Returns {@link ChainEntry}. + */ -Chain.prototype.getBlock = function getBlock(hash) { - return this.db.getBlock(hash); -}; + getEntry(hash) { + return this.db.getEntry(hash); + } -/** - * Retrieve a block from the database (not filled with coins). - * @param {Hash} hash - * @returns {Promise} - Returns {@link Block}. - */ + /** + * Retrieve a chain entry by height. + * @param {Number} height + * @returns {Promise} - Returns {@link ChainEntry}. + */ -Chain.prototype.getRawBlock = function getRawBlock(block) { - return this.db.getRawBlock(block); -}; + getEntryByHeight(height) { + return this.db.getEntryByHeight(height); + } -/** - * Get a historical block coin viewpoint. - * @param {Block} hash - * @returns {Promise} - Returns {@link CoinView}. - */ + /** + * Retrieve a chain entry by hash. + * @param {Hash} hash + * @returns {Promise} - Returns {@link ChainEntry}. + */ -Chain.prototype.getBlockView = function getBlockView(block) { - return this.db.getBlockView(block); -}; + getEntryByHash(hash) { + return this.db.getEntryByHash(hash); + } -/** - * Get a transaction with metadata. - * @param {Hash} hash - * @returns {Promise} - Returns {@link TXMeta}. - */ + /** + * Get the hash of a block by height. Note that this + * will only return hashes in the main chain. + * @param {Number} height + * @returns {Promise} - Returns {@link Hash}. + */ -Chain.prototype.getMeta = function getMeta(hash) { - return this.db.getMeta(hash); -}; + getHash(height) { + return this.db.getHash(height); + } -/** - * Retrieve a transaction. - * @param {Hash} hash - * @returns {Promise} - Returns {@link TX}. - */ + /** + * Get the height of a block by hash. + * @param {Hash} hash + * @returns {Promise} - Returns Number. + */ -Chain.prototype.getTX = function getTX(hash) { - return this.db.getTX(hash); -}; + getHeight(hash) { + return this.db.getHeight(hash); + } -/** - * @param {Hash} hash - * @returns {Promise} - Returns Boolean. - */ + /** + * Test the chain to see if it contains a block. + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ -Chain.prototype.hasTX = function hasTX(hash) { - return this.db.hasTX(hash); -}; + hasEntry(hash) { + return this.db.hasEntry(hash); + } -/** - * Get all coins pertinent to an address. - * @param {Address[]} addrs - * @returns {Promise} - Returns {@link Coin}[]. - */ + /** + * Get the _next_ block hash (does not work by height). + * @param {Hash} hash + * @returns {Promise} - Returns {@link Hash}. + */ -Chain.prototype.getCoinsByAddress = function getCoinsByAddress(addrs) { - return this.db.getCoinsByAddress(addrs); -}; + getNextHash(hash) { + return this.db.getNextHash(hash); + } -/** - * Get all transaction hashes to an address. - * @param {Address[]} addrs - * @returns {Promise} - Returns {@link Hash}[]. - */ + /** + * Check whether coins are still unspent. Necessary for bip30. + * @see https://bitcointalk.org/index.php?topic=67738.0 + * @param {TX} tx + * @returns {Promise} - Returns Boolean. + */ -Chain.prototype.getHashesByAddress = function getHashesByAddress(addrs) { - return this.db.getHashesByAddress(addrs); -}; + hasCoins(tx) { + return this.db.hasCoins(tx); + } -/** - * Get all transactions pertinent to an address. - * @param {Address[]} addrs - * @returns {Promise} - Returns {@link TX}[]. - */ + /** + * Get all tip hashes. + * @returns {Promise} - Returns {@link Hash}[]. + */ -Chain.prototype.getTXByAddress = function getTXByAddress(addrs) { - return this.db.getTXByAddress(addrs); -}; + getTips() { + return this.db.getTips(); + } -/** - * Get all transactions pertinent to an address. - * @param {Address[]} addrs - * @returns {Promise} - Returns {@link TXMeta}[]. - */ + /** + * Get range of hashes. + * @param {Number} [start=-1] + * @param {Number} [end=-1] + * @returns {Promise} + */ -Chain.prototype.getMetaByAddress = function getMetaByAddress(addrs) { - return this.db.getMetaByAddress(addrs); -}; + getHashes(start = -1, end = -1) { + return this.db.getHashes(start, end); + } -/** - * Get an orphan block. - * @param {Hash} hash - * @returns {Block} - */ + /** + * Get a coin (unspents only). + * @private + * @param {Outpoint} prevout + * @returns {Promise} - Returns {@link CoinEntry}. + */ -Chain.prototype.getOrphan = function getOrphan(hash) { - return this.orphanMap.get(hash) || null; -}; + readCoin(prevout) { + return this.db.readCoin(prevout); + } -/** - * Test the chain to see if it contains an orphan. - * @param {Hash} hash - * @returns {Promise} - Returns Boolean. - */ + /** + * Get a coin (unspents only). + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} - Returns {@link Coin}. + */ -Chain.prototype.hasOrphan = function hasOrphan(hash) { - return this.orphanMap.has(hash); -}; + getCoin(hash, index) { + return this.db.getCoin(hash, index); + } -/** - * Test the chain to see if it contains a pending block in its queue. - * @param {Hash} hash - * @returns {Promise} - Returns Boolean. - */ + /** + * Retrieve a block from the database (not filled with coins). + * @param {Hash} hash + * @returns {Promise} - Returns {@link Block}. + */ -Chain.prototype.hasPending = function hasPending(hash) { - return this.locker.hasPending(hash); -}; + getBlock(hash) { + return this.db.getBlock(hash); + } -/** - * Get coin viewpoint. - * @param {TX} tx - * @returns {Promise} - Returns {@link CoinView}. - */ + /** + * Retrieve a block from the database (not filled with coins). + * @param {Hash} hash + * @returns {Promise} - Returns {@link Block}. + */ -Chain.prototype.getCoinView = function getCoinView(tx) { - return this.db.getCoinView(tx); -}; + getRawBlock(block) { + return this.db.getRawBlock(block); + } -/** - * Get coin viewpoint (spent). - * @param {TX} tx - * @returns {Promise} - Returns {@link CoinView}. - */ + /** + * Get a historical block coin viewpoint. + * @param {Block} hash + * @returns {Promise} - Returns {@link CoinView}. + */ -Chain.prototype.getSpentView = async function getSpentView(tx) { - const unlock = await this.locker.lock(); - try { - return await this.db.getSpentView(tx); - } finally { - unlock(); + getBlockView(block) { + return this.db.getBlockView(block); } -}; -/** - * Test the chain to see if it is synced. - * @returns {Boolean} - */ + /** + * Get an orphan block. + * @param {Hash} hash + * @returns {Block} + */ -Chain.prototype.isFull = function isFull() { - return this.synced; -}; + getOrphan(hash) { + return this.orphanMap.get(hash) || null; + } -/** - * Potentially emit a `full` event. - * @private - */ + /** + * Test the chain to see if it contains an orphan. + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ + + hasOrphan(hash) { + return this.orphanMap.has(hash); + } -Chain.prototype.maybeSync = function maybeSync() { - if (this.synced) - return; + /** + * Test the chain to see if it contains a pending block in its queue. + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ - if (this.options.checkpoints) { - if (this.height < this.network.lastCheckpoint) - return; + hasPending(hash) { + return this.locker.pending(hash); } - if (this.tip.time < util.now() - this.network.block.maxTipAge) - return; + /** + * Get coin viewpoint. + * @param {TX} tx + * @returns {Promise} - Returns {@link CoinView}. + */ - if (!this.hasChainwork()) - return; + getCoinView(tx) { + return this.db.getCoinView(tx); + } - this.synced = true; - this.emit('full'); -}; + /** + * Test the chain to see if it is synced. + * @returns {Boolean} + */ -/** - * Test the chain to see if it has the - * minimum required chainwork for the - * network. - * @returns {Boolean} - */ + isFull() { + return this.synced; + } -Chain.prototype.hasChainwork = function hasChainwork() { - return this.tip.chainwork.gte(this.network.pow.chainwork); -}; + /** + * Potentially emit a `full` event. + * @private + */ -/** - * Get the fill percentage. - * @returns {Number} percent - Ranges from 0.0 to 1.0. - */ + maybeSync() { + if (this.synced) + return; -Chain.prototype.getProgress = function getProgress() { - const start = this.network.genesis.time; - const current = this.tip.time - start; - const end = util.now() - start - 40 * 60; - return Math.min(1, current / end); -}; + if (this.options.checkpoints) { + if (this.height < this.network.lastCheckpoint) + return; + } -/** - * Calculate chain locator (an array of hashes). - * @param {Hash?} start - Height or hash to treat as the tip. - * The current tip will be used if not present. Note that this can be a - * non-existent hash, which is useful for headers-first locators. - * @returns {Promise} - Returns {@link Hash}[]. - */ + if (this.tip.time < util.now() - this.network.block.maxTipAge) + return; + + if (!this.hasChainwork()) + return; -Chain.prototype.getLocator = async function getLocator(start) { - const unlock = await this.locker.lock(); - try { - return await this._getLocator(start); - } finally { - unlock(); + this.synced = true; + this.emit('full'); } -}; -/** - * Calculate chain locator without a lock. - * @private - * @param {Hash?} start - * @returns {Promise} - */ + /** + * Test the chain to see if it has the + * minimum required chainwork for the + * network. + * @returns {Boolean} + */ -Chain.prototype._getLocator = async function _getLocator(start) { - if (start == null) - start = this.tip.hash; + hasChainwork() { + return this.tip.chainwork.gte(this.network.pow.chainwork); + } - assert(typeof start === 'string'); + /** + * Get the fill percentage. + * @returns {Number} percent - Ranges from 0.0 to 1.0. + */ - let entry = await this.getEntry(start); + getProgress() { + const start = this.network.genesis.time; + const current = this.tip.time - start; + const end = util.now() - start - 40 * 60; + return Math.min(1, current / end); + } - const hashes = []; + /** + * Calculate chain locator (an array of hashes). + * @param {Hash?} start - Height or hash to treat as the tip. + * The current tip will be used if not present. Note that this can be a + * non-existent hash, which is useful for headers-first locators. + * @returns {Promise} - Returns {@link Hash}[]. + */ - if (!entry) { - entry = this.tip; - hashes.push(start); + async getLocator(start) { + const unlock = await this.locker.lock(); + try { + return await this._getLocator(start); + } finally { + unlock(); + } } - let main = await this.isMainChain(entry); - let hash = entry.hash; - let height = entry.height; - let step = 1; + /** + * Calculate chain locator without a lock. + * @private + * @param {Hash?} start + * @returns {Promise} + */ - hashes.push(hash); + async _getLocator(start) { + if (start == null) + start = this.tip.hash; - while (height > 0) { - height -= step; + assert(Buffer.isBuffer(start)); - if (height < 0) - height = 0; + let entry = await this.getEntry(start); - if (hashes.length > 10) - step *= 2; + const hashes = []; - if (main) { - // If we're on the main chain, we can - // do a fast lookup of the hash. - hash = await this.getHash(height); - assert(hash); - } else { - const ancestor = await this.getAncestor(entry, height); - assert(ancestor); - main = await this.isMainChain(ancestor); - hash = ancestor.hash; + if (!entry) { + entry = this.tip; + hashes.push(start); } - hashes.push(hash); - } + let main = await this.isMainChain(entry); + let hash = entry.hash; + let height = entry.height; + let step = 1; - return hashes; -}; + hashes.push(hash); -/** - * Calculate the orphan root of the hash (if it is an orphan). - * @param {Hash} hash - * @returns {Hash} - */ + while (height > 0) { + height -= step; + + if (height < 0) + height = 0; + + if (hashes.length > 10) + step *= 2; + + if (main) { + // If we're on the main chain, we can + // do a fast lookup of the hash. + hash = await this.getHash(height); + assert(hash); + } else { + const ancestor = await this.getAncestor(entry, height); + assert(ancestor); + main = await this.isMainChain(ancestor); + hash = ancestor.hash; + } -Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { - let root = null; + hashes.push(hash); + } - assert(hash); + return hashes; + } - for (;;) { - const orphan = this.orphanMap.get(hash); + /** + * Calculate the orphan root of the hash (if it is an orphan). + * @param {Hash} hash + * @returns {Hash} + */ - if (!orphan) - break; + getOrphanRoot(hash) { + let root = null; - root = hash; - hash = orphan.block.prevBlock; - } + assert(hash); - return root; -}; + for (;;) { + const orphan = this.orphanMap.get(hash); -/** - * Calculate the time difference (in seconds) - * between two blocks by examining chainworks. - * @param {ChainEntry} to - * @param {ChainEntry} from - * @returns {Number} - */ + if (!orphan) + break; -Chain.prototype.getProofTime = function getProofTime(to, from) { - const pow = this.network.pow; - let sign, work; + root = hash; + hash = orphan.block.prevBlock; + } - if (to.chainwork.gt(from.chainwork)) { - work = to.chainwork.sub(from.chainwork); - sign = 1; - } else { - work = from.chainwork.sub(to.chainwork); - sign = -1; + return root; } - work = work.imuln(pow.targetSpacing); - work = work.div(this.tip.getProof()); + /** + * Calculate the time difference (in seconds) + * between two blocks by examining chainworks. + * @param {ChainEntry} to + * @param {ChainEntry} from + * @returns {Number} + */ - if (work.bitLength() > 53) - return sign * Number.MAX_SAFE_INTEGER; + getProofTime(to, from) { + const pow = this.network.pow; + let sign, work; - return sign * work.toNumber(); -}; + if (to.chainwork.gt(from.chainwork)) { + work = to.chainwork.sub(from.chainwork); + sign = 1; + } else { + work = from.chainwork.sub(to.chainwork); + sign = -1; + } -/** - * Calculate the next target based on the chain tip. - * @returns {Promise} - returns Number - * (target is in compact/mantissa form). - */ + work = work.imuln(pow.targetSpacing); + work = work.div(this.tip.getProof()); -Chain.prototype.getCurrentTarget = async function getCurrentTarget() { - return await this.getTarget(this.network.now(), this.tip); -}; + if (work.bitLength() > 53) + return sign * Number.MAX_SAFE_INTEGER; -/** - * Calculate the next target. - * @param {Number} time - Next block timestamp. - * @param {ChainEntry} prev - Previous entry. - * @returns {Promise} - returns Number - * (target is in compact/mantissa form). - */ + return sign * work.toNumber(); + } -Chain.prototype.getTarget = async function getTarget(time, prev) { - const pow = this.network.pow; + /** + * Calculate the next target based on the chain tip. + * @returns {Promise} - returns Number + * (target is in compact/mantissa form). + */ - // Genesis - if (!prev) { - assert(time === this.network.genesis.time); - return pow.bits; + async getCurrentTarget() { + return this.getTarget(this.network.now(), this.tip); } - let retargetInterval, targetSpacing, averagingInterval; + /** + * Calculate the next target. + * @param {Number} time - Next block timestamp. + * @param {ChainEntry} prev - Previous entry. + * @returns {Promise} - returns Number + * (target is in compact/mantissa form). + */ + + async getTarget(time, prev) { + const pow = this.network.pow; - if (prev.height < pow.blockHeight_version2){ + // Genesis + if (!prev) { + assert(time === this.network.genesis.time); + return pow.bits; + } - retargetInterval = pow.retargetInterval_version1; - targetSpacing = pow.targetSpacing_version1; - averagingInterval = pow.averagingInterval_version1; + let retargetInterval, averagingInterval, targetTimespan; - } else if (prev.height >= pow.blockHeight_version2 && prev.height < pow.blockHeight_version3){ - - retargetInterval = pow.retargetInterval_version2; - targetSpacing = pow.targetSpacing_version2; - averagingInterval = pow.averagingInterval_version2; + let targetSpacing = pow.targetSpacing - } else if (prev.height >= pow.blockHeight_version3) { + if ((prev.height + 1) < pow.blockHeight_Version2){ - retargetInterval = pow.retargetInterval_version3; - targetSpacing = pow.targetSpacing_version3; - averagingInterval = pow.averagingInterval_version3; + retargetInterval = pow.retargetInterval_Version1; + averagingInterval = pow.averagingInterval_Version1; + targetTimespan = pow.targetTimespan_Version1; - } + } else if ((prev.height + 1) < pow.blockHeight_Version3){ + + retargetInterval = pow.retargetInterval_Version2; + averagingInterval = pow.averagingInterval_Version2; + targetTimespan = pow.targetTimespan_Version2 * targetSpacing; + + } else { + + retargetInterval = pow.retargetInterval_Version3; + averagingInterval = pow.averagingInterval_Version3; + targetTimespan = pow.targetTimespan_Version3 * targetSpacing; - // Do not retarget - if ((prev.height + 1) % retargetInterval !== 0) { - if (pow.targetReset) { - // Special behavior for testnet: - // console.log("Time: " + time) - // console.log("Prev Time: " + prev.time) - // console.log("Target Spacing*2: " + (targetSpacing * 2)) - // console.log("prev+targ: " + (prev.time + targetSpacing * 2)) - if (time > prev.time + targetSpacing * 2) - return prev.bits; + } - while (prev.height !== 0 - && prev.height % retargetInterval !== 0 - && prev.bits === pow.bits) { - const cache = this.getPrevCache(prev); + // Do not retarget + if ((prev.height + 1) % retargetInterval !== 0) { + if (pow.targetReset) { + // Special behavior for testnet + if (time > prev.time + (targetTimespan * 2)) { + return pow.bits; + } + while (prev.height !== 0 + && prev.height % retargetInterval !== 0 + && prev.bits === pow.bits) { + const cache = this.getPrevCache(prev); - if (cache) - prev = cache; - else - prev = await this.getPrevious(prev); + if (cache) + prev = cache; + else + prev = await this.getPrevious(prev); - assert(prev); + assert(prev); + } } + return prev.bits; } - return prev.bits; - } - // Back 6 block - var back = averagingInterval - 1; + // Back 6 block + var back = averagingInterval - 1; + + if (prev.height + 1 !== averagingInterval) + back = averagingInterval; + + let first = prev; + for (let i = 0; i < back; i++){ + if (first) + first = await this.getPrevious(first) + } - - if (prev.height + 1 !== averagingInterval) - back = averagingInterval; + assert(first); - - let first = prev; - for (let i = 0; i < back; i++){ - if (first) - first = await this.getPrevious(first) + return this.retarget(prev, first); } - assert(first); + /** + * Retarget. This is called when the chain height + * hits a retarget diff interval. + * @param {ChainEntry} prev - Previous entry. + * @param {ChainEntry} first - Chain entry from 2 weeks prior. + * @returns {Number} target - Target in compact/mantissa form. + */ - return this.retarget(prev, first); -}; + retarget(prev, first) { + const pow = this.network.pow; -/** - * Retarget. This is called when the chain height - * hits a retarget diff interval. - * @param {ChainEntry} prev - Previous entry. - * @param {ChainEntry} first - Chain entry from 2 weeks prior. - * @returns {Number} target - Target in compact/mantissa form. - */ + if (pow.noRetargeting) + return prev.bits; -Chain.prototype.retarget = function retarget(prev, first) { - const pow = this.network.pow; + let height = prev.height + 1; - let height = prev.height; + let targetTimespan, averagingIntervalTimespan, targetSpacing, adjustUp, adjustDown; - let targetTimespan, averagingIntervalTimespan, targetSpacing, adjustUp, adjustDown; + targetSpacing = pow.targetSpacing - if (height < pow.blockHeight_version2){ + if (height < pow.blockHeight_Version2){ - targetTimespan = pow.targetTimespan_version1; - averagingIntervalTimespan = pow.averagingIntervalTimespan_version1; - targetSpacing = pow.targetSpacing_version1; - adjustUp = pow.adjustUp_version1; - adjustDown = pow.adjustDown_version1; + targetTimespan = pow.targetTimespan_Version1; + averagingIntervalTimespan = pow.averagingIntervalTimespan_Version1; + adjustUp = pow.adjustUp_Version1; + adjustDown = pow.adjustDown_Version1; - } else if (height >= pow.blockHeight_version2 && height < pow.blockHeight_version3){ + } else if (height < pow.blockHeight_Version3){ - targetTimespan = pow.targetTimespan_version2; - averagingIntervalTimespan = pow.averagingIntervalTimespan_version2; - targetSpacing = pow.targetSpacing_version2; - adjustUp = pow.adjustUp_version2; - adjustDown = pow.adjustDown_version2; + targetTimespan = pow.targetTimespan_Version2; + averagingIntervalTimespan = pow.averagingIntervalTimespan_Version2; + adjustUp = pow.adjustUp_Version2; + adjustDown = pow.adjustDown_Version2; - } else if (height >= pow.blockHeight_version3) { + } else { - targetTimespan = pow.targetTimespan_version3; - averagingIntervalTimespan = pow.averagingIntervalTimespan_version3; - targetSpacing = pow.targetSpacing_version3; - adjustUp = pow.adjustUp_version3; - adjustDown = pow.adjustDown_version3; + targetTimespan = pow.targetTimespan_Version3; + averagingIntervalTimespan = pow.averagingIntervalTimespan_Version3; + adjustUp = pow.adjustUp_Version3; + adjustDown = pow.adjustDown_Version3; - } else { - // Difficulty NOT handled?!? - } + } - if (pow.noRetargeting) - return prev.bits; + averagingIntervalTimespan = averagingIntervalTimespan * targetSpacing - let actualTimespan = prev.time - first.time; + let actualTimespan = prev.time - first.time; - // console.log("Actual Timespan: " + actualTimespan); + let minActualTimespan = Math.floor(averagingIntervalTimespan * (100 - adjustUp) / 100) + let maxActualTimespan = Math.floor(averagingIntervalTimespan * (100 + adjustDown) / 100) - let minActualTimespan = Math.floor(averagingIntervalTimespan * (100 - adjustUp) / 100) - let maxActualTimespan = Math.floor(averagingIntervalTimespan * (100 + adjustDown) / 100) + if (actualTimespan < minActualTimespan) + actualTimespan = minActualTimespan; - // console.log("minActualTimespan: " + minActualTimespan); - // console.log("maxActualTimespan: " + maxActualTimespan); + if (actualTimespan > maxActualTimespan) + actualTimespan = maxActualTimespan; - if (actualTimespan < minActualTimespan) - actualTimespan = minActualTimespan; + // Retarget + let target = consensus.fromCompact(prev.bits); - // console.log("New Actual Timespan: " + actualTimespan) + // FLO: intermediate uint256 can overflow by 1 bit + let fShift = (consensus.toCompact(target) > pow.bits - 1) - if (actualTimespan > maxActualTimespan) - actualTimespan = maxActualTimespan; + if (fShift) + target.ishl(new BN(1)) - // console.log("New Actual Timespan: " + actualTimespan) + target.imuln(actualTimespan); + target.idivn(targetTimespan); - // Retarget - let target = consensus.fromCompact(prev.bits); + if (fShift) + target.ishr(new BN(1)) - // console.log("Target: " + target) + if (consensus.toCompact(target) > pow.bits){ + return pow.bits; + } - target.imuln(actualTimespan); - // console.log("imuln: " + target) - target.idivn(targetTimespan); - // console.log("idivn: " + target) + return consensus.toCompact(target); + } - if (target.gt(pow.limit)) - return pow.bits; + /** + * Find a locator. Analagous to bitcoind's `FindForkInGlobalIndex()`. + * @param {Hash[]} locator - Hashes. + * @returns {Promise} - Returns {@link Hash} (the + * hash of the latest known block). + */ - // console.log("gt: " + target) + async findLocator(locator) { + for (const hash of locator) { + if (await this.isMainHash(hash)) + return hash; + } - return consensus.toCompact(target); -}; + return this.network.genesis.hash; + } -/** - * Find a locator. Analagous to bitcoind's `FindForkInGlobalIndex()`. - * @param {Hash[]} locator - Hashes. - * @returns {Promise} - Returns {@link Hash} (the - * hash of the latest known block). - */ + /** + * Check whether a versionbits deployment is active (BIP9: versionbits). + * @example + * await chain.isActive(tip, deployments.segwit); + * @see https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki + * @param {ChainEntry} prev - Previous chain entry. + * @param {String} id - Deployment id. + * @returns {Promise} - Returns Number. + */ -Chain.prototype.findLocator = async function findLocator(locator) { - for (const hash of locator) { - if (await this.isMainHash(hash)) - return hash; + async isActive(prev, deployment) { + const state = await this.getState(prev, deployment); + return state === thresholdStates.ACTIVE; } - return this.network.genesis.hash; -}; - -/** - * Check whether a versionbits deployment is active (BIP9: versionbits). - * @example - * await chain.isActive(tip, deployments.segwit); - * @see https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki - * @param {ChainEntry} prev - Previous chain entry. - * @param {String} id - Deployment id. - * @returns {Promise} - Returns Number. - */ + /** + * Get chain entry state for a deployment (BIP9: versionbits). + * @example + * await chain.getState(tip, deployments.segwit); + * @see https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki + * @param {ChainEntry} prev - Previous chain entry. + * @param {String} id - Deployment id. + * @returns {Promise} - Returns Number. + */ -Chain.prototype.isActive = async function isActive(prev, deployment) { - const state = await this.getState(prev, deployment); - return state === thresholdStates.ACTIVE; -}; + async getState(prev, deployment) { + const bit = deployment.bit; -/** - * Get chain entry state for a deployment (BIP9: versionbits). - * @example - * await chain.getState(tip, deployments.segwit); - * @see https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki - * @param {ChainEntry} prev - Previous chain entry. - * @param {String} id - Deployment id. - * @returns {Promise} - Returns Number. - */ + if (deployment.startTime === -1) + return thresholdStates.ACTIVE; -Chain.prototype.getState = async function getState(prev, deployment) { - const bit = deployment.bit; + let window = this.network.minerWindow; + let threshold = this.network.activationThreshold; - let window = this.network.minerWindow; - let threshold = this.network.activationThreshold; + if (deployment.threshold !== -1) + threshold = deployment.threshold; - if (deployment.threshold !== -1) - threshold = deployment.threshold; + if (deployment.window !== -1) + window = deployment.window; - if (deployment.window !== -1) - window = deployment.window; + if (((prev.height + 1) % window) !== 0) { + const height = prev.height - ((prev.height + 1) % window); - if (((prev.height + 1) % window) !== 0) { - const height = prev.height - ((prev.height + 1) % window); + prev = await this.getAncestor(prev, height); - prev = await this.getAncestor(prev, height); + if (!prev) + return thresholdStates.DEFINED; - if (!prev) - return thresholdStates.DEFINED; + assert(prev.height === height); + assert(((prev.height + 1) % window) === 0); + } - assert(prev.height === height); - assert(((prev.height + 1) % window) === 0); - } + let entry = prev; + let state = thresholdStates.DEFINED; - let entry = prev; - let state = thresholdStates.DEFINED; + const compute = []; - const compute = []; + while (entry) { + const cached = this.db.stateCache.get(bit, entry); - while (entry) { - const cached = this.db.stateCache.get(bit, entry); + if (cached !== -1) { + state = cached; + break; + } - if (cached !== -1) { - state = cached; - break; - } + const time = await this.getMedianTime(entry); - const time = await this.getMedianTime(entry); + if (time < deployment.startTime) { + state = thresholdStates.DEFINED; + this.db.stateCache.set(bit, entry, state); + break; + } - if (time < deployment.startTime) { - state = thresholdStates.DEFINED; - this.db.stateCache.set(bit, entry, state); - break; - } + compute.push(entry); - compute.push(entry); + const height = entry.height - window; - const height = entry.height - window; + entry = await this.getAncestor(entry, height); + } - entry = await this.getAncestor(entry, height); - } + while (compute.length) { + const entry = compute.pop(); - while (compute.length) { - const entry = compute.pop(); + switch (state) { + case thresholdStates.DEFINED: { + const time = await this.getMedianTime(entry); - switch (state) { - case thresholdStates.DEFINED: { - const time = await this.getMedianTime(entry); + if (time >= deployment.timeout) { + state = thresholdStates.FAILED; + break; + } - if (time >= deployment.timeout) { - state = thresholdStates.FAILED; - break; - } + if (time >= deployment.startTime) { + state = thresholdStates.STARTED; + break; + } - if (time >= deployment.startTime) { - state = thresholdStates.STARTED; break; } + case thresholdStates.STARTED: { + const time = await this.getMedianTime(entry); - break; - } - case thresholdStates.STARTED: { - const time = await this.getMedianTime(entry); + if (time >= deployment.timeout) { + state = thresholdStates.FAILED; + break; + } - if (time >= deployment.timeout) { - state = thresholdStates.FAILED; - break; - } + let block = entry; + let count = 0; - let block = entry; - let count = 0; + for (let i = 0; i < window; i++) { + if (block.hasBit(bit)) + count++; - for (let i = 0; i < window; i++) { - if (block.hasBit(bit)) - count++; + if (count >= threshold) { + state = thresholdStates.LOCKED_IN; + break; + } - if (count >= threshold) { - state = thresholdStates.LOCKED_IN; - break; + block = await this.getPrevious(block); + assert(block); } - block = await this.getPrevious(block); - assert(block); + break; + } + case thresholdStates.LOCKED_IN: { + state = thresholdStates.ACTIVE; + break; + } + case thresholdStates.FAILED: + case thresholdStates.ACTIVE: { + break; + } + default: { + assert(false, 'Bad state.'); + break; } - - break; - } - case thresholdStates.LOCKED_IN: { - state = thresholdStates.ACTIVE; - break; - } - case thresholdStates.FAILED: - case thresholdStates.ACTIVE: { - break; - } - default: { - assert(false, 'Bad state.'); - break; } + + this.db.stateCache.set(bit, entry, state); } - this.db.stateCache.set(bit, entry, state); + return state; } - return state; -}; - -/** - * Compute the version for a new block (BIP9: versionbits). - * @see https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki - * @param {ChainEntry} prev - Previous chain entry (usually the tip). - * @returns {Promise} - Returns Number. - */ + /** + * Compute the version for a new block (BIP9: versionbits). + * @see https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki + * @param {ChainEntry} prev - Previous chain entry (usually the tip). + * @returns {Promise} - Returns Number. + */ -Chain.prototype.computeBlockVersion = async function computeBlockVersion(prev) { - let version = 0; + async computeBlockVersion(prev) { + let version = 0; - for (const deployment of this.network.deploys) { - const state = await this.getState(prev, deployment); + for (const deployment of this.network.deploys) { + const state = await this.getState(prev, deployment); - if (state === thresholdStates.LOCKED_IN - || state === thresholdStates.STARTED) { - version |= 1 << deployment.bit; + if (state === thresholdStates.LOCKED_IN + || state === thresholdStates.STARTED) { + version |= 1 << deployment.bit; + } } + + version |= consensus.VERSION_TOP_BITS; + version >>>= 0; + + return version; } - version |= consensus.VERSION_TOP_BITS; - version >>>= 0; + /** + * Get the current deployment state of the chain. Called on load. + * @private + * @returns {Promise} - Returns {@link DeploymentState}. + */ - return version; -}; + async getDeploymentState() { + const prev = await this.getPrevious(this.tip); -/** - * Get the current deployment state of the chain. Called on load. - * @private - * @returns {Promise} - Returns {@link DeploymentState}. - */ + if (!prev) { + assert(this.tip.isGenesis()); + return this.state; + } -Chain.prototype.getDeploymentState = async function getDeploymentState() { - const prev = await this.getPrevious(this.tip); + if (this.options.spv) + return this.state; - if (!prev) { - assert(this.tip.isGenesis()); - return this.state; + return this.getDeployments(this.tip.time, prev); } - if (this.options.spv) - return this.state; + /** + * Check transaction finality, taking into account MEDIAN_TIME_PAST + * if it is present in the lock flags. + * @param {ChainEntry} prev - Previous chain entry. + * @param {TX} tx + * @param {LockFlags} flags + * @returns {Promise} - Returns Boolean. + */ - return await this.getDeployments(this.tip.time, prev); -}; - -/** - * Check transaction finality, taking into account MEDIAN_TIME_PAST - * if it is present in the lock flags. - * @param {ChainEntry} prev - Previous chain entry. - * @param {TX} tx - * @param {LockFlags} flags - * @returns {Promise} - Returns Boolean. - */ + async verifyFinal(prev, tx, flags) { + const height = prev.height + 1; -Chain.prototype.verifyFinal = async function verifyFinal(prev, tx, flags) { - const height = prev.height + 1; + // We can skip MTP if the locktime is height. + if (tx.locktime < consensus.LOCKTIME_THRESHOLD) + return tx.isFinal(height, -1); - // We can skip MTP if the locktime is height. - if (tx.locktime < consensus.LOCKTIME_THRESHOLD) - return tx.isFinal(height, -1); + if (flags & common.lockFlags.MEDIAN_TIME_PAST) { + const time = await this.getMedianTime(prev); + return tx.isFinal(height, time); + } - if (flags & common.lockFlags.MEDIAN_TIME_PAST) { - const time = await this.getMedianTime(prev); - return tx.isFinal(height, time); + return tx.isFinal(height, this.network.now()); } - return tx.isFinal(height, this.network.now()); -}; + /** + * Get the necessary minimum time and height sequence locks for a transaction. + * @param {ChainEntry} prev + * @param {TX} tx + * @param {CoinView} view + * @param {LockFlags} flags + * @returns {Promise} + */ -/** - * Get the necessary minimum time and height sequence locks for a transaction. - * @param {ChainEntry} prev - * @param {TX} tx - * @param {CoinView} view - * @param {LockFlags} flags - * @returns {Promise} - */ + async getLocks(prev, tx, view, flags) { + const GRANULARITY = consensus.SEQUENCE_GRANULARITY; + const DISABLE_FLAG = consensus.SEQUENCE_DISABLE_FLAG; + const TYPE_FLAG = consensus.SEQUENCE_TYPE_FLAG; + const MASK = consensus.SEQUENCE_MASK; -Chain.prototype.getLocks = async function getLocks(prev, tx, view, flags) { - const GRANULARITY = consensus.SEQUENCE_GRANULARITY; - const DISABLE_FLAG = consensus.SEQUENCE_DISABLE_FLAG; - const TYPE_FLAG = consensus.SEQUENCE_TYPE_FLAG; - const MASK = consensus.SEQUENCE_MASK; + if (!(flags & common.lockFlags.VERIFY_SEQUENCE)) + return [-1, -1]; - if (!(flags & common.lockFlags.VERIFY_SEQUENCE)) - return [-1, -1]; + if (tx.isCoinbase() || tx.version < 2) + return [-1, -1]; - if (tx.isCoinbase() || tx.version < 2) - return [-1, -1]; + let minHeight = -1; + let minTime = -1; - let minHeight = -1; - let minTime = -1; + for (const {prevout, sequence} of tx.inputs) { + if (sequence & DISABLE_FLAG) + continue; - for (const {prevout, sequence} of tx.inputs) { - if (sequence & DISABLE_FLAG) - continue; + let height = view.getHeight(prevout); - let height = view.getHeight(prevout); + if (height === -1) + height = this.height + 1; - if (height === -1) - height = this.height + 1; + if (!(sequence & TYPE_FLAG)) { + height += (sequence & MASK) - 1; + minHeight = Math.max(minHeight, height); + continue; + } - if (!(sequence & TYPE_FLAG)) { - height += (sequence & MASK) - 1; - minHeight = Math.max(minHeight, height); - continue; - } + height = Math.max(height - 1, 0); - height = Math.max(height - 1, 0); + const entry = await this.getAncestor(prev, height); + assert(entry, 'Database is corrupt.'); - const entry = await this.getAncestor(prev, height); - assert(entry, 'Database is corrupt.'); + let time = await this.getMedianTime(entry); + time += ((sequence & MASK) << GRANULARITY) - 1; + minTime = Math.max(minTime, time); + } - let time = await this.getMedianTime(entry); - time += ((sequence & MASK) << GRANULARITY) - 1; - minTime = Math.max(minTime, time); + return [minHeight, minTime]; } - return [minHeight, minTime]; -}; + /** + * Verify sequence locks. + * @param {ChainEntry} prev + * @param {TX} tx + * @param {CoinView} view + * @param {LockFlags} flags + * @returns {Promise} - Returns Boolean. + */ -/** - * Verify sequence locks. - * @param {ChainEntry} prev - * @param {TX} tx - * @param {CoinView} view - * @param {LockFlags} flags - * @returns {Promise} - Returns Boolean. - */ + async verifyLocks(prev, tx, view, flags) { + const [height, time] = await this.getLocks(prev, tx, view, flags); -Chain.prototype.verifyLocks = async function verifyLocks(prev, tx, view, flags) { - const [height, time] = await this.getLocks(prev, tx, view, flags); + if (height !== -1) { + if (height >= prev.height + 1) + return false; + } - if (height !== -1) { - if (height >= prev.height + 1) - return false; - } + if (time !== -1) { + const mtp = await this.getMedianTime(prev); - if (time !== -1) { - const mtp = await this.getMedianTime(prev); + if (time >= mtp) + return false; + } - if (time >= mtp) - return false; + return true; } - - return true; -}; +} /** * ChainOptions * @alias module:blockchain.ChainOptions - * @constructor - * @param {Object} options */ -function ChainOptions(options) { - if (!(this instanceof ChainOptions)) - return new ChainOptions(options); - - this.network = Network.primary; - this.logger = Logger.global; - this.workers = null; - - this.prefix = null; - this.location = null; - this.db = 'memory'; - this.maxFiles = 64; - this.cacheSize = 32 << 20; - this.compression = true; - this.bufferKeys = ChainDB.layout.binary; - - this.spv = false; - this.bip91 = false; - this.bip148 = false; - this.prune = false; - this.indexTX = false; - this.indexAddress = false; - this.forceFlags = false; - - this.coinCache = 0; - this.entryCache = 5000; - this.maxOrphans = 20; - this.checkpoints = true; - - if (options) - this.fromOptions(options); -} +class ChainOptions { + /** + * Create chain options. + * @constructor + * @param {Object} options + */ + + constructor(options) { + this.network = Network.primary; + this.logger = Logger.global; + this.blocks = null; + this.workers = null; + + this.prefix = null; + this.location = null; + this.memory = true; + this.maxFiles = 64; + this.cacheSize = 32 << 20; + this.compression = true; + + this.spv = false; + this.bip91 = false; + this.bip148 = false; + this.prune = false; + this.forceFlags = false; + + this.entryCache = 5000; + this.maxOrphans = 20; + this.checkpoints = true; + + if (options) + this.fromOptions(options); + } + + /** + * Inject properties from object. + * @private + * @param {Object} options + * @returns {ChainOptions} + */ + + fromOptions(options) { + if (!options.spv) { + assert(options.blocks && typeof options.blocks === 'object', + 'Chain requires a blockstore.'); + } -/** - * Inject properties from object. - * @private - * @param {Object} options - * @returns {ChainOptions} - */ + this.blocks = options.blocks; -ChainOptions.prototype.fromOptions = function fromOptions(options) { - if (options.network != null) - this.network = Network.get(options.network); + if (options.network != null) + this.network = Network.get(options.network); - if (options.logger != null) { - assert(typeof options.logger === 'object'); - this.logger = options.logger; - } + if (options.logger != null) { + assert(typeof options.logger === 'object'); + this.logger = options.logger; + } - if (options.workers != null) { - assert(typeof options.workers === 'object'); - this.workers = options.workers; - } + if (options.workers != null) { + assert(typeof options.workers === 'object'); + this.workers = options.workers; + } - if (options.spv != null) { - assert(typeof options.spv === 'boolean'); - this.spv = options.spv; - } + if (options.spv != null) { + assert(typeof options.spv === 'boolean'); + this.spv = options.spv; + } - if (options.prefix != null) { - assert(typeof options.prefix === 'string'); - this.prefix = options.prefix; - this.location = this.spv - ? path.join(this.prefix, 'spvchain') - : path.join(this.prefix, 'chain'); - } + if (options.prefix != null) { + assert(typeof options.prefix === 'string'); + this.prefix = options.prefix; + this.location = this.spv + ? path.join(this.prefix, 'spvchain') + : path.join(this.prefix, 'chain'); + } - if (options.location != null) { - assert(typeof options.location === 'string'); - this.location = options.location; - } + if (options.location != null) { + assert(typeof options.location === 'string'); + this.location = options.location; + } - if (options.db != null) { - assert(typeof options.db === 'string'); - this.db = options.db; - } + if (options.memory != null) { + assert(typeof options.memory === 'boolean'); + this.memory = options.memory; + } - if (options.maxFiles != null) { - assert(util.isU32(options.maxFiles)); - this.maxFiles = options.maxFiles; - } + if (options.maxFiles != null) { + assert((options.maxFiles >>> 0) === options.maxFiles); + this.maxFiles = options.maxFiles; + } - if (options.cacheSize != null) { - assert(util.isU64(options.cacheSize)); - this.cacheSize = options.cacheSize; - } + if (options.cacheSize != null) { + assert(Number.isSafeInteger(options.cacheSize)); + assert(options.cacheSize >= 0); + this.cacheSize = options.cacheSize; + } - if (options.compression != null) { - assert(typeof options.compression === 'boolean'); - this.compression = options.compression; - } + if (options.compression != null) { + assert(typeof options.compression === 'boolean'); + this.compression = options.compression; + } - if (options.prune != null) { - assert(typeof options.prune === 'boolean'); - this.prune = options.prune; - } + if (options.prune != null) { + assert(typeof options.prune === 'boolean'); + this.prune = options.prune; + } - if (options.indexTX != null) { - assert(typeof options.indexTX === 'boolean'); - this.indexTX = options.indexTX; - } + if (options.forceFlags != null) { + assert(typeof options.forceFlags === 'boolean'); + this.forceFlags = options.forceFlags; + } - if (options.indexAddress != null) { - assert(typeof options.indexAddress === 'boolean'); - this.indexAddress = options.indexAddress; - } + if (options.bip91 != null) { + assert(typeof options.bip91 === 'boolean'); + this.bip91 = options.bip91; + } - if (options.forceFlags != null) { - assert(typeof options.forceFlags === 'boolean'); - this.forceFlags = options.forceFlags; - } + if (options.bip148 != null) { + assert(typeof options.bip148 === 'boolean'); + this.bip148 = options.bip148; + } - if (options.bip91 != null) { - assert(typeof options.bip91 === 'boolean'); - this.bip91 = options.bip91; - } + if (options.entryCache != null) { + assert((options.entryCache >>> 0) === options.entryCache); + this.entryCache = options.entryCache; + } - if (options.bip148 != null) { - assert(typeof options.bip148 === 'boolean'); - this.bip148 = options.bip148; - } + if (options.maxOrphans != null) { + assert((options.maxOrphans >>> 0) === options.maxOrphans); + this.maxOrphans = options.maxOrphans; + } - if (options.coinCache != null) { - assert(util.isU64(options.coinCache)); - this.coinCache = options.coinCache; - } + if (options.checkpoints != null) { + assert(typeof options.checkpoints === 'boolean'); + this.checkpoints = options.checkpoints; + } - if (options.entryCache != null) { - assert(util.isU32(options.entryCache)); - this.entryCache = options.entryCache; + return this; } - if (options.maxOrphans != null) { - assert(util.isU32(options.maxOrphans)); - this.maxOrphans = options.maxOrphans; - } + /** + * Instantiate chain options from object. + * @param {Object} options + * @returns {ChainOptions} + */ - if (options.checkpoints != null) { - assert(typeof options.checkpoints === 'boolean'); - this.checkpoints = options.checkpoints; + static fromOptions(options) { + return new ChainOptions().fromOptions(options); } - - return this; -}; - -/** - * Instantiate chain options from object. - * @param {Object} options - * @returns {ChainOptions} - */ - -ChainOptions.fromOptions = function fromOptions(options) { - return new ChainOptions().fromOptions(options); -}; +} /** - * Represents the deployment state of the chain. + * Deployment State * @alias module:blockchain.DeploymentState - * @constructor * @property {VerifyFlags} flags * @property {LockFlags} lockFlags * @property {Boolean} bip34 */ -function DeploymentState() { - if (!(this instanceof DeploymentState)) - return new DeploymentState(); +class DeploymentState { + /** + * Create a deployment state. + * @constructor + */ - this.flags = Script.flags.MANDATORY_VERIFY_FLAGS; - this.flags &= ~Script.flags.VERIFY_P2SH; - this.lockFlags = common.lockFlags.MANDATORY_LOCKTIME_FLAGS; - this.bip34 = false; - this.bip91 = false; - this.bip148 = false; -} + constructor() { + this.flags = Script.flags.MANDATORY_VERIFY_FLAGS; + this.flags &= ~Script.flags.VERIFY_P2SH; + this.lockFlags = common.lockFlags.MANDATORY_LOCKTIME_FLAGS; + this.bip34 = false; + this.bip91 = false; + this.bip148 = false; + } -/** - * Test whether p2sh is active. - * @returns {Boolean} - */ + /** + * Test whether p2sh is active. + * @returns {Boolean} + */ -DeploymentState.prototype.hasP2SH = function hasP2SH() { - return (this.flags & Script.flags.VERIFY_P2SH) !== 0; -}; + hasP2SH() { + return (this.flags & Script.flags.VERIFY_P2SH) !== 0; + } -/** - * Test whether bip34 (coinbase height) is active. - * @returns {Boolean} - */ + /** + * Test whether bip34 (coinbase height) is active. + * @returns {Boolean} + */ -DeploymentState.prototype.hasBIP34 = function hasBIP34() { - return this.bip34; -}; + hasBIP34() { + return this.bip34; + } -/** - * Test whether bip66 (VERIFY_DERSIG) is active. - * @returns {Boolean} - */ + /** + * Test whether bip66 (VERIFY_DERSIG) is active. + * @returns {Boolean} + */ -DeploymentState.prototype.hasBIP66 = function hasBIP66() { - return (this.flags & Script.flags.VERIFY_DERSIG) !== 0; -}; + hasBIP66() { + return (this.flags & Script.flags.VERIFY_DERSIG) !== 0; + } -/** - * Test whether cltv is active. - * @returns {Boolean} - */ + /** + * Test whether cltv is active. + * @returns {Boolean} + */ -DeploymentState.prototype.hasCLTV = function hasCLTV() { - return (this.flags & Script.flags.VERIFY_CHECKLOCKTIMEVERIFY) !== 0; -}; + hasCLTV() { + return (this.flags & Script.flags.VERIFY_CHECKLOCKTIMEVERIFY) !== 0; + } -/** - * Test whether median time past locktime is active. - * @returns {Boolean} - */ + /** + * Test whether median time past locktime is active. + * @returns {Boolean} + */ -DeploymentState.prototype.hasMTP = function hasMTP() { - return (this.lockFlags & common.lockFlags.MEDIAN_TIME_PAST) !== 0; -}; + hasMTP() { + return (this.lockFlags & common.lockFlags.MEDIAN_TIME_PAST) !== 0; + } -/** - * Test whether csv is active. - * @returns {Boolean} - */ + /** + * Test whether csv is active. + * @returns {Boolean} + */ -DeploymentState.prototype.hasCSV = function hasCSV() { - return (this.flags & Script.flags.VERIFY_CHECKSEQUENCEVERIFY) !== 0; -}; + hasCSV() { + return (this.flags & Script.flags.VERIFY_CHECKSEQUENCEVERIFY) !== 0; + } -/** - * Test whether segwit is active. - * @returns {Boolean} - */ + /** + * Test whether segwit is active. + * @returns {Boolean} + */ -DeploymentState.prototype.hasWitness = function hasWitness() { - return (this.flags & Script.flags.VERIFY_WITNESS) !== 0; -}; + hasWitness() { + return (this.flags & Script.flags.VERIFY_WITNESS) !== 0; + } -/** - * Test whether bip91 is active. - * @returns {Boolean} - */ + /** + * Test whether bip91 is active. + * @returns {Boolean} + */ -DeploymentState.prototype.hasBIP91 = function hasBIP91() { - return this.bip91; -}; + hasBIP91() { + return this.bip91; + } -/** - * Test whether bip148 is active. - * @returns {Boolean} - */ + /** + * Test whether bip148 is active. + * @returns {Boolean} + */ -DeploymentState.prototype.hasBIP148 = function hasBIP148() { - return this.bip148; -}; + hasBIP148() { + return this.bip148; + } +} /** * Orphan - * @constructor * @ignore */ -function Orphan(block, flags, id) { - this.block = block; - this.flags = flags; - this.id = id; - this.time = util.now(); +class Orphan { + /** + * Create an orphan. + * @constructor + */ + + constructor(block, flags, id) { + this.block = block; + this.flags = flags; + this.id = id; + this.time = util.now(); + } } /* diff --git a/lib/blockchain/chaindb.js b/lib/blockchain/chaindb.js index 322af916c..f42862bd9 100644 --- a/lib/blockchain/chaindb.js +++ b/lib/blockchain/chaindb.js @@ -7,2267 +7,1946 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); +const assert = require('bsert'); +const bdb = require('bdb'); +const bio = require('bufio'); +const LRU = require('blru'); +const {BufferMap} = require('buffer-map'); const Amount = require('../btc/amount'); -const encoding = require('../utils/encoding'); const Network = require('../protocol/network'); const CoinView = require('../coins/coinview'); const UndoCoins = require('../coins/undocoins'); -const LDB = require('../db/ldb'); const layout = require('./layout'); -const LRU = require('../utils/lru'); +const consensus = require('../protocol/consensus'); const Block = require('../primitives/block'); const Outpoint = require('../primitives/outpoint'); -const Address = require('../primitives/address'); const ChainEntry = require('./chainentry'); -const TXMeta = require('../primitives/txmeta'); const CoinEntry = require('../coins/coinentry'); -const U8 = encoding.U8; -const U32 = encoding.U32; /** - * The database backend for the {@link Chain} object. + * ChainDB * @alias module:blockchain.ChainDB - * @constructor - * @param {Boolean?} options.prune - Whether to prune the chain. - * @param {Boolean?} options.spv - SPV-mode, will not save block - * data, only entries. - * @param {String?} options.name - Database name - * @param {String?} options.location - Database location - * @param {String?} options.db - Database backend name - * @property {Boolean} prune - * @emits ChainDB#open - * @emits ChainDB#error */ -function ChainDB(options) { - if (!(this instanceof ChainDB)) - return new ChainDB(options); +class ChainDB { + /** + * Create a chaindb. + * @constructor + */ - this.options = options; - this.network = this.options.network; - this.logger = this.options.logger.context('chaindb'); + constructor(options) { + this.options = options; + this.network = this.options.network; + this.logger = this.options.logger.context('chaindb'); + this.blocks = this.options.blocks; - this.db = LDB(this.options); - this.stateCache = new StateCache(this.network); - this.state = new ChainState(); - this.pending = null; - this.current = null; + this.db = bdb.create(this.options); - this.coinCache = new LRU(this.options.coinCache, getSize); - this.cacheHash = new LRU(this.options.entryCache); - this.cacheHeight = new LRU(this.options.entryCache); -} + this.stateCache = new StateCache(this.network); + this.state = new ChainState(); + this.pending = null; + this.current = null; -/** - * Database layout. - * @type {Object} - */ + this.cacheHash = new LRU(this.options.entryCache, null, BufferMap); + this.cacheHeight = new LRU(this.options.entryCache); + } -ChainDB.layout = layout; + /** + * Open and wait for the database to load. + * @returns {Promise} + */ -/** - * Open the chain db, wait for the database to load. - * @returns {Promise} - */ + async open() { + this.logger.info('Opening ChainDB...'); -ChainDB.prototype.open = async function open() { - this.logger.info('Opening ChainDB...'); + await this.db.open(); + await this.db.verify(layout.V.encode(), 'chain', 6); - await this.db.open(); - await this.db.checkVersion('V', 3); + const state = await this.getState(); - const state = await this.getState(); + if (state) { + // Verify options have not changed. + await this.verifyFlags(state); - if (state) { - // Verify options have not changed. - await this.verifyFlags(state); + // Verify deployment params have not changed. + await this.verifyDeployments(); - // Verify deployment params have not changed. - await this.verifyDeployments(); + // Load state caches. + this.stateCache = await this.getStateCache(); - // Load state caches. - this.stateCache = await this.getStateCache(); + // Grab the chainstate if we have one. + this.state = state; - // Grab the chainstate if we have one. - this.state = state; + this.logger.info('ChainDB successfully loaded.'); + } else { + // Database is fresh. + // Write initial state. + await this.saveFlags(); + await this.saveDeployments(); + await this.saveGenesis(); - this.logger.info('ChainDB successfully loaded.'); - } else { - // Database is fresh. - // Write initial state. - await this.saveFlags(); - await this.saveDeployments(); - await this.saveGenesis(); + this.logger.info('ChainDB successfully initialized.'); + } - this.logger.info('ChainDB successfully initialized.'); + this.logger.info( + 'Chain State: hash=%h tx=%d coin=%d value=%s.', + this.state.tip, + this.state.tx, + this.state.coin, + Amount.btc(this.state.value)); } - this.logger.info( - 'Chain State: hash=%s tx=%d coin=%d value=%s.', - this.state.rhash(), - this.state.tx, - this.state.coin, - Amount.btc(this.state.value)); -}; + /** + * Close and wait for the database to close. + * @returns {Promise} + */ -/** - * Close the chain db, wait for the database to close. - * @returns {Promise} - */ + async close() { + return this.db.close(); + } -ChainDB.prototype.close = function close() { - return this.db.close(); -}; + /** + * Start a batch. + * @returns {Batch} + */ -/** - * Start a batch. - * @returns {Batch} - */ + start() { + assert(!this.current); + assert(!this.pending); -ChainDB.prototype.start = function start() { - assert(!this.current); - assert(!this.pending); + this.current = this.db.batch(); + this.pending = this.state.clone(); - this.current = this.db.batch(); - this.pending = this.state.clone(); + this.cacheHash.start(); + this.cacheHeight.start(); - this.coinCache.start(); - this.cacheHash.start(); - this.cacheHeight.start(); + return this.current; + } - return this.current; -}; + /** + * Put key and value to current batch. + * @param {String} key + * @param {Buffer} value + */ -/** - * Put key and value to current batch. - * @param {String} key - * @param {Buffer} value - */ + put(key, value) { + assert(this.current); + this.current.put(key, value); + } -ChainDB.prototype.put = function put(key, value) { - assert(this.current); - this.current.put(key, value); -}; + /** + * Delete key from current batch. + * @param {String} key + */ -/** - * Delete key from current batch. - * @param {String} key - */ + del(key) { + assert(this.current); + this.current.del(key); + } -ChainDB.prototype.del = function del(key) { - assert(this.current); - this.current.del(key); -}; + /** + * Get current batch. + * @returns {Batch} + */ -/** - * Get current batch. - * @returns {Batch} - */ + batch() { + assert(this.current); + return this.current; + } -ChainDB.prototype.batch = function batch() { - assert(this.current); - return this.current; -}; + /** + * Drop current batch. + * @returns {Batch} + */ -/** - * Drop current batch. - * @returns {Batch} - */ + drop() { + const batch = this.current; -ChainDB.prototype.drop = function drop() { - const batch = this.current; + assert(this.current); + assert(this.pending); - assert(this.current); - assert(this.pending); + this.current = null; + this.pending = null; + + this.cacheHash.drop(); + this.cacheHeight.drop(); + this.stateCache.drop(); - this.current = null; - this.pending = null; + batch.clear(); + } - this.coinCache.drop(); - this.cacheHash.drop(); - this.cacheHeight.drop(); - this.stateCache.drop(); + /** + * Commit current batch. + * @returns {Promise} + */ - batch.clear(); -}; + async commit() { + assert(this.current); + assert(this.pending); -/** - * Commit current batch. - * @returns {Promise} - */ + try { + await this.current.write(); + } catch (e) { + this.current = null; + this.pending = null; + this.cacheHash.drop(); + this.cacheHeight.drop(); + throw e; + } -ChainDB.prototype.commit = async function commit() { - assert(this.current); - assert(this.pending); + // Overwrite the entire state + // with our new best state + // only if it is committed. + // Note that alternate chain + // tips do not commit anything. + if (this.pending.committed) + this.state = this.pending; - try { - await this.current.write(); - } catch (e) { this.current = null; this.pending = null; - this.coinCache.drop(); - this.cacheHash.drop(); - this.cacheHeight.drop(); - throw e; - } - // Overwrite the entire state - // with our new best state - // only if it is committed. - // Note that alternate chain - // tips do not commit anything. - if (this.pending.committed) - this.state = this.pending; + this.cacheHash.commit(); + this.cacheHeight.commit(); + this.stateCache.commit(); + } - this.current = null; - this.pending = null; + /** + * Test the cache for a present entry hash or height. + * @param {Hash|Number} block - Hash or height. + */ - this.coinCache.commit(); - this.cacheHash.commit(); - this.cacheHeight.commit(); - this.stateCache.commit(); -}; + hasCache(block) { + if (typeof block === 'number') + return this.cacheHeight.has(block); -/** - * Test the cache for a present entry hash or height. - * @param {Hash|Number} block - Hash or height. - */ + assert(Buffer.isBuffer(block)); -ChainDB.prototype.hasCache = function hasCache(block) { - if (typeof block === 'number') - return this.cacheHeight.has(block); + return this.cacheHash.has(block); + } - assert(typeof block === 'string'); + /** + * Get an entry directly from the LRU cache. + * @param {Hash|Number} block - Hash or height. + */ - return this.cacheHash.has(block); -}; + getCache(block) { + if (typeof block === 'number') + return this.cacheHeight.get(block); -/** - * Get an entry directly from the LRU cache. - * @param {Hash|Number} block - Hash or height. - */ + assert(Buffer.isBuffer(block)); -ChainDB.prototype.getCache = function getCache(block) { - if (typeof block === 'number') - return this.cacheHeight.get(block); + return this.cacheHash.get(block); + } - assert(typeof block === 'string'); + /** + * Get the height of a block by hash. + * @param {Hash} hash + * @returns {Promise} - Returns Number. + */ - return this.cacheHash.get(block); -}; + async getHeight(hash) { + if (typeof hash === 'number') + return hash; -/** - * Get the height of a block by hash. - * @param {Hash} hash - * @returns {Promise} - Returns Number. - */ + assert(Buffer.isBuffer(hash)); -ChainDB.prototype.getHeight = async function getHeight(hash) { - if (typeof hash === 'number') - return hash; + if (hash.equals(consensus.ZERO_HASH)) + return -1; - assert(typeof hash === 'string'); + const entry = this.cacheHash.get(hash); - if (hash === encoding.NULL_HASH) - return -1; + if (entry) + return entry.height; - const entry = this.cacheHash.get(hash); + const height = await this.db.get(layout.h.encode(hash)); - if (entry) - return entry.height; + if (!height) + return -1; - const height = await this.db.get(layout.h(hash)); + return height.readUInt32LE(0, true); + } - if (!height) - return -1; + /** + * Get the hash of a block by height. Note that this + * will only return hashes in the main chain. + * @param {Number} height + * @returns {Promise} - Returns {@link Hash}. + */ - return height.readUInt32LE(0, true); -}; + async getHash(height) { + if (Buffer.isBuffer(height)) + return height; -/** - * Get the hash of a block by height. Note that this - * will only return hashes in the main chain. - * @param {Number} height - * @returns {Promise} - Returns {@link Hash}. - */ + assert(typeof height === 'number'); -ChainDB.prototype.getHash = async function getHash(height) { - if (typeof height === 'string') - return height; + if (height < 0) + return null; - assert(typeof height === 'number'); + const entry = this.cacheHeight.get(height); - if (height < 0) - return null; + if (entry) + return entry.hash; - const entry = this.cacheHeight.get(height); + return this.db.get(layout.H.encode(height)); + } - if (entry) - return entry.hash; + /** + * Retrieve a chain entry by height. + * @param {Number} height + * @returns {Promise} - Returns {@link ChainEntry}. + */ - const hash = await this.db.get(layout.H(height)); + async getEntryByHeight(height) { + assert(typeof height === 'number'); - if (!hash) - return null; + if (height < 0) + return null; - return hash.toString('hex'); -}; + const cache = this.cacheHeight.get(height); -/** - * Retrieve a chain entry by height. - * @param {Number} height - * @returns {Promise} - Returns {@link ChainEntry}. - */ + if (cache) + return cache; -ChainDB.prototype.getEntryByHeight = async function getEntryByHeight(height) { - assert(typeof height === 'number'); + const hash = await this.db.get(layout.H.encode(height)); - if (height < 0) - return null; + if (!hash) + return null; - const cache = this.cacheHeight.get(height); + const state = this.state; + const entry = await this.getEntryByHash(hash); - if (cache) - return cache; + if (!entry) + return null; - const data = await this.db.get(layout.H(height)); + // By the time getEntry has completed, + // a reorg may have occurred. This entry + // may not be on the main chain anymore. + if (this.state === state) + this.cacheHeight.set(entry.height, entry); - if (!data) - return null; + return entry; + } - const hash = data.toString('hex'); + /** + * Retrieve a chain entry by hash. + * @param {Hash} hash + * @returns {Promise} - Returns {@link ChainEntry}. + */ - const state = this.state; - const entry = await this.getEntryByHash(hash); + async getEntryByHash(hash) { + assert(Buffer.isBuffer(hash)); - if (!entry) - return null; + if (hash.equals(consensus.ZERO_HASH)) + return null; - // By the time getEntry has completed, - // a reorg may have occurred. This entry - // may not be on the main chain anymore. - if (this.state === state) - this.cacheHeight.set(entry.height, entry); + const cache = this.cacheHash.get(hash); - return entry; -}; + if (cache) + return cache; -/** - * Retrieve a chain entry by hash. - * @param {Hash} hash - * @returns {Promise} - Returns {@link ChainEntry}. - */ + const raw = await this.db.get(layout.e.encode(hash)); -ChainDB.prototype.getEntryByHash = async function getEntryByHash(hash) { - assert(typeof hash === 'string'); + if (!raw) + return null; - if (hash === encoding.NULL_HASH) - return null; + const entry = ChainEntry.fromRaw(raw); - const cache = this.cacheHash.get(hash); + // There's no efficient way to check whether + // this is in the main chain or not, so + // don't add it to the height cache. + this.cacheHash.set(entry.hash, entry); - if (cache) - return cache; + return entry; + } - const raw = await this.db.get(layout.e(hash)); + /** + * Retrieve a chain entry. + * @param {Number|Hash} block - Height or hash. + * @returns {Promise} - Returns {@link ChainEntry}. + */ - if (!raw) - return null; + getEntry(block) { + if (typeof block === 'number') + return this.getEntryByHeight(block); + return this.getEntryByHash(block); + } - const entry = ChainEntry.fromRaw(raw); + /** + * Test whether the chain contains a block. + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ - // There's no efficient way to check whether - // this is in the main chain or not, so - // don't add it to the height cache. - this.cacheHash.set(entry.hash, entry); + async hasEntry(hash) { + const height = await this.getHeight(hash); + return height !== -1; + } - return entry; -}; + /** + * Get ancestor by `height`. + * @param {ChainEntry} entry + * @param {Number} height + * @returns {Promise} - Returns ChainEntry. + */ -/** - * Retrieve a chain entry. - * @param {Number|Hash} block - Height or hash. - * @returns {Promise} - Returns {@link ChainEntry}. - */ + async getAncestor(entry, height) { + if (height < 0) + return null; -ChainDB.prototype.getEntry = function getEntry(block) { - if (typeof block === 'number') - return this.getEntryByHeight(block); - return this.getEntryByHash(block); -}; + assert(height >= 0); + assert(height <= entry.height); -/** - * Test whether the chain contains a block. - * @param {Hash} hash - * @returns {Promise} - Returns Boolean. - */ + if (await this.isMainChain(entry)) + return this.getEntryByHeight(height); -ChainDB.prototype.hasEntry = async function hasEntry(hash) { - const height = await this.getHeight(hash); - return height !== -1; -}; + while (entry.height !== height) { + const cache = this.getPrevCache(entry); -/** - * Get ancestor by `height`. - * @param {ChainEntry} entry - * @param {Number} height - * @returns {Promise} - Returns ChainEntry. - */ + if (cache) + entry = cache; + else + entry = await this.getPrevious(entry); -ChainDB.prototype.getAncestor = async function getAncestor(entry, height) { - if (height < 0) - return null; + assert(entry); + } - assert(height >= 0); - assert(height <= entry.height); + return entry; + } - if (await this.isMainChain(entry)) - return await this.getEntryByHeight(height); + /** + * Get previous entry. + * @param {ChainEntry} entry + * @returns {Promise} - Returns ChainEntry. + */ - while (entry.height !== height) { - const cache = this.getPrevCache(entry); + getPrevious(entry) { + return this.getEntryByHash(entry.prevBlock); + } - if (cache) - entry = cache; - else - entry = await this.getPrevious(entry); + /** + * Get previous cached entry. + * @param {ChainEntry} entry + * @returns {ChainEntry|null} + */ - assert(entry); + getPrevCache(entry) { + return this.cacheHash.get(entry.prevBlock) || null; } - return entry; -}; - -/** - * Get previous entry. - * @param {ChainEntry} entry - * @returns {Promise} - Returns ChainEntry. - */ + /** + * Get next entry. + * @param {ChainEntry} entry + * @returns {Promise} - Returns ChainEntry. + */ -ChainDB.prototype.getPrevious = function getPrevious(entry) { - return this.getEntryByHash(entry.prevBlock); -}; + async getNext(entry) { + const hash = await this.getNextHash(entry.hash); -/** - * Get previous cached entry. - * @param {ChainEntry} entry - * @returns {ChainEntry|null} - */ + if (!hash) + return null; -ChainDB.prototype.getPrevCache = function getPrevCache(entry) { - return this.cacheHash.get(entry.prevBlock) || null; -}; + return this.getEntryByHash(hash); + } -/** - * Get next entry. - * @param {ChainEntry} entry - * @returns {Promise} - Returns ChainEntry. - */ + /** + * Get next entry. + * @param {ChainEntry} entry + * @returns {Promise} - Returns ChainEntry. + */ -ChainDB.prototype.getNext = async function getNext(entry) { - const hash = await this.getNextHash(entry.hash); + async getNextEntry(entry) { + const next = await this.getEntryByHeight(entry.height + 1); - if (!hash) - return null; + if (!next) + return null; - return await this.getEntryByHash(hash); -}; + // Not on main chain. + if (!next.prevBlock.equals(entry.hash)) + return null; -/** - * Get next entry. - * @param {ChainEntry} entry - * @returns {Promise} - Returns ChainEntry. - */ + return next; + } -ChainDB.prototype.getNextEntry = async function getNextEntry(entry) { - const next = await this.getEntryByHeight(entry.height + 1); + /** + * Retrieve the tip entry from the tip record. + * @returns {Promise} - Returns {@link ChainEntry}. + */ - if (!next) - return null; + getTip() { + return this.getEntryByHash(this.state.tip); + } - // Not on main chain. - if (next.prevBlock !== entry.hash) - return null; + /** + * Retrieve the tip entry from the tip record. + * @returns {Promise} - Returns {@link ChainState}. + */ - return next; -}; + async getState() { + const data = await this.db.get(layout.R.encode()); -/** - * Retrieve the tip entry from the tip record. - * @returns {Promise} - Returns {@link ChainEntry}. - */ + if (!data) + return null; -ChainDB.prototype.getTip = function getTip() { - return this.getEntryByHash(this.state.tip); -}; + return ChainState.fromRaw(data); + } -/** - * Retrieve the tip entry from the tip record. - * @returns {Promise} - Returns {@link ChainState}. - */ + /** + * Write genesis block to database. + * @returns {Promise} + */ -ChainDB.prototype.getState = async function getState() { - const data = await this.db.get(layout.R); + async saveGenesis() { + const genesis = this.network.genesisBlock; + const block = Block.fromRaw(genesis, 'hex'); + const entry = ChainEntry.fromBlock(block); - if (!data) - return null; + this.logger.info('Writing genesis block to ChainDB.'); - return ChainState.fromRaw(data); -}; + return this.save(entry, block, new CoinView()); + } -/** - * Write genesis block to database. - * @returns {Promise} - */ + /** + * Retrieve the database flags. + * @returns {Promise} - Returns {@link ChainFlags}. + */ -ChainDB.prototype.saveGenesis = async function saveGenesis() { - const genesis = this.network.genesisBlock; - const block = Block.fromRaw(genesis, 'hex'); - const entry = ChainEntry.fromBlock(block); + async getFlags() { + const data = await this.db.get(layout.O.encode()); - this.logger.info('Writing genesis block to ChainDB.'); + if (!data) + return null; - await this.save(entry, block, new CoinView()); -}; + return ChainFlags.fromRaw(data); + } -/** - * Retrieve the database flags. - * @returns {Promise} - Returns {@link ChainFlags}. - */ + /** + * Verify current options against db options. + * @param {ChainState} state + * @returns {Promise} + */ -ChainDB.prototype.getFlags = async function getFlags() { - const data = await this.db.get(layout.O); + async verifyFlags(state) { + const options = this.options; + const flags = await this.getFlags(); - if (!data) - return null; + let needsSave = false; + let needsPrune = false; - return ChainFlags.fromRaw(data); -}; + if (!flags) + throw new Error('No flags found.'); -/** - * Verify current options against db options. - * @param {ChainState} state - * @returns {Promise} - */ + if (options.network !== flags.network) + throw new Error('Network mismatch for chain.'); -ChainDB.prototype.verifyFlags = async function verifyFlags(state) { - const options = this.options; - const flags = await this.getFlags(); + if (options.spv && !flags.spv) + throw new Error('Cannot retroactively enable SPV.'); - let needsSave = false; - let needsPrune = false; + if (!options.spv && flags.spv) + throw new Error('Cannot retroactively disable SPV.'); - if (!flags) - throw new Error('No flags found.'); + if (!flags.witness) { + if (!options.forceFlags) + throw new Error('Cannot retroactively enable witness.'); + needsSave = true; + } - if (options.network !== flags.network) - throw new Error('Network mismatch for chain.'); + if (options.bip91 !== flags.bip91) { + if (!options.forceFlags) + throw new Error('Cannot retroactively alter BIP91 flag.'); + needsSave = true; + } - if (options.spv && !flags.spv) - throw new Error('Cannot retroactively enable SPV.'); + if (options.bip148 !== flags.bip148) { + if (!options.forceFlags) + throw new Error('Cannot retroactively alter BIP148 flag.'); + needsSave = true; + } - if (!options.spv && flags.spv) - throw new Error('Cannot retroactively disable SPV.'); + if (options.prune && !flags.prune) { + if (!options.forceFlags) + throw new Error('Cannot retroactively prune.'); + needsPrune = true; + } - if (!flags.witness) { - if (!options.forceFlags) - throw new Error('Cannot retroactively enable witness.'); - needsSave = true; - } + if (!options.prune && flags.prune) + throw new Error('Cannot retroactively unprune.'); - if (options.bip91 !== flags.bip91) { - if (!options.forceFlags) - throw new Error('Cannot retroactively alter BIP91 flag.'); - needsSave = true; - } + if (needsSave) { + await this.logger.info('Rewriting chain flags.'); + await this.saveFlags(); + } - if (options.bip148 !== flags.bip148) { - if (!options.forceFlags) - throw new Error('Cannot retroactively alter BIP148 flag.'); - needsSave = true; + if (needsPrune) { + await this.logger.info('Retroactively pruning chain.'); + await this.prune(state.tip); + } } - if (options.prune && !flags.prune) { - if (!options.forceFlags) - throw new Error('Cannot retroactively prune.'); - needsPrune = true; - } + /** + * Get state caches. + * @returns {Promise} - Returns {@link StateCache}. + */ - if (!options.prune && flags.prune) - throw new Error('Cannot retroactively unprune.'); + async getStateCache() { + const stateCache = new StateCache(this.network); - if (options.indexTX && !flags.indexTX) - throw new Error('Cannot retroactively enable TX indexing.'); + const items = await this.db.range({ + gte: layout.v.min(), + lte: layout.v.max(), + values: true + }); - if (!options.indexTX && flags.indexTX) - throw new Error('Cannot retroactively disable TX indexing.'); + for (const item of items) { + const [bit, hash] = layout.v.decode(item.key); + const state = item.value[0]; + stateCache.insert(bit, hash, state); + } - if (options.indexAddress && !flags.indexAddress) - throw new Error('Cannot retroactively enable address indexing.'); + return stateCache; + } - if (!options.indexAddress && flags.indexAddress) - throw new Error('Cannot retroactively disable address indexing.'); + /** + * Save deployment table. + * @returns {Promise} + */ - if (needsSave) { - await this.logger.info('Rewriting chain flags.'); - await this.saveFlags(); + saveDeployments() { + const b = this.db.batch(); + this.writeDeployments(b); + return b.write(); } - if (needsPrune) { - await this.logger.info('Retroactively pruning chain.'); - await this.prune(state.tip); - } -}; + /** + * Save deployment table. + * @returns {Promise} + */ -/** - * Get state caches. - * @returns {Promise} - Returns {@link StateCache}. - */ + writeDeployments(b) { + const bw = bio.write(1 + 21 * this.network.deploys.length); -ChainDB.prototype.getStateCache = async function getStateCache() { - const stateCache = new StateCache(this.network); + bw.writeU8(this.network.deploys.length); - const items = await this.db.range({ - gte: layout.v(0, encoding.ZERO_HASH), - lte: layout.v(255, encoding.MAX_HASH), - values: true - }); + for (const deployment of this.network.deploys) { + bw.writeU8(deployment.bit); + bw.writeI64(deployment.startTime); + bw.writeU32(deployment.timeout); + bw.writeI32(deployment.threshold); + bw.writeI32(deployment.window); + } - for (const item of items) { - const [bit, hash] = layout.vv(item.key); - const state = item.value[0]; - stateCache.insert(bit, hash, state); + b.put(layout.D.encode(), bw.render()); } - return stateCache; -}; - -/** - * Save deployment table. - * @returns {Promise} - */ - -ChainDB.prototype.saveDeployments = function saveDeployments() { - const batch = this.db.batch(); - this.writeDeployments(batch); - return batch.write(); -}; - -/** - * Save deployment table. - * @returns {Promise} - */ - -ChainDB.prototype.writeDeployments = function writeDeployments(batch) { - const bw = new StaticWriter(1 + 17 * this.network.deploys.length); + /** + * Check for outdated deployments. + * @private + * @returns {Promise} + */ + + async checkDeployments() { + const raw = await this.db.get(layout.D.encode()); + + assert(raw, 'No deployment table found.'); + + const br = bio.read(raw); + const count = br.readU8(); + const invalid = []; + + for (let i = 0; i < count; i++) { + const bit = br.readU8(); + const start = br.readI64(); + const timeout = br.readU32(); + const threshold = br.readI32(); + const window = br.readI32(); + const deployment = this.network.byBit(bit); + + if (deployment + && start === deployment.startTime + && timeout === deployment.timeout + && threshold === deployment.threshold + && window === deployment.window) { + continue; + } - bw.writeU8(this.network.deploys.length); + invalid.push(bit); + } - for (const deployment of this.network.deploys) { - bw.writeU8(deployment.bit); - bw.writeU32(deployment.startTime); - bw.writeU32(deployment.timeout); - bw.writeI32(deployment.threshold); - bw.writeI32(deployment.window); + return invalid; } - batch.put(layout.V, bw.render()); -}; + /** + * Potentially invalidate state cache. + * @returns {Promise} + */ -/** - * Check for outdated deployments. - * @private - * @returns {Promise} - */ + async verifyDeployments() { + let invalid; -ChainDB.prototype.checkDeployments = async function checkDeployments() { - const raw = await this.db.get(layout.V); - - assert(raw, 'No deployment table found.'); - - const br = new BufferReader(raw); - const count = br.readU8(); - const invalid = []; - - for (let i = 0; i < count; i++) { - const bit = br.readU8(); - const start = br.readU32(); - const timeout = br.readU32(); - const threshold = br.readI32(); - const window = br.readI32(); - const deployment = this.network.byBit(bit); - - if (deployment - && start === deployment.startTime - && timeout === deployment.timeout - && threshold === deployment.threshold - && window === deployment.window) { - continue; + try { + invalid = await this.checkDeployments(); + } catch (e) { + if (e.type !== 'EncodingError') + throw e; + invalid = []; + for (let i = 0; i < 32; i++) + invalid.push(i); } - invalid.push(bit); - } + if (invalid.length === 0) + return true; - return invalid; -}; + const b = this.db.batch(); -/** - * Potentially invalidate state cache. - * @returns {Promise} - */ + for (const bit of invalid) { + this.logger.warning('Versionbit deployment params modified.'); + this.logger.warning('Invalidating cache for bit %d.', bit); + await this.invalidateCache(bit, b); + } -ChainDB.prototype.verifyDeployments = async function verifyDeployments() { - let invalid; + this.writeDeployments(b); - try { - invalid = await this.checkDeployments(); - } catch (e) { - if (e.type !== 'EncodingError') - throw e; - invalid = []; - for (let i = 0; i < 32; i++) - invalid.push(i); + await b.write(); + + return false; } - if (invalid.length === 0) - return true; + /** + * Invalidate state cache. + * @private + * @returns {Promise} + */ - const batch = this.db.batch(); + async invalidateCache(bit, b) { + const keys = await this.db.keys({ + gte: layout.v.min(bit), + lte: layout.v.max(bit) + }); - for (const bit of invalid) { - this.logger.warning('Versionbit deployment params modified.'); - this.logger.warning('Invalidating cache for bit %d.', bit); - await this.invalidateCache(bit, batch); + for (const key of keys) + b.del(key); } - this.writeDeployments(batch); - - await batch.write(); + /** + * Retroactively prune the database. + * @returns {Promise} + */ - return false; -}; + async prune() { + const options = this.options; + const keepBlocks = this.network.block.keepBlocks; + const pruneAfter = this.network.block.pruneAfterHeight; -/** - * Invalidate state cache. - * @private - * @returns {Promise} - */ + const flags = await this.getFlags(); -ChainDB.prototype.invalidateCache = async function invalidateCache(bit, batch) { - const keys = await this.db.keys({ - gte: layout.v(bit, encoding.ZERO_HASH), - lte: layout.v(bit, encoding.MAX_HASH) - }); + if (flags.prune) + throw new Error('Chain is already pruned.'); - for (const key of keys) - batch.del(key); -}; - -/** - * Retroactively prune the database. - * @returns {Promise} - */ + const height = await this.getHeight(this.state.tip); -ChainDB.prototype.prune = async function prune() { - const options = this.options; - const keepBlocks = this.network.block.keepBlocks; - const pruneAfter = this.network.block.pruneAfterHeight; + if (height <= pruneAfter + keepBlocks) + return false; - const flags = await this.getFlags(); + const start = pruneAfter + 1; + const end = height - keepBlocks; - if (flags.prune) - throw new Error('Chain is already pruned.'); + for (let i = start; i <= end; i++) { + const hash = await this.getHash(i); - const height = await this.getHeight(this.state.tip); + if (!hash) + throw new Error(`Cannot find hash for ${i}.`); - if (height <= pruneAfter + keepBlocks) - return false; + await this.blocks.pruneUndo(hash); + await this.blocks.prune(hash); + } - const start = pruneAfter + 1; - const end = height - keepBlocks; - const batch = this.db.batch(); + try { + options.prune = true; - for (let i = start; i <= end; i++) { - const hash = await this.getHash(i); + const flags = ChainFlags.fromOptions(options); + assert(flags.prune); - if (!hash) - throw new Error(`Cannot find hash for ${i}.`); + await this.db.put(layout.O.encode(), flags.toRaw()); + } catch (e) { + options.prune = false; + throw e; + } - batch.del(layout.b(hash)); - batch.del(layout.u(hash)); + return true; } - try { - options.prune = true; - - const flags = ChainFlags.fromOptions(options); - assert(flags.prune); + /** + * Get the _next_ block hash (does not work by height). + * @param {Hash} hash + * @returns {Promise} - Returns {@link Hash}. + */ - batch.put(layout.O, flags.toRaw()); - - await batch.write(); - } catch (e) { - options.prune = false; - throw e; + async getNextHash(hash) { + return this.db.get(layout.n.encode(hash)); } - await this.db.compactRange(); + /** + * Check to see if a block is on the main chain. + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ - return true; -}; + async isMainHash(hash) { + assert(Buffer.isBuffer(hash)); -/** - * Get the _next_ block hash (does not work by height). - * @param {Hash} hash - * @returns {Promise} - Returns {@link Hash}. - */ + if (hash.equals(consensus.ZERO_HASH)) + return false; -ChainDB.prototype.getNextHash = async function getNextHash(hash) { - const data = await this.db.get(layout.n(hash)); + if (hash.equals(this.network.genesis.hash)) + return true; - if (!data) - return null; + if (hash.equals(this.state.tip)) + return true; - return data.toString('hex'); -}; + const cacheHash = this.cacheHash.get(hash); -/** - * Check to see if a block is on the main chain. - * @param {Hash} hash - * @returns {Promise} - Returns Boolean. - */ + if (cacheHash) { + const cacheHeight = this.cacheHeight.get(cacheHash.height); + if (cacheHeight) + return cacheHeight.hash.equals(hash); + } -ChainDB.prototype.isMainHash = async function isMainHash(hash) { - assert(typeof hash === 'string'); + if (await this.getNextHash(hash)) + return true; - if (hash === encoding.NULL_HASH) return false; + } - if (hash === this.network.genesis.hash) - return true; + /** + * Test whether the entry is in the main chain. + * @param {ChainEntry} entry + * @returns {Promise} - Returns Boolean. + */ - if (hash === this.state.tip) - return true; + async isMainChain(entry) { + if (entry.isGenesis()) + return true; - const cacheHash = this.cacheHash.get(hash); + if (entry.hash.equals(this.state.tip)) + return true; - if (cacheHash) { - const cacheHeight = this.cacheHeight.get(cacheHash.height); - if (cacheHeight) - return cacheHeight.hash === hash; - } + const cache = this.getCache(entry.height); - if (await this.getNextHash(hash)) - return true; + if (cache) + return entry.hash.equals(cache.hash); - return false; -}; + if (await this.getNextHash(entry.hash)) + return true; -/** - * Test whether the entry is in the main chain. - * @param {ChainEntry} entry - * @returns {Promise} - Returns Boolean. - */ + return false; + } -ChainDB.prototype.isMainChain = async function isMainChain(entry) { - if (entry.isGenesis()) - return true; + /** + * Get hash range. + * @param {Number} [start=-1] + * @param {Number} [end=-1] + * @returns {Promise} + */ - if (entry.hash === this.state.tip) - return true; + async getHashes(start = -1, end = -1) { + if (start === -1) + start = 0; - const cache = this.getCache(entry.height); + if (end === -1) + end >>>= 0; - if (cache) - return entry.hash === cache.hash; + assert((start >>> 0) === start); + assert((end >>> 0) === end); - if (await this.getNextHash(entry.hash)) - return true; + return this.db.values({ + gte: layout.H.min(start), + lte: layout.H.max(end) + }); + } - return false; -}; + /** + * Get all entries. + * @returns {Promise} - Returns {@link ChainEntry}[]. + */ -/** - * Get all entries. - * @returns {Promise} - Returns {@link ChainEntry}[]. - */ + async getEntries() { + return this.db.values({ + gte: layout.e.min(), + lte: layout.e.max(), + parse: data => ChainEntry.fromRaw(data) + }); + } -ChainDB.prototype.getEntries = function getEntries() { - return this.db.values({ - gte: layout.e(encoding.ZERO_HASH), - lte: layout.e(encoding.MAX_HASH), - parse: value => ChainEntry.fromRaw(value) - }); -}; + /** + * Get all tip hashes. + * @returns {Promise} - Returns {@link Hash}[]. + */ -/** - * Get all tip hashes. - * @returns {Promise} - Returns {@link Hash}[]. - */ + async getTips() { + return this.db.keys({ + gte: layout.p.min(), + lte: layout.p.max(), + parse: key => layout.p.decode(key)[0] + }); + } -ChainDB.prototype.getTips = function getTips() { - return this.db.keys({ - gte: layout.p(encoding.ZERO_HASH), - lte: layout.p(encoding.MAX_HASH), - parse: layout.pp - }); -}; + /** + * Get a coin (unspents only). + * @private + * @param {Outpoint} prevout + * @returns {Promise} - Returns {@link CoinEntry}. + */ -/** - * Get a coin (unspents only). - * @private - * @param {Outpoint} prevout - * @returns {Promise} - Returns {@link CoinEntry}. - */ + async readCoin(prevout) { + if (this.options.spv) + return null; -ChainDB.prototype.readCoin = async function readCoin(prevout) { - if (this.options.spv) - return null; + const {hash, index} = prevout; - const {hash, index} = prevout; - const key = prevout.toKey(); - const state = this.state; + const raw = await this.db.get(layout.c.encode(hash, index)); - const cache = this.coinCache.get(key); + if (!raw) + return null; - if (cache) - return CoinEntry.fromRaw(cache); + return CoinEntry.fromRaw(raw); + } - const raw = await this.db.get(layout.c(hash, index)); + /** + * Get a coin (unspents only). + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} - Returns {@link Coin}. + */ - if (!raw) - return null; + async getCoin(hash, index) { + const prevout = new Outpoint(hash, index); + const coin = await this.readCoin(prevout); - if (state === this.state) - this.coinCache.set(key, raw); + if (!coin) + return null; - return CoinEntry.fromRaw(raw); -}; + return coin.toCoin(prevout); + } -/** - * Get a coin (unspents only). - * @param {Hash} hash - * @param {Number} index - * @returns {Promise} - Returns {@link Coin}. - */ + /** + * Check whether coins are still unspent. Necessary for bip30. + * @see https://bitcointalk.org/index.php?topic=67738.0 + * @param {TX} tx + * @returns {Promise} - Returns Boolean. + */ + + async hasCoins(tx) { + for (let i = 0; i < tx.outputs.length; i++) { + const key = layout.c.encode(tx.hash(), i); + if (await this.db.has(key)) + return true; + } + return false; + } -ChainDB.prototype.getCoin = async function getCoin(hash, index) { - const prevout = new Outpoint(hash, index); - const coin = await this.readCoin(prevout); + /** + * Get coin viewpoint. + * @param {TX} tx + * @returns {Promise} - Returns {@link CoinView}. + */ - if (!coin) - return null; + async getCoinView(tx) { + const view = new CoinView(); - return coin.toCoin(prevout); -}; + for (const {prevout} of tx.inputs) { + const coin = await this.readCoin(prevout); -/** - * Check whether coins are still unspent. Necessary for bip30. - * @see https://bitcointalk.org/index.php?topic=67738.0 - * @param {TX} tx - * @returns {Promise} - Returns Boolean. - */ + if (coin) + view.addEntry(prevout, coin); + } -ChainDB.prototype.hasCoins = async function hasCoins(tx) { - for (let i = 0; i < tx.outputs.length; i++) { - const key = layout.c(tx.hash(), i); - if (await this.db.has(key)) - return true; + return view; } - return false; -}; -/** - * Get coin viewpoint. - * @param {TX} tx - * @returns {Promise} - Returns {@link CoinView}. - */ + /** + * Get coins necessary to be resurrected during a reorg. + * @param {Hash} hash + * @returns {Promise} - Returns {@link Coin}[]. + */ -ChainDB.prototype.getCoinView = async function getCoinView(tx) { - const view = new CoinView(); + async getUndoCoins(hash) { + const data = await this.blocks.readUndo(hash); - for (const {prevout} of tx.inputs) { - const coin = await this.readCoin(prevout); + if (!data) + return new UndoCoins(); - if (coin) - view.addEntry(prevout, coin); + return UndoCoins.fromRaw(data); } - return view; -}; + /** + * Retrieve a block from the database (not filled with coins). + * @param {Hash} hash + * @returns {Promise} - Returns {@link Block}. + */ -/** - * Get coin viewpoint (historical). - * @param {TX} tx - * @returns {Promise} - Returns {@link CoinView}. - */ + async getBlock(hash) { + const data = await this.getRawBlock(hash); + + if (!data) + return null; -ChainDB.prototype.getSpentView = async function getSpentView(tx) { - const view = await this.getCoinView(tx); + return Block.fromRaw(data); + } - for (const {prevout} of tx.inputs) { - if (view.hasEntry(prevout)) - continue; + /** + * Retrieve a block from the database (not filled with coins). + * @param {Hash} hash + * @returns {Promise} - Returns {@link Block}. + */ - const {hash, index} = prevout; - const meta = await this.getMeta(hash); + async getRawBlock(block) { + if (this.options.spv) + return null; - if (!meta) - continue; + const hash = await this.getHash(block); - const {tx, height} = meta; + if (!hash) + return null; - if (index < tx.outputs.length) - view.addIndex(tx, index, height); + return this.blocks.read(hash); } - return view; -}; - -/** - * Get coins necessary to be resurrected during a reorg. - * @param {Hash} hash - * @returns {Promise} - Returns {@link Coin}[]. - */ + /** + * Get a historical block coin viewpoint. + * @param {Block} hash + * @returns {Promise} - Returns {@link CoinView}. + */ -ChainDB.prototype.getUndoCoins = async function getUndoCoins(hash) { - const data = await this.db.get(layout.u(hash)); + async getBlockView(block) { + const view = new CoinView(); + const undo = await this.getUndoCoins(block.hash()); - if (!data) - return new UndoCoins(); + if (undo.isEmpty()) + return view; - return UndoCoins.fromRaw(data); -}; + for (let i = block.txs.length - 1; i > 0; i--) { + const tx = block.txs[i]; -/** - * Retrieve a block from the database (not filled with coins). - * @param {Hash} hash - * @returns {Promise} - Returns {@link Block}. - */ + for (let j = tx.inputs.length - 1; j >= 0; j--) { + const input = tx.inputs[j]; + undo.apply(view, input.prevout); + } + } -ChainDB.prototype.getBlock = async function getBlock(hash) { - const data = await this.getRawBlock(hash); + // Undo coins should be empty. + assert(undo.isEmpty(), 'Undo coins data inconsistency.'); - if (!data) - return null; + return view; + } - return Block.fromRaw(data); -}; + /** + * Scan the blockchain for transactions containing specified address hashes. + * @param {Hash} start - Block hash to start at. + * @param {Bloom} filter - Bloom filter containing tx and address hashes. + * @param {Function} iter - Iterator. + * @returns {Promise} + */ -/** - * Retrieve a block from the database (not filled with coins). - * @param {Hash} hash - * @returns {Promise} - Returns {@link Block}. - */ + async scan(start, filter, iter) { + if (start == null) + start = this.network.genesis.hash; -ChainDB.prototype.getRawBlock = async function getRawBlock(block) { - if (this.options.spv) - return null; + if (typeof start === 'number') + this.logger.info('Scanning from height %d.', start); + else + this.logger.info('Scanning from block %h.', start); - const hash = await this.getHash(block); + let entry = await this.getEntry(start); - if (!hash) - return null; + if (!entry) + return; - return await this.db.get(layout.b(hash)); -}; + if (!await this.isMainChain(entry)) + throw new Error('Cannot rescan an alternate chain.'); -/** - * Get a historical block coin viewpoint. - * @param {Block} hash - * @returns {Promise} - Returns {@link CoinView}. - */ + let total = 0; -ChainDB.prototype.getBlockView = async function getBlockView(block) { - const view = new CoinView(); - const undo = await this.getUndoCoins(block.hash()); + while (entry) { + const block = await this.getBlock(entry.hash); + const txs = []; - if (undo.isEmpty()) - return view; + total += 1; - for (let i = block.txs.length - 1; i > 0; i--) { - const tx = block.txs[i]; + if (!block) { + if (!this.options.spv && !this.options.prune) + throw new Error('Block not found.'); + await iter(entry, txs); + entry = await this.getNext(entry); + continue; + } - for (let j = tx.inputs.length - 1; j >= 0; j--) { - const input = tx.inputs[j]; - undo.apply(view, input.prevout); - } - } + this.logger.info( + 'Scanning block %h (%d).', + entry.hash, entry.height); - // Undo coins should be empty. - assert(undo.isEmpty(), 'Undo coins data inconsistency.'); + for (let i = 0; i < block.txs.length; i++) { + const tx = block.txs[i]; - return view; -}; + let found = false; -/** - * Get a transaction with metadata. - * @param {Hash} hash - * @returns {Promise} - Returns {@link TXMeta}. - */ + for (let j = 0; j < tx.outputs.length; j++) { + const output = tx.outputs[j]; + const hash = output.getHash(); -ChainDB.prototype.getMeta = async function getMeta(hash) { - if (!this.options.indexTX) - return null; + if (!hash) + continue; - const data = await this.db.get(layout.t(hash)); + if (filter.test(hash)) { + const prevout = Outpoint.fromTX(tx, j); + filter.add(prevout.toRaw()); + found = true; + } + } - if (!data) - return null; + if (found) { + txs.push(tx); + continue; + } - return TXMeta.fromRaw(data); -}; + if (i === 0) + continue; -/** - * Retrieve a transaction. - * @param {Hash} hash - * @returns {Promise} - Returns {@link TX}. - */ + for (const {prevout} of tx.inputs) { + if (filter.test(prevout.toRaw())) { + txs.push(tx); + break; + } + } + } -ChainDB.prototype.getTX = async function getTX(hash) { - const meta = await this.getMeta(hash); + await iter(entry, txs); - if (!meta) - return null; - - return meta.tx; -}; - -/** - * @param {Hash} hash - * @returns {Promise} - Returns Boolean. - */ - -ChainDB.prototype.hasTX = async function hasTX(hash) { - if (!this.options.indexTX) - return false; - - return await this.db.has(layout.t(hash)); -}; - -/** - * Get all coins pertinent to an address. - * @param {Address[]} addrs - * @returns {Promise} - Returns {@link Coin}[]. - */ - -ChainDB.prototype.getCoinsByAddress = async function getCoinsByAddress(addrs) { - if (!this.options.indexAddress) - return []; - - if (!Array.isArray(addrs)) - addrs = [addrs]; - - const coins = []; - - for (const addr of addrs) { - const hash = Address.getHash(addr); - - const keys = await this.db.keys({ - gte: layout.C(hash, encoding.ZERO_HASH, 0), - lte: layout.C(hash, encoding.MAX_HASH, 0xffffffff), - parse: layout.Cc - }); - - for (const [hash, index] of keys) { - const coin = await this.getCoin(hash, index); - assert(coin); - coins.push(coin); + entry = await this.getNext(entry); } - } - - return coins; -}; - -/** - * Get all transaction hashes to an address. - * @param {Address[]} addrs - * @returns {Promise} - Returns {@link Hash}[]. - */ - -ChainDB.prototype.getHashesByAddress = async function getHashesByAddress(addrs) { - if (!this.options.indexTX || !this.options.indexAddress) - return []; - - const hashes = Object.create(null); - for (const addr of addrs) { - const hash = Address.getHash(addr); - - await this.db.keys({ - gte: layout.T(hash, encoding.ZERO_HASH), - lte: layout.T(hash, encoding.MAX_HASH), - parse: (key) => { - const hash = layout.Tt(key); - hashes[hash] = true; - } - }); + this.logger.info('Finished scanning %d blocks.', total); } - return Object.keys(hashes); -}; - -/** - * Get all transactions pertinent to an address. - * @param {Address[]} addrs - * @returns {Promise} - Returns {@link TX}[]. - */ - -ChainDB.prototype.getTXByAddress = async function getTXByAddress(addrs) { - const mtxs = await this.getMetaByAddress(addrs); - const out = []; - - for (const mtx of mtxs) - out.push(mtx.tx); - - return out; -}; - -/** - * Get all transactions pertinent to an address. - * @param {Address[]} addrs - * @returns {Promise} - Returns {@link TXMeta}[]. - */ - -ChainDB.prototype.getMetaByAddress = async function getMetaByAddress(addrs) { - if (!this.options.indexTX || !this.options.indexAddress) - return []; - - if (!Array.isArray(addrs)) - addrs = [addrs]; - - const hashes = await this.getHashesByAddress(addrs); - const txs = []; - - for (const hash of hashes) { - const tx = await this.getMeta(hash); - assert(tx); - txs.push(tx); + /** + * Save an entry to the database and optionally + * connect it as the tip. Note that this method + * does _not_ perform any verification which is + * instead performed in {@link Chain#add}. + * @param {ChainEntry} entry + * @param {Block} block + * @param {CoinView?} view - Will not connect if null. + * @returns {Promise} + */ + + async save(entry, block, view) { + this.start(); + try { + await this._save(entry, block, view); + } catch (e) { + this.drop(); + throw e; + } + await this.commit(); } - return txs; -}; + /** + * Save an entry. + * @private + * @param {ChainEntry} entry + * @param {Block} block + * @param {CoinView?} view + * @returns {Promise} + */ -/** - * Scan the blockchain for transactions containing specified address hashes. - * @param {Hash} start - Block hash to start at. - * @param {Bloom} filter - Bloom filter containing tx and address hashes. - * @param {Function} iter - Iterator. - * @returns {Promise} - */ + async _save(entry, block, view) { + const hash = block.hash(); -ChainDB.prototype.scan = async function scan(start, filter, iter) { - if (start == null) - start = this.network.genesis.hash; + // Hash->height index. + this.put(layout.h.encode(hash), fromU32(entry.height)); - if (typeof start === 'number') - this.logger.info('Scanning from height %d.', start); - else - this.logger.info('Scanning from block %s.', util.revHex(start)); + // Entry data. + this.put(layout.e.encode(hash), entry.toRaw()); + this.cacheHash.push(entry.hash, entry); - let entry = await this.getEntry(start); + // Tip index. + this.del(layout.p.encode(entry.prevBlock)); + this.put(layout.p.encode(hash), null); - if (!entry) - return; + // Update state caches. + this.saveUpdates(); - if (!await this.isMainChain(entry)) - throw new Error('Cannot rescan an alternate chain.'); - - let total = 0; - - while (entry) { - const block = await this.getBlock(entry.hash); - const txs = []; - - total++; - - if (!block) { - if (!this.options.spv && !this.options.prune) - throw new Error('Block not found.'); - await iter(entry, txs); - entry = await this.getNext(entry); - continue; + if (!view) { + // Save block data. + await this.saveBlock(entry, block); + return; } - this.logger.info( - 'Scanning block %s (%d).', - entry.rhash(), entry.height); - - for (let i = 0; i < block.txs.length; i++) { - const tx = block.txs[i]; - let found = false; - - for (let j = 0; j < tx.outputs.length; j++) { - const output = tx.outputs[j]; - const hash = output.getHash(); + // Hash->next-block index. + if (!entry.isGenesis()) + this.put(layout.n.encode(entry.prevBlock), hash); - if (!hash) - continue; + // Height->hash index. + this.put(layout.H.encode(entry.height), hash); + this.cacheHeight.push(entry.height, entry); - if (filter.test(hash)) { - const prevout = Outpoint.fromTX(tx, j); - filter.add(prevout.toRaw()); - found = true; - } - } + // Connect block and save data. + await this.saveBlock(entry, block, view); - if (found) { - txs.push(tx); - continue; - } + // Commit new chain state. + this.put(layout.R.encode(), this.pending.commit(hash)); + } - if (i === 0) - continue; + /** + * Reconnect the block to the chain. + * @param {ChainEntry} entry + * @param {Block} block + * @param {CoinView} view + * @returns {Promise} + */ - for (const {prevout} of tx.inputs) { - if (filter.test(prevout.toRaw())) { - txs.push(tx); - break; - } - } + async reconnect(entry, block, view) { + this.start(); + try { + await this._reconnect(entry, block, view); + } catch (e) { + this.drop(); + throw e; } - - await iter(entry, txs); - - entry = await this.getNext(entry); + await this.commit(); } - this.logger.info('Finished scanning %d blocks.', total); -}; - -/** - * Save an entry to the database and optionally - * connect it as the tip. Note that this method - * does _not_ perform any verification which is - * instead performed in {@link Chain#add}. - * @param {ChainEntry} entry - * @param {Block} block - * @param {CoinView?} view - Will not connect if null. - * @returns {Promise} - */ + /** + * Reconnect block. + * @private + * @param {ChainEntry} entry + * @param {Block} block + * @param {CoinView} view + * @returns {Promise} + */ -ChainDB.prototype.save = async function save(entry, block, view) { - this.start(); - try { - await this._save(entry, block, view); - } catch (e) { - this.drop(); - throw e; - } - await this.commit(); -}; + async _reconnect(entry, block, view) { + const hash = block.hash(); -/** - * Save an entry without a batch. - * @private - * @param {ChainEntry} entry - * @param {Block} block - * @param {CoinView?} view - * @returns {Promise} - */ + assert(!entry.isGenesis()); -ChainDB.prototype._save = async function _save(entry, block, view) { - const hash = block.hash(); + // We can now add a hash->next-block index. + this.put(layout.n.encode(entry.prevBlock), hash); - // Hash->height index. - this.put(layout.h(hash), U32(entry.height)); + // We can now add a height->hash index. + this.put(layout.H.encode(entry.height), hash); + this.cacheHeight.push(entry.height, entry); - // Entry data. - this.put(layout.e(hash), entry.toRaw()); - this.cacheHash.push(entry.hash, entry); + // Re-insert into cache. + this.cacheHash.push(entry.hash, entry); - // Tip index. - this.del(layout.p(entry.prevBlock)); - this.put(layout.p(hash), null); + // Update state caches. + this.saveUpdates(); - // Update state caches. - this.saveUpdates(); + // Connect inputs. + await this.connectBlock(entry, block, view); - if (!view) { - // Save block data. - await this.saveBlock(entry, block); - return; + // Update chain state. + this.put(layout.R.encode(), this.pending.commit(hash)); } - // Hash->next-block index. - if (!entry.isGenesis()) - this.put(layout.n(entry.prevBlock), hash); - - // Height->hash index. - this.put(layout.H(entry.height), hash); - this.cacheHeight.push(entry.height, entry); + /** + * Disconnect block from the chain. + * @param {ChainEntry} entry + * @param {Block} block + * @returns {Promise} + */ - // Connect block and save data. - await this.saveBlock(entry, block, view); + async disconnect(entry, block) { + this.start(); - // Commit new chain state. - this.put(layout.R, this.pending.commit(hash)); -}; + let view; + try { + view = await this._disconnect(entry, block); + } catch (e) { + this.drop(); + throw e; + } -/** - * Reconnect the block to the chain. - * @param {ChainEntry} entry - * @param {Block} block - * @param {CoinView} view - * @returns {Promise} - */ + await this.commit(); -ChainDB.prototype.reconnect = async function reconnect(entry, block, view) { - this.start(); - try { - await this._reconnect(entry, block, view); - } catch (e) { - this.drop(); - throw e; + return view; } - await this.commit(); -}; -/** - * Reconnect block without a batch. - * @private - * @param {ChainEntry} entry - * @param {Block} block - * @param {CoinView} view - * @returns {Promise} - */ + /** + * Disconnect block. + * @private + * @param {ChainEntry} entry + * @param {Block} block + * @returns {Promise} - Returns {@link CoinView}. + */ -ChainDB.prototype._reconnect = async function _reconnect(entry, block, view) { - const hash = block.hash(); + async _disconnect(entry, block) { + // Remove hash->next-block index. + this.del(layout.n.encode(entry.prevBlock)); - assert(!entry.isGenesis()); + // Remove height->hash index. + this.del(layout.H.encode(entry.height)); + this.cacheHeight.unpush(entry.height); - // We can now add a hash->next-block index. - this.put(layout.n(entry.prevBlock), hash); + // Update state caches. + this.saveUpdates(); - // We can now add a height->hash index. - this.put(layout.H(entry.height), hash); - this.cacheHeight.push(entry.height, entry); + // Disconnect inputs. + const view = await this.disconnectBlock(entry, block); - // Re-insert into cache. - this.cacheHash.push(entry.hash, entry); + // Revert chain state to previous tip. + this.put(layout.R.encode(), this.pending.commit(entry.prevBlock)); - // Update state caches. - this.saveUpdates(); + return view; + } - // Connect inputs. - await this.connectBlock(entry, block, view); + /** + * Save state cache updates. + * @private + */ - // Update chain state. - this.put(layout.R, this.pending.commit(hash)); -}; + saveUpdates() { + const updates = this.stateCache.updates; -/** - * Disconnect block from the chain. - * @param {ChainEntry} entry - * @param {Block} block - * @returns {Promise} - */ + if (updates.length === 0) + return; -ChainDB.prototype.disconnect = async function disconnect(entry, block) { - this.start(); + this.logger.info('Saving %d state cache updates.', updates.length); - let view; - try { - view = await this._disconnect(entry, block); - } catch (e) { - this.drop(); - throw e; + for (const update of updates) { + const {bit, hash} = update; + this.put(layout.v.encode(bit, hash), update.toRaw()); + } } - await this.commit(); - - return view; -}; + /** + * Reset the chain to a height or hash. Useful for replaying + * the blockchain download for SPV. + * @param {Hash|Number} block - hash/height + * @returns {Promise} + */ -/** - * Disconnect block without a batch. - * @private - * @param {ChainEntry} entry - * @param {Block} block - * @returns {Promise} - Returns {@link CoinView}. - */ + async reset(block) { + const entry = await this.getEntry(block); -ChainDB.prototype._disconnect = async function _disconnect(entry, block) { - // Remove hash->next-block index. - this.del(layout.n(entry.prevBlock)); + if (!entry) + throw new Error('Block not found.'); - // Remove height->hash index. - this.del(layout.H(entry.height)); - this.cacheHeight.unpush(entry.height); + if (!await this.isMainChain(entry)) + throw new Error('Cannot reset on alternate chain.'); - // Update state caches. - this.saveUpdates(); + if (this.options.prune) + throw new Error('Cannot reset when pruned.'); - // Disconnect inputs. - const view = await this.disconnectBlock(entry, block); + // We need to remove all alternate + // chains first. This is ugly, but + // it's the only safe way to reset + // the chain. + await this.removeChains(); - // Revert chain state to previous tip. - this.put(layout.R, this.pending.commit(entry.prevBlock)); - - return view; -}; - -/** - * Save state cache updates. - * @private - */ - -ChainDB.prototype.saveUpdates = function saveUpdates() { - const updates = this.stateCache.updates; + let tip = await this.getTip(); + assert(tip); - if (updates.length === 0) - return; + this.logger.debug('Resetting main chain to: %h', entry.hash); - this.logger.info('Saving %d state cache updates.', updates.length); + for (;;) { + this.start(); - for (const update of updates) { - const {bit, hash} = update; - this.put(layout.v(bit, hash), update.toRaw()); - } -}; + // Stop once we hit our target tip. + if (tip.hash.equals(entry.hash)) { + this.put(layout.R.encode(), this.pending.commit(tip.hash)); + await this.commit(); + break; + } -/** - * Reset the chain to a height or hash. Useful for replaying - * the blockchain download for SPV. - * @param {Hash|Number} block - hash/height - * @returns {Promise} - */ + assert(!tip.isGenesis()); + + // Revert the tip index. + this.del(layout.p.encode(tip.hash)); + this.put(layout.p.encode(tip.prevBlock), null); + + // Remove all records (including + // main-chain-only records). + this.del(layout.H.encode(tip.height)); + this.del(layout.h.encode(tip.hash)); + this.del(layout.e.encode(tip.hash)); + this.del(layout.n.encode(tip.prevBlock)); + + // Disconnect and remove block data. + try { + await this.removeBlock(tip); + } catch (e) { + this.drop(); + throw e; + } -ChainDB.prototype.reset = async function reset(block) { - const entry = await this.getEntry(block); + // Revert chain state to previous tip. + this.put(layout.R.encode(), this.pending.commit(tip.prevBlock)); - if (!entry) - throw new Error('Block not found.'); + await this.commit(); - if (!await this.isMainChain(entry)) - throw new Error('Cannot reset on alternate chain.'); + // Update caches _after_ successful commit. + this.cacheHeight.remove(tip.height); + this.cacheHash.remove(tip.hash); - if (this.options.prune) - throw new Error('Cannot reset when pruned.'); + tip = await this.getPrevious(tip); + assert(tip); + } - // We need to remove all alternate - // chains first. This is ugly, but - // it's the only safe way to reset - // the chain. - await this.removeChains(); + return tip; + } - let tip = await this.getTip(); - assert(tip); + /** + * Remove all alternate chains. + * @returns {Promise} + */ - this.logger.debug('Resetting main chain to: %s', entry.rhash()); + async removeChains() { + const tips = await this.getTips(); - for (;;) { + // Note that this has to be + // one giant atomic write! this.start(); - // Stop once we hit our target tip. - if (tip.hash === entry.hash) { - this.put(layout.R, this.pending.commit(tip.hash)); - await this.commit(); - break; - } - - assert(!tip.isGenesis()); - - // Revert the tip index. - this.del(layout.p(tip.hash)); - this.put(layout.p(tip.prevBlock), null); - - // Remove all records (including - // main-chain-only records). - this.del(layout.H(tip.height)); - this.del(layout.h(tip.hash)); - this.del(layout.e(tip.hash)); - this.del(layout.n(tip.prevBlock)); - - // Disconnect and remove block data. try { - await this.removeBlock(tip); + for (const tip of tips) + await this._removeChain(tip); } catch (e) { this.drop(); throw e; } - // Revert chain state to previous tip. - this.put(layout.R, this.pending.commit(tip.prevBlock)); - await this.commit(); - - // Update caches _after_ successful commit. - this.cacheHeight.remove(tip.height); - this.cacheHash.remove(tip.hash); - - tip = await this.getPrevious(tip); - assert(tip); } - return tip; -}; + /** + * Remove an alternate chain. + * @private + * @param {Hash} hash - Alternate chain tip. + * @returns {Promise} + */ -/** - * Remove all alternate chains. - * @returns {Promise} - */ + async _removeChain(hash) { + let tip = await this.getEntryByHash(hash); -ChainDB.prototype.removeChains = async function removeChains() { - const tips = await this.getTips(); + if (!tip) + throw new Error('Alternate chain tip not found.'); - // Note that this has to be - // one giant atomic write! - this.start(); - - try { - for (const tip of tips) - await this._removeChain(tip); - } catch (e) { - this.drop(); - throw e; - } - - await this.commit(); -}; - -/** - * Remove an alternate chain. - * @private - * @param {Hash} hash - Alternate chain tip. - * @returns {Promise} - */ + this.logger.debug('Removing alternate chain: %h.', tip.hash); -ChainDB.prototype._removeChain = async function _removeChain(hash) { - let tip = await this.getEntryByHash(hash); + for (;;) { + if (await this.isMainChain(tip)) + break; - if (!tip) - throw new Error('Alternate chain tip not found.'); + assert(!tip.isGenesis()); - this.logger.debug('Removing alternate chain: %s.', tip.rhash()); + // Remove all non-main-chain records. + this.del(layout.p.encode(tip.hash)); + this.del(layout.h.encode(tip.hash)); + this.del(layout.e.encode(tip.hash)); - for (;;) { - if (await this.isMainChain(tip)) - break; + // Queue up hash to be removed + // on successful write. + this.cacheHash.unpush(tip.hash); - assert(!tip.isGenesis()); - - // Remove all non-main-chain records. - this.del(layout.p(tip.hash)); - this.del(layout.h(tip.hash)); - this.del(layout.e(tip.hash)); - this.del(layout.b(tip.hash)); - - // Queue up hash to be removed - // on successful write. - this.cacheHash.unpush(tip.hash); - - tip = await this.getPrevious(tip); - assert(tip); + tip = await this.getPrevious(tip); + assert(tip); + } } -}; -/** - * Save a block (not an entry) to the - * database and potentially connect the inputs. - * @param {ChainEntry} entry - * @param {Block} block - * @param {CoinView?} view - * @returns {Promise} - Returns {@link Block}. - */ + /** + * Save a block (not an entry) to the + * database and potentially connect the inputs. + * @param {ChainEntry} entry + * @param {Block} block + * @param {CoinView?} view + * @returns {Promise} - Returns {@link Block}. + */ -ChainDB.prototype.saveBlock = async function saveBlock(entry, block, view) { - const hash = block.hash(); + async saveBlock(entry, block, view) { + const hash = block.hash(); - if (this.options.spv) - return; + if (this.options.spv) + return; - // Write actual block data (this may be - // better suited to flat files in the future). - this.put(layout.b(hash), block.toRaw()); + // Write actual block data. + await this.blocks.write(hash, block.toRaw()); - if (!view) - return; + if (!view) + return; - await this.connectBlock(entry, block, view); -}; + await this.connectBlock(entry, block, view); + } -/** - * Remove a block (not an entry) to the database. - * Disconnect inputs. - * @param {ChainEntry} entry - * @returns {Promise} - Returns {@link Block}. - */ + /** + * Remove a block (not an entry) to the database. + * Disconnect inputs. + * @param {ChainEntry} entry + * @returns {Promise} - Returns {@link Block}. + */ -ChainDB.prototype.removeBlock = async function removeBlock(entry) { - if (this.options.spv) - return new CoinView(); + async removeBlock(entry) { + if (this.options.spv) + return new CoinView(); - const block = await this.getBlock(entry.hash); + const block = await this.getBlock(entry.hash); - if (!block) - throw new Error('Block not found.'); + if (!block) + throw new Error('Block not found.'); - this.del(layout.b(block.hash())); + return this.disconnectBlock(entry, block); + } - return await this.disconnectBlock(entry, block); -}; + /** + * Commit coin view to database. + * @private + * @param {CoinView} view + */ + + saveView(view) { + for (const [hash, coins] of view.map) { + for (const [index, coin] of coins.outputs) { + if (coin.spent) { + this.del(layout.c.encode(hash, index)); + continue; + } -/** - * Commit coin view to database. - * @private - * @param {CoinView} view - */ + const raw = coin.toRaw(); -ChainDB.prototype.saveView = function saveView(view) { - for (const [hash, coins] of view.map) { - for (const [index, coin] of coins.outputs) { - if (coin.spent) { - this.del(layout.c(hash, index)); - this.coinCache.unpush(hash + index); - continue; + this.put(layout.c.encode(hash, index), raw); } - - const raw = coin.toRaw(); - - this.put(layout.c(hash, index), raw); - this.coinCache.push(hash + index, raw); } } -}; -/** - * Connect block inputs. - * @param {ChainEntry} entry - * @param {Block} block - * @param {CoinView} view - * @returns {Promise} - Returns {@link Block}. - */ + /** + * Connect block inputs. + * @param {ChainEntry} entry + * @param {Block} block + * @param {CoinView} view + * @returns {Promise} - Returns {@link Block}. + */ -ChainDB.prototype.connectBlock = async function connectBlock(entry, block, view) { - if (this.options.spv) - return; + async connectBlock(entry, block, view) { + if (this.options.spv) + return undefined; - const hash = block.hash(); + const hash = block.hash(); - this.pending.connect(block); + this.pending.connect(block); - // Genesis block's coinbase is unspendable. - if (entry.isGenesis()) - return; + // Genesis block's coinbase is unspendable. + if (entry.isGenesis()) + return undefined; - // Update chain state value. - for (let i = 0; i < block.txs.length; i++) { - const tx = block.txs[i]; + // Update chain state value. + for (let i = 0; i < block.txs.length; i++) { + const tx = block.txs[i]; - if (i > 0) { - for (const {prevout} of tx.inputs) - this.pending.spend(view.getOutput(prevout)); - } + if (i > 0) { + for (const {prevout} of tx.inputs) + this.pending.spend(view.getOutput(prevout)); + } - for (const output of tx.outputs) { - if (output.script.isUnspendable()) - continue; + for (const output of tx.outputs) { + if (output.script.isUnspendable()) + continue; - this.pending.add(output); + this.pending.add(output); + } } - // Index the transaction if enabled. - this.indexTX(tx, view, entry, i); - } - - // Commit new coin state. - this.saveView(view); + // Commit new coin state. + this.saveView(view); - // Write undo coins (if there are any). - if (!view.undo.isEmpty()) - this.put(layout.u(hash), view.undo.commit()); + // Write undo coins (if there are any). + if (!view.undo.isEmpty()) + await this.blocks.writeUndo(hash, view.undo.commit()); - // Prune height-288 if pruning is enabled. - await this.pruneBlock(entry); -}; + // Prune height-288 if pruning is enabled. + return this.pruneBlock(entry); + } -/** - * Disconnect block inputs. - * @param {ChainEntry} entry - * @param {Block} block - * @returns {Promise} - Returns {@link CoinView}. - */ + /** + * Disconnect block inputs. + * @param {ChainEntry} entry + * @param {Block} block + * @returns {Promise} - Returns {@link CoinView}. + */ -ChainDB.prototype.disconnectBlock = async function disconnectBlock(entry, block) { - const view = new CoinView(); + async disconnectBlock(entry, block) { + const view = new CoinView(); - if (this.options.spv) - return view; + if (this.options.spv) + return view; - const hash = block.hash(); - const undo = await this.getUndoCoins(hash); + const hash = block.hash(); + const undo = await this.getUndoCoins(hash); - this.pending.disconnect(block); + this.pending.disconnect(block); - // Disconnect all transactions. - for (let i = block.txs.length - 1; i >= 0; i--) { - const tx = block.txs[i]; + // Disconnect all transactions. + for (let i = block.txs.length - 1; i >= 0; i--) { + const tx = block.txs[i]; - if (i > 0) { - for (let j = tx.inputs.length - 1; j >= 0; j--) { - const {prevout} = tx.inputs[j]; - undo.apply(view, prevout); - this.pending.add(view.getOutput(prevout)); + if (i > 0) { + for (let j = tx.inputs.length - 1; j >= 0; j--) { + const {prevout} = tx.inputs[j]; + undo.apply(view, prevout); + this.pending.add(view.getOutput(prevout)); + } } - } - // Remove any created coins. - view.removeTX(tx, entry.height); + // Remove any created coins. + view.removeTX(tx, entry.height); - for (let j = tx.outputs.length - 1; j >= 0; j--) { - const output = tx.outputs[j]; + for (let j = tx.outputs.length - 1; j >= 0; j--) { + const output = tx.outputs[j]; - if (output.script.isUnspendable()) - continue; + if (output.script.isUnspendable()) + continue; - this.pending.spend(output); + this.pending.spend(output); + } } - // Remove from transaction index. - this.unindexTX(tx, view); - } + // Undo coins should be empty. + assert(undo.isEmpty(), 'Undo coins data inconsistency.'); - // Undo coins should be empty. - assert(undo.isEmpty(), 'Undo coins data inconsistency.'); + // Commit new coin state. + this.saveView(view); - // Commit new coin state. - this.saveView(view); + return view; + } - // Remove undo coins. - this.del(layout.u(hash)); + /** + * Prune a block from the chain and + * add current block to the prune queue. + * @private + * @param {ChainEntry} entry + * @returns {Promise} + */ - return view; -}; + async pruneBlock(entry) { + if (this.options.spv) + return; -/** - * Prune a block from the chain and - * add current block to the prune queue. - * @private - * @param {ChainEntry} entry - * @returns {Promise} - */ + if (!this.options.prune) + return; -ChainDB.prototype.pruneBlock = async function pruneBlock(entry) { - if (this.options.spv) - return; + const height = entry.height - this.network.block.keepBlocks; - if (!this.options.prune) - return; + if (height <= this.network.block.pruneAfterHeight) + return; - const height = entry.height - this.network.block.keepBlocks; + const hash = await this.getHash(height); - if (height <= this.network.block.pruneAfterHeight) - return; + if (!hash) + return; - const hash = await this.getHash(height); + await this.blocks.pruneUndo(hash); + await this.blocks.prune(hash); + } - if (!hash) - return; + /** + * Save database options. + * @returns {Promise} + */ - this.del(layout.b(hash)); - this.del(layout.u(hash)); -}; + saveFlags() { + const flags = ChainFlags.fromOptions(this.options); + const b = this.db.batch(); + b.put(layout.O.encode(), flags.toRaw()); + return b.write(); + } +} /** - * Save database options. - * @returns {Promise} + * ChainFlags */ -ChainDB.prototype.saveFlags = function saveFlags() { - const flags = ChainFlags.fromOptions(this.options); - const batch = this.db.batch(); - batch.put(layout.O, flags.toRaw()); - return batch.write(); -}; - -/** - * Index a transaction by txid and address. - * @private - * @param {TX} tx - * @param {CoinView} view - * @param {ChainEntry} entry - * @param {Number} index - */ +class ChainFlags { + /** + * Create chain flags. + * @alias module:blockchain.ChainFlags + * @constructor + */ -ChainDB.prototype.indexTX = function indexTX(tx, view, entry, index) { - const hash = tx.hash(); + constructor(options) { + this.network = Network.primary; + this.spv = false; + this.witness = true; + this.bip91 = false; + this.bip148 = false; + this.prune = false; - if (this.options.indexTX) { - const meta = TXMeta.fromTX(tx, entry, index); + if (options) + this.fromOptions(options); + } - this.put(layout.t(hash), meta.toRaw()); + fromOptions(options) { + this.network = Network.get(options.network); - if (this.options.indexAddress) { - const hashes = tx.getHashes(view); - for (const addr of hashes) - this.put(layout.T(addr, hash), null); + if (options.spv != null) { + assert(typeof options.spv === 'boolean'); + this.spv = options.spv; } - } - if (!this.options.indexAddress) - return; - - if (!tx.isCoinbase()) { - for (const {prevout} of tx.inputs) { - const addr = view.getOutput(prevout).getHash(); + if (options.bip91 != null) { + assert(typeof options.bip91 === 'boolean'); + this.bip91 = options.bip91; + } - if (!addr) - continue; + if (options.bip148 != null) { + assert(typeof options.bip148 === 'boolean'); + this.bip148 = options.bip148; + } - this.del(layout.C(addr, prevout.hash, prevout.index)); + if (options.prune != null) { + assert(typeof options.prune === 'boolean'); + this.prune = options.prune; } + + return this; } - for (let i = 0; i < tx.outputs.length; i++) { - const output = tx.outputs[i]; - const addr = output.getHash(); + static fromOptions(data) { + return new ChainFlags().fromOptions(data); + } - if (!addr) - continue; + toRaw() { + const bw = bio.write(12); - this.put(layout.C(addr, hash, i), null); - } -}; + let flags = 0; -/** - * Remove transaction from index. - * @private - * @param {TX} tx - * @param {CoinView} view - */ + if (this.spv) + flags |= 1 << 0; -ChainDB.prototype.unindexTX = function unindexTX(tx, view) { - const hash = tx.hash(); + if (this.witness) + flags |= 1 << 1; - if (this.options.indexTX) { - this.del(layout.t(hash)); - if (this.options.indexAddress) { - const hashes = tx.getHashes(view); - for (const addr of hashes) - this.del(layout.T(addr, hash)); - } - } + if (this.prune) + flags |= 1 << 2; - if (!this.options.indexAddress) - return; + if (this.bip91) + flags |= 1 << 5; - if (!tx.isCoinbase()) { - for (const {prevout} of tx.inputs) { - const addr = view.getOutput(prevout).getHash(); + if (this.bip148) + flags |= 1 << 6; - if (!addr) - continue; + bw.writeU32(this.network.magic); + bw.writeU32(flags); + bw.writeU32(0); - this.put(layout.C(addr, prevout.hash, prevout.index), null); - } + return bw.render(); } - for (let i = 0; i < tx.outputs.length; i++) { - const output = tx.outputs[i]; - const addr = output.getHash(); + fromRaw(data) { + const br = bio.read(data); - if (!addr) - continue; + this.network = Network.fromMagic(br.readU32()); + + const flags = br.readU32(); + + this.spv = (flags & 1) !== 0; + this.witness = (flags & 2) !== 0; + this.prune = (flags & 4) !== 0; + this.bip91 = (flags & 32) !== 0; + this.bip148 = (flags & 64) !== 0; + + return this; + } - this.del(layout.C(addr, hash, i)); + static fromRaw(data) { + return new ChainFlags().fromRaw(data); } -}; +} /** - * Chain Flags - * @alias module:blockchain.ChainFlags - * @constructor + * Chain State */ -function ChainFlags(options) { - if (!(this instanceof ChainFlags)) - return new ChainFlags(options); - - this.network = Network.primary; - this.spv = false; - this.witness = true; - this.bip91 = false; - this.bip148 = false; - this.prune = false; - this.indexTX = false; - this.indexAddress = false; - - if (options) - this.fromOptions(options); -} +class ChainState { + /** + * Create chain state. + * @alias module:blockchain.ChainState + * @constructor + */ -ChainFlags.prototype.fromOptions = function fromOptions(options) { - this.network = Network.get(options.network); - - if (options.spv != null) { - assert(typeof options.spv === 'boolean'); - this.spv = options.spv; + constructor() { + this.tip = consensus.ZERO_HASH; + this.tx = 0; + this.coin = 0; + this.value = 0; + this.committed = false; } - if (options.bip91 != null) { - assert(typeof options.bip91 === 'boolean'); - this.bip91 = options.bip91; + clone() { + const state = new ChainState(); + state.tip = this.tip; + state.tx = this.tx; + state.coin = this.coin; + state.value = this.value; + return state; } - if (options.bip148 != null) { - assert(typeof options.bip148 === 'boolean'); - this.bip148 = options.bip148; + connect(block) { + this.tx += block.txs.length; } - if (options.prune != null) { - assert(typeof options.prune === 'boolean'); - this.prune = options.prune; + disconnect(block) { + this.tx -= block.txs.length; } - if (options.indexTX != null) { - assert(typeof options.indexTX === 'boolean'); - this.indexTX = options.indexTX; + add(coin) { + this.coin += 1; + this.value += coin.value; } - if (options.indexAddress != null) { - assert(typeof options.indexAddress === 'boolean'); - this.indexAddress = options.indexAddress; + spend(coin) { + this.coin -= 1; + this.value -= coin.value; } - return this; -}; - -ChainFlags.fromOptions = function fromOptions(data) { - return new ChainFlags().fromOptions(data); -}; - -ChainFlags.prototype.toRaw = function toRaw() { - const bw = new StaticWriter(12); - let flags = 0; - - if (this.spv) - flags |= 1 << 0; - - if (this.witness) - flags |= 1 << 1; - - if (this.prune) - flags |= 1 << 2; - - if (this.indexTX) - flags |= 1 << 3; - - if (this.indexAddress) - flags |= 1 << 4; - - if (this.bip91) - flags |= 1 << 5; - - if (this.bip148) - flags |= 1 << 6; - - bw.writeU32(this.network.magic); - bw.writeU32(flags); - bw.writeU32(0); - - return bw.render(); -}; - -ChainFlags.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); - - this.network = Network.fromMagic(br.readU32()); + commit(hash) { + this.tip = hash; + this.committed = true; + return this.toRaw(); + } - const flags = br.readU32(); + toRaw() { + const bw = bio.write(64); + bw.writeHash(this.tip); + bw.writeU64(this.tx); + bw.writeU64(this.coin); + + var wholeCoinValue = Math.floor(this.value / Math.pow(10,8)) + var satoshiCoinValue = this.value - (wholeCoinValue * Math.pow(10,8)); + + bw.writeU64BE(wholeCoinValue); + bw.writeU64BE(satoshiCoinValue); + return bw.render(); + } - this.spv = (flags & 1) !== 0; - this.witness = (flags & 2) !== 0; - this.prune = (flags & 4) !== 0; - this.indexTX = (flags & 8) !== 0; - this.indexAddress = (flags & 16) !== 0; - this.bip91 = (flags & 32) !== 0; - this.bip148 = (flags & 64) !== 0; + static fromRaw(data) { + const state = new ChainState(); + const br = bio.read(data); + state.tip = br.readHash(); + state.tx = br.readU64(); + state.coin = br.readU64(); - return this; -}; + var wholeCoinValue = br.readU64BE(); + var satoshiCoinValue = br.readU64BE(); -ChainFlags.fromRaw = function fromRaw(data) { - return new ChainFlags().fromRaw(data); -}; + state.value = (wholeCoinValue * Math.pow(10,8)) + satoshiCoinValue; -/** - * Chain State - * @alias module:blockchain.ChainState - * @constructor - */ - -function ChainState() { - this.tip = encoding.NULL_HASH; - this.tx = 0; - this.coin = 0; - this.value = 0; - this.committed = false; + return state; + } } -ChainState.prototype.rhash = function rhash() { - return util.revHex(this.tip); -}; - -ChainState.prototype.clone = function clone() { - const state = new ChainState(); - state.tip = this.tip; - state.tx = this.tx; - state.coin = this.coin; - state.value = this.value; - return state; -}; - -ChainState.prototype.connect = function connect(block) { - this.tx += block.txs.length; -}; - -ChainState.prototype.disconnect = function disconnect(block) { - this.tx -= block.txs.length; -}; - -ChainState.prototype.add = function add(coin) { - this.coin++; - this.value += coin.value; -}; - -ChainState.prototype.spend = function spend(coin) { - this.coin--; - this.value -= coin.value; -}; - -ChainState.prototype.commit = function commit(hash) { - if (typeof hash !== 'string') - hash = hash.toString('hex'); - this.tip = hash; - this.committed = true; - return this.toRaw(); -}; - -ChainState.prototype.toRaw = function toRaw() { - const bw = new StaticWriter(64); - bw.writeHash(this.tip); - bw.writeU64(this.tx); - bw.writeU64(this.coin); - - var wholeCoinValue = Math.floor(this.value / Math.pow(10,8)) - - var satoshiCoinValue = this.value - (wholeCoinValue * Math.pow(10,8)); - - bw.writeU64BE(wholeCoinValue); - bw.writeU64BE(satoshiCoinValue); - return bw.render(); -}; - -ChainState.fromRaw = function fromRaw(data) { - const state = new ChainState(); - const br = new BufferReader(data); - state.tip = br.readHash('hex'); - state.tx = br.readU64(); - state.coin = br.readU64(); - - var wholeCoinValue = br.readU64BE(); - var satoshiCoinValue = br.readU64BE(); - - state.value = (wholeCoinValue * Math.pow(10,8)) + satoshiCoinValue; - - return state; -}; - /** - * StateCache - * @alias module:blockchain.StateCache - * @constructor + * State Cache */ -function StateCache(network) { - this.network = network; - this.bits = []; - this.updates = []; - this._init(); -} +class StateCache { + /** + * Create state cache. + * @alias module:blockchain.StateCache + * @constructor + */ -StateCache.prototype._init = function _init() { - for (let i = 0; i < 32; i++) - this.bits.push(null); + constructor(network) { + this.network = network; + this.bits = []; + this.updates = []; + this.init(); + } + + init() { + for (let i = 0; i < 32; i++) + this.bits.push(null); - for (const {bit} of this.network.deploys) { - assert(!this.bits[bit]); - this.bits[bit] = new Map(); + for (const {bit} of this.network.deploys) { + assert(!this.bits[bit]); + this.bits[bit] = new BufferMap(); + } } -}; -StateCache.prototype.set = function set(bit, entry, state) { - const cache = this.bits[bit]; + set(bit, entry, state) { + const cache = this.bits[bit]; - assert(cache); + assert(cache); - if (cache.get(entry.hash) !== state) { - cache.set(entry.hash, state); - this.updates.push(new CacheUpdate(bit, entry.hash, state)); + if (cache.get(entry.hash) !== state) { + cache.set(entry.hash, state); + this.updates.push(new CacheUpdate(bit, entry.hash, state)); + } } -}; -StateCache.prototype.get = function get(bit, entry) { - const cache = this.bits[bit]; + get(bit, entry) { + const cache = this.bits[bit]; - assert(cache); + assert(cache); + + const state = cache.get(entry.hash); - const state = cache.get(entry.hash); + if (state == null) + return -1; + + return state; + } - if (state == null) - return -1; + commit() { + this.updates.length = 0; + } - return state; -}; + drop() { + for (const {bit, hash} of this.updates) { + const cache = this.bits[bit]; + assert(cache); + cache.delete(hash); + } -StateCache.prototype.commit = function commit() { - this.updates.length = 0; -}; + this.updates.length = 0; + } -StateCache.prototype.drop = function drop() { - for (const {bit, hash} of this.updates) { + insert(bit, hash, state) { const cache = this.bits[bit]; assert(cache); - cache.delete(hash); + cache.set(hash, state); } - - this.updates.length = 0; -}; - -StateCache.prototype.insert = function insert(bit, hash, state) { - const cache = this.bits[bit]; - assert(cache); - cache.set(hash, state); -}; +} /** - * CacheUpdate - * @constructor - * @ignore + * Cache Update */ -function CacheUpdate(bit, hash, state) { - this.bit = bit; - this.hash = hash; - this.state = state; -} +class CacheUpdate { + /** + * Create cache update. + * @constructor + * @ignore + */ + + constructor(bit, hash, state) { + this.bit = bit; + this.hash = hash; + this.state = state; + } -CacheUpdate.prototype.toRaw = function toRaw() { - return U8(this.state); -}; + toRaw() { + const data = Buffer.allocUnsafe(1); + data[0] = this.state; + return data; + } +} /* * Helpers */ -function getSize(value) { - return value.length + 80; +function fromU32(num) { + const data = Buffer.allocUnsafe(4); + data.writeUInt32LE(num, 0, true); + return data; } /* diff --git a/lib/blockchain/chainentry.js b/lib/blockchain/chainentry.js index d621869be..4ad89f36d 100644 --- a/lib/blockchain/chainentry.js +++ b/lib/blockchain/chainentry.js @@ -7,31 +7,32 @@ 'use strict'; -const assert = require('assert'); -const BN = require('../crypto/bn'); +const assert = require('bsert'); +const bio = require('bufio'); +const BN = require('bcrypto/lib/bn.js'); const consensus = require('../protocol/consensus'); +const hash256 = require('bcrypto/lib/hash256'); const util = require('../utils/util'); -const digest = require('../crypto/digest'); -const encoding = require('../utils/encoding'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); const Headers = require('../primitives/headers'); const InvItem = require('../primitives/invitem'); +const {inspectSymbol} = require('../utils'); + +/* + * Constants + */ + const ZERO = new BN(0); /** + * Chain Entry * Represents an entry in the chain. Unlike * other bitcoin fullnodes, we store the * chainwork _with_ the entry in order to * avoid reading the entire chain index on * boot and recalculating the chainworks. * @alias module:blockchain.ChainEntry - * @constructor - * @param {Object?} options * @property {Hash} hash - * @property {Number} version - Transaction version. Note that Bcoin reads - * versions as unsigned even though they are signed at the protocol level. - * This value will never be negative. + * @property {Number} version * @property {Hash} prevBlock * @property {Hash} merkleRoot * @property {Number} time @@ -39,25 +40,331 @@ const ZERO = new BN(0); * @property {Number} nonce * @property {Number} height * @property {BN} chainwork - * @property {ReversedHash} rhash - Reversed block hash (uint256le). + * @property {Hash} rhash */ -function ChainEntry(options) { - if (!(this instanceof ChainEntry)) - return new ChainEntry(options); - - this.hash = encoding.NULL_HASH; - this.version = 1; - this.prevBlock = encoding.NULL_HASH; - this.merkleRoot = encoding.NULL_HASH; - this.time = 0; - this.bits = 0; - this.nonce = 0; - this.height = 0; - this.chainwork = ZERO; - - if (options) - this.fromOptions(options); +class ChainEntry { + /** + * Create a chain entry. + * @constructor + * @param {Object?} options + */ + + constructor(options) { + this.hash = consensus.ZERO_HASH; + this.version = 1; + this.prevBlock = consensus.ZERO_HASH; + this.merkleRoot = consensus.ZERO_HASH; + this.time = 0; + this.bits = 0; + this.nonce = 0; + this.height = 0; + this.chainwork = ZERO; + + if (options) + this.fromOptions(options); + } + + /** + * Inject properties from options. + * @private + * @param {Object} options + */ + + fromOptions(options) { + assert(options, 'Block data is required.'); + assert(Buffer.isBuffer(options.hash)); + assert((options.version >>> 0) === options.version); + assert(Buffer.isBuffer(options.prevBlock)); + assert(Buffer.isBuffer(options.merkleRoot)); + assert((options.time >>> 0) === options.time); + assert((options.bits >>> 0) === options.bits); + assert((options.nonce >>> 0) === options.nonce); + assert((options.height >>> 0) === options.height); + assert(!options.chainwork || BN.isBN(options.chainwork)); + + this.hash = options.hash; + this.version = options.version; + this.prevBlock = options.prevBlock; + this.merkleRoot = options.merkleRoot; + this.time = options.time; + this.bits = options.bits; + this.nonce = options.nonce; + this.height = options.height; + this.chainwork = options.chainwork || ZERO; + + return this; + } + + /** + * Instantiate chainentry from options. + * @param {Object} options + * @param {ChainEntry} prev - Previous entry. + * @returns {ChainEntry} + */ + + static fromOptions(options, prev) { + return new this().fromOptions(options, prev); + } + + /** + * Calculate the proof: (1 << 256) / (target + 1) + * @returns {BN} proof + */ + + getProof() { + const target = consensus.fromCompact(this.bits); + + if (target.isNeg() || target.isZero()) + return new BN(0); + + return ChainEntry.MAX_CHAINWORK.div(target.iaddn(1)); + } + + /** + * Calculate the chainwork by + * adding proof to previous chainwork. + * @returns {BN} chainwork + */ + + getChainwork(prev) { + const proof = this.getProof(); + + if (!prev) + return proof; + + return proof.iadd(prev.chainwork); + } + + /** + * Test against the genesis block. + * @returns {Boolean} + */ + + isGenesis() { + return this.height === 0; + } + + /** + * Test whether the entry contains an unknown version bit. + * @param {Network} network + * @returns {Boolean} + */ + + hasUnknown(network) { + const TOP_MASK = consensus.VERSION_TOP_MASK; + const TOP_BITS = consensus.VERSION_TOP_BITS; + const bits = (this.version & TOP_MASK) >>> 0; + + if (bits !== TOP_BITS) + return false; + + return (this.version & network.unknownBits) !== 0; + } + + /** + * Test whether the entry contains a version bit. + * @param {Number} bit + * @returns {Boolean} + */ + + hasBit(bit) { + return consensus.hasBit(this.version, bit); + } + + /** + * Get little-endian block hash. + * @returns {Hash} + */ + + rhash() { + return util.revHex(this.hash); + } + + /** + * Inject properties from block. + * @private + * @param {Block|MerkleBlock} block + * @param {ChainEntry} prev - Previous entry. + */ + + fromBlock(block, prev) { + this.hash = block.hash(); + this.version = block.version; + this.prevBlock = block.prevBlock; + this.merkleRoot = block.merkleRoot; + this.time = block.time; + this.bits = block.bits; + this.nonce = block.nonce; + this.height = prev ? prev.height + 1: 0; + this.chainwork = this.getChainwork(prev); + return this; + } + + /** + * Instantiate chainentry from block. + * @param {Block|MerkleBlock} block + * @param {ChainEntry} prev - Previous entry. + * @returns {ChainEntry} + */ + + static fromBlock(block, prev) { + return new this().fromBlock(block, prev); + } + + /** + * Serialize the entry to internal database format. + * @returns {Buffer} + */ + + toRaw() { + const bw = bio.write(116); + + bw.writeU32(this.version); + bw.writeHash(this.prevBlock); + bw.writeHash(this.merkleRoot); + bw.writeU32(this.time); + bw.writeU32(this.bits); + bw.writeU32(this.nonce); + bw.writeU32(this.height); + bw.writeBytes(this.chainwork.toArrayLike(Buffer, 'le', 32)); + + return bw.render(); + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + const br = bio.read(data, true); + const hash = hash256.digest(br.readBytes(80)); + + br.seek(-80); + + this.hash = hash; + this.version = br.readU32(); + this.prevBlock = br.readHash(); + this.merkleRoot = br.readHash(); + this.time = br.readU32(); + this.bits = br.readU32(); + this.nonce = br.readU32(); + this.height = br.readU32(); + this.chainwork = new BN(br.readBytes(32), 'le'); + + return this; + } + + /** + * Deserialize the entry. + * @param {Buffer} data + * @returns {ChainEntry} + */ + + static fromRaw(data) { + return new this().fromRaw(data); + } + + /** + * Serialize the entry to an object more + * suitable for JSON serialization. + * @returns {Object} + */ + + toJSON() { + return { + hash: util.revHex(this.hash), + version: this.version, + prevBlock: util.revHex(this.prevBlock), + merkleRoot: util.revHex(this.merkleRoot), + time: this.time, + bits: this.bits, + nonce: this.nonce, + height: this.height, + chainwork: this.chainwork.toString('hex', 64) + }; + } + + /** + * Inject properties from json object. + * @private + * @param {Object} json + */ + + fromJSON(json) { + assert(json, 'Block data is required.'); + assert(typeof json.hash === 'string'); + assert((json.version >>> 0) === json.version); + assert(typeof json.prevBlock === 'string'); + assert(typeof json.merkleRoot === 'string'); + assert((json.time >>> 0) === json.time); + assert((json.bits >>> 0) === json.bits); + assert((json.nonce >>> 0) === json.nonce); + assert(typeof json.chainwork === 'string'); + + this.hash = util.fromRev(json.hash); + this.version = json.version; + this.prevBlock = util.fromRev(json.prevBlock); + this.merkleRoot = util.fromRev(json.merkleRoot); + this.time = json.time; + this.bits = json.bits; + this.nonce = json.nonce; + this.height = json.height; + this.chainwork = new BN(json.chainwork, 'hex'); + + return this; + } + + /** + * Instantiate block from jsonified object. + * @param {Object} json + * @returns {ChainEntry} + */ + + static fromJSON(json) { + return new this().fromJSON(json); + } + + /** + * Convert the entry to a headers object. + * @returns {Headers} + */ + + toHeaders() { + return Headers.fromEntry(this); + } + + /** + * Convert the entry to an inv item. + * @returns {InvItem} + */ + + toInv() { + return new InvItem(InvItem.types.BLOCK, this.hash); + } + + /** + * Return a more user-friendly object. + * @returns {Object} + */ + + [inspectSymbol]() { + const json = this.toJSON(); + json.version = json.version.toString(16); + return json; + } + + /** + * Test whether an object is a {@link ChainEntry}. + * @param {Object} obj + * @returns {Boolean} + */ + + static isChainEntry(obj) { + return obj instanceof ChainEntry; + } } /** @@ -67,307 +374,6 @@ function ChainEntry(options) { ChainEntry.MAX_CHAINWORK = new BN(1).ushln(256); -/** - * Inject properties from options. - * @private - * @param {Object} options - */ - -ChainEntry.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Block data is required.'); - assert(typeof options.hash === 'string'); - assert(util.isU32(options.version)); - assert(typeof options.prevBlock === 'string'); - assert(typeof options.merkleRoot === 'string'); - assert(util.isU32(options.time)); - assert(util.isU32(options.bits)); - assert(util.isU32(options.nonce)); - assert(util.isU32(options.height)); - assert(!options.chainwork || BN.isBN(options.chainwork)); - - this.hash = options.hash; - this.version = options.version; - this.prevBlock = options.prevBlock; - this.merkleRoot = options.merkleRoot; - this.time = options.time; - this.bits = options.bits; - this.nonce = options.nonce; - this.height = options.height; - this.chainwork = options.chainwork || ZERO; - - return this; -}; - -/** - * Instantiate chainentry from options. - * @param {Object} options - * @param {ChainEntry} prev - Previous entry. - * @returns {ChainEntry} - */ - -ChainEntry.fromOptions = function fromOptions(options, prev) { - return new ChainEntry().fromOptions(options, prev); -}; - -/** - * Calculate the proof: (1 << 256) / (target + 1) - * @returns {BN} proof - */ - -ChainEntry.prototype.getProof = function getProof() { - const target = consensus.fromCompact(this.bits); - - if (target.isNeg() || target.isZero()) - return new BN(0); - - return ChainEntry.MAX_CHAINWORK.div(target.iaddn(1)); -}; - -/** - * Calculate the chainwork by - * adding proof to previous chainwork. - * @returns {BN} chainwork - */ - -ChainEntry.prototype.getChainwork = function getChainwork(prev) { - const proof = this.getProof(); - - if (!prev) - return proof; - - return proof.iadd(prev.chainwork); -}; - -/** - * Test against the genesis block. - * @returns {Boolean} - */ - -ChainEntry.prototype.isGenesis = function isGenesis() { - return this.height === 0; -}; - -/** - * Test whether the entry contains an unknown version bit. - * @param {Network} network - * @returns {Boolean} - */ - -ChainEntry.prototype.hasUnknown = function hasUnknown(network) { - const TOP_MASK = consensus.VERSION_TOP_MASK; - const TOP_BITS = consensus.VERSION_TOP_BITS; - const bits = (this.version & TOP_MASK) >>> 0; - - if (bits !== TOP_BITS) - return false; - - return (this.version & network.unknownBits) !== 0; -}; - -/** - * Test whether the entry contains a version bit. - * @param {Number} bit - * @returns {Boolean} - */ - -ChainEntry.prototype.hasBit = function hasBit(bit) { - return consensus.hasBit(this.version, bit); -}; - -/** - * Get little-endian block hash. - * @returns {Hash} - */ - -ChainEntry.prototype.rhash = function rhash() { - return util.revHex(this.hash); -}; - -/** - * Inject properties from block. - * @private - * @param {Block|MerkleBlock} block - * @param {ChainEntry} prev - Previous entry. - */ - -ChainEntry.prototype.fromBlock = function fromBlock(block, prev) { - this.hash = block.hash('hex'); - this.version = block.version; - this.prevBlock = block.prevBlock; - this.merkleRoot = block.merkleRoot; - this.time = block.time; - this.bits = block.bits; - this.nonce = block.nonce; - this.height = prev ? prev.height + 1: 0; - this.chainwork = this.getChainwork(prev); - return this; -}; - -/** - * Instantiate chainentry from block. - * @param {Block|MerkleBlock} block - * @param {ChainEntry} prev - Previous entry. - * @returns {ChainEntry} - */ - -ChainEntry.fromBlock = function fromBlock(block, prev) { - return new ChainEntry().fromBlock(block, prev); -}; - -/** - * Serialize the entry to internal database format. - * @returns {Buffer} - */ - -ChainEntry.prototype.toRaw = function toRaw() { - const bw = new StaticWriter(116); - - bw.writeU32(this.version); - bw.writeHash(this.prevBlock); - bw.writeHash(this.merkleRoot); - bw.writeU32(this.time); - bw.writeU32(this.bits); - bw.writeU32(this.nonce); - bw.writeU32(this.height); - bw.writeBytes(this.chainwork.toArrayLike(Buffer, 'le', 32)); - - return bw.render(); -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -ChainEntry.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); - const hash = digest.hash256(br.readBytes(80)); - - br.seek(-80); - - this.hash = hash.toString('hex'); - this.version = br.readU32(); - this.prevBlock = br.readHash('hex'); - this.merkleRoot = br.readHash('hex'); - this.time = br.readU32(); - this.bits = br.readU32(); - this.nonce = br.readU32(); - this.height = br.readU32(); - this.chainwork = new BN(br.readBytes(32), 'le'); - - return this; -}; - -/** - * Deserialize the entry. - * @param {Buffer} data - * @returns {ChainEntry} - */ - -ChainEntry.fromRaw = function fromRaw(data) { - return new ChainEntry().fromRaw(data); -}; - -/** - * Serialize the entry to an object more - * suitable for JSON serialization. - * @returns {Object} - */ - -ChainEntry.prototype.toJSON = function toJSON() { - return { - hash: util.revHex(this.hash), - version: this.version, - prevBlock: util.revHex(this.prevBlock), - merkleRoot: util.revHex(this.merkleRoot), - time: this.time, - bits: this.bits, - nonce: this.nonce, - height: this.height, - chainwork: this.chainwork.toString('hex', 64) - }; -}; - -/** - * Inject properties from json object. - * @private - * @param {Object} json - */ - -ChainEntry.prototype.fromJSON = function fromJSON(json) { - assert(json, 'Block data is required.'); - assert(typeof json.hash === 'string'); - assert(util.isU32(json.version)); - assert(typeof json.prevBlock === 'string'); - assert(typeof json.merkleRoot === 'string'); - assert(util.isU32(json.time)); - assert(util.isU32(json.bits)); - assert(util.isU32(json.nonce)); - assert(typeof json.chainwork === 'string'); - - this.hash = util.revHex(json.hash); - this.version = json.version; - this.prevBlock = util.revHex(json.prevBlock); - this.merkleRoot = util.revHex(json.merkleRoot); - this.time = json.time; - this.bits = json.bits; - this.nonce = json.nonce; - this.height = json.height; - this.chainwork = new BN(json.chainwork, 'hex'); - - return this; -}; - -/** - * Instantiate block from jsonified object. - * @param {Object} json - * @returns {ChainEntry} - */ - -ChainEntry.fromJSON = function fromJSON(json) { - return new ChainEntry().fromJSON(json); -}; - -/** - * Convert the entry to a headers object. - * @returns {Headers} - */ - -ChainEntry.prototype.toHeaders = function toHeaders() { - return Headers.fromEntry(this); -}; - -/** - * Convert the entry to an inv item. - * @returns {InvItem} - */ - -ChainEntry.prototype.toInv = function toInv() { - return new InvItem(InvItem.types.BLOCK, this.hash); -}; - -/** - * Return a more user-friendly object. - * @returns {Object} - */ - -ChainEntry.prototype.inspect = function inspect() { - const json = this.toJSON(); - json.version = util.hex32(json.version); - return json; -}; - -/** - * Test whether an object is a {@link ChainEntry}. - * @param {Object} obj - * @returns {Boolean} - */ - -ChainEntry.isChainEntry = function isChainEntry(obj) { - return obj instanceof ChainEntry; -}; - /* * Expose */ diff --git a/lib/blockchain/layout-browser.js b/lib/blockchain/layout-browser.js deleted file mode 100644 index 701a627b8..000000000 --- a/lib/blockchain/layout-browser.js +++ /dev/null @@ -1,119 +0,0 @@ -/*! - * layout-browser.js - chaindb layout for browser. - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const util = require('../utils/util'); -const pad8 = util.pad8; -const pad32 = util.pad32; - -const layout = { - binary: false, - R: 'R', - O: 'O', - V: 'v', - e: function e(hash) { - return 'e' + hex(hash); - }, - h: function h(hash) { - return 'h' + hex(hash); - }, - H: function H(height) { - return 'H' + pad32(height); - }, - n: function n(hash) { - return 'n' + hex(hash); - }, - p: function p(hash) { - return 'p' + hex(hash); - }, - b: function b(hash) { - return 'b' + hex(hash); - }, - t: function t(hash) { - return 't' + hex(hash); - }, - c: function c(hash, index) { - return 'c' + hex(hash) + pad32(index); - }, - u: function u(hash) { - return 'u' + hex(hash); - }, - v: function v(bit, hash) { - return 'v' + pad8(bit) + hex(hash); - }, - vv: function vv(key) { - assert(typeof key === 'string'); - assert(key.length === 36); - return [parseInt(key.slice(1, 4), 10), key.slice(4, 36)]; - }, - T: function T(addr, hash) { - addr = hex(addr); - - if (addr.length === 64) - return 'W' + addr + hex(hash); - - assert(addr.length === 40); - return 'T' + addr + hex(hash); - }, - C: function C(addr, hash, index) { - addr = hex(addr); - - if (addr.length === 64) - return 'X' + addr + hex(hash) + pad32(index); - - assert(addr.length === 40); - return 'C' + addr + hex(hash) + pad32(index); - }, - pp: function pp(key) { - assert(typeof key === 'string'); - assert(key.length === 65); - return key.slice(1, 65); - }, - Cc: function Cc(key) { - assert(typeof key === 'string'); - - let hash, index; - if (key.length === 139) { - hash = key.slice(65, 129); - index = parseInt(key.slice(129), 10); - } else if (key.length === 115) { - hash = key.slice(41, 105); - index = parseInt(key.slice(105), 10); - } else { - assert(false); - } - - return [hash, index]; - }, - Tt: function Tt(key) { - assert(typeof key === 'string'); - - if (key.length === 129) - return key.slice(64); - - assert(key.length === 105); - return key.slice(41); - } -}; - -/* - * Helpers - */ - -function hex(hash) { - if (Buffer.isBuffer(hash)) - hash = hash.toString('hex'); - assert(typeof hash === 'string'); - return hash; -} - -/* - * Expose - */ - -module.exports = layout; diff --git a/lib/blockchain/layout.js b/lib/blockchain/layout.js index 5a232962e..337f95900 100644 --- a/lib/blockchain/layout.js +++ b/lib/blockchain/layout.js @@ -6,191 +6,47 @@ 'use strict'; -const assert = require('assert'); +const bdb = require('bdb'); /* * Database Layout: - * R -> tip hash + * V -> db version * O -> chain options + * R -> tip hash + * D -> versionbits deployments * e[hash] -> entry * h[hash] -> height * H[height] -> hash * n[hash] -> next hash * p[hash] -> tip index - * b[hash] -> block - * t[hash] -> extended tx + * b[hash] -> block (deprecated) + * t[hash] -> extended tx (deprecated) * c[hash] -> coins - * u[hash] -> undo coins - * v -> versionbits deployments + * u[hash] -> undo coins (deprecated) * v[bit][hash] -> versionbits state - * T[addr-hash][hash] -> dummy (tx by address) - * C[addr-hash][hash][index] -> dummy (coin by address) - * W+T[witaddr-hash][hash] -> dummy (tx by address) - * W+C[witaddr-hash][hash][index] -> dummy (coin by address) + * T[addr-hash][hash] -> dummy (tx by address) (deprecated) + * C[addr-hash][hash][index] -> dummy (coin by address) (deprecated) */ const layout = { - binary: true, - R: Buffer.from([0x52]), - O: Buffer.from([0x4f]), - V: Buffer.from([0x76]), - e: function e(hash) { - return pair(0x65, hash); - }, - h: function h(hash) { - return pair(0x68, hash); - }, - H: function H(height) { - return ipair(0x48, height); - }, - n: function n(hash) { - return pair(0x6e, hash); - }, - p: function p(hash) { - return pair(0x70, hash); - }, - b: function b(hash) { - return pair(0x62, hash); - }, - t: function t(hash) { - return pair(0x74, hash); - }, - c: function c(hash, index) { - return bpair(0x63, hash, index); - }, - u: function u(hash) { - return pair(0x75, hash); - }, - v: function v(bit, hash) { - const key = Buffer.allocUnsafe(1 + 1 + 32); - assert(typeof bit === 'number'); - key[0] = 0x76; - key[1] = bit; - write(key, hash, 2); - return key; - }, - vv: function vv(key) { - assert(Buffer.isBuffer(key)); - assert(key.length === 34); - return [key[1], key.toString('hex', 2, 34)]; - }, - T: function T(addr, hash) { - let len = addr.length; - - if (typeof addr === 'string') - len /= 2; - - let key; - if (len === 32) { - key = Buffer.allocUnsafe(65); - key[0] = 0xab; // W + T - write(key, addr, 1); - write(key, hash, 33); - } else if (len === 20) { - key = Buffer.allocUnsafe(53); - key[0] = 0x54; // T - write(key, addr, 1); - write(key, hash, 21); - } else { - assert(false); - } - - return key; - }, - C: function C(addr, hash, index) { - let len = addr.length; - - assert(typeof index === 'number'); - - if (typeof addr === 'string') - len /= 2; - - let key; - if (len === 32) { - key = Buffer.allocUnsafe(69); - key[0] = 0x9a; // W + C - write(key, addr, 1); - write(key, hash, 33); - key.writeUInt32BE(index, 65, true); - } else if (len === 20) { - key = Buffer.allocUnsafe(57); - key[0] = 0x43; // C - write(key, addr, 1); - write(key, hash, 21); - key.writeUInt32BE(index, 53, true); - } else { - assert(false); - } - - return key; - }, - pp: function pp(key) { - assert(Buffer.isBuffer(key)); - assert(key.length === 33); - return key.toString('hex', 1, 33); - }, - Cc: function Cc(key) { - assert(Buffer.isBuffer(key)); - - let hash, index; - if (key.length === 69) { - hash = key.toString('hex', 33, 65); - index = key.readUInt32BE(65, 0); - } else if (key.length === 57) { - hash = key.toString('hex', 21, 53); - index = key.readUInt32BE(53, 0); - } else { - assert(false); - } - - return [hash, index]; - }, - Tt: function Tt(key) { - assert(Buffer.isBuffer(key)); - - if (key.length === 65) - return key.toString('hex', 33, 65); - - assert(key.length === 53); - return key.toString('hex', 21, 53); - } + V: bdb.key('V'), + O: bdb.key('O'), + R: bdb.key('R'), + D: bdb.key('D'), + e: bdb.key('e', ['hash256']), + h: bdb.key('h', ['hash256']), + H: bdb.key('H', ['uint32']), + n: bdb.key('n', ['hash256']), + p: bdb.key('p', ['hash256']), + b: bdb.key('b', ['hash256']), + t: bdb.key('t', ['hash256']), + c: bdb.key('c', ['hash256', 'uint32']), + u: bdb.key('u', ['hash256']), + v: bdb.key('v', ['uint8', 'hash256']), + T: bdb.key('T', ['hash', 'hash256']), + C: bdb.key('C', ['hash', 'hash256', 'uint32']) }; -/* - * Helpers - */ - -function write(data, str, off) { - if (Buffer.isBuffer(str)) - return str.copy(data, off); - assert(typeof str === 'string'); - return data.write(str, off, 'hex'); -} - -function pair(prefix, hash) { - const key = Buffer.allocUnsafe(33); - key[0] = prefix; - write(key, hash, 1); - return key; -} - -function ipair(prefix, num) { - const key = Buffer.allocUnsafe(5); - assert(typeof num === 'number'); - key[0] = prefix; - key.writeUInt32BE(num, 1, true); - return key; -} - -function bpair(prefix, hash, index) { - const key = Buffer.allocUnsafe(37); - assert(typeof index === 'number'); - key[0] = prefix; - write(key, hash, 1); - key.writeUInt32BE(index, 33, true); - return key; -} - /* * Expose */ diff --git a/lib/blockstore/README.md b/lib/blockstore/README.md new file mode 100644 index 000000000..a8b35b19c --- /dev/null +++ b/lib/blockstore/README.md @@ -0,0 +1,148 @@ +# BlockStore + +BlockStore `lib/blockstore` is a bcoin module intended to be used as a backend +for storing block and undo coin data. It includes a backend that uses flat +files for storage. Its key benefit is performance improvements across the +board in disk I/O, which is the major bottleneck for the initial block sync. + +Blocks are stored in wire format directly to the disk, while some additional +metadata is stored in a key-value store, i.e. LevelDB, to help with the data +management. Both the flat files and the metadata db, are exposed through a +unified interace so that the users can simply read and write blocks without +having to worry about managing data layout on the disk. + +In addition to blocks, undo coin data, which is used to revert the changes +applied by a block (in case of a re-org), is also stored on disk, in a similar +fashion. + +## Interface + +The `AbstractBlockStore` interface defines the following abstract methods to be +defined by concrete implementations: + +### Basic housekeeping + +* `ensure()` +* `open()` +* `close()` + +### Block I/O + +* `read(hash, offset, size)` +* `write(hash, data)` +* `prune(hash)` +* `has(hash)` + +### Undo Coins I/O + +* `readUndo(hash)` +* `writeUndo(hash, data)` +* `pruneUndo(hash)` +* `hasUndo(hash)` + +The interface is implemented by `FileBlockStore` and `LevelBlockStore`, backed +by flat files and LevelDB respectively. We will focus here on the +`FileBlockStore`, which is the backend that implements a flat file based +storage. + +## FileBlockStore + +`FileBlockStore` implements the flat file backend for `AbstractBlockStore`. As +the name suggests, it uses flat files for block/undo data and LevelDB for +metadata. + +Let's create a file blockstore, write a block and walk-through the disk storage: + +```js +// nodejs +const store = blockstore.create({ + network: 'regtest', + prefix: '/tmp/blockstore' +}); +await store.ensure(); +await store.open(); +await store.write(hash, block); +``` + +```sh +// shell +tree /tmp/blockstore/ +/tmp/blockstore/ +└── blocks + ├── blk00000.dat + └── index + ├── LOG + ... +``` + +As we can see, the store writes to the file `blk00000.dat` in +`/tmp/blockstore/blocks/`, and the metadata is written to +`/tmp/blockstore/index`. + +Raw blocks are written to the disk in flat files named `blkXXXXX.dat`, where +`XXXXX` is the number of file being currently written, starting at +`blk00000.dat`. We store the file number as an integer in the metadata db, +expanding the digits to five places. + +The metadata db key `layout.F` tracks the last file used for writing. Each +file in turn tracks the number of blocks in it, the number of bytes used and +its max length. This data is stored in the db key `layout.f`. + + f['block'][0] => [1, 5, 128] // blk00000.dat: 1 block written, 5 bytes used, 128 bytes length + F['block'] => 0 // writing to file blk00000.dat + +Each raw block data is preceded by a magic marker defined as follows, to help +identify data written by us: + + magic (8 bytes) = network.magic (4 bytes) + block data length (4 bytes) + +For raw undo block data, the hash of the block is also included: + + magic (40 bytes) = network.magic (4 bytes) + length (4 bytes) + hash (32 bytes) + +But a marker alone is not sufficient to track the data we write to the files. +For each block we write, we need to store a pointer to the position in the file +where to start reading, and the size of the data we need to seek. This data is +stored in the metadata db using the key `layout.b`: + + b['block']['hash'] => [0, 8, 285] // 'hash' points to file blk00000.dat, position 8, size 285 + +Using this we know that our block is in `blk00000.dat`, bytes 8 through 293 and its size +is 285 bytes. + +Note that the position indicates that the block data is preceded by 8 bytes of +the magic marker. + + +Examples: + +> `store.write('hash', 'block')` + + blk00000: + 0xfabfb5da05000000 block + + index: + b['block']['hash'] => [0, 8, 5] + f['block'][0] => [1, 13, 128] + F['block'] => 0 + +> `store.write('hash1', 'block1')` + + blk00000: + 0xfabfb5da05000000 block 0xfabfb5da06000000 block1 + + index: + b['block']['hash'] => [0, 8, 5] + b['block']['hash1'] => [0, 13, 6] + f['block'][0] => [2, 19, 128] + F['block'] => 0 + +> `store.prune('hash1', 'block1')` + + blk00000: + 0xfabfb5da05000000 block 0xfabfb5da06000000 block1 + + index: + b['block']['hash'] => [0, 8, 5] + f['block'][0] => [1, 19, 128] + F['block'] => 0 diff --git a/lib/blockstore/abstract.js b/lib/blockstore/abstract.js new file mode 100644 index 000000000..efde8bd5b --- /dev/null +++ b/lib/blockstore/abstract.js @@ -0,0 +1,142 @@ +/*! + * blockstore/abstract.js - abstract blockstore for bcoin + * Copyright (c) 2019, Braydon Fuller (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const Logger = require('blgr'); + +/** + * Abstract Block Store + * + * @alias module:blockstore.AbstractBlockStore + * @abstract + */ + +class AbstractBlockStore { + /** + * Create an abstract blockstore. + * @constructor + */ + + constructor(options) { + this.options = options || {}; + + if (this.options.logger != null) + this.logger = this.options.logger.context('blockstore'); + else + this.logger = Logger.global.context('blockstore'); + } + + /** + * This method ensures that resources are available + * before opening. + * @returns {Promise} + */ + + async ensure() { + throw new Error('Abstract method.'); + } + + /** + * This method opens any necessary resources and + * initializes the store to be ready to be queried. + * @returns {Promise} + */ + + async open() { + throw new Error('Abstract method.'); + } + + /** + * This method closes resources and prepares + * the store to be closed. + * @returns {Promise} + */ + + async close() { + throw new Error('Abstract method.'); + } + + /** + * This method stores block undo coin data. + * @returns {Promise} + */ + + async writeUndo(hash, data) { + throw new Error('Abstract method.'); + } + + /** + * This method stores block data. + * @returns {Promise} + */ + + async write(hash, data) { + throw new Error('Abstract method.'); + } + + /** + * This method will retrieve block undo coin data. + * @returns {Promise} + */ + + async readUndo(hash) { + throw new Error('Abstract method.'); + } + + /** + * This method will retrieve block data. Smaller portions of + * the block can be read by using the offset and size arguments. + * @returns {Promise} + */ + + async read(hash, offset, size) { + throw new Error('Abstract method.'); + } + + /** + * This will free resources for storing the block undo coin data. + * @returns {Promise} + */ + + async pruneUndo(hash) { + throw new Error('Abstract method.'); + } + + /** + * This will free resources for storing the block data. + * @returns {Promise} + */ + + async prune(hash) { + throw new Error('Abstract method.'); + } + + /** + * This will check if a block undo coin data has been stored + * and is available. + * @returns {Promise} + */ + + async hasUndo(hash) { + throw new Error('Abstract method.'); + } + + /** + * This will check if a block has been stored and is available. + * @returns {Promise} + */ + + async has(hash) { + throw new Error('Abstract method.'); + } +} + +/* + * Expose + */ + +module.exports = AbstractBlockStore; diff --git a/lib/blockstore/common.js b/lib/blockstore/common.js new file mode 100644 index 000000000..f1cb310fa --- /dev/null +++ b/lib/blockstore/common.js @@ -0,0 +1,31 @@ +/*! + * common.js - blockstore constants for bcoin + * Copyright (c) 2019, Braydon Fuller (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +/** + * @module blockstore/common + */ + +/** + * Block data types. + * @enum {Number} + */ + +exports.types = { + BLOCK: 1, + UNDO: 2 +}; + +/** + * File prefixes for block data types. + * @enum {String} + */ + +exports.prefixes = { + 1: 'blk', + 2: 'blu' +}; diff --git a/lib/blockstore/file.js b/lib/blockstore/file.js new file mode 100644 index 000000000..027333363 --- /dev/null +++ b/lib/blockstore/file.js @@ -0,0 +1,590 @@ +/*! + * blockstore/file.js - file blockstore for bcoin + * Copyright (c) 2019, Braydon Fuller (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const {isAbsolute, resolve, join} = require('path'); +const bdb = require('bdb'); +const assert = require('bsert'); +const fs = require('bfile'); +const bio = require('bufio'); +const hash256 = require('bcrypto/lib/hash256'); +const Network = require('../protocol/network'); +const AbstractBlockStore = require('./abstract'); +const {BlockRecord, FileRecord} = require('./records'); +const layout = require('./layout'); +const {types, prefixes} = require('./common'); + +/** + * File Block Store + * + * @alias module:blockstore:FileBlockStore + * @abstract + */ + +class FileBlockStore extends AbstractBlockStore { + /** + * Create a blockstore that stores blocks in files. + * @constructor + */ + + constructor(options) { + super(options); + + assert(isAbsolute(options.location), 'Location not absolute.'); + + this.location = options.location; + this.indexLocation = resolve(this.location, './index'); + + this.db = bdb.create({ + location: this.indexLocation, + cacheSize: options.cacheSize, + compression: false + }); + + this.maxFileLength = options.maxFileLength || 128 * 1024 * 1024; + + assert(Number.isSafeInteger(this.maxFileLength), + 'Invalid max file length.'); + + this.network = Network.primary; + + if (options.network != null) + this.network = Network.get(options.network); + + this.writing = false; + } + + /** + * Compares the number of files in the directory + * with the recorded number of files. + * @param {Number} type - The type of block data + * @private + * @returns {Promise} + */ + + async check(type) { + const prefix = prefixes[type]; + const regexp = new RegExp(`^${prefix}(\\d{5})\\.dat$`); + const all = await fs.readdir(this.location); + const dats = all.filter(f => regexp.test(f)); + const filenos = dats.map(f => parseInt(f.match(regexp)[1])); + + let missing = false; + + for (const fileno of filenos) { + const rec = await this.db.get(layout.f.encode(type, fileno)); + if (!rec) { + missing = true; + break; + } + } + + return {missing, filenos}; + } + + /** + * Creates indexes from files for a block type. Reads the hash of + * the block data from the magic prefix, except for a block which + * the hash is read from the block header. + * @private + * @param {Number} type - The type of block data + * @returns {Promise} + */ + + async _index(type) { + const {missing, filenos} = await this.check(type); + + if (!missing) + return; + + this.logger.info('Indexing block type %d...', type); + + for (const fileno of filenos) { + const b = this.db.batch(); + const filepath = this.filepath(type, fileno); + const data = await fs.readFile(filepath); + const reader = bio.read(data); + let magic = null; + let blocks = 0; + + while (reader.left() >= 4) { + magic = reader.readU32(); + + // Move forward a byte from the last read + // if the magic doesn't match. + if (magic !== this.network.magic) { + reader.seek(-3); + continue; + } + + let hash = null; + let position = 0; + let length = 0; + + try { + length = reader.readU32(); + + if (type === types.BLOCK) { + position = reader.offset; + hash = hash256.digest(reader.readBytes(80, true)); + reader.seek(length - 80); + } else { + hash = reader.readHash(); + position = reader.offset; + reader.seek(length); + } + } catch (err) { + this.logger.warning( + 'Unknown block in file: %s, reason: %s', + filepath, err.message); + continue; + } + + const blockrecord = new BlockRecord({ + file: fileno, + position: position, + length: length + }); + + blocks += 1; + b.put(layout.b.encode(type, hash), blockrecord.toRaw()); + } + + const filerecord = new FileRecord({ + blocks: blocks, + used: reader.offset, + length: this.maxFileLength + }); + + b.put(layout.f.encode(type, fileno), filerecord.toRaw()); + + await b.write(); + + this.logger.info('Indexed %d blocks (file=%s).', blocks, filepath); + } + } + + /** + * Compares the number of files in the directory + * with the recorded number of files. If there are any + * inconsistencies it will reindex all blocks. + * @private + * @returns {Promise} + */ + + async index() { + await this._index(types.BLOCK); + await this._index(types.UNDO); + } + + /** + * This method ensures that both the block storage directory + * and index directory exist. + * before opening. + * @returns {Promise} + */ + + async ensure() { + return fs.mkdirp(this.indexLocation); + } + + /** + * Opens the file block store. It will regenerate necessary block + * indexing if the index is missing or inconsistent. + * @returns {Promise} + */ + + async open() { + this.logger.info('Opening FileBlockStore...'); + + await this.db.open(); + await this.db.verify(layout.V.encode(), 'fileblockstore', 0); + + await this.index(); + } + + /** + * This closes the file block store and underlying + * indexing databases. + */ + + async close() { + this.logger.info('Closing FileBlockStore...'); + + await this.db.close(); + } + + /** + * This method will determine the file path based on the file number + * and the current block data location. + * @private + * @param {Number} type - The type of block data + * @param {Number} fileno - The number of the file. + * @returns {Promise} + */ + + filepath(type, fileno) { + const pad = 5; + + let num = fileno.toString(10); + + if (num.length > pad) + throw new Error('File number too large.'); + + while (num.length < pad) + num = `0${num}`; + + let filepath = null; + + const prefix = prefixes[type]; + + if (!prefix) + throw new Error('Unknown file prefix.'); + + filepath = join(this.location, `${prefix}${num}.dat`); + + return filepath; + } + + /** + * This method will select and potentially allocate a file to + * write a block based on the size and type. + * @private + * @param {Number} type - The type of block data + * @param {Number} length - The number of bytes + * @returns {Promise} + */ + + async allocate(type, length) { + if (length > this.maxFileLength) + throw new Error('Block length above max file length.'); + + let fileno = 0; + let filerecord = null; + let filepath = null; + + const last = await this.db.get(layout.F.encode(type)); + if (last) + fileno = bio.readU32(last, 0); + + filepath = this.filepath(type, fileno); + + const rec = await this.db.get(layout.f.encode(type, fileno)); + + let touch = false; + + if (rec) { + filerecord = FileRecord.fromRaw(rec); + } else { + touch = true; + filerecord = new FileRecord({ + blocks: 0, + used: 0, + length: this.maxFileLength + }); + } + + if (filerecord.used + length > filerecord.length) { + fileno += 1; + filepath = this.filepath(type, fileno); + touch = true; + filerecord = new FileRecord({ + blocks: 0, + used: 0, + length: this.maxFileLength + }); + } + + if (touch) { + const fd = await fs.open(filepath, 'w'); + await fs.close(fd); + } + + return {fileno, filerecord, filepath}; + } + + /** + * This method stores block undo coin data in files. + * @param {Buffer} hash - The block hash + * @param {Buffer} data - The block data + * @returns {Promise} + */ + + async writeUndo(hash, data) { + return this._write(types.UNDO, hash, data); + } + + /** + * This method stores block data in files. + * @param {Buffer} hash - The block hash + * @param {Buffer} data - The block data + * @returns {Promise} + */ + + async write(hash, data) { + return this._write(types.BLOCK, hash, data); + } + + /** + * This method stores block data in files with by appending + * data to the last written file and updating indexes to point + * to the file and position. + * @private + * @param {Number} type - The type of block data + * @param {Buffer} hash - The block hash + * @param {Buffer} data - The block data + * @returns {Promise} + */ + + async _write(type, hash, data) { + if (this.writing) + throw new Error('Already writing.'); + + this.writing = true; + + if (await this.db.has(layout.b.encode(type, hash))) { + this.writing = false; + return false; + } + + let mlength = 8; + + // Hash for a block is not stored with + // the magic prefix as it's read from the header + // of the block data. + if (type !== types.BLOCK) + mlength += 32; + + const blength = data.length; + const length = data.length + mlength; + + const bwm = bio.write(mlength); + + bwm.writeU32(this.network.magic); + bwm.writeU32(blength); + + if (type !== types.BLOCK) + bwm.writeHash(hash); + + const magic = bwm.render(); + + const { + fileno, + filerecord, + filepath + } = await this.allocate(type, length); + + const mposition = filerecord.used; + const bposition = filerecord.used + mlength; + + const fd = await fs.open(filepath, 'r+'); + + let mwritten = 0; + let bwritten = 0; + + try { + mwritten = await fs.write(fd, magic, 0, mlength, mposition); + bwritten = await fs.write(fd, data, 0, blength, bposition); + } finally { + await fs.close(fd); + } + + if (mwritten !== mlength) { + this.writing = false; + throw new Error('Could not write block magic.'); + } + + if (bwritten !== blength) { + this.writing = false; + throw new Error('Could not write block.'); + } + + filerecord.blocks += 1; + filerecord.used += length; + + const b = this.db.batch(); + + const blockrecord = new BlockRecord({ + file: fileno, + position: bposition, + length: blength + }); + + b.put(layout.b.encode(type, hash), blockrecord.toRaw()); + b.put(layout.f.encode(type, fileno), filerecord.toRaw()); + + const last = bio.write(4).writeU32(fileno).render(); + b.put(layout.F.encode(type), last); + + await b.write(); + + this.writing = false; + + return true; + } + + /** + * This method will retrieve block undo coin data. + * @param {Buffer} hash - The block hash + * @returns {Promise} + */ + + async readUndo(hash) { + return this._read(types.UNDO, hash); + } + + /** + * This method will retrieve block data. Smaller portions of the + * block (e.g. transactions) can be read by using the offset and + * length arguments. + * @param {Buffer} hash - The block hash + * @param {Number} offset - The offset within the block + * @param {Number} length - The number of bytes of the data + * @returns {Promise} + */ + + async read(hash, offset, length) { + return this._read(types.BLOCK, hash, offset, length); + } + + /** + * This methods reads data from disk by retrieving the index of + * the data and reading from the corresponding file and location. + * @private + * @param {Number} type - The type of block data + * @param {Buffer} hash - The block hash + * @param {Number} offset - The offset within the block + * @param {Number} length - The number of bytes of the data + * @returns {Promise} + */ + + async _read(type, hash, offset, length) { + const raw = await this.db.get(layout.b.encode(type, hash)); + if (!raw) + return null; + + const blockrecord = BlockRecord.fromRaw(raw); + + const filepath = this.filepath(type, blockrecord.file); + + let position = blockrecord.position; + + if (offset) + position += offset; + + if (!length) + length = blockrecord.length; + + if (offset + length > blockrecord.length) + throw new Error('Out-of-bounds read.'); + + const data = Buffer.alloc(length); + + const fd = await fs.open(filepath, 'r'); + let bytes = 0; + + try { + bytes = await fs.read(fd, data, 0, length, position); + } finally { + await fs.close(fd); + } + + if (bytes !== length) + throw new Error('Wrong number of bytes read.'); + + return data; + } + + /** + * This will free resources for storing the block undo coin data. + * @param {Buffer} hash - The block hash + * @returns {Promise} + */ + + async pruneUndo(hash) { + return this._prune(types.UNDO, hash); + } + + /** + * This will free resources for storing the block data. + * @param {Buffer} hash - The block hash + * @returns {Promise} + */ + + async prune(hash) { + return this._prune(types.BLOCK, hash); + } + + /** + * This will free resources for storing the block data. The block + * data may not be deleted from disk immediately, the index for the + * block is removed and will not be able to be read. The underlying + * file is unlinked when all blocks in a file have been pruned. + * @private + * @param {Buffer} hash - The block hash + * @returns {Promise} + */ + + async _prune(type, hash) { + const braw = await this.db.get(layout.b.encode(type, hash)); + if (!braw) + return false; + + const blockrecord = BlockRecord.fromRaw(braw); + + const fraw = await this.db.get(layout.f.encode(type, blockrecord.file)); + if (!fraw) + return false; + + const filerecord = FileRecord.fromRaw(fraw); + + filerecord.blocks -= 1; + + const b = this.db.batch(); + + if (filerecord.blocks === 0) + b.del(layout.f.encode(type, blockrecord.file)); + else + b.put(layout.f.encode(type, blockrecord.file), filerecord.toRaw()); + + b.del(layout.b.encode(type, hash)); + + await b.write(); + + if (filerecord.blocks === 0) + await fs.unlink(this.filepath(type, blockrecord.file)); + + return true; + } + + /** + * This will check if a block undo coin has been stored + * and is available. + * @param {Buffer} hash - The block hash + * @returns {Promise} + */ + + async hasUndo(hash) { + return await this.db.has(layout.b.encode(types.UNDO, hash)); + } + + /** + * This will check if a block has been stored and is available. + * @param {Buffer} hash - The block hash + * @returns {Promise} + */ + + async has(hash) { + return await this.db.has(layout.b.encode(types.BLOCK, hash)); + } +} + +/* + * Expose + */ + +module.exports = FileBlockStore; diff --git a/lib/blockstore/index.js b/lib/blockstore/index.js new file mode 100644 index 000000000..b8e4c6348 --- /dev/null +++ b/lib/blockstore/index.js @@ -0,0 +1,42 @@ +/*! + * blockstore/index.js - bitcoin blockstore for bcoin + * Copyright (c) 2019, Braydon Fuller (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const {join} = require('path'); + +const AbstractBlockStore = require('./abstract'); +const LevelBlockStore = require('./level'); +const FileBlockStore = require('./file'); + +/** + * @module blockstore + */ + +exports.create = (options) => { + const location = join(options.prefix, 'blocks'); + + if (options.memory) { + return new LevelBlockStore({ + network: options.network, + logger: options.logger, + location: location, + cacheSize: options.cacheSize, + memory: options.memory + }); + } + + return new FileBlockStore({ + network: options.network, + logger: options.logger, + location: location, + cacheSize: options.cacheSize + }); +}; + +exports.AbstractBlockStore = AbstractBlockStore; +exports.FileBlockStore = FileBlockStore; +exports.LevelBlockStore = LevelBlockStore; diff --git a/lib/blockstore/layout.js b/lib/blockstore/layout.js new file mode 100644 index 000000000..ce5cf2903 --- /dev/null +++ b/lib/blockstore/layout.js @@ -0,0 +1,30 @@ +/*! + * blockstore/layout.js - file blockstore data layout for bcoin + * Copyright (c) 2019, Braydon Fuller (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const bdb = require('bdb'); + +/* + * Database Layout: + * V -> db version + * F[type] -> last file record by type + * f[type][fileno] -> file record by type and file number + * b[type][hash] -> block record by type and block hash + */ + +const layout = { + V: bdb.key('V'), + F: bdb.key('F', ['uint32']), + f: bdb.key('f', ['uint32', 'uint32']), + b: bdb.key('b', ['uint32', 'hash256']) +}; + +/* + * Expose + */ + +module.exports = layout; diff --git a/lib/blockstore/level.js b/lib/blockstore/level.js new file mode 100644 index 000000000..bb0f7f6cc --- /dev/null +++ b/lib/blockstore/level.js @@ -0,0 +1,189 @@ +/*! + * blockstore/level.js - leveldb blockstore for bcoin + * Copyright (c) 2019, Braydon Fuller (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const bdb = require('bdb'); +const fs = require('bfile'); +const AbstractBlockStore = require('./abstract'); +const layout = require('./layout'); +const {types} = require('./common'); + +/** + * LevelDB Block Store + * + * @alias module:blockstore:LevelBlockStore + * @abstract + */ + +class LevelBlockStore extends AbstractBlockStore { + /** + * Create a blockstore that stores blocks in LevelDB. + * @constructor + */ + + constructor(options) { + super(options); + + this.location = options.location; + + this.db = bdb.create({ + location: this.location, + cacheSize: options.cacheSize, + compression: false, + memory: options.memory + }); + } + + /** + * This method ensures that the storage directory exists + * before opening. + * @returns {Promise} + */ + + async ensure() { + return fs.mkdirp(this.location); + } + + /** + * Opens the block storage. + * @returns {Promise} + */ + + async open() { + this.logger.info('Opening LevelBlockStore...'); + + await this.db.open(); + await this.db.verify(layout.V.encode(), 'levelblockstore', 0); + } + + /** + * Closes the block storage. + */ + + async close() { + this.logger.info('Closing LevelBlockStore...'); + + await this.db.close(); + } + + /** + * This method stores block undo coin data in LevelDB. + * @param {Buffer} hash - The block hash + * @param {Buffer} data - The block data + * @returns {Promise} + */ + + async writeUndo(hash, data) { + return this.db.put(layout.b.encode(types.UNDO, hash), data); + } + + /** + * This method stores block data in LevelDB. + * @param {Buffer} hash - The block hash + * @param {Buffer} data - The block data + * @returns {Promise} + */ + + async write(hash, data) { + return this.db.put(layout.b.encode(types.BLOCK, hash), data); + } + + /** + * This method will retrieve block undo coin data. + * @param {Buffer} hash - The block hash + * @returns {Promise} + */ + + async readUndo(hash) { + return this.db.get(layout.b.encode(types.UNDO, hash)); + } + + /** + * This method will retrieve block data. Smaller portions of the + * block (e.g. transactions) can be returned using the offset and + * length arguments. However, the entire block will be read as the + * data is stored in a key/value database. + * @param {Buffer} hash - The block hash + * @param {Number} offset - The offset within the block + * @param {Number} length - The number of bytes of the data + * @returns {Promise} + */ + + async read(hash, offset, length) { + let raw = await this.db.get(layout.b.encode(types.BLOCK, hash)); + + if (offset) { + if (offset + length > raw.length) + throw new Error('Out-of-bounds read.'); + + raw = raw.slice(offset, offset + length); + } + + return raw; + } + + /** + * This will free resources for storing the block undo coin data. + * The block data may not be immediately removed from disk, and will + * be reclaimed during LevelDB compaction. + * @param {Buffer} hash - The block hash + * @returns {Promise} + */ + + async pruneUndo(hash) { + if (!await this.hasUndo(hash)) + return false; + + await this.db.del(layout.b.encode(types.UNDO, hash)); + + return true; + } + + /** + * This will free resources for storing the block data. The block + * data may not be immediately removed from disk, and will be reclaimed + * during LevelDB compaction. + * @param {Buffer} hash - The block hash + * @returns {Promise} + */ + + async prune(hash) { + if (!await this.has(hash)) + return false; + + await this.db.del(layout.b.encode(types.BLOCK, hash)); + + return true; + } + + /** + * This will check if a block undo coin data has been stored + * and is available. + * @param {Buffer} hash - The block hash + * @returns {Promise} + */ + + async hasUndo(hash) { + return this.db.has(layout.b.encode(types.UNDO, hash)); + } + + /** + * This will check if a block has been stored and is available. + * @param {Buffer} hash - The block hash + * @returns {Promise} + */ + + async has(hash) { + return this.db.has(layout.b.encode(types.BLOCK, hash)); + } +} + +/* + * Expose + */ + +module.exports = LevelBlockStore; diff --git a/lib/blockstore/records.js b/lib/blockstore/records.js new file mode 100644 index 000000000..d387171b9 --- /dev/null +++ b/lib/blockstore/records.js @@ -0,0 +1,149 @@ +/*! + * blockstore/records.js - blockstore records + * Copyright (c) 2019, Braydon Fuller (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const assert = require('bsert'); +const bio = require('bufio'); + +/** + * @module blockstore/records + */ + +/** + * Block Record + */ + +class BlockRecord { + /** + * Create a block record. + * @constructor + */ + + constructor(options = {}) { + this.file = options.file || 0; + this.position = options.position || 0; + this.length = options.length || 0; + + assert((this.file >>> 0) === this.file); + assert((this.position >>> 0) === this.position); + assert((this.length >>> 0) === this.length); + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + const br = bio.read(data); + + this.file = br.readU32(); + this.position = br.readU32(); + this.length = br.readU32(); + + return this; + } + + /** + * Instantiate block record from serialized data. + * @param {Hash} hash + * @param {Buffer} data + * @returns {BlockRecord} + */ + + static fromRaw(data) { + return new this().fromRaw(data); + } + + /** + * Serialize the block record. + * @returns {Buffer} + */ + + toRaw() { + const bw = bio.write(12); + + bw.writeU32(this.file); + bw.writeU32(this.position); + bw.writeU32(this.length); + + return bw.render(); + } +} + +/** + * File Record + */ + +class FileRecord { + /** + * Create a file record. + * @constructor + */ + + constructor(options = {}) { + this.blocks = options.blocks || 0; + this.used = options.used || 0; + this.length = options.length || 0; + + assert((this.blocks >>> 0) === this.blocks); + assert((this.used >>> 0) === this.used); + assert((this.length >>> 0) === this.length); + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + const br = bio.read(data); + + this.blocks = br.readU32(); + this.used = br.readU32(); + this.length = br.readU32(); + + return this; + } + + /** + * Instantiate file record from serialized data. + * @param {Hash} hash + * @param {Buffer} data + * @returns {ChainState} + */ + + static fromRaw(data) { + return new this().fromRaw(data); + } + + /** + * Serialize the file record. + * @returns {Buffer} + */ + + toRaw() { + const bw = bio.write(12); + + bw.writeU32(this.blocks); + bw.writeU32(this.used); + bw.writeU32(this.length); + + return bw.render(); + } +} + +/* + * Expose + */ + +exports.BlockRecord = BlockRecord; +exports.FileRecord = FileRecord; + +module.exports = exports; diff --git a/lib/btc/amount.js b/lib/btc/amount.js index 7c9bb45d0..0dd2066fc 100644 --- a/lib/btc/amount.js +++ b/lib/btc/amount.js @@ -6,352 +6,358 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); +const assert = require('bsert'); +const fixed = require('../utils/fixed'); +const {inspectSymbol} = require('../utils'); /** + * Amount * Represents a bitcoin amount (satoshis internally). * @alias module:btc.Amount - * @constructor - * @param {(String|Number)?} value - * @param {String?} unit * @property {Amount} value */ -function Amount(value, unit) { - if (!(this instanceof Amount)) - return new Amount(value, unit); +class Amount { + /** + * Create an amount. + * @constructor + * @param {(String|Number)?} value + * @param {String?} unit + */ - this.value = 0; + constructor(value, unit) { + this.value = 0; - if (value != null) - this.fromOptions(value, unit); -} - -/** - * Inject properties from options. - * @private - * @param {(String|Number)?} value - * @param {String?} unit - * @returns {Amount} - */ + if (value != null) + this.fromOptions(value, unit); + } -Amount.prototype.fromOptions = function fromOptions(value, unit) { - if (typeof unit === 'string') - return this.from(unit, value); + /** + * Inject properties from options. + * @private + * @param {(String|Number)?} value + * @param {String?} unit + * @returns {Amount} + */ - if (typeof value === 'number') - return this.fromValue(value); + fromOptions(value, unit) { + if (typeof unit === 'string') + return this.from(unit, value); - return this.fromBTC(value); -}; + if (typeof value === 'number') + return this.fromValue(value); -/** - * Get satoshi value. - * @returns {Amount} - */ - -Amount.prototype.toValue = function toValue() { - return this.value; -}; + return this.fromBTC(value); + } -/** - * Get satoshi string or value. - * @param {Boolean?} num - * @returns {String|Amount} - */ + /** + * Get satoshi value. + * @returns {Amount} + */ -Amount.prototype.toSatoshis = function toSatoshis(num) { - if (num) + toValue() { return this.value; + } - return this.value.toString(10); -}; - -/** - * Get bits string or value. - * @param {Boolean?} num - * @returns {String|Amount} - */ - -Amount.prototype.toBits = function toBits(num) { - return Amount.encode(this.value, 2, num); -}; + /** + * Get satoshi string or value. + * @param {Boolean?} num + * @returns {String|Amount} + */ -/** - * Get mbtc string or value. - * @param {Boolean?} num - * @returns {String|Amount} - */ + toSatoshis(num) { + if (num) + return this.value; -Amount.prototype.toMBTC = function toMBTC(num) { - return Amount.encode(this.value, 5, num); -}; + return this.value.toString(10); + } -/** - * Get btc string or value. - * @param {Boolean?} num - * @returns {String|Amount} - */ + /** + * Get bits string or value. + * @param {Boolean?} num + * @returns {String|Amount} + */ -Amount.prototype.toBTC = function toBTC(num) { - return Amount.encode(this.value, 8, num); -}; + toBits(num) { + return Amount.encode(this.value, 2, num); + } -/** - * Get unit string or value. - * @param {String} unit - Can be `sat`, - * `ubtc`, `bits`, `mbtc`, or `btc`. - * @param {Boolean?} num - * @returns {String|Amount} - */ + /** + * Get mbtc string or value. + * @param {Boolean?} num + * @returns {String|Amount} + */ -Amount.prototype.to = function to(unit, num) { - switch (unit) { - case 'sat': - return this.toSatoshis(num); - case 'ubtc': - case 'bits': - return this.toBits(num); - case 'mbtc': - return this.toMBTC(num); - case 'btc': - return this.toBTC(num); + toMBTC(num) { + return Amount.encode(this.value, 5, num); } - throw new Error(`Unknown unit "${unit}".`); -}; -/** - * Convert amount to bitcoin string. - * @returns {String} - */ + /** + * Get btc string or value. + * @param {Boolean?} num + * @returns {String|Amount} + */ -Amount.prototype.toString = function toString() { - return this.toBTC(); -}; + toBTC(num) { + return Amount.encode(this.value, 8, num); + } -/** - * Inject properties from value. - * @private - * @param {Amount} value - * @returns {Amount} - */ + /** + * Get unit string or value. + * @param {String} unit - Can be `sat`, + * `ubtc`, `bits`, `mbtc`, or `btc`. + * @param {Boolean?} num + * @returns {String|Amount} + */ + + to(unit, num) { + switch (unit) { + case 'sat': + return this.toSatoshis(num); + case 'ubtc': + case 'bits': + return this.toBits(num); + case 'mbtc': + return this.toMBTC(num); + case 'btc': + return this.toBTC(num); + } + throw new Error(`Unknown unit "${unit}".`); + } -Amount.prototype.fromValue = function fromValue(value) { - assert(util.isI64(value), 'Value must be an int64.'); - this.value = value; - return this; -}; + /** + * Convert amount to bitcoin string. + * @returns {String} + */ -/** - * Inject properties from satoshis. - * @private - * @param {Number|String} value - * @returns {Amount} - */ + toString() { + return this.toBTC(); + } -Amount.prototype.fromSatoshis = function fromSatoshis(value) { - this.value = Amount.decode(value, 0); - return this; -}; + /** + * Inject properties from value. + * @private + * @param {Amount} value + * @returns {Amount} + */ + + fromValue(value) { + assert(Number.isSafeInteger(value) && value >= 0, + 'Value must be an int64.'); + this.value = value; + return this; + } -/** - * Inject properties from bits. - * @private - * @param {Number|String} value - * @returns {Amount} - */ + /** + * Inject properties from satoshis. + * @private + * @param {Number|String} value + * @returns {Amount} + */ -Amount.prototype.fromBits = function fromBits(value) { - this.value = Amount.decode(value, 2); - return this; -}; + fromSatoshis(value) { + this.value = Amount.decode(value, 0); + return this; + } -/** - * Inject properties from mbtc. - * @private - * @param {Number|String} value - * @returns {Amount} - */ + /** + * Inject properties from bits. + * @private + * @param {Number|String} value + * @returns {Amount} + */ -Amount.prototype.fromMBTC = function fromMBTC(value) { - this.value = Amount.decode(value, 5); - return this; -}; + fromBits(value) { + this.value = Amount.decode(value, 2); + return this; + } -/** - * Inject properties from btc. - * @private - * @param {Number|String} value - * @returns {Amount} - */ + /** + * Inject properties from mbtc. + * @private + * @param {Number|String} value + * @returns {Amount} + */ -Amount.prototype.fromBTC = function fromBTC(value) { - this.value = Amount.decode(value, 8); - return this; -}; + fromMBTC(value) { + this.value = Amount.decode(value, 5); + return this; + } -/** - * Inject properties from unit. - * @private - * @param {String} unit - * @param {Number|String} value - * @returns {Amount} - */ + /** + * Inject properties from btc. + * @private + * @param {Number|String} value + * @returns {Amount} + */ -Amount.prototype.from = function from(unit, value) { - switch (unit) { - case 'sat': - return this.fromSatoshis(value); - case 'ubtc': - case 'bits': - return this.fromBits(value); - case 'mbtc': - return this.fromMBTC(value); - case 'btc': - return this.fromBTC(value); + fromBTC(value) { + this.value = Amount.decode(value, 8); + return this; } - throw new Error(`Unknown unit "${unit}".`); -}; -/** - * Instantiate amount from options. - * @param {(String|Number)?} value - * @param {String?} unit - * @returns {Amount} - */ + /** + * Inject properties from unit. + * @private + * @param {String} unit + * @param {Number|String} value + * @returns {Amount} + */ + + from(unit, value) { + switch (unit) { + case 'sat': + return this.fromSatoshis(value); + case 'ubtc': + case 'bits': + return this.fromBits(value); + case 'mbtc': + return this.fromMBTC(value); + case 'btc': + return this.fromBTC(value); + } + throw new Error(`Unknown unit "${unit}".`); + } -Amount.fromOptions = function fromOptions(value, unit) { - return new Amount().fromOptions(value, unit); -}; + /** + * Instantiate amount from options. + * @param {(String|Number)?} value + * @param {String?} unit + * @returns {Amount} + */ -/** - * Instantiate amount from value. - * @private - * @param {Amount} value - * @returns {Amount} - */ + static fromOptions(value, unit) { + return new this().fromOptions(value, unit); + } -Amount.fromValue = function fromValue(value) { - return new Amount().fromValue(value); -}; + /** + * Instantiate amount from value. + * @private + * @param {Amount} value + * @returns {Amount} + */ -/** - * Instantiate amount from satoshis. - * @param {Number|String} value - * @returns {Amount} - */ + static fromValue(value) { + return new this().fromValue(value); + } -Amount.fromSatoshis = function fromSatoshis(value) { - return new Amount().fromSatoshis(value); -}; + /** + * Instantiate amount from satoshis. + * @param {Number|String} value + * @returns {Amount} + */ -/** - * Instantiate amount from bits. - * @param {Number|String} value - * @returns {Amount} - */ - -Amount.fromBits = function fromBits(value) { - return new Amount().fromBits(value); -}; + static fromSatoshis(value) { + return new this().fromSatoshis(value); + } -/** - * Instantiate amount from mbtc. - * @param {Number|String} value - * @returns {Amount} - */ + /** + * Instantiate amount from bits. + * @param {Number|String} value + * @returns {Amount} + */ -Amount.fromMBTC = function fromMBTC(value) { - return new Amount().fromMBTC(value); -}; + static fromBits(value) { + return new this().fromBits(value); + } -/** - * Instantiate amount from btc. - * @param {Number|String} value - * @returns {Amount} - */ + /** + * Instantiate amount from mbtc. + * @param {Number|String} value + * @returns {Amount} + */ -Amount.fromBTC = function fromBTC(value) { - return new Amount().fromBTC(value); -}; + static fromMBTC(value) { + return new this().fromMBTC(value); + } -/** - * Instantiate amount from unit. - * @param {String} unit - * @param {Number|String} value - * @returns {Amount} - */ + /** + * Instantiate amount from btc. + * @param {Number|String} value + * @returns {Amount} + */ -Amount.from = function from(unit, value) { - return new Amount().from(unit, value); -}; + static fromBTC(value) { + return new this().fromBTC(value); + } -/** - * Inspect amount. - * @returns {String} - */ + /** + * Instantiate amount from unit. + * @param {String} unit + * @param {Number|String} value + * @returns {Amount} + */ -Amount.prototype.inspect = function inspect() { - return ``; -}; + static from(unit, value) { + return new this().from(unit, value); + } -/** - * Safely convert satoshis to a BTC string. - * This function explicitly avoids any - * floating point arithmetic. - * @param {Amount} value - Satoshis. - * @returns {String} BTC string. - */ + /** + * Inspect amount. + * @returns {String} + */ -Amount.btc = function btc(value, num) { - if (typeof value === 'string') - return value; + [inspectSymbol]() { + return ``; + } - return Amount.encode(value, 8, num); -}; + /** + * Safely convert satoshis to a BTC string. + * This function explicitly avoids any + * floating point arithmetic. + * @param {Amount} value - Satoshis. + * @returns {String} BTC string. + */ -/** - * Safely convert a BTC string to satoshis. - * @param {String} str - BTC - * @returns {Amount} Satoshis. - * @throws on parse error - */ + static btc(value, num) { + if (typeof value === 'string') + return value; -Amount.value = function value(str) { - if (typeof str === 'number') - return str; + return Amount.encode(value, 8, num); + } - return Amount.decode(str, 8); -}; + /** + * Safely convert a BTC string to satoshis. + * @param {String} str - BTC + * @returns {Amount} Satoshis. + * @throws on parse error + */ -/** - * Safely convert satoshis to a BTC string. - * @param {Amount} value - * @param {Number} exp - Exponent. - * @param {Boolean} num - Return a number. - * @returns {String|Number} - */ + static value(str) { + if (typeof str === 'number') + return str; -Amount.encode = function encode(value, exp, num) { - if (num) - return util.toFloat(value, exp); - return util.toFixed(value, exp); -}; + return Amount.decode(str, 8); + } -/** - * Safely convert a BTC string to satoshis. - * @param {String|Number} value - BTC - * @param {Number} exp - Exponent. - * @returns {Amount} Satoshis. - * @throws on parse error - */ + /** + * Safely convert satoshis to a BTC string. + * @param {Amount} value + * @param {Number} exp - Exponent. + * @param {Boolean} num - Return a number. + * @returns {String|Number} + */ + + static encode(value, exp, num) { + if (num) + return fixed.toFloat(value, exp); + return fixed.encode(value, exp); + } -Amount.decode = function decode(value, exp) { - if (typeof value === 'number') - return util.fromFloat(value, exp); - return util.fromFixed(value, exp); -}; + /** + * Safely convert a BTC string to satoshis. + * @param {String|Number} value - BTC + * @param {Number} exp - Exponent. + * @returns {Amount} Satoshis. + * @throws on parse error + */ + + static decode(value, exp) { + if (typeof value === 'number') + return fixed.fromFloat(value, exp); + return fixed.decode(value, exp); + } +} /* * Expose diff --git a/lib/btc/uri.js b/lib/btc/uri.js index 22d086441..e0353ede6 100644 --- a/lib/btc/uri.js +++ b/lib/btc/uri.js @@ -6,16 +6,15 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); +const assert = require('bsert'); const Address = require('../primitives/address'); const Amount = require('./amount'); +const {inspectSymbol} = require('../utils'); /** + * URI * Represents a bitcoin URI. * @alias module:btc.URI - * @constructor - * @param {Object|String} options * @property {Address} address * @property {Amount} amount * @property {String|null} label @@ -23,184 +22,194 @@ const Amount = require('./amount'); * @property {String|null} request */ -function URI(options) { - if (!(this instanceof URI)) - return new URI(options); - - this.address = new Address(); - this.amount = -1; - this.label = null; - this.message = null; - this.request = null; +class URI { + /** + * Create a bitcoin URI. + * @alias module:btc.URI + * @constructor + * @param {Object|String} options + */ + + constructor(options) { + this.address = new Address(); + this.amount = -1; + this.label = null; + this.message = null; + this.request = null; + + if (options) + this.fromOptions(options); + } - if (options) - this.fromOptions(options); -} + /** + * Inject properties from options object. + * @private + * @param {Object|String} options + * @returns {URI} + */ -/** - * Inject properties from options object. - * @private - * @param {Object|String} options - * @returns {URI} - */ + fromOptions(options) { + if (typeof options === 'string') + return this.fromString(options); -URI.prototype.fromOptions = function fromOptions(options) { - if (typeof options === 'string') - return this.fromString(options); + if (options.address) + this.address.fromOptions(options.address); - if (options.address) - this.address.fromOptions(options.address); + if (options.amount != null) { + assert(Number.isSafeInteger(options.amount) && options.amount >= 0, + 'Amount must be a uint64.'); + this.amount = options.amount; + } - if (options.amount != null) { - assert(util.isU64(options.amount), 'Amount must be a uint64.'); - this.amount = options.amount; - } + if (options.label) { + assert(typeof options.label === 'string', 'Label must be a string.'); + this.label = options.label; + } - if (options.label) { - assert(typeof options.label === 'string', 'Label must be a string.'); - this.label = options.label; - } + if (options.message) { + assert(typeof options.message === 'string', 'Message must be a string.'); + this.message = options.message; + } - if (options.message) { - assert(typeof options.message === 'string', 'Message must be a string.'); - this.message = options.message; - } + if (options.request) { + assert(typeof options.request === 'string', 'Request must be a string.'); + this.request = options.request; + } - if (options.request) { - assert(typeof options.request === 'string', 'Request must be a string.'); - this.request = options.request; + return this; } - return this; -}; + /** + * Instantiate URI from options. + * @param {Object|String} options + * @returns {URI} + */ -/** - * Instantiate URI from options. - * @param {Object|String} options - * @returns {URI} - */ + static fromOptions(options) { + return new this().fromOptions(options); + } -URI.fromOptions = function fromOptions(options) { - return new URI().fromOptions(options); -}; + /** + * Parse and inject properties from string. + * @private + * @param {String} str + * @param {Network?} network + * @returns {URI} + */ -/** - * Parse and inject properties from string. - * @private - * @param {String} str - * @param {Network?} network - * @returns {URI} - */ + fromString(str, network) { + assert(typeof str === 'string'); + assert(str.length > 8, 'Not a bitcoin URI.'); -URI.prototype.fromString = function fromString(str, network) { - assert(typeof str === 'string'); - assert(str.length > 8, 'Not a bitcoin URI.'); + const prefix = str.substring(0, 8); - const prefix = str.substring(0, 8); - assert(prefix === 'flo:', 'Not a bitcoin URI.'); - str = str.substring(8); + str = str.substring(8); - const index = str.indexOf('?'); + const index = str.indexOf('?'); - let addr, qs; - if (index === -1) { - addr = str; - } else { - addr = str.substring(0, index); - qs = str.substring(index + 1); - } + let addr, qs; + if (index === -1) { + addr = str; + } else { + addr = str.substring(0, index); + qs = str.substring(index + 1); + } - this.address.fromString(addr, network); + this.address.fromString(addr, network); - if (!qs) - return this; + if (!qs) + return this; - const query = parsePairs(qs); + const query = parsePairs(qs); - if (query.amount) { - assert(query.amount.length > 0, 'Value is empty.'); - assert(query.amount[0] !== '-', 'Value is negative.'); - this.amount = Amount.value(query.amount); - } + if (query.amount) { + assert(query.amount.length > 0, 'Value is empty.'); + assert(query.amount[0] !== '-', 'Value is negative.'); + this.amount = Amount.value(query.amount); + } - if (query.label) - this.label = query.label; + if (query.label) + this.label = query.label; - if (query.message) - this.message = query.message; + if (query.message) + this.message = query.message; - if (query.r) - this.request = query.r; + if (query.r) + this.request = query.r; - return this; -}; + return this; + } -/** - * Instantiate uri from string. - * @param {String} str - * @param {Network?} network - * @returns {URI} - */ + /** + * Instantiate uri from string. + * @param {String} str + * @param {Network?} network + * @returns {URI} + */ -URI.fromString = function fromString(str, network) { - return new URI().fromString(str, network); -}; + static fromString(str, network) { + return new this().fromString(str, network); + } -/** - * Serialize uri to a string. - * @returns {String} - */ + /** + * Serialize uri to a string. + * @returns {String} + */ -URI.prototype.toString = function toString() { - let str = 'flo:'; + toString() { + let str = 'flo:'; - str += this.address.toString(); + str += this.address.toString(); - const query = []; + const query = []; - if (this.amount !== -1) - query.push(`amount=${Amount.btc(this.amount)}`); + if (this.amount !== -1) + query.push(`amount=${Amount.btc(this.amount)}`); - if (this.label) - query.push(`label=${escape(this.label)}`); + if (this.label) + query.push(`label=${escape(this.label)}`); - if (this.message) - query.push(`message=${escape(this.message)}`); + if (this.message) + query.push(`message=${escape(this.message)}`); - if (this.request) - query.push(`r=${escape(this.request)}`); + if (this.request) + query.push(`r=${escape(this.request)}`); - if (query.length > 0) - str += '?' + query.join('&'); + if (query.length > 0) + str += '?' + query.join('&'); - return str; -}; + return str; + } -/** - * Inspect bitcoin uri. - * @returns {String} - */ + /** + * Inspect bitcoin uri. + * @returns {String} + */ -URI.prototype.inspect = function inspect() { - return ``; -}; + [inspectSymbol]() { + return ``; + } +} /* * Helpers */ -function BitcoinQuery() { - this.amount = null; - this.label = null; - this.message = null; - this.r = null; +class BitcoinQuery { + constructor() { + this.amount = null; + this.label = null; + this.message = null; + this.r = null; + } } function parsePairs(str) { const parts = str.split('&'); const data = new BitcoinQuery(); + let size = 0; for (const pair of parts) { @@ -244,7 +253,7 @@ function parsePairs(str) { break; } - size++; + size += 1; } return data; diff --git a/lib/coins/coinentry.js b/lib/coins/coinentry.js index 757ca6a7d..45e2592cc 100644 --- a/lib/coins/coinentry.js +++ b/lib/coins/coinentry.js @@ -6,13 +6,12 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); +const bio = require('bufio'); const Coin = require('../primitives/coin'); const Output = require('../primitives/output'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); -const encoding = require('../utils/encoding'); const compress = require('./compress'); +const {encoding} = bio; /* * Constants @@ -22,9 +21,9 @@ const NUM_FLAGS = 1; const MAX_HEIGHT = ((1 << (32 - NUM_FLAGS)) >>> 0) - 1; /** + * Coin Entry * Represents an unspent output. * @alias module:coins.CoinEntry - * @constructor * @property {Number} version - Transaction version. * @property {Number} height - Transaction height (-1 if unconfirmed). * @property {Boolean} coinbase - Whether the containing @@ -34,241 +33,245 @@ const MAX_HEIGHT = ((1 << (32 - NUM_FLAGS)) >>> 0) - 1; * @property {Buffer} raw */ -function CoinEntry() { - if (!(this instanceof CoinEntry)) - return new CoinEntry(); +class CoinEntry { + /** + * Create a coin entry. + * @constructor + */ + + constructor() { + this.version = 1; + this.height = -1; + this.coinbase = false; + this.output = new Output(); + this.spent = false; + this.raw = null; + } - this.version = 1; - this.height = -1; - this.coinbase = false; - this.output = new Output(); - this.spent = false; - this.raw = null; -} + /** + * Convert coin entry to an output. + * @returns {Output} + */ -/** - * Convert coin entry to an output. - * @returns {Output} - */ + toOutput() { + return this.output; + } -CoinEntry.prototype.toOutput = function toOutput() { - return this.output; -}; + /** + * Convert coin entry to a coin. + * @param {Outpoint} prevout + * @returns {Coin} + */ + + toCoin(prevout) { + const coin = new Coin(); + coin.version = this.version; + coin.height = this.height; + coin.coinbase = this.coinbase; + coin.script = this.output.script; + coin.value = this.output.value; + coin.hash = prevout.hash; + coin.index = prevout.index; + return coin; + } -/** - * Convert coin entry to a coin. - * @param {Outpoint} prevout - * @returns {Coin} - */ + /** + * Inject properties from TX. + * @param {TX} tx + * @param {Number} index + */ -CoinEntry.prototype.toCoin = function toCoin(prevout) { - const coin = new Coin(); - coin.version = this.version; - coin.height = this.height; - coin.coinbase = this.coinbase; - coin.script = this.output.script; - coin.value = this.output.value; - coin.hash = prevout.hash; - coin.index = prevout.index; - return coin; -}; + fromOutput(output) { + this.output = output; + return this; + } -/** - * Inject properties from TX. - * @param {TX} tx - * @param {Number} index - */ + /** + * Instantiate a coin from a TX + * @param {TX} tx + * @param {Number} index - Output index. + * @returns {CoinEntry} + */ -CoinEntry.prototype.fromOutput = function fromOutput(output) { - this.output = output; - return this; -}; + static fromOutput(output) { + return new this().fromOutput(output); + } -/** - * Instantiate a coin from a TX - * @param {TX} tx - * @param {Number} index - Output index. - * @returns {CoinEntry} - */ + /** + * Inject properties from TX. + * @param {TX} tx + * @param {Number} index + */ + + fromCoin(coin) { + this.version = coin.version; + this.height = coin.height; + this.coinbase = coin.coinbase; + this.output.script = coin.script; + this.output.value = coin.value; + return this; + } -CoinEntry.fromOutput = function fromOutput(output) { - return new CoinEntry().fromOutput(output); -}; + /** + * Instantiate a coin from a TX + * @param {TX} tx + * @param {Number} index - Output index. + * @returns {CoinEntry} + */ -/** - * Inject properties from TX. - * @param {TX} tx - * @param {Number} index - */ + static fromCoin(coin) { + return new this().fromCoin(coin); + } -CoinEntry.prototype.fromCoin = function fromCoin(coin) { - this.version = coin.version; - this.height = coin.height; - this.coinbase = coin.coinbase; - this.output.script = coin.script; - this.output.value = coin.value; - return this; -}; + /** + * Inject properties from TX. + * @param {TX} tx + * @param {Number} index + */ + + fromTX(tx, index, height) { + assert(typeof index === 'number'); + assert(typeof height === 'number'); + assert(index >= 0 && index < tx.outputs.length); + this.version = tx.version; + this.height = height; + this.coinbase = tx.isCoinbase(); + this.output = tx.outputs[index]; + return this; + } -/** - * Instantiate a coin from a TX - * @param {TX} tx - * @param {Number} index - Output index. - * @returns {CoinEntry} - */ + /** + * Instantiate a coin from a TX + * @param {TX} tx + * @param {Number} index - Output index. + * @returns {CoinEntry} + */ -CoinEntry.fromCoin = function fromCoin(coin) { - return new CoinEntry().fromCoin(coin); -}; + static fromTX(tx, index, height) { + return new this().fromTX(tx, index, height); + } -/** - * Inject properties from TX. - * @param {TX} tx - * @param {Number} index - */ + /** + * Calculate size of coin. + * @returns {Number} + */ -CoinEntry.prototype.fromTX = function fromTX(tx, index, height) { - assert(typeof index === 'number'); - assert(typeof height === 'number'); - assert(index >= 0 && index < tx.outputs.length); - this.version = tx.version; - this.height = height; - this.coinbase = tx.isCoinbase(); - this.output = tx.outputs[index]; - return this; -}; + getSize() { + if (this.raw) + return this.raw.length; -/** - * Instantiate a coin from a TX - * @param {TX} tx - * @param {Number} index - Output index. - * @returns {CoinEntry} - */ + let size = 0; + size += encoding.sizeVarint(this.version); + size += 4; + size += compress.size(this.output); -CoinEntry.fromTX = function fromTX(tx, index, height) { - return new CoinEntry().fromTX(tx, index, height); -}; + return size; + } -/** - * Calculate size of coin. - * @returns {Number} - */ + /** + * Write the coin to a buffer writer. + * @param {BufferWriter} bw + */ -CoinEntry.prototype.getSize = function getSize() { - if (this.raw) - return this.raw.length; + toWriter(bw) { + if (this.raw) { + bw.writeBytes(this.raw); + return bw; + } - let size = 0; - size += encoding.sizeVarint(this.version); - size += 4; - size += compress.size(this.output); + let height = this.height; + let field = 0; - return size; -}; + if (this.coinbase) + field |= 1; -/** - * Write the coin to a buffer writer. - * @param {BufferWriter} bw - */ + if (height === -1) + height = MAX_HEIGHT; -CoinEntry.prototype.toWriter = function toWriter(bw) { - if (this.raw) { - bw.writeBytes(this.raw); - return bw; - } + field |= height << NUM_FLAGS; - let height = this.height; - let field = 0; + bw.writeVarint(this.version); + bw.writeU32(field); + compress.pack(this.output, bw); - if (this.coinbase) - field |= 1; + return bw; + } - if (height === -1) - height = MAX_HEIGHT; + /** + * Serialize the coin. + * @returns {Buffer} + */ - field |= height << NUM_FLAGS; + toRaw() { + if (this.raw) + return this.raw; - bw.writeVarint(this.version); - bw.writeU32(field); - compress.pack(this.output, bw); + const size = this.getSize(); + const bw = bio.write(size); - return bw; -}; + this.toWriter(bw); -/** - * Serialize the coin. - * @returns {Buffer} - */ + this.raw = bw.render(); -CoinEntry.prototype.toRaw = function toRaw() { - if (this.raw) return this.raw; + } - const size = this.getSize(); - const bw = new StaticWriter(size); - - this.toWriter(bw); - - this.raw = bw.render(); + /** + * Inject properties from serialized buffer writer. + * @private + * @param {BufferReader} br + */ - return this.raw; -}; + fromReader(br) { + const version = br.readVarint(); + const field = br.readU32(); -/** - * Inject properties from serialized buffer writer. - * @private - * @param {BufferReader} br - */ + let height = field >>> NUM_FLAGS; -CoinEntry.prototype.fromReader = function fromReader(br) { - const version = br.readVarint(); - const field = br.readU32(); + if (height === MAX_HEIGHT) + height = -1; - let height = field >>> NUM_FLAGS; + this.version = version; + this.coinbase = (field & 1) !== 0; + this.height = height; - if (height === MAX_HEIGHT) - height = -1; + compress.unpack(this.output, br); - this.version = version; - this.coinbase = (field & 1) !== 0; - this.height = height; - - compress.unpack(this.output, br); + return this; + } - return this; -}; + /** + * Instantiate a coin from a serialized Buffer. + * @param {Buffer} data + * @returns {CoinEntry} + */ -/** - * Instantiate a coin from a serialized Buffer. - * @param {Buffer} data - * @returns {CoinEntry} - */ + static fromReader(data) { + return new this().fromReader(data); + } -CoinEntry.fromReader = function fromReader(data) { - return new CoinEntry().fromReader(data); -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -CoinEntry.prototype.fromRaw = function fromRaw(data) { - this.fromReader(new BufferReader(data)); - this.raw = data; - return this; -}; + fromRaw(data) { + this.fromReader(bio.read(data)); + this.raw = data; + return this; + } -/** - * Instantiate a coin from a serialized Buffer. - * @param {Buffer} data - * @returns {CoinEntry} - */ + /** + * Instantiate a coin from a serialized Buffer. + * @param {Buffer} data + * @returns {CoinEntry} + */ -CoinEntry.fromRaw = function fromRaw(data) { - return new CoinEntry().fromRaw(data); -}; + static fromRaw(data) { + return new this().fromRaw(data); + } +} /* * Expose diff --git a/lib/coins/coins.js b/lib/coins/coins.js index 1eb2affc2..a1e7377b6 100644 --- a/lib/coins/coins.js +++ b/lib/coins/coins.js @@ -6,213 +6,217 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const CoinEntry = require('./coinentry'); /** + * Coins * Represents the outputs for a single transaction. * @alias module:coins.Coins - * @constructor * @property {Map[]} outputs - Coins. */ -function Coins() { - if (!(this instanceof Coins)) - return new Coins(); +class Coins { + /** + * Create coins. + * @constructor + */ - this.outputs = new Map(); -} + constructor() { + this.outputs = new Map(); + } -/** - * Add a single entry to the collection. - * @param {Number} index - * @param {CoinEntry} coin - * @returns {CoinEntry} - */ + /** + * Add a single entry to the collection. + * @param {Number} index + * @param {CoinEntry} coin + * @returns {CoinEntry} + */ + + add(index, coin) { + assert((index >>> 0) === index); + assert(coin); + this.outputs.set(index, coin); + return coin; + } -Coins.prototype.add = function add(index, coin) { - assert((index >>> 0) === index); - assert(coin); - this.outputs.set(index, coin); - return coin; -}; + /** + * Add a single output to the collection. + * @param {Number} index + * @param {Output} output + * @returns {CoinEntry} + */ -/** - * Add a single output to the collection. - * @param {Number} index - * @param {Output} output - * @returns {CoinEntry} - */ + addOutput(index, output) { + return this.add(index, CoinEntry.fromOutput(output)); + } -Coins.prototype.addOutput = function addOutput(index, output) { - return this.add(index, CoinEntry.fromOutput(output)); -}; + /** + * Add an output to the collection by output index. + * @param {TX} tx + * @param {Number} index + * @param {Number} height + * @returns {CoinEntry} + */ -/** - * Add an output to the collection by output index. - * @param {TX} tx - * @param {Number} index - * @param {Number} height - * @returns {CoinEntry} - */ + addIndex(tx, index, height) { + return this.add(index, CoinEntry.fromTX(tx, index, height)); + } -Coins.prototype.addIndex = function addIndex(tx, index, height) { - return this.add(index, CoinEntry.fromTX(tx, index, height)); -}; + /** + * Add a single coin to the collection. + * @param {Coin} coin + * @returns {CoinEntry} + */ -/** - * Add a single coin to the collection. - * @param {Coin} coin - * @returns {CoinEntry} - */ + addCoin(coin) { + return this.add(coin.index, CoinEntry.fromCoin(coin)); + } -Coins.prototype.addCoin = function addCoin(coin) { - return this.add(coin.index, CoinEntry.fromCoin(coin)); -}; + /** + * Test whether the collection has a coin. + * @param {Number} index + * @returns {Boolean} + */ -/** - * Test whether the collection has a coin. - * @param {Number} index - * @returns {Boolean} - */ + has(index) { + return this.outputs.has(index); + } -Coins.prototype.has = function has(index) { - return this.outputs.has(index); -}; + /** + * Test whether the collection has an unspent coin. + * @param {Number} index + * @returns {Boolean} + */ -/** - * Test whether the collection has an unspent coin. - * @param {Number} index - * @returns {Boolean} - */ + isUnspent(index) { + const coin = this.outputs.get(index); -Coins.prototype.isUnspent = function isUnspent(index) { - const coin = this.outputs.get(index); + if (!coin || coin.spent) + return false; - if (!coin || coin.spent) - return false; + return true; + } - return true; -}; + /** + * Get a coin entry. + * @param {Number} index + * @returns {CoinEntry|null} + */ -/** - * Get a coin entry. - * @param {Number} index - * @returns {CoinEntry|null} - */ + get(index) { + return this.outputs.get(index) || null; + } -Coins.prototype.get = function get(index) { - return this.outputs.get(index) || null; -}; + /** + * Get an output. + * @param {Number} index + * @returns {Output|null} + */ -/** - * Get an output. - * @param {Number} index - * @returns {Output|null} - */ + getOutput(index) { + const coin = this.outputs.get(index); -Coins.prototype.getOutput = function getOutput(index) { - const coin = this.outputs.get(index); + if (!coin) + return null; - if (!coin) - return null; + return coin.output; + } - return coin.output; -}; + /** + * Get a coin. + * @param {Outpoint} prevout + * @returns {Coin|null} + */ -/** - * Get a coin. - * @param {Outpoint} prevout - * @returns {Coin|null} - */ + getCoin(prevout) { + const coin = this.outputs.get(prevout.index); -Coins.prototype.getCoin = function getCoin(prevout) { - const coin = this.outputs.get(prevout.index); + if (!coin) + return null; - if (!coin) - return null; + return coin.toCoin(prevout); + } - return coin.toCoin(prevout); -}; + /** + * Spend a coin entry and return it. + * @param {Number} index + * @returns {CoinEntry|null} + */ -/** - * Spend a coin entry and return it. - * @param {Number} index - * @returns {CoinEntry|null} - */ + spend(index) { + const coin = this.get(index); -Coins.prototype.spend = function spend(index) { - const coin = this.get(index); + if (!coin || coin.spent) + return null; - if (!coin || coin.spent) - return null; + coin.spent = true; - coin.spent = true; + return coin; + } - return coin; -}; + /** + * Remove a coin entry and return it. + * @param {Number} index + * @returns {CoinEntry|null} + */ -/** - * Remove a coin entry and return it. - * @param {Number} index - * @returns {CoinEntry|null} - */ + remove(index) { + const coin = this.get(index); -Coins.prototype.remove = function remove(index) { - const coin = this.get(index); + if (!coin) + return null; - if (!coin) - return null; + this.outputs.delete(index); - this.outputs.delete(index); + return coin; + } - return coin; -}; + /** + * Test whether the coins are fully spent. + * @returns {Boolean} + */ -/** - * Test whether the coins are fully spent. - * @returns {Boolean} - */ + isEmpty() { + return this.outputs.size === 0; + } -Coins.prototype.isEmpty = function isEmpty() { - return this.outputs.size === 0; -}; + /** + * Inject properties from tx. + * @private + * @param {TX} tx + * @param {Number} height + * @returns {Coins} + */ -/** - * Inject properties from tx. - * @private - * @param {TX} tx - * @param {Number} height - * @returns {Coins} - */ + fromTX(tx, height) { + assert(typeof height === 'number'); -Coins.prototype.fromTX = function fromTX(tx, height) { - assert(typeof height === 'number'); + for (let i = 0; i < tx.outputs.length; i++) { + const output = tx.outputs[i]; - for (let i = 0; i < tx.outputs.length; i++) { - const output = tx.outputs[i]; + if (output.script.isUnspendable()) + continue; - if (output.script.isUnspendable()) - continue; + const entry = CoinEntry.fromTX(tx, i, height); - const entry = CoinEntry.fromTX(tx, i, height); + this.outputs.set(i, entry); + } - this.outputs.set(i, entry); + return this; } - return this; -}; + /** + * Instantiate a coins object from a transaction. + * @param {TX} tx + * @param {Number} height + * @returns {Coins} + */ -/** - * Instantiate a coins object from a transaction. - * @param {TX} tx - * @param {Number} height - * @returns {Coins} - */ - -Coins.fromTX = function fromTX(tx, height) { - return new Coins().fromTX(tx, height); -}; + static fromTX(tx, height) { + return new this().fromTX(tx, height); + } +} /* * Expose diff --git a/lib/coins/coinview.js b/lib/coins/coinview.js index 38898585e..c82097c94 100644 --- a/lib/coins/coinview.js +++ b/lib/coins/coinview.js @@ -6,542 +6,547 @@ 'use strict'; +const {BufferMap} = require('buffer-map'); const Coins = require('./coins'); const UndoCoins = require('./undocoins'); const CoinEntry = require('./coinentry'); /** + * Coin View * Represents a coin viewpoint: * a snapshot of {@link Coins} objects. * @alias module:coins.CoinView - * @constructor * @property {Object} map * @property {UndoCoins} undo */ -function CoinView() { - if (!(this instanceof CoinView)) - return new CoinView(); +class CoinView { + /** + * Create a coin view. + * @constructor + */ - this.map = new Map(); - this.undo = new UndoCoins(); -} - -/** - * Get coins. - * @param {Hash} hash - * @returns {Coins} coins - */ - -CoinView.prototype.get = function get(hash) { - return this.map.get(hash); -}; - -/** - * Test whether the view has an entry. - * @param {Hash} hash - * @returns {Boolean} - */ + constructor() { + this.map = new BufferMap(); + this.undo = new UndoCoins(); + } -CoinView.prototype.has = function has(hash) { - return this.map.has(hash); -}; + /** + * Get coins. + * @param {Hash} hash + * @returns {Coins} coins + */ -/** - * Add coins to the collection. - * @param {Hash} hash - * @param {Coins} coins - * @returns {Coins} - */ + get(hash) { + return this.map.get(hash); + } -CoinView.prototype.add = function add(hash, coins) { - this.map.set(hash, coins); - return coins; -}; + /** + * Test whether the view has an entry. + * @param {Hash} hash + * @returns {Boolean} + */ -/** - * Ensure existence of coins object in the collection. - * @param {Hash} hash - * @returns {Coins} - */ + has(hash) { + return this.map.has(hash); + } -CoinView.prototype.ensure = function ensure(hash) { - const coins = this.map.get(hash); + /** + * Add coins to the collection. + * @param {Hash} hash + * @param {Coins} coins + * @returns {Coins} + */ - if (coins) + add(hash, coins) { + this.map.set(hash, coins); return coins; + } - return this.add(hash, new Coins()); -}; + /** + * Ensure existence of coins object in the collection. + * @param {Hash} hash + * @returns {Coins} + */ -/** - * Remove coins from the collection. - * @param {Coins} coins - * @returns {Coins|null} - */ + ensure(hash) { + const coins = this.map.get(hash); -CoinView.prototype.remove = function remove(hash) { - const coins = this.map.get(hash); + if (coins) + return coins; - if (!coins) - return null; + return this.add(hash, new Coins()); + } - this.map.delete(hash); + /** + * Remove coins from the collection. + * @param {Coins} coins + * @returns {Coins|null} + */ - return coins; -}; + remove(hash) { + const coins = this.map.get(hash); -/** - * Add a tx to the collection. - * @param {TX} tx - * @param {Number} height - * @returns {Coins} - */ + if (!coins) + return null; -CoinView.prototype.addTX = function addTX(tx, height) { - const hash = tx.hash('hex'); - const coins = Coins.fromTX(tx, height); - return this.add(hash, coins); -}; + this.map.delete(hash); -/** - * Remove a tx from the collection. - * @param {TX} tx - * @param {Number} height - * @returns {Coins} - */ + return coins; + } -CoinView.prototype.removeTX = function removeTX(tx, height) { - const hash = tx.hash('hex'); - const coins = Coins.fromTX(tx, height); + /** + * Add a tx to the collection. + * @param {TX} tx + * @param {Number} height + * @returns {Coins} + */ + + addTX(tx, height) { + const hash = tx.hash(); + const coins = Coins.fromTX(tx, height); + return this.add(hash, coins); + } - for (const coin of coins.outputs.values()) - coin.spent = true; + /** + * Remove a tx from the collection. + * @param {TX} tx + * @param {Number} height + * @returns {Coins} + */ - return this.add(hash, coins); -}; + removeTX(tx, height) { + const hash = tx.hash(); + const coins = Coins.fromTX(tx, height); -/** - * Add an entry to the collection. - * @param {Outpoint} prevout - * @param {CoinEntry} coin - * @returns {CoinEntry|null} - */ + for (const coin of coins.outputs.values()) + coin.spent = true; -CoinView.prototype.addEntry = function addEntry(prevout, coin) { - const {hash, index} = prevout; - const coins = this.ensure(hash); - return coins.add(index, coin); -}; + return this.add(hash, coins); + } -/** - * Add a coin to the collection. - * @param {Coin} coin - * @returns {CoinEntry|null} - */ + /** + * Add an entry to the collection. + * @param {Outpoint} prevout + * @param {CoinEntry} coin + * @returns {CoinEntry|null} + */ + + addEntry(prevout, coin) { + const {hash, index} = prevout; + const coins = this.ensure(hash); + return coins.add(index, coin); + } -CoinView.prototype.addCoin = function addCoin(coin) { - const coins = this.ensure(coin.hash); - return coins.addCoin(coin); -}; + /** + * Add a coin to the collection. + * @param {Coin} coin + * @returns {CoinEntry|null} + */ -/** - * Add an output to the collection. - * @param {Outpoint} prevout - * @param {Output} output - * @returns {CoinEntry|null} - */ + addCoin(coin) { + const coins = this.ensure(coin.hash); + return coins.addCoin(coin); + } -CoinView.prototype.addOutput = function addOutput(prevout, output) { - const {hash, index} = prevout; - const coins = this.ensure(hash); - return coins.addOutput(index, output); -}; + /** + * Add an output to the collection. + * @param {Outpoint} prevout + * @param {Output} output + * @returns {CoinEntry|null} + */ + + addOutput(prevout, output) { + const {hash, index} = prevout; + const coins = this.ensure(hash); + return coins.addOutput(index, output); + } -/** - * Add an output to the collection by output index. - * @param {TX} tx - * @param {Number} index - * @param {Number} height - * @returns {CoinEntry|null} - */ + /** + * Add an output to the collection by output index. + * @param {TX} tx + * @param {Number} index + * @param {Number} height + * @returns {CoinEntry|null} + */ + + addIndex(tx, index, height) { + const hash = tx.hash(); + const coins = this.ensure(hash); + return coins.addIndex(tx, index, height); + } -CoinView.prototype.addIndex = function addIndex(tx, index, height) { - const hash = tx.hash('hex'); - const coins = this.ensure(hash); - return coins.addIndex(tx, index, height); -}; + /** + * Spend an output. + * @param {Outpoint} prevout + * @returns {CoinEntry|null} + */ -/** - * Spend an output. - * @param {Outpoint} prevout - * @returns {CoinEntry|null} - */ + spendEntry(prevout) { + const {hash, index} = prevout; + const coins = this.get(hash); -CoinView.prototype.spendEntry = function spendEntry(prevout) { - const {hash, index} = prevout; - const coins = this.get(hash); + if (!coins) + return null; - if (!coins) - return null; + const coin = coins.spend(index); - const coin = coins.spend(index); + if (!coin) + return null; - if (!coin) - return null; + this.undo.push(coin); - this.undo.push(coin); + return coin; + } - return coin; -}; + /** + * Remove an output. + * @param {Outpoint} prevout + * @returns {CoinEntry|null} + */ -/** - * Remove an output. - * @param {Outpoint} prevout - * @returns {CoinEntry|null} - */ + removeEntry(prevout) { + const {hash, index} = prevout; + const coins = this.get(hash); -CoinView.prototype.removeEntry = function removeEntry(prevout) { - const {hash, index} = prevout; - const coins = this.get(hash); + if (!coins) + return null; - if (!coins) - return null; + return coins.remove(index); + } - return coins.remove(index); -}; + /** + * Test whether the view has an entry by prevout. + * @param {Outpoint} prevout + * @returns {Boolean} + */ -/** - * Test whether the view has an entry by prevout. - * @param {Outpoint} prevout - * @returns {Boolean} - */ + hasEntry(prevout) { + const {hash, index} = prevout; + const coins = this.get(hash); -CoinView.prototype.hasEntry = function hasEntry(prevout) { - const {hash, index} = prevout; - const coins = this.get(hash); + if (!coins) + return false; - if (!coins) - return false; + return coins.has(index); + } - return coins.has(index); -}; + /** + * Get a single entry by prevout. + * @param {Outpoint} prevout + * @returns {CoinEntry|null} + */ -/** - * Get a single entry by prevout. - * @param {Outpoint} prevout - * @returns {CoinEntry|null} - */ + getEntry(prevout) { + const {hash, index} = prevout; + const coins = this.get(hash); -CoinView.prototype.getEntry = function getEntry(prevout) { - const {hash, index} = prevout; - const coins = this.get(hash); + if (!coins) + return null; - if (!coins) - return null; + return coins.get(index); + } - return coins.get(index); -}; + /** + * Test whether an entry has been spent by prevout. + * @param {Outpoint} prevout + * @returns {Boolean} + */ -/** - * Test whether an entry has been spent by prevout. - * @param {Outpoint} prevout - * @returns {Boolean} - */ + isUnspent(prevout) { + const {hash, index} = prevout; + const coins = this.get(hash); -CoinView.prototype.isUnspent = function isUnspent(prevout) { - const {hash, index} = prevout; - const coins = this.get(hash); + if (!coins) + return false; - if (!coins) - return false; + return coins.isUnspent(index); + } - return coins.isUnspent(index); -}; + /** + * Get a single coin by prevout. + * @param {Outpoint} prevout + * @returns {Coin|null} + */ -/** - * Get a single coin by prevout. - * @param {Outpoint} prevout - * @returns {Coin|null} - */ + getCoin(prevout) { + const coins = this.get(prevout.hash); -CoinView.prototype.getCoin = function getCoin(prevout) { - const coins = this.get(prevout.hash); + if (!coins) + return null; - if (!coins) - return null; + return coins.getCoin(prevout); + } - return coins.getCoin(prevout); -}; + /** + * Get a single output by prevout. + * @param {Outpoint} prevout + * @returns {Output|null} + */ -/** - * Get a single output by prevout. - * @param {Outpoint} prevout - * @returns {Output|null} - */ + getOutput(prevout) { + const {hash, index} = prevout; + const coins = this.get(hash); -CoinView.prototype.getOutput = function getOutput(prevout) { - const {hash, index} = prevout; - const coins = this.get(hash); + if (!coins) + return null; - if (!coins) - return null; + return coins.getOutput(index); + } - return coins.getOutput(index); -}; + /** + * Get coins height by prevout. + * @param {Outpoint} prevout + * @returns {Number} + */ -/** - * Get coins height by prevout. - * @param {Outpoint} prevout - * @returns {Number} - */ + getHeight(prevout) { + const coin = this.getEntry(prevout); -CoinView.prototype.getHeight = function getHeight(prevout) { - const coin = this.getEntry(prevout); + if (!coin) + return -1; - if (!coin) - return -1; + return coin.height; + } - return coin.height; -}; + /** + * Get coins coinbase flag by prevout. + * @param {Outpoint} prevout + * @returns {Boolean} + */ -/** - * Get coins coinbase flag by prevout. - * @param {Outpoint} prevout - * @returns {Boolean} - */ + isCoinbase(prevout) { + const coin = this.getEntry(prevout); -CoinView.prototype.isCoinbase = function isCoinbase(prevout) { - const coin = this.getEntry(prevout); + if (!coin) + return false; - if (!coin) - return false; + return coin.coinbase; + } - return coin.coinbase; -}; + /** + * Test whether the view has an entry by input. + * @param {Input} input + * @returns {Boolean} + */ -/** - * Test whether the view has an entry by input. - * @param {Input} input - * @returns {Boolean} - */ + hasEntryFor(input) { + return this.hasEntry(input.prevout); + } -CoinView.prototype.hasEntryFor = function hasEntryFor(input) { - return this.hasEntry(input.prevout); -}; + /** + * Get a single entry by input. + * @param {Input} input + * @returns {CoinEntry|null} + */ -/** - * Get a single entry by input. - * @param {Input} input - * @returns {CoinEntry|null} - */ + getEntryFor(input) { + return this.getEntry(input.prevout); + } -CoinView.prototype.getEntryFor = function getEntryFor(input) { - return this.getEntry(input.prevout); -}; + /** + * Test whether an entry has been spent by input. + * @param {Input} input + * @returns {Boolean} + */ -/** - * Test whether an entry has been spent by input. - * @param {Input} input - * @returns {Boolean} - */ + isUnspentFor(input) { + return this.isUnspent(input.prevout); + } -CoinView.prototype.isUnspentFor = function isUnspentFor(input) { - return this.isUnspent(input.prevout); -}; + /** + * Get a single coin by input. + * @param {Input} input + * @returns {Coin|null} + */ -/** - * Get a single coin by input. - * @param {Input} input - * @returns {Coin|null} - */ + getCoinFor(input) { + return this.getCoin(input.prevout); + } -CoinView.prototype.getCoinFor = function getCoinFor(input) { - return this.getCoin(input.prevout); -}; + /** + * Get a single output by input. + * @param {Input} input + * @returns {Output|null} + */ -/** - * Get a single output by input. - * @param {Input} input - * @returns {Output|null} - */ + getOutputFor(input) { + return this.getOutput(input.prevout); + } -CoinView.prototype.getOutputFor = function getOutputFor(input) { - return this.getOutput(input.prevout); -}; + /** + * Get coins height by input. + * @param {Input} input + * @returns {Number} + */ -/** - * Get coins height by input. - * @param {Input} input - * @returns {Number} - */ + getHeightFor(input) { + return this.getHeight(input.prevout); + } -CoinView.prototype.getHeightFor = function getHeightFor(input) { - return this.getHeight(input.prevout); -}; + /** + * Get coins coinbase flag by input. + * @param {Input} input + * @returns {Boolean} + */ -/** - * Get coins coinbase flag by input. - * @param {Input} input - * @returns {Boolean} - */ + isCoinbaseFor(input) { + return this.isCoinbase(input.prevout); + } -CoinView.prototype.isCoinbaseFor = function isCoinbaseFor(input) { - return this.isCoinbase(input.prevout); -}; + /** + * Retrieve coins from database. + * @method + * @param {ChainDB} db + * @param {Outpoint} prevout + * @returns {Promise} - Returns {@link CoinEntry}. + */ -/** - * Retrieve coins from database. - * @method - * @param {ChainDB} db - * @param {Outpoint} prevout - * @returns {Promise} - Returns {@link CoinEntry}. - */ + async readCoin(db, prevout) { + const cache = this.getEntry(prevout); -CoinView.prototype.readCoin = async function readCoin(db, prevout) { - const cache = this.getEntry(prevout); + if (cache) + return cache; - if (cache) - return cache; + const coin = await db.readCoin(prevout); - const coin = await db.readCoin(prevout); + if (!coin) + return null; - if (!coin) - return null; + return this.addEntry(prevout, coin); + } - return this.addEntry(prevout, coin); -}; + /** + * Read all input coins into unspent map. + * @method + * @param {ChainDB} db + * @param {TX} tx + * @returns {Promise} - Returns {Boolean}. + */ -/** - * Read all input coins into unspent map. - * @method - * @param {ChainDB} db - * @param {TX} tx - * @returns {Promise} - Returns {Boolean}. - */ + async readInputs(db, tx) { + let found = true; -CoinView.prototype.readInputs = async function readInputs(db, tx) { - let found = true; + for (const {prevout} of tx.inputs) { + if (!await this.readCoin(db, prevout)) + found = false; + } - for (const {prevout} of tx.inputs) { - if (!await this.readCoin(db, prevout)) - found = false; + return found; } - return found; -}; + /** + * Spend coins for transaction. + * @method + * @param {ChainDB} db + * @param {TX} tx + * @returns {Promise} - Returns {Boolean}. + */ -/** - * Spend coins for transaction. - * @method - * @param {ChainDB} db - * @param {TX} tx - * @returns {Promise} - Returns {Boolean}. - */ + async spendInputs(db, tx) { + let i = 0; -CoinView.prototype.spendInputs = async function spendInputs(db, tx) { - let i = 0; + while (i < tx.inputs.length) { + const len = Math.min(i + 4, tx.inputs.length); + const jobs = []; - while (i < tx.inputs.length) { - const len = Math.min(i + 4, tx.inputs.length); - const jobs = []; - - for (; i < len; i++) { - const {prevout} = tx.inputs[i]; - jobs.push(this.readCoin(db, prevout)); - } + for (; i < len; i++) { + const {prevout} = tx.inputs[i]; + jobs.push(this.readCoin(db, prevout)); + } - const coins = await Promise.all(jobs); + const coins = await Promise.all(jobs); - for (const coin of coins) { - if (!coin || coin.spent) - return false; + for (const coin of coins) { + if (!coin || coin.spent) + return false; - coin.spent = true; - this.undo.push(coin); + coin.spent = true; + this.undo.push(coin); + } } + + return true; } - return true; -}; + /** + * Calculate serialization size. + * @returns {Number} + */ -/** - * Calculate serialization size. - * @returns {Number} - */ + getSize(tx) { + let size = 0; -CoinView.prototype.getSize = function getSize(tx) { - let size = 0; + size += tx.inputs.length; - size += tx.inputs.length; + for (const {prevout} of tx.inputs) { + const coin = this.getEntry(prevout); - for (const {prevout} of tx.inputs) { - const coin = this.getEntry(prevout); + if (!coin) + continue; - if (!coin) - continue; + size += coin.getSize(); + } - size += coin.getSize(); + return size; } - return size; -}; + /** + * Write coin data to buffer writer + * as it pertains to a transaction. + * @param {BufferWriter} bw + * @param {TX} tx + */ -/** - * Write coin data to buffer writer - * as it pertains to a transaction. - * @param {BufferWriter} bw - * @param {TX} tx - */ + toWriter(bw, tx) { + for (const {prevout} of tx.inputs) { + const coin = this.getEntry(prevout); -CoinView.prototype.toWriter = function toWriter(bw, tx) { - for (const {prevout} of tx.inputs) { - const coin = this.getEntry(prevout); + if (!coin) { + bw.writeU8(0); + continue; + } - if (!coin) { - bw.writeU8(0); - continue; + bw.writeU8(1); + coin.toWriter(bw); } - bw.writeU8(1); - coin.toWriter(bw); + return bw; } - return bw; -}; + /** + * Read serialized view data from a buffer + * reader as it pertains to a transaction. + * @private + * @param {BufferReader} br + * @param {TX} tx + */ -/** - * Read serialized view data from a buffer - * reader as it pertains to a transaction. - * @private - * @param {BufferReader} br - * @param {TX} tx - */ + fromReader(br, tx) { + for (const {prevout} of tx.inputs) { + if (br.readU8() === 0) + continue; -CoinView.prototype.fromReader = function fromReader(br, tx) { - for (const {prevout} of tx.inputs) { - if (br.readU8() === 0) - continue; + const coin = CoinEntry.fromReader(br); - const coin = CoinEntry.fromReader(br); + this.addEntry(prevout, coin); + } - this.addEntry(prevout, coin); + return this; } - return this; -}; + /** + * Read serialized view data from a buffer + * reader as it pertains to a transaction. + * @param {BufferReader} br + * @param {TX} tx + * @returns {CoinView} + */ -/** - * Read serialized view data from a buffer - * reader as it pertains to a transaction. - * @param {BufferReader} br - * @param {TX} tx - * @returns {CoinView} - */ - -CoinView.fromReader = function fromReader(br, tx) { - return new CoinView().fromReader(br, tx); -}; + static fromReader(br, tx) { + return new this().fromReader(br, tx); + } +} /* * Expose diff --git a/lib/coins/compress.js b/lib/coins/compress.js index 001649420..c807310e7 100644 --- a/lib/coins/compress.js +++ b/lib/coins/compress.js @@ -11,9 +11,9 @@ * @ignore */ -const assert = require('assert'); -const secp256k1 = require('../crypto/secp256k1'); -const encoding = require('../utils/encoding'); +const assert = require('bsert'); +const {encoding} = require('bufio'); +const secp256k1 = require('bcrypto/lib/secp256k1'); const consensus = require('../protocol/consensus'); /* diff --git a/lib/coins/undocoins.js b/lib/coins/undocoins.js index 1396609bc..3cb5d5f65 100644 --- a/lib/coins/undocoins.js +++ b/lib/coins/undocoins.js @@ -6,132 +6,134 @@ 'use strict'; -const assert = require('assert'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); +const assert = require('bsert'); +const bio = require('bufio'); const CoinEntry = require('../coins/coinentry'); /** - * UndoCoins + * Undo Coins * Coins need to be resurrected from somewhere * during a reorg. The undo coins store all * spent coins in a single record per block * (in a compressed format). * @alias module:coins.UndoCoins - * @constructor * @property {UndoCoin[]} items */ -function UndoCoins() { - if (!(this instanceof UndoCoins)) - return new UndoCoins(); +class UndoCoins { + /** + * Create undo coins. + * @constructor + */ - this.items = []; -} + constructor() { + this.items = []; + } -/** - * Push coin entry onto undo coin array. - * @param {CoinEntry} - * @returns {Number} - */ + /** + * Push coin entry onto undo coin array. + * @param {CoinEntry} + * @returns {Number} + */ -UndoCoins.prototype.push = function push(coin) { - return this.items.push(coin); -}; + push(coin) { + return this.items.push(coin); + } -/** - * Calculate undo coins size. - * @returns {Number} - */ + /** + * Calculate undo coins size. + * @returns {Number} + */ -UndoCoins.prototype.getSize = function getSize() { - let size = 0; + getSize() { + let size = 0; - size += 4; + size += 4; - for (const coin of this.items) - size += coin.getSize(); + for (const coin of this.items) + size += coin.getSize(); - return size; -}; + return size; + } -/** - * Serialize all undo coins. - * @returns {Buffer} - */ + /** + * Serialize all undo coins. + * @returns {Buffer} + */ -UndoCoins.prototype.toRaw = function toRaw() { - const size = this.getSize(); - const bw = new StaticWriter(size); + toRaw() { + const size = this.getSize(); + const bw = bio.write(size); - bw.writeU32(this.items.length); + bw.writeU32(this.items.length); - for (const coin of this.items) - coin.toWriter(bw); + for (const coin of this.items) + coin.toWriter(bw); - return bw.render(); -}; + return bw.render(); + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @returns {UndoCoins} - */ + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + * @returns {UndoCoins} + */ -UndoCoins.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); - const count = br.readU32(); + fromRaw(data) { + const br = bio.read(data); + const count = br.readU32(); - for (let i = 0; i < count; i++) - this.items.push(CoinEntry.fromReader(br)); + for (let i = 0; i < count; i++) + this.items.push(CoinEntry.fromReader(br)); - return this; -}; + return this; + } -/** - * Instantiate undo coins from serialized data. - * @param {Buffer} data - * @returns {UndoCoins} - */ + /** + * Instantiate undo coins from serialized data. + * @param {Buffer} data + * @returns {UndoCoins} + */ -UndoCoins.fromRaw = function fromRaw(data) { - return new UndoCoins().fromRaw(data); -}; + static fromRaw(data) { + return new this().fromRaw(data); + } -/** - * Test whether the undo coins have any members. - * @returns {Boolean} - */ + /** + * Test whether the undo coins have any members. + * @returns {Boolean} + */ -UndoCoins.prototype.isEmpty = function isEmpty() { - return this.items.length === 0; -}; + isEmpty() { + return this.items.length === 0; + } -/** - * Render the undo coins. - * @returns {Buffer} - */ + /** + * Render the undo coins. + * @returns {Buffer} + */ -UndoCoins.prototype.commit = function commit() { - const raw = this.toRaw(); - this.items.length = 0; - return raw; -}; + commit() { + const raw = this.toRaw(); + this.items.length = 0; + return raw; + } -/** - * Re-apply undo coins to a view, effectively unspending them. - * @param {CoinView} view - * @param {Outpoint} prevout - */ + /** + * Re-apply undo coins to a view, effectively unspending them. + * @param {CoinView} view + * @param {Outpoint} prevout + */ -UndoCoins.prototype.apply = function apply(view, prevout) { - const undo = this.items.pop(); + apply(view, prevout) { + const undo = this.items.pop(); - assert(undo); + assert(undo); - view.addEntry(prevout, undo); -}; + view.addEntry(prevout, undo); + } +} /* * Expose diff --git a/lib/crypto/aead.js b/lib/crypto/aead.js deleted file mode 100644 index 13a3a583d..000000000 --- a/lib/crypto/aead.js +++ /dev/null @@ -1,172 +0,0 @@ -/*! - * aead.js - aead for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const ChaCha20 = require('./chacha20'); -const Poly1305 = require('./poly1305'); - -/** - * AEAD (used for bip151) - * @alias module:crypto.AEAD - * @constructor - * @see https://github.com/openssh/openssh-portable - * @see https://tools.ietf.org/html/rfc7539#section-2.8 - */ - -function AEAD() { - if (!(this instanceof AEAD)) - return new AEAD(); - - this.chacha20 = new ChaCha20(); - this.poly1305 = new Poly1305(); - this.aadLen = 0; - this.cipherLen = 0; - this.polyKey = null; -} - -/** - * Initialize the AEAD with a key and iv. - * @param {Buffer} key - * @param {Buffer} iv - IV / packet sequence number. - */ - -AEAD.prototype.init = function init(key, iv) { - const polyKey = Buffer.allocUnsafe(32); - polyKey.fill(0); - - this.chacha20.init(key, iv); - this.chacha20.encrypt(polyKey); - this.poly1305.init(polyKey); - - // We need to encrypt a full block - // to get the cipher in the correct state. - this.chacha20.encrypt(Buffer.allocUnsafe(32)); - - // Counter should be one. - assert(this.chacha20.getCounter() === 1); - - // Expose for debugging. - this.polyKey = polyKey; - - this.aadLen = 0; - this.cipherLen = 0; -}; - -/** - * Update the aad (will be finalized - * on an encrypt/decrypt call). - * @param {Buffer} aad - */ - -AEAD.prototype.aad = function aad(data) { - assert(this.cipherLen === 0, 'Cannot update aad.'); - this.poly1305.update(data); - this.aadLen += data.length; -}; - -/** - * Encrypt a piece of data. - * @param {Buffer} data - */ - -AEAD.prototype.encrypt = function encrypt(data) { - if (this.cipherLen === 0) - this.pad16(this.aadLen); - - this.chacha20.encrypt(data); - this.poly1305.update(data); - this.cipherLen += data.length; - - return data; -}; - -/** - * Decrypt a piece of data. - * @param {Buffer} data - */ - -AEAD.prototype.decrypt = function decrypt(data) { - if (this.cipherLen === 0) - this.pad16(this.aadLen); - - this.cipherLen += data.length; - this.poly1305.update(data); - this.chacha20.encrypt(data); - - return data; -}; - -/** - * Authenticate data without decrypting. - * @param {Buffer} data - */ - -AEAD.prototype.auth = function auth(data) { - if (this.cipherLen === 0) - this.pad16(this.aadLen); - - this.cipherLen += data.length; - this.poly1305.update(data); - - return data; -}; - -/** - * Finalize the aead and generate a MAC. - * @returns {Buffer} MAC - */ - -AEAD.prototype.finish = function finish() { - const len = Buffer.allocUnsafe(16); - let lo, hi; - - // The RFC says these are supposed to be - // uint32le, but their own fucking test - // cases fail unless they are uint64le's. - lo = this.aadLen % 0x100000000; - hi = (this.aadLen - lo) / 0x100000000; - len.writeUInt32LE(lo, 0, true); - len.writeUInt32LE(hi, 4, true); - - lo = this.cipherLen % 0x100000000; - hi = (this.cipherLen - lo) / 0x100000000; - len.writeUInt32LE(lo, 8, true); - len.writeUInt32LE(hi, 12, true); - - if (this.cipherLen === 0) - this.pad16(this.aadLen); - - this.pad16(this.cipherLen); - this.poly1305.update(len); - - return this.poly1305.finish(); -}; - -/** - * Pad a chunk before updating mac. - * @private - * @param {Number} size - */ - -AEAD.prototype.pad16 = function pad16(size) { - size %= 16; - - if (size === 0) - return; - - const pad = Buffer.allocUnsafe(16 - size); - pad.fill(0); - - this.poly1305.update(pad); -}; - -/* - * Expose - */ - -module.exports = AEAD; diff --git a/lib/crypto/aes-browser.js b/lib/crypto/aes-browser.js deleted file mode 100644 index b0924ec66..000000000 --- a/lib/crypto/aes-browser.js +++ /dev/null @@ -1,1321 +0,0 @@ -/*! - * aes.js - aes128/192/256 for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - * - * Ported from: - * https://github.com/openssl/openssl/blob/master/crypto/aes/aes_core.c - * Entered into the public domain by Vincent Rijmen. - */ - -'use strict'; - -const assert = require('assert'); - -/** - * @exports crypto.aes-browser - * @ignore - */ - -const AES = exports; - -/* - * Tables - */ - -const TE0 = [ - 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, - 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, - 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, - 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, - 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, - 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, - 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, - 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, - 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, - 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, - 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, - 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, - 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, - 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, - 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, - 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, - 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, - 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, - 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, - 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, - 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, - 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, - 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, - 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, - 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, - 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, - 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, - 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, - 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, - 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, - 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, - 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, - 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, - 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, - 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, - 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, - 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, - 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, - 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, - 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, - 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, - 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, - 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, - 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, - 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, - 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, - 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, - 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, - 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, - 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, - 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, - 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, - 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, - 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, - 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, - 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, - 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, - 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, - 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, - 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, - 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, - 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, - 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, - 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a -]; - -const TE1 = [ - 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, - 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, - 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, - 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, - 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, - 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, - 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, - 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, - 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, - 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, - 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, - 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, - 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, - 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, - 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, - 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, - 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, - 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, - 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, - 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, - 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, - 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, - 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, - 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, - 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, - 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, - 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, - 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, - 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, - 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, - 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, - 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, - 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, - 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, - 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, - 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, - 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, - 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, - 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, - 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, - 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, - 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, - 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, - 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, - 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, - 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, - 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, - 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, - 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, - 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, - 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, - 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, - 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, - 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, - 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, - 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, - 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, - 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, - 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, - 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, - 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, - 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, - 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, - 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616 -]; - -const TE2 = [ - 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, - 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, - 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, - 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, - 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, - 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, - 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, - 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, - 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, - 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, - 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, - 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, - 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, - 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, - 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, - 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, - 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, - 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, - 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, - 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, - 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, - 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, - 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, - 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, - 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, - 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, - 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, - 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, - 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, - 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, - 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, - 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, - 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, - 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, - 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, - 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, - 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, - 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, - 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, - 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, - 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, - 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, - 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, - 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, - 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, - 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, - 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, - 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, - 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, - 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, - 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, - 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, - 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, - 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, - 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, - 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, - 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, - 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, - 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, - 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, - 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, - 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, - 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, - 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16 -]; - -const TE3 = [ - 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, - 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, - 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, - 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, - 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, - 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, - 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, - 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, - 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, - 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, - 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, - 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, - 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, - 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, - 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, - 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, - 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, - 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, - 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, - 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, - 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, - 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, - 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, - 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, - 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, - 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, - 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, - 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, - 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, - 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, - 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, - 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, - 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, - 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, - 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, - 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, - 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, - 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, - 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, - 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, - 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, - 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, - 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, - 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, - 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, - 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, - 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, - 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, - 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, - 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, - 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, - 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, - 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, - 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, - 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, - 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, - 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, - 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, - 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, - 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, - 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, - 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, - 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, - 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c -]; - -const TD0 = [ - 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, - 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, - 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, - 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, - 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, - 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, - 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, - 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, - 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, - 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, - 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, - 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, - 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, - 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, - 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, - 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, - 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, - 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, - 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, - 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, - 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, - 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, - 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, - 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, - 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, - 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, - 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, - 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, - 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, - 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, - 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, - 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, - 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, - 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, - 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, - 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, - 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, - 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, - 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, - 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, - 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, - 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, - 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, - 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, - 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, - 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, - 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, - 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, - 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, - 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, - 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, - 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, - 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, - 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, - 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, - 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, - 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, - 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, - 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, - 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, - 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, - 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, - 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, - 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742 -]; - -const TD1 = [ - 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, - 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, - 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, - 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, - 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, - 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, - 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, - 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, - 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, - 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, - 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, - 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, - 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, - 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, - 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, - 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, - 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, - 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, - 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, - 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, - 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, - 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, - 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, - 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, - 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, - 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, - 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, - 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, - 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, - 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, - 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, - 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, - 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, - 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, - 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, - 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, - 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, - 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, - 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, - 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, - 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, - 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, - 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, - 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, - 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, - 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, - 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, - 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, - 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, - 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, - 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, - 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, - 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, - 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, - 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, - 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, - 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, - 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, - 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, - 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, - 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, - 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, - 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, - 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857 -]; - -const TD2 = [ - 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, - 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, - 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, - 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, - 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, - 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, - 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, - 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, - 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, - 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, - 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, - 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, - 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, - 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, - 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, - 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, - 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, - 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, - 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, - 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, - 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, - 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, - 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, - 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, - 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, - 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, - 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, - 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, - 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, - 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, - 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, - 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, - 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, - 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, - 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, - 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, - 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, - 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, - 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, - 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, - 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, - 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, - 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, - 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, - 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, - 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, - 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, - 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, - 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, - 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, - 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, - 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, - 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, - 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, - 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, - 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, - 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, - 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, - 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, - 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, - 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, - 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, - 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, - 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8 -]; - -const TD3 = [ - 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, - 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, - 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, - 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, - 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, - 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, - 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, - 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, - 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, - 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, - 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, - 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, - 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, - 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, - 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, - 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, - 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, - 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, - 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, - 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, - 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, - 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, - 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, - 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, - 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, - 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, - 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, - 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, - 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, - 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, - 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, - 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, - 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, - 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, - 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, - 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, - 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, - 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, - 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, - 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, - 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, - 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, - 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, - 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, - 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, - 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, - 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, - 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, - 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, - 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, - 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, - 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, - 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, - 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, - 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, - 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, - 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, - 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, - 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, - 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, - 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, - 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, - 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, - 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0 -]; - -const TD4 = [ - 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, - 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, - 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, - 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, - 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, - 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, - 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, - 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, - 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, - 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, - 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, - 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, - 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, - 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, - 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, - 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, - 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, - 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, - 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, - 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, - 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, - 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, - 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, - 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, - 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, - 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, - 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, - 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, - 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, - 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, - 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, - 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d -]; - -const RCON = [ - 0x01000000, 0x02000000, 0x04000000, 0x08000000, - 0x10000000, 0x20000000, 0x40000000, 0x80000000, - 0x1b000000, 0x36000000 -]; - -/** - * An AES key object for encrypting - * and decrypting blocks. - * @constructor - * @ignore - * @param {Buffer} key - * @param {Number} bits - */ - -function AESKey(key, bits) { - if (!(this instanceof AESKey)) - return new AESKey(key, bits); - - this.rounds = null; - this.userKey = key; - this.bits = bits; - - switch (this.bits) { - case 128: - this.rounds = 10; - break; - case 192: - this.rounds = 12; - break; - case 256: - this.rounds = 14; - break; - default: - throw new Error('Bad key size.'); - } - - assert(Buffer.isBuffer(key)); - assert(key.length === this.bits / 8); - - this.decryptKey = null; - this.encryptKey = null; -} - -/** - * Destroy the object and zero the keys. - */ - -AESKey.prototype.destroy = function destroy() { - assert(this.userKey, 'Already destroyed.'); - - // User should zero this. - this.userKey = null; - - if (this.decryptKey) { - for (let i = 0; i < this.decryptKey.length; i++) - this.decryptKey[i] = 0; - this.decryptKey = null; - } - - if (this.encryptKey) { - for (let i = 0; i < this.encryptKey.length; i++) - this.encryptKey[i] = 0; - this.encryptKey = null; - } -}; - -/** - * Convert the user key into an encryption key. - * @returns {Uint32Array} key - */ - -AESKey.prototype.getEncryptKey = function getEncryptKey() { - assert(this.userKey, 'Cannot use key once it is destroyed.'); - - if (this.encryptKey) - return this.encryptKey; - - const key = new Uint32Array(60); - let kp = 0; - let i = 0; - - key[kp + 0] = readU32(this.userKey, 0); - key[kp + 1] = readU32(this.userKey, 4); - key[kp + 2] = readU32(this.userKey, 8); - key[kp + 3] = readU32(this.userKey, 12); - - this.encryptKey = key; - - if (this.bits === 128) { - for (;;) { - const tmp = key[kp + 3]; - - key[kp + 4] = key[kp + 0] - ^ (TE2[(tmp >>> 16) & 0xff] & 0xff000000) - ^ (TE3[(tmp >>> 8) & 0xff] & 0x00ff0000) - ^ (TE0[(tmp >>> 0) & 0xff] & 0x0000ff00) - ^ (TE1[(tmp >>> 24) & 0xff] & 0x000000ff) - ^ RCON[i]; - key[kp + 5] = key[kp + 1] ^ key[kp + 4]; - key[kp + 6] = key[kp + 2] ^ key[kp + 5]; - key[kp + 7] = key[kp + 3] ^ key[kp + 6]; - - if (++i === 10) - return key; - - kp += 4; - } - } - - key[kp + 4] = readU32(this.userKey, 16); - key[kp + 5] = readU32(this.userKey, 20); - - if (this.bits === 192) { - for (;;) { - const tmp = key[kp + 5]; - - key[kp + 6] = key[kp + 0] - ^ (TE2[(tmp >>> 16) & 0xff] & 0xff000000) - ^ (TE3[(tmp >>> 8) & 0xff] & 0x00ff0000) - ^ (TE0[(tmp >>> 0) & 0xff] & 0x0000ff00) - ^ (TE1[(tmp >>> 24) & 0xff] & 0x000000ff) - ^ RCON[i]; - key[kp + 7] = key[kp + 1] ^ key[kp + 6]; - key[kp + 8] = key[kp + 2] ^ key[kp + 7]; - key[kp + 9] = key[kp + 3] ^ key[kp + 8]; - - if (++i === 8) - return key; - - key[kp + 10] = key[kp + 4] ^ key[kp + 9]; - key[kp + 11] = key[kp + 5] ^ key[kp + 10]; - kp += 6; - } - } - - key[kp + 6] = readU32(this.userKey, 24); - key[kp + 7] = readU32(this.userKey, 28); - - if (this.bits === 256) { - for (;;) { - let tmp = key[kp + 7]; - - key[kp + 8] = key[kp + 0] - ^ (TE2[(tmp >>> 16) & 0xff] & 0xff000000) - ^ (TE3[(tmp >>> 8) & 0xff] & 0x00ff0000) - ^ (TE0[(tmp >>> 0) & 0xff] & 0x0000ff00) - ^ (TE1[(tmp >>> 24) & 0xff] & 0x000000ff) - ^ RCON[i]; - key[kp + 9] = key[kp + 1] ^ key[kp + 8]; - key[kp + 10] = key[kp + 2] ^ key[kp + 9]; - key[kp + 11] = key[kp + 3] ^ key[kp + 10]; - - if (++i === 7) - return key; - - tmp = key[kp + 11]; - - key[kp + 12] = key[kp + 4] - ^ (TE2[(tmp >>> 24) & 0xff] & 0xff000000) - ^ (TE3[(tmp >>> 16) & 0xff] & 0x00ff0000) - ^ (TE0[(tmp >>> 8) & 0xff] & 0x0000ff00) - ^ (TE1[(tmp >>> 0) & 0xff] & 0x000000ff); - key[kp + 13] = key[kp + 5] ^ key[kp + 12]; - key[kp + 14] = key[kp + 6] ^ key[kp + 13]; - key[kp + 15] = key[kp + 7] ^ key[kp + 14]; - - kp += 8; - } - } - - return key; -}; - -/** - * Convert the user key into a decryption key. - * @returns {Uint32Array} key - */ - -AESKey.prototype.getDecryptKey = function getDecryptKey() { - assert(this.userKey, 'Cannot use key once it is destroyed.'); - - if (this.decryptKey) - return this.decryptKey; - - // First, start with an encryption schedule. - const enc = this.getEncryptKey(); - const key = new Uint32Array(60); - let kp = 0; - - for (let i = 0; i < enc.length; i++) - key[i] = enc[i]; - - this.decryptKey = key; - - // Invert the order of the round keys. - for (let i = 0, j = 4 * this.rounds; i < j; i += 4, j -= 4) { - let tmp = key[kp + i + 0]; - - key[kp + i + 0] = key[kp + j + 0]; - key[kp + j + 0] = tmp; - - tmp = key[kp + i + 1]; - key[kp + i + 1] = key[kp + j + 1]; - key[kp + j + 1] = tmp; - - tmp = key[kp + i + 2]; - key[kp + i + 2] = key[kp + j + 2]; - key[kp + j + 2] = tmp; - - tmp = key[kp + i + 3]; - key[kp + i + 3] = key[kp + j + 3]; - key[kp + j + 3] = tmp; - } - - // Apply the inverse MixColumn transform to - // all round keys but the first and the last. - for (let i = 1; i < this.rounds; i++) { - kp += 4; - key[kp + 0] = TD0[TE1[(key[kp + 0] >>> 24) & 0xff] & 0xff] - ^ TD1[TE1[(key[kp + 0] >>> 16) & 0xff] & 0xff] - ^ TD2[TE1[(key[kp + 0] >>> 8) & 0xff] & 0xff] - ^ TD3[TE1[(key[kp + 0] >>> 0) & 0xff] & 0xff]; - key[kp + 1] = TD0[TE1[(key[kp + 1] >>> 24) & 0xff] & 0xff] - ^ TD1[TE1[(key[kp + 1] >>> 16) & 0xff] & 0xff] - ^ TD2[TE1[(key[kp + 1] >>> 8) & 0xff] & 0xff] - ^ TD3[TE1[(key[kp + 1] >>> 0) & 0xff] & 0xff]; - key[kp + 2] = TD0[TE1[(key[kp + 2] >>> 24) & 0xff] & 0xff] - ^ TD1[TE1[(key[kp + 2] >>> 16) & 0xff] & 0xff] - ^ TD2[TE1[(key[kp + 2] >>> 8) & 0xff] & 0xff] - ^ TD3[TE1[(key[kp + 2] >>> 0) & 0xff] & 0xff]; - key[kp + 3] = TD0[TE1[(key[kp + 3] >>> 24) & 0xff] & 0xff] - ^ TD1[TE1[(key[kp + 3] >>> 16) & 0xff] & 0xff] - ^ TD2[TE1[(key[kp + 3] >>> 8) & 0xff] & 0xff] - ^ TD3[TE1[(key[kp + 3] >>> 0) & 0xff] & 0xff]; - } - - return key; -}; - -/** - * Encrypt a 16 byte block of data. - * @param {Buffer} input - * @returns {Buffer} - */ - -AESKey.prototype.encryptBlock = function encryptBlock(input) { - assert(this.userKey, 'Cannot use key once it is destroyed.'); - - const key = this.getEncryptKey(); - let kp = 0; - - // Map byte array block to cipher - // state and add initial round key. - let s0 = readU32(input, 0) ^ key[0]; - let s1 = readU32(input, 4) ^ key[1]; - let s2 = readU32(input, 8) ^ key[2]; - let s3 = readU32(input, 12) ^ key[3]; - - // Nr - 1 full rounds - let r = this.rounds >>> 1; - let t0, t1, t2, t3; - - for (;;) { - t0 = TE0[(s0 >>> 24) & 0xff] - ^ TE1[(s1 >>> 16) & 0xff] - ^ TE2[(s2 >>> 8) & 0xff] - ^ TE3[(s3 >>> 0) & 0xff] - ^ key[kp + 4]; - t1 = TE0[(s1 >>> 24) & 0xff] - ^ TE1[(s2 >>> 16) & 0xff] - ^ TE2[(s3 >>> 8) & 0xff] - ^ TE3[(s0 >>> 0) & 0xff] - ^ key[kp + 5]; - t2 = TE0[(s2 >>> 24) & 0xff] - ^ TE1[(s3 >>> 16) & 0xff] - ^ TE2[(s0 >>> 8) & 0xff] - ^ TE3[(s1 >>> 0) & 0xff] - ^ key[kp + 6]; - t3 = TE0[(s3 >>> 24) & 0xff] - ^ TE1[(s0 >>> 16) & 0xff] - ^ TE2[(s1 >>> 8) & 0xff] - ^ TE3[(s2 >>> 0) & 0xff] - ^ key[kp + 7]; - - kp += 8; - - if (--r === 0) - break; - - s0 = TE0[(t0 >>> 24) & 0xff] - ^ TE1[(t1 >>> 16) & 0xff] - ^ TE2[(t2 >>> 8) & 0xff] - ^ TE3[(t3 >>> 0) & 0xff] - ^ key[kp + 0]; - s1 = TE0[(t1 >>> 24) & 0xff] - ^ TE1[(t2 >>> 16) & 0xff] - ^ TE2[(t3 >>> 8) & 0xff] - ^ TE3[(t0 >>> 0) & 0xff] - ^ key[kp + 1]; - s2 = TE0[(t2 >>> 24) & 0xff] - ^ TE1[(t3 >>> 16) & 0xff] - ^ TE2[(t0 >>> 8) & 0xff] - ^ TE3[(t1 >>> 0) & 0xff] - ^ key[kp + 2]; - s3 = TE0[(t3 >>> 24) & 0xff] - ^ TE1[(t0 >>> 16) & 0xff] - ^ TE2[(t1 >>> 8) & 0xff] - ^ TE3[(t2 >>> 0) & 0xff] - ^ key[kp + 3]; - } - - // Apply last round and map cipher - // state to byte array block. - s0 = (TE2[(t0 >>> 24) & 0xff] & 0xff000000) - ^ (TE3[(t1 >>> 16) & 0xff] & 0x00ff0000) - ^ (TE0[(t2 >>> 8) & 0xff] & 0x0000ff00) - ^ (TE1[(t3 >>> 0) & 0xff] & 0x000000ff) - ^ key[kp + 0]; - s1 = (TE2[(t1 >>> 24) & 0xff] & 0xff000000) - ^ (TE3[(t2 >>> 16) & 0xff] & 0x00ff0000) - ^ (TE0[(t3 >>> 8) & 0xff] & 0x0000ff00) - ^ (TE1[(t0 >>> 0) & 0xff] & 0x000000ff) - ^ key[kp + 1]; - s2 = (TE2[(t2 >>> 24) & 0xff] & 0xff000000) - ^ (TE3[(t3 >>> 16) & 0xff] & 0x00ff0000) - ^ (TE0[(t0 >>> 8) & 0xff] & 0x0000ff00) - ^ (TE1[(t1 >>> 0) & 0xff] & 0x000000ff) - ^ key[kp + 2]; - s3 = (TE2[(t3 >>> 24) & 0xff] & 0xff000000) - ^ (TE3[(t0 >>> 16) & 0xff] & 0x00ff0000) - ^ (TE0[(t1 >>> 8) & 0xff] & 0x0000ff00) - ^ (TE1[(t2 >>> 0) & 0xff] & 0x000000ff) - ^ key[kp + 3]; - - const output = Buffer.allocUnsafe(16); - writeU32(output, s0, 0); - writeU32(output, s1, 4); - writeU32(output, s2, 8); - writeU32(output, s3, 12); - - return output; -}; - -/** - * Decrypt a 16 byte block of data. - * @param {Buffer} input - * @returns {Buffer} - */ - -AESKey.prototype.decryptBlock = function decryptBlock(input) { - assert(this.userKey, 'Cannot use AESKey once it is destroyed.'); - - const key = this.getDecryptKey(); - let kp = 0; - - // Map byte array block to cipher - // state and add initial round key. - let s0 = readU32(input, 0) ^ key[kp + 0]; - let s1 = readU32(input, 4) ^ key[kp + 1]; - let s2 = readU32(input, 8) ^ key[kp + 2]; - let s3 = readU32(input, 12) ^ key[kp + 3]; - - // Nr - 1 full rounds - let r = this.rounds >>> 1; - let t0, t1, t2, t3; - - for (;;) { - t0 = TD0[(s0 >>> 24) & 0xff] - ^ TD1[(s3 >>> 16) & 0xff] - ^ TD2[(s2 >>> 8) & 0xff] - ^ TD3[(s1 >>> 0) & 0xff] - ^ key[kp + 4]; - t1 = TD0[(s1 >>> 24) & 0xff] - ^ TD1[(s0 >>> 16) & 0xff] - ^ TD2[(s3 >>> 8) & 0xff] - ^ TD3[(s2 >>> 0) & 0xff] - ^ key[kp + 5]; - t2 = TD0[(s2 >>> 24) & 0xff] - ^ TD1[(s1 >>> 16) & 0xff] - ^ TD2[(s0 >>> 8) & 0xff] - ^ TD3[(s3 >>> 0) & 0xff] - ^ key[kp + 6]; - t3 = TD0[(s3 >>> 24) & 0xff] - ^ TD1[(s2 >>> 16) & 0xff] - ^ TD2[(s1 >>> 8) & 0xff] - ^ TD3[(s0 >>> 0) & 0xff] - ^ key[kp + 7]; - - kp += 8; - - if (--r === 0) - break; - - s0 = TD0[(t0 >>> 24) & 0xff] - ^ TD1[(t3 >>> 16) & 0xff] - ^ TD2[(t2 >>> 8) & 0xff] - ^ TD3[(t1 >>> 0) & 0xff] - ^ key[kp + 0]; - s1 = TD0[(t1 >>> 24) & 0xff] - ^ TD1[(t0 >>> 16) & 0xff] - ^ TD2[(t3 >>> 8) & 0xff] - ^ TD3[(t2 >>> 0) & 0xff] - ^ key[kp + 1]; - s2 = TD0[(t2 >>> 24) & 0xff] - ^ TD1[(t1 >>> 16) & 0xff] - ^ TD2[(t0 >>> 8) & 0xff] - ^ TD3[(t3 >>> 0) & 0xff] - ^ key[kp + 2]; - s3 = TD0[(t3 >>> 24) & 0xff] - ^ TD1[(t2 >>> 16) & 0xff] - ^ TD2[(t1 >>> 8) & 0xff] - ^ TD3[(t0 >>> 0) & 0xff] - ^ key[kp + 3]; - } - - // Apply last round and map cipher - // state to byte array block. - s0 = (TD4[(t0 >>> 24) & 0xff] << 24) - ^ (TD4[(t3 >>> 16) & 0xff] << 16) - ^ (TD4[(t2 >>> 8) & 0xff] << 8) - ^ (TD4[(t1 >>> 0) & 0xff] << 0) - ^ key[kp + 0]; - s1 = (TD4[(t1 >>> 24) & 0xff] << 24) - ^ (TD4[(t0 >>> 16) & 0xff] << 16) - ^ (TD4[(t3 >>> 8) & 0xff] << 8) - ^ (TD4[(t2 >>> 0) & 0xff] << 0) - ^ key[kp + 1]; - s2 = (TD4[(t2 >>> 24) & 0xff] << 24) - ^ (TD4[(t1 >>> 16) & 0xff] << 16) - ^ (TD4[(t0 >>> 8) & 0xff] << 8) - ^ (TD4[(t3 >>> 0) & 0xff] << 0) - ^ key[kp + 2]; - s3 = (TD4[(t3 >>> 24) & 0xff] << 24) - ^ (TD4[(t2 >>> 16) & 0xff] << 16) - ^ (TD4[(t1 >>> 8) & 0xff] << 8) - ^ (TD4[(t0 >>> 0) & 0xff] << 0) - ^ key[kp + 3]; - - const output = Buffer.allocUnsafe(16); - writeU32(output, s0, 0); - writeU32(output, s1, 4); - writeU32(output, s2, 8); - writeU32(output, s3, 12); - - return output; -}; - -/** - * AES cipher. - * @constructor - * @ignore - * @param {Buffer} key - * @param {Buffer} iv - * @param {Number} bits - * @param {String} mode - */ - -function AESCipher(key, iv, bits, mode) { - if (!(this instanceof AESCipher)) - return new AESCipher(key, iv, mode); - - assert(mode === 'ecb' || mode === 'cbc', 'Unknown mode.'); - - this.key = new AESKey(key, bits); - this.mode = mode; - this.prev = iv; - this.waiting = null; -} - -/** - * Encrypt blocks of data. - * @param {Buffer} data - * @returns {Buffer} - */ - -AESCipher.prototype.update = function update(data) { - const blocks = []; - - if (this.waiting) { - data = concat(this.waiting, data); - this.waiting = null; - } - - const trailing = data.length % 16; - const len = data.length - trailing; - - // Encrypt all blocks except for the last. - for (let i = 0; i < len; i += 16) { - let block = data.slice(i, i + 16); - if (this.mode === 'cbc') - block = xor(block, this.prev); - this.prev = this.key.encryptBlock(block); - blocks.push(this.prev); - } - - if (trailing > 0) - this.waiting = data.slice(len); - - return Buffer.concat(blocks); -}; - -/** - * Finalize the cipher. - * @returns {Buffer} - */ - -AESCipher.prototype.final = function final() { - let block; - - // Handle padding on the last block. - if (!this.waiting) { - block = Buffer.allocUnsafe(16); - block.fill(16); - } else { - const left = 16 - this.waiting.length; - const pad = Buffer.allocUnsafe(left); - pad.fill(left); - block = concat(this.waiting, pad); - } - - // Encrypt the last block, - // as well as the padding. - if (this.mode === 'cbc') - block = xor(block, this.prev); - - block = this.key.encryptBlock(block); - - this.key.destroy(); - - return block; -}; - -/** - * AES decipher. - * @constructor - * @ignore - * @param {Buffer} key - * @param {Buffer} iv - * @param {Number} bits - * @param {String} mode - */ - -function AESDecipher(key, iv, bits, mode) { - if (!(this instanceof AESDecipher)) - return new AESDecipher(key, iv, mode); - - assert(mode === 'ecb' || mode === 'cbc', 'Unknown mode.'); - - this.key = new AESKey(key, bits); - this.mode = mode; - this.prev = iv; - this.waiting = null; - this.lastBlock = null; -} - -/** - * Decrypt blocks of data. - * @param {Buffer} data - */ - -AESDecipher.prototype.update = function update(data) { - const blocks = []; - - if (this.waiting) { - data = concat(this.waiting, data); - this.waiting = null; - } - - const trailing = data.length % 16; - const len = data.length - trailing; - - // Decrypt all blocks. - for (let i = 0; i < len; i += 16) { - const chunk = this.prev; - - this.prev = data.slice(i, i + 16); - - let block = this.key.decryptBlock(this.prev); - - if (this.mode === 'cbc') - block = xor(block, chunk); - - blocks.push(block); - } - - if (trailing > 0) - this.waiting = data.slice(len); - - if (this.lastBlock) { - blocks.unshift(this.lastBlock); - this.lastBlock = null; - } - - // Keep a reference to the last - // block for the padding check. - this.lastBlock = blocks.pop(); - - return Buffer.concat(blocks); -}; - -/** - * Finalize the decipher. - * @returns {Buffer} - */ - -AESDecipher.prototype.final = function final() { - this.key.destroy(); - - assert(!this.waiting, 'Bad decrypt (trailing bytes).'); - assert(this.lastBlock, 'Bad decrypt (no data).'); - - // Check padding on the last block. - let block = this.lastBlock; - let b = 16; - const n = block[b - 1]; - - if (n === 0 || n > b) - throw new Error('Bad decrypt (padding).'); - - for (let i = 0; i < n; i++) { - if (block[--b] !== n) - throw new Error('Bad decrypt (padding).'); - } - - // Slice off the padding unless - // the entire block was padding. - if (n === 16) - return Buffer.alloc(0); - - block = block.slice(0, -n); - - return block; -}; - -/** - * Encrypt data with aes 256. - * @param {Buffer} data - * @param {Buffer} key - * @param {Buffer} iv - * @param {String} mode - * @returns {Buffer} - */ - -AES.encrypt = function encrypt(data, key, iv, bits, mode) { - const cipher = new AESCipher(key, iv, bits, mode); - return concat(cipher.update(data), cipher.final()); -}; - -/** - * Decrypt data with aes 256. - * @param {Buffer} data - * @param {Buffer} key - * @param {Buffer|null} iv - * @param {Number} bits - * @param {String} mode - * @returns {Buffer} - */ - -AES.decrypt = function decrypt(data, key, iv, bits, mode) { - const decipher = new AESDecipher(key, iv, bits, mode); - return concat(decipher.update(data), decipher.final()); -}; - -/** - * Encrypt data with aes 256 cbc. - * @param {Buffer} data - * @param {Buffer} key - * @param {Buffer} iv - * @returns {Buffer} - */ - -AES.encipher = function encipher(data, key, iv) { - assert(Buffer.isBuffer(data)); - assert(key.length === 32); - assert(iv.length === 16); - return AES.encrypt(data, key, iv, 256, 'cbc'); -}; - -/** - * Decrypt data with aes 256 cbc. - * @param {Buffer} data - * @param {Buffer} key - * @param {Buffer} iv - * @returns {Buffer} - */ - -AES.decipher = function decipher(data, key, iv) { - assert(Buffer.isBuffer(data)); - assert(key.length === 32); - assert(iv.length === 16); - return AES.decrypt(data, key, iv, 256, 'cbc'); -}; - -/* - * Helpers - */ - -function xor(v1, v2) { - const out = Buffer.allocUnsafe(v1.length); - for (let i = 0; i < v1.length; i++) - out[i] = v1[i] ^ v2[i]; - return out; -} - -function readU32(data, i) { - return (data[i + 0] << 24) - ^ (data[i + 1] << 16) - ^ (data[i + 2] << 8) - ^ data[i + 3]; -} - -function writeU32(data, value, i) { - data[i + 0] = (value >>> 24) & 0xff; - data[i + 1] = (value >>> 16) & 0xff; - data[i + 2] = (value >>> 8) & 0xff; - data[i + 3] = value & 0xff; -} - -function concat(a, b) { - const data = Buffer.allocUnsafe(a.length + b.length); - a.copy(data, 0); - b.copy(data, a.length); - return data; -} diff --git a/lib/crypto/aes.js b/lib/crypto/aes.js deleted file mode 100644 index 15618fcd4..000000000 --- a/lib/crypto/aes.js +++ /dev/null @@ -1,49 +0,0 @@ -/*! - * aes.js - aes for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module crypto.aes - */ - -const crypto = require('crypto'); -const native = require('../native').binding; - -/** - * Encrypt data with aes 256 cbc. - * @param {Buffer} data - * @param {Buffer} key - * @param {Buffer} iv - * @returns {Buffer} - */ - -exports.encipher = function encipher(data, key, iv) { - const ctx = crypto.createCipheriv('aes-256-cbc', key, iv); - return Buffer.concat([ctx.update(data), ctx.final()]); -}; - -/** - * Decrypt data with aes 256 cbc. - * @param {Buffer} data - * @param {Buffer} key - * @param {Buffer} iv - * @returns {Buffer} - */ - -exports.decipher = function decipher(data, key, iv) { - const ctx = crypto.createDecipheriv('aes-256-cbc', key, iv); - try { - return Buffer.concat([ctx.update(data), ctx.final()]); - } catch (e) { - throw new Error('Bad key for decryption.'); - } -}; - -if (native) { - exports.encipher = native.encipher; - exports.decipher = native.decipher; -} diff --git a/lib/crypto/bn.js b/lib/crypto/bn.js deleted file mode 100644 index 1cfaa76c7..000000000 --- a/lib/crypto/bn.js +++ /dev/null @@ -1,19 +0,0 @@ -/*! - * bn.js - big numbers for bcoin - * Copyright (c) 2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module crypto.BN - */ - -/** - * bn.js - * @constructor - * @see https://github.com/indutny/bn.js - */ - -module.exports = require('bn.js'); diff --git a/lib/crypto/ccmp.js b/lib/crypto/ccmp.js deleted file mode 100644 index dfc4ced17..000000000 --- a/lib/crypto/ccmp.js +++ /dev/null @@ -1,36 +0,0 @@ -/*! - * ccmp.js - constant-time compare for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); - -/** - * memcmp in constant time (can only return true or false). - * This protects us against timing attacks when - * comparing an input against a secret string. - * @alias module:crypto.ccmp - * @see https://cryptocoding.net/index.php/Coding_rules - * @see `$ man 3 memcmp` (NetBSD's consttime_memequal) - * @param {Buffer} a - * @param {Buffer} b - * @returns {Boolean} - */ - -module.exports = function ccmp(a, b) { - assert(Buffer.isBuffer(a)); - assert(Buffer.isBuffer(b)); - - if (b.length === 0) - return a.length === 0; - - let res = a.length ^ b.length; - - for (let i = 0; i < a.length; i++) - res |= a[i] ^ b[i % b.length]; - - return res === 0; -}; diff --git a/lib/crypto/chacha20.js b/lib/crypto/chacha20.js deleted file mode 100644 index 42e8db164..000000000 --- a/lib/crypto/chacha20.js +++ /dev/null @@ -1,205 +0,0 @@ -/*! - * chacha20.js - chacha20 for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const native = require('../native').binding; - -const BIG_ENDIAN = new Int8Array(new Int16Array([1]).buffer)[0] === 0; - -/** - * ChaCha20 (used for bip151) - * @alias module:crypto.ChaCha20 - * @constructor - * @see https://tools.ietf.org/html/rfc7539#section-2 - */ - -function ChaCha20() { - if (!(this instanceof ChaCha20)) - return new ChaCha20(); - - this.state = new Uint32Array(16); - this.stream = new Uint32Array(16); - this.bytes = new Uint8Array(this.stream.buffer); - - if (BIG_ENDIAN) - this.bytes = Buffer.allocUnsafe(64); - - this.pos = 0; - this.ivSize = 0; -} - -/** - * Initialize chacha20 with a key, iv, and counter. - * @param {Buffer} key - * @param {Buffer} iv - * @param {Number} counter - */ - -ChaCha20.prototype.init = function init(key, iv, counter) { - if (key) - this.initKey(key); - - if (iv) - this.initIV(iv, counter); -}; - -/** - * Set key. - * @param {Buffer} key - */ - -ChaCha20.prototype.initKey = function initKey(key) { - this.state[0] = 0x61707865; - this.state[1] = 0x3320646e; - this.state[2] = 0x79622d32; - this.state[3] = 0x6b206574; - - this.state[4] = key.readUInt32LE(0, true); - this.state[5] = key.readUInt32LE(4, true); - this.state[6] = key.readUInt32LE(8, true); - this.state[7] = key.readUInt32LE(12, true); - this.state[8] = key.readUInt32LE(16, true); - this.state[9] = key.readUInt32LE(20, true); - this.state[10] = key.readUInt32LE(24, true); - this.state[11] = key.readUInt32LE(28, true); - - this.state[12] = 0; - - this.pos = 0xffffffff; -}; - -/** - * Set IV and counter. - * @param {Buffer} iv - * @param {Number} counter - */ - -ChaCha20.prototype.initIV = function initIV(iv, counter) { - if (iv.length === 8) { - this.state[13] = 0; - this.state[14] = iv.readUInt32LE(0, true); - this.state[15] = iv.readUInt32LE(4, true); - } else if (iv.length === 12) { - this.state[13] = iv.readUInt32LE(0, true); - this.state[14] = iv.readUInt32LE(4, true); - this.state[15] = iv.readUInt32LE(8, true); - } else { - assert(false, 'Bad iv size.'); - } - - this.ivSize = iv.length * 8; - - this.setCounter(counter); -}; - -/** - * Encrypt/decrypt data. - * @param {Buffer} data - Will be mutated. - */ - -ChaCha20.prototype.encrypt = function encrypt(data) { - for (let i = 0; i < data.length; i++) { - if (this.pos >= 64) { - for (let j = 0; j < 16; j++) - this.stream[j] = this.state[j]; - - for (let j = 0; j < 10; j++) { - qround(this.stream, 0, 4, 8, 12); - qround(this.stream, 1, 5, 9, 13); - qround(this.stream, 2, 6, 10, 14); - qround(this.stream, 3, 7, 11, 15); - qround(this.stream, 0, 5, 10, 15); - qround(this.stream, 1, 6, 11, 12); - qround(this.stream, 2, 7, 8, 13); - qround(this.stream, 3, 4, 9, 14); - } - - for (let j = 0; j < 16; j++) { - this.stream[j] += this.state[j]; - if (BIG_ENDIAN) - this.bytes.writeUInt32LE(this.stream[j], j * 4, true); - } - - this.state[12]++; - - if (this.state[12] === 0) { - assert(this.ivSize === 64, 'Counter overflow.'); - this.state[13]++; - assert(this.state[13] !== 0, 'Counter overflow.'); - } - - this.pos = 0; - } - - data[i] ^= this.bytes[this.pos++]; - } - - return data; -}; - -/** - * Artificially set the counter. - * @param {Number} counter - */ - -ChaCha20.prototype.setCounter = function setCounter(counter) { - if (!counter) - counter = 0; - - const lo = counter % 0x100000000; - const hi = (counter - lo) / 0x100000000; - - this.state[12] = lo; - - if (this.ivSize === 64) - this.state[13] = hi; -}; - -/** - * Get the counter as a uint64. - * @returns {Number} - */ - -ChaCha20.prototype.getCounter = function getCounter() { - const lo = this.state[12]; - const hi = this.state[13]; - if (this.ivSize === 64) - return hi * 0x100000000 + lo; - return lo; -}; - -if (native) - ChaCha20 = native.ChaCha20; - -/* - * Helpers - */ - -function qround(x, a, b, c, d) { - x[a] += x[b]; - x[d] = rotl32(x[d] ^ x[a], 16); - - x[c] += x[d]; - x[b] = rotl32(x[b] ^ x[c], 12); - - x[a] += x[b]; - x[d] = rotl32(x[d] ^ x[a], 8); - - x[c] += x[d]; - x[b] = rotl32(x[b] ^ x[c], 7); -} - -function rotl32(w, b) { - return (w << b) | (w >>> (32 - b)); -} - -/* - * Expose - */ - -module.exports = ChaCha20; diff --git a/lib/crypto/cleanse.js b/lib/crypto/cleanse.js deleted file mode 100644 index de77100d7..000000000 --- a/lib/crypto/cleanse.js +++ /dev/null @@ -1,34 +0,0 @@ -/*! - * cleanse.js - memzero for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module crypto.cleanse - */ - -const native = require('../native').binding; - -let counter = 0; - -/** - * A maybe-secure memzero. - * @param {Buffer} data - */ - -module.exports = function cleanse(data) { - let ctr = counter; - - for (let i = 0; i < data.length; i++) { - data[i] = ctr & 0xff; - ctr += i; - } - - counter = ctr >>> 0; -}; - -if (native) - exports.cleanse = native.cleanse; diff --git a/lib/crypto/digest-browser.js b/lib/crypto/digest-browser.js deleted file mode 100644 index 4f7f45576..000000000 --- a/lib/crypto/digest-browser.js +++ /dev/null @@ -1,122 +0,0 @@ -/*! - * digest-browser.js - hash functions for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module crypto.digest-browser - * @ignore - */ - -const assert = require('assert'); -const hashjs = require('hash.js'); -const SHA256 = require('./sha256'); -const POOL64 = Buffer.allocUnsafe(64); - -/** - * Hash with chosen algorithm. - * @param {String} alg - * @param {Buffer} data - * @returns {Buffer} - */ - -exports.hash = function hash(alg, data) { - if (alg === 'sha256') - return SHA256.digest(data); - - const algo = hashjs[alg]; - - assert(algo != null, 'Unknown algorithm.'); - - return Buffer.from(algo().update(data).digest()); -}; - -/** - * Hash with ripemd160. - * @param {Buffer} data - * @returns {Buffer} - */ - -exports.ripemd160 = function ripemd160(data) { - return exports.hash('ripemd160', data); -}; - -/** - * Hash with sha1. - * @param {Buffer} data - * @returns {Buffer} - */ - -exports.sha1 = function sha1(data) { - return exports.hash('sha1', data); -}; - -/** - * Hash with sha256. - * @param {Buffer} data - * @returns {Buffer} - */ - -exports.sha256 = function sha256(data) { - return SHA256.digest(data); -}; - -/** - * Hash with sha256 and ripemd160 (OP_HASH160). - * @param {Buffer} data - * @returns {Buffer} - */ - -exports.hash160 = function hash160(data) { - return exports.hash('ripemd160', SHA256.digest(data)); -}; - -/** - * Hash with sha256 twice (OP_HASH256). - * @param {Buffer} data - * @returns {Buffer} - */ - -exports.hash256 = function hash256(data) { - return SHA256.hash256(data); -}; - -/** - * Hash left and right hashes with hash256. - * @param {Buffer} left - * @param {Buffer} right - * @returns {Buffer} - */ - -exports.root256 = function root256(left, right) { - const data = POOL64; - - assert(left.length === 32); - assert(right.length === 32); - - left.copy(data, 0); - right.copy(data, 32); - - return exports.hash256(data); -}; - -/** - * Create an HMAC. - * @param {String} alg - * @param {Buffer} data - * @param {Buffer} key - * @returns {Buffer} HMAC - */ - -exports.hmac = function hmac(alg, data, key) { - const algo = hashjs[alg]; - - assert(algo != null, 'Unknown algorithm.'); - - const ctx = hashjs.hmac(algo, key); - - return Buffer.from(ctx.update(data).digest()); -}; diff --git a/lib/crypto/digest.js b/lib/crypto/digest.js deleted file mode 100644 index d65d72a5a..000000000 --- a/lib/crypto/digest.js +++ /dev/null @@ -1,120 +0,0 @@ -/*! - * digest.js - hash functions for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module crypto.digest - */ - -const assert = require('assert'); -const crypto = require('crypto'); -const native = require('../native').binding; -const POOL64 = Buffer.allocUnsafe(64); - -/** - * Hash with chosen algorithm. - * @param {String} alg - * @param {Buffer} data - * @returns {Buffer} - */ - -exports.hash = function hash(alg, data) { - return crypto.createHash(alg).update(data).digest(); -}; - -/** - * Hash with ripemd160. - * @param {Buffer} data - * @returns {Buffer} - */ - -exports.ripemd160 = function ripemd160(data) { - return exports.hash('ripemd160', data); -}; - -/** - * Hash with sha1. - * @param {Buffer} data - * @returns {Buffer} - */ - -exports.sha1 = function sha1(data) { - return exports.hash('sha1', data); -}; - -/** - * Hash with sha256. - * @param {Buffer} data - * @returns {Buffer} - */ - -exports.sha256 = function sha256(data) { - return exports.hash('sha256', data); -}; - -/** - * Hash with sha256 and ripemd160 (OP_HASH160). - * @param {Buffer} data - * @returns {Buffer} - */ - -exports.hash160 = function hash160(data) { - return exports.ripemd160(exports.sha256(data)); -}; - -/** - * Hash with sha256 twice (OP_HASH256). - * @param {Buffer} data - * @returns {Buffer} - */ - -exports.hash256 = function hash256(data) { - return exports.sha256(exports.sha256(data)); -}; - -/** - * Hash left and right hashes with hash256. - * @param {Buffer} left - * @param {Buffer} right - * @returns {Buffer} - */ - -exports.root256 = function root256(left, right) { - const data = POOL64; - - assert(left.length === 32); - assert(right.length === 32); - - left.copy(data, 0); - right.copy(data, 32); - - return exports.hash256(data); -}; - -/** - * Create an HMAC. - * @param {String} alg - * @param {Buffer} data - * @param {Buffer} key - * @returns {Buffer} HMAC - */ - -exports.hmac = function hmac(alg, data, key) { - const ctx = crypto.createHmac(alg, key); - return ctx.update(data).digest(); -}; - -if (native) { - exports.hash = native.hash; - exports.hmac = native.hmac; - exports.ripemd160 = native.ripemd160; - exports.sha1 = native.sha1; - exports.sha256 = native.sha256; - exports.hash160 = native.hash160; - exports.hash256 = native.hash256; - exports.root256 = native.root256; -} diff --git a/lib/crypto/ecdsa.js b/lib/crypto/ecdsa.js deleted file mode 100644 index 4639156b0..000000000 --- a/lib/crypto/ecdsa.js +++ /dev/null @@ -1,65 +0,0 @@ -/*! - * ecdsa.js - ecdsa for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module crypto/ecdsa - */ - -const assert = require('assert'); -const elliptic = require('elliptic'); -const digest = require('./digest'); - -/** - * Verify ECDSA signature. - * @param {String} curve - Curve name. - * @param {String} alg - Hash algorithm. - * @param {Buffer} msg - Signed message. - * @param {Buffer} sig - Signature. - * @param {Buffer} key - ASN1 serialized ECDSA key. - * @returns {Boolean} - */ - -exports.verify = function verify(curve, alg, msg, sig, key) { - assert(typeof curve === 'string', 'No curve selected.'); - assert(typeof alg === 'string', 'No algorithm selected.'); - assert(Buffer.isBuffer(msg)); - assert(Buffer.isBuffer(sig)); - assert(Buffer.isBuffer(key)); - - const ec = elliptic.ec(curve); - const hash = digest.hash(alg, msg); - - try { - return ec.verify(hash, sig, key); - } catch (e) { - return false; - } -}; - -/** - * Sign message with ECDSA key. - * @memberof module:crypto/pk.ecdsa - * @param {String} curve - Curve name. - * @param {String} alg - Hash algorithm. - * @param {Buffer} msg - Signed message. - * @param {Buffer} key - ASN1 serialized ECDSA key. - * @returns {Buffer} Signature (DER) - */ - -exports.sign = function sign(curve, alg, msg, key) { - assert(typeof curve === 'string', 'No curve selected.'); - assert(typeof alg === 'string', 'No algorithm selected.'); - assert(Buffer.isBuffer(msg)); - assert(Buffer.isBuffer(key)); - - const ec = elliptic.ec(curve); - const hash = digest.hash(alg, msg); - const sig = ec.sign(hash, key, { canonical: true }); - - return Buffer.from(sig.toDER()); -}; diff --git a/lib/crypto/hkdf.js b/lib/crypto/hkdf.js deleted file mode 100644 index 64f4c9df3..000000000 --- a/lib/crypto/hkdf.js +++ /dev/null @@ -1,65 +0,0 @@ -/*! - * hkdf.js - hkdf for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module crypto/hkdf - */ - -const digest = require('./digest'); - -/** - * Perform hkdf extraction. - * @param {Buffer} ikm - * @param {Buffer} key - * @param {String} alg - * @returns {Buffer} - */ - -exports.extract = function extract(ikm, key, alg) { - return digest.hmac(alg, ikm, key); -}; - -/** - * Perform hkdf expansion. - * @param {Buffer} prk - * @param {Buffer} info - * @param {Number} len - * @param {String} alg - * @returns {Buffer} - */ - -exports.expand = function expand(prk, info, len, alg) { - const size = digest.hash(alg, Buffer.alloc(0)).length; - const blocks = Math.ceil(len / size); - - if (blocks > 255) - throw new Error('Too many blocks.'); - - const okm = Buffer.allocUnsafe(len); - - if (blocks === 0) - return okm; - - const buf = Buffer.allocUnsafe(size + info.length + 1); - - // First round: - info.copy(buf, size); - buf[buf.length - 1] = 1; - - let out = digest.hmac(alg, buf.slice(size), prk); - out.copy(okm, 0); - - for (let i = 1; i < blocks; i++) { - out.copy(buf, 0); - buf[buf.length - 1]++; - out = digest.hmac(alg, buf, prk); - out.copy(okm, i * size); - } - - return okm; -}; diff --git a/lib/crypto/hmac-drbg.js b/lib/crypto/hmac-drbg.js deleted file mode 100644 index 559807941..000000000 --- a/lib/crypto/hmac-drbg.js +++ /dev/null @@ -1,120 +0,0 @@ -/*! - * hmac-drbg.js - hmac-drbg implementation for bcoin - * Copyright (c) 2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - * Parts of this software based on hmac-drbg. - */ - -'use strict'; - -const assert = require('assert'); -const digest = require('./digest'); - -/* - * Constants - */ - -const HASH_ALG = 'sha256'; -const HASH_SIZE = 32; -const RESEED_INTERVAL = 0x1000000000000; -const POOL33 = Buffer.allocUnsafe(HASH_SIZE + 1); -const POOL112 = Buffer.allocUnsafe(HASH_SIZE * 2 + 48); -const POOL145 = Buffer.allocUnsafe(POOL33.length + POOL112.length); - -/** - * HmacDRBG - * @constructor - */ - -function HmacDRBG(entropy, nonce, pers) { - if (!(this instanceof HmacDRBG)) - return new HmacDRBG(entropy, nonce, pers); - - this.K = Buffer.allocUnsafe(HASH_SIZE); - this.V = Buffer.allocUnsafe(HASH_SIZE); - this.rounds = 0; - - this.init(entropy, nonce, pers); -} - -HmacDRBG.prototype.init = function init(entropy, nonce, pers) { - for (let i = 0; i < this.V.length; i++) { - this.K[i] = 0x00; - this.V[i] = 0x01; - } - - this.reseed(entropy, nonce, pers); -}; - -HmacDRBG.prototype.reseed = function reseed(entropy, nonce, pers) { - const seed = POOL112; - - assert(Buffer.isBuffer(entropy)); - assert(Buffer.isBuffer(nonce)); - assert(Buffer.isBuffer(pers)); - - assert(entropy.length === HASH_SIZE); - assert(nonce.length === HASH_SIZE); - assert(pers.length === 48); - - entropy.copy(seed, 0); - nonce.copy(seed, HASH_SIZE); - pers.copy(seed, HASH_SIZE * 2); - - this.update(seed); - this.rounds = 1; -}; - -HmacDRBG.prototype.iterate = function iterate() { - const data = POOL33; - - this.V.copy(data, 0); - data[HASH_SIZE] = 0x00; - - this.K = digest.hmac(HASH_ALG, data, this.K); - this.V = digest.hmac(HASH_ALG, this.V, this.K); -}; - -HmacDRBG.prototype.update = function update(seed) { - const data = POOL145; - - assert(Buffer.isBuffer(seed)); - assert(seed.length === HASH_SIZE * 2 + 48); - - this.V.copy(data, 0); - data[HASH_SIZE] = 0x00; - seed.copy(data, HASH_SIZE + 1); - - this.K = digest.hmac(HASH_ALG, data, this.K); - this.V = digest.hmac(HASH_ALG, this.V, this.K); - - data[HASH_SIZE] = 0x01; - - this.K = digest.hmac(HASH_ALG, data, this.K); - this.V = digest.hmac(HASH_ALG, this.V, this.K); -}; - -HmacDRBG.prototype.generate = function generate(len) { - if (this.rounds > RESEED_INTERVAL) - throw new Error('Reseed is required.'); - - const data = Buffer.allocUnsafe(len); - let pos = 0; - - while (pos < len) { - this.V = digest.hmac(HASH_ALG, this.V, this.K); - this.V.copy(data, pos); - pos += HASH_SIZE; - } - - this.iterate(); - this.rounds++; - - return data; -}; - -/* - * Expose - */ - -module.exports = HmacDRBG; diff --git a/lib/crypto/index.js b/lib/crypto/index.js deleted file mode 100644 index 1112ed0fb..000000000 --- a/lib/crypto/index.js +++ /dev/null @@ -1,51 +0,0 @@ -/*! - * crypto/index.js - crypto for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module crypto - */ - -const digest = require('./digest'); -const random = require('./random'); -const aes = require('./aes'); - -exports.aes = require('./aes'); -exports.AEAD = require('./aead'); -exports.BN = require('./bn'); -exports.ccmp = require('./ccmp'); -exports.ChaCha20 = require('./chacha20'); -exports.cleanse = require('./cleanse'); -exports.digest = require('./digest'); -exports.ecdsa = require('./ecdsa'); -exports.hkdf = require('./hkdf'); -exports.HmacDRBG = require('./hmac-drbg'); -exports.merkle = require('./merkle'); -exports.pbkdf2 = require('./pbkdf2'); -exports.Poly1305 = require('./poly1305'); -exports.random = require('./random'); -exports.rsa = require('./rsa'); -exports.schnorr = require('./schnorr'); -exports.scrypt = require('./scrypt'); -exports.secp256k1 = require('./secp256k1'); -exports.siphash = require('./siphash'); - -exports.hash = digest.hash; -exports.ripemd160 = digest.ripemd160; -exports.sha1 = digest.sha1; -exports.sha256 = digest.sha256; -exports.hash160 = digest.hash160; -exports.hash256 = digest.hash256; -exports.root256 = digest.root256; -exports.hmac = digest.hmac; - -exports.encipher = aes.encipher; -exports.decipher = aes.decipher; - -exports.randomBytes = random.randomBytes; -exports.randomInt = random.randomInt; -exports.randomRange = random.randomRange; diff --git a/lib/crypto/merkle.js b/lib/crypto/merkle.js deleted file mode 100644 index 1adeeca1e..000000000 --- a/lib/crypto/merkle.js +++ /dev/null @@ -1,115 +0,0 @@ -/*! - * merkle.js - merkle trees for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module crypto/merkle - */ - -const digest = require('./digest'); - -/** - * Build a merkle tree from leaves. - * Note that this will mutate the `leaves` array! - * @param {Buffer[]} leaves - * @returns {Array} [nodes, malleated] - */ - -exports.createTree = function createTree(leaves) { - const nodes = leaves; - let size = leaves.length; - let malleated = false; - let i = 0; - - if (size === 0) { - nodes.push(Buffer.alloc(32)); - return [nodes, malleated]; - } - - while (size > 1) { - for (let j = 0; j < size; j += 2) { - const k = Math.min(j + 1, size - 1); - const left = nodes[i + j]; - const right = nodes[i + k]; - - if (k === j + 1 && k + 1 === size - && left.equals(right)) { - malleated = true; - } - - const hash = digest.root256(left, right); - - nodes.push(hash); - } - i += size; - size += 1; - size >>>= 1; - } - - return [nodes, malleated]; -}; - -/** - * Calculate merkle root from leaves. - * @param {Buffer[]} leaves - * @returns {Array} [root, malleated] - */ - -exports.createRoot = function createRoot(leaves) { - const [nodes, malleated] = exports.createTree(leaves); - const root = nodes[nodes.length - 1]; - return [root, malleated]; -}; - -/** - * Collect a merkle branch from vector index. - * @param {Number} index - * @param {Buffer[]} leaves - * @returns {Buffer[]} branch - */ - -exports.createBranch = function createBranch(index, leaves) { - let size = leaves.length; - const [nodes] = exports.createTree(leaves); - const branch = []; - let i = 0; - - while (size > 1) { - const j = Math.min(index ^ 1, size - 1); - branch.push(nodes[i + j]); - index >>>= 1; - i += size; - size += 1; - size >>>= 1; - } - - return branch; -}; - -/** - * Derive merkle root from branch. - * @param {Buffer} hash - * @param {Buffer[]} branch - * @param {Number} index - * @returns {Buffer} root - */ - -exports.deriveRoot = function deriveRoot(hash, branch, index) { - let root = hash; - - for (const hash of branch) { - if (index & 1) - root = digest.root256(hash, root); - else - root = digest.root256(root, hash); - - index >>>= 1; - } - - return root; -}; diff --git a/lib/crypto/pbkdf2-browser.js b/lib/crypto/pbkdf2-browser.js deleted file mode 100644 index 92b879cfb..000000000 --- a/lib/crypto/pbkdf2-browser.js +++ /dev/null @@ -1,101 +0,0 @@ -/*! - * pbkdf2.js - pbkdf2 for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module crypto.pbkdf2-browser - * @ignore - */ - -const digest = require('./digest'); -const crypto = global.crypto || global.msCrypto || {}; -const subtle = crypto.subtle || {}; - -/** - * Perform key derivation using PBKDF2. - * @param {Buffer} key - * @param {Buffer} salt - * @param {Number} iter - * @param {Number} len - * @param {String} alg - * @returns {Buffer} - */ - -exports.derive = function derive(key, salt, iter, len, alg) { - const size = digest.hash(alg, Buffer.alloc(0)).length; - const blocks = Math.ceil(len / size); - const out = Buffer.allocUnsafe(len); - const buf = Buffer.allocUnsafe(salt.length + 4); - const block = Buffer.allocUnsafe(size); - let pos = 0; - - salt.copy(buf, 0); - - for (let i = 0; i < blocks; i++) { - buf.writeUInt32BE(i + 1, salt.length, true); - let mac = digest.hmac(alg, buf, key); - mac.copy(block, 0); - for (let j = 1; j < iter; j++) { - mac = digest.hmac(alg, mac, key); - for (let k = 0; k < size; k++) - block[k] ^= mac[k]; - } - block.copy(out, pos); - pos += size; - } - - return out; -}; - -/** - * Execute pbkdf2 asynchronously. - * @param {Buffer} key - * @param {Buffer} salt - * @param {Number} iter - * @param {Number} len - * @param {String} alg - * @returns {Promise} - */ - -exports.deriveAsync = async function deriveAsync(key, salt, iter, len, alg) { - const algo = { name: 'PBKDF2' }; - const use = ['deriveBits']; - - if (!subtle.importKey || !subtle.deriveBits) - return exports.derive(key, salt, iter, len, alg); - - const options = { - name: 'PBKDF2', - salt: salt, - iterations: iter, - hash: getHash(alg) - }; - - const imported = await subtle.importKey('raw', key, algo, false, use); - const data = await subtle.deriveBits(options, imported, len * 8); - - return Buffer.from(data); -}; - -/* - * Helpers - */ - -function getHash(name) { - switch (name) { - case 'sha1': - return 'SHA-1'; - case 'sha256': - return 'SHA-256'; - case 'sha384': - return 'SHA-384'; - case 'sha512': - return 'SHA-512'; - default: - throw new Error(`Algorithm not supported: ${name}.`); - } -} diff --git a/lib/crypto/pbkdf2.js b/lib/crypto/pbkdf2.js deleted file mode 100644 index 3f387907f..000000000 --- a/lib/crypto/pbkdf2.js +++ /dev/null @@ -1,44 +0,0 @@ -/*! - * pbkdf2.js - pbkdf2 for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module crypto.pbkdf2 - */ - -const crypto = require('crypto'); -const co = require('../utils/co'); - -/** - * Perform key derivation using PBKDF2. - * @param {Buffer} key - * @param {Buffer} salt - * @param {Number} iter - * @param {Number} len - * @param {String} alg - * @returns {Buffer} - */ - -exports.derive = function derive(key, salt, iter, len, alg) { - return crypto.pbkdf2Sync(key, salt, iter, len, alg); -}; - -/** - * Execute pbkdf2 asynchronously. - * @param {Buffer} key - * @param {Buffer} salt - * @param {Number} iter - * @param {Number} len - * @param {String} alg - * @returns {Promise} - */ - -exports.deriveAsync = function deriveAsync(key, salt, iter, len, alg) { - return new Promise((resolve, reject) => { - crypto.pbkdf2(key, salt, iter, len, alg, co.wrap(resolve, reject)); - }); -}; diff --git a/lib/crypto/poly1305.js b/lib/crypto/poly1305.js deleted file mode 100644 index 50ecdda94..000000000 --- a/lib/crypto/poly1305.js +++ /dev/null @@ -1,321 +0,0 @@ -/*! - * poly1305.js - poly1305 for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const native = require('../native').binding; - -/** - * Poly1305 (used for bip151) - * @alias module:crypto/chachapoly.Poly1305 - * @constructor - * @see https://github.com/floodyberry/poly1305-donna - * @see https://tools.ietf.org/html/rfc7539#section-2.5 - */ - -function Poly1305() { - if (!(this instanceof Poly1305)) - return new Poly1305(); - - this.r = new Uint16Array(10); - this.h = new Uint16Array(10); - this.pad = new Uint16Array(8); - this.fin = 0; - this.leftover = 0; - this.buffer = Buffer.allocUnsafe(16); -} - -/** - * Initialize poly1305 with a key. - * @param {Buffer} key - */ - -Poly1305.prototype.init = function init(key) { - // r &= 0xffffffc0ffffffc0ffffffc0fffffff - const t0 = key.readUInt16LE(0, true); - const t1 = key.readUInt16LE(2, true); - const t2 = key.readUInt16LE(4, true); - const t3 = key.readUInt16LE(6, true); - const t4 = key.readUInt16LE(8, true); - const t5 = key.readUInt16LE(10, true); - const t6 = key.readUInt16LE(12, true); - const t7 = key.readUInt16LE(14, true); - - this.r[0] = t0 & 0x1fff; - this.r[1] = ((t0 >>> 13) | (t1 << 3)) & 0x1fff; - this.r[2] = ((t1 >>> 10) | (t2 << 6)) & 0x1f03; - this.r[3] = ((t2 >>> 7) | (t3 << 9)) & 0x1fff; - this.r[4] = ((t3 >>> 4) | (t4 << 12)) & 0x00ff; - this.r[5] = (t4 >>> 1) & 0x1ffe; - this.r[6] = ((t4 >>> 14) | (t5 << 2)) & 0x1fff; - this.r[7] = ((t5 >>> 11) | (t6 << 5)) & 0x1f81; - this.r[8] = ((t6 >>> 8) | (t7 << 8)) & 0x1fff; - this.r[9] = (t7 >>> 5) & 0x007f; - - // h = 0 - for (let i = 0; i < 10; i++) - this.h[i] = 0; - - // save pad for later - for (let i = 0; i < 8; i++) - this.pad[i] = key.readUInt16LE(16 + (2 * i), true); - - this.leftover = 0; - this.fin = 0; -}; - -/** - * Process 16 byte blocks. - * @param {Buffer} data - Blocks. - * @param {Number} bytes - Size. - * @param {Number} m - Offset pointer. - */ - -Poly1305.prototype.blocks = function blocks(data, bytes, m) { - const hibit = this.fin ? 0 : (1 << 11); // 1 << 128 - const d = new Uint32Array(10); - - while (bytes >= 16) { - // h += m[i] - const t0 = data.readUInt16LE(m + 0, true); - const t1 = data.readUInt16LE(m + 2, true); - const t2 = data.readUInt16LE(m + 4, true); - const t3 = data.readUInt16LE(m + 6, true); - const t4 = data.readUInt16LE(m + 8, true); - const t5 = data.readUInt16LE(m + 10, true); - const t6 = data.readUInt16LE(m + 12, true); - const t7 = data.readUInt16LE(m + 14, true); - - this.h[0] += t0 & 0x1fff; - this.h[1] += ((t0 >>> 13) | (t1 << 3)) & 0x1fff; - this.h[2] += ((t1 >>> 10) | (t2 << 6)) & 0x1fff; - this.h[3] += ((t2 >>> 7) | (t3 << 9)) & 0x1fff; - this.h[4] += ((t3 >>> 4) | (t4 << 12)) & 0x1fff; - this.h[5] += ((t4 >>> 1)) & 0x1fff; - this.h[6] += ((t4 >>> 14) | (t5 << 2)) & 0x1fff; - this.h[7] += ((t5 >>> 11) | (t6 << 5)) & 0x1fff; - this.h[8] += ((t6 >>> 8) | (t7 << 8)) & 0x1fff; - this.h[9] += ((t7 >>> 5)) | hibit; - - // h *= r, (partial) h %= p - let c = 0; - for (let i = 0; i < 10; i++) { - d[i] = c; - - for (let j = 0; j < 10; j++) { - let a = this.h[j]; - - if (j <= i) - a *= this.r[i - j]; - else - a *= 5 * this.r[i + 10 - j]; - - d[i] += a; - - // Sum(h[i] * r[i] * 5) will overflow slightly - // above 6 products with an unclamped r, so - // carry at 5 - if (j === 4) { - c = d[i] >>> 13; - d[i] &= 0x1fff; - } - } - - c += d[i] >>> 13; - d[i] &= 0x1fff; - } - - c = (c << 2) + c; // c *= 5 - c += d[0]; - d[0] = (c & 0x1fff); - c = c >>> 13; - d[1] += c; - - for (let i = 0; i < 10; i++) - this.h[i] = d[i]; - - m += 16; - bytes -= 16; - } -}; - -/** - * Update the MAC with data (will be - * processed as 16 byte blocks). - * @param {Buffer} data - */ - -Poly1305.prototype.update = function update(data) { - let bytes = data.length; - let m = 0; - - // handle leftover - if (this.leftover) { - let want = 16 - this.leftover; - - if (want > bytes) - want = bytes; - - for (let i = 0; i < want; i++) - this.buffer[this.leftover + i] = data[m + i]; - - bytes -= want; - m += want; - - this.leftover += want; - - if (this.leftover < 16) - return; - - this.blocks(this.buffer, 16, 0); - this.leftover = 0; - } - - // process full blocks - if (bytes >= 16) { - const want = bytes & ~(16 - 1); - this.blocks(data, want, m); - m += want; - bytes -= want; - } - - // store leftover - if (bytes) { - for (let i = 0; i < bytes; i++) - this.buffer[this.leftover + i] = data[m + i]; - this.leftover += bytes; - } -}; - -/** - * Finalize and return a 16-byte MAC. - * @returns {Buffer} - */ - -Poly1305.prototype.finish = function finish() { - const mac = Buffer.allocUnsafe(16); - const g = new Uint16Array(10); - - // process the remaining block - if (this.leftover) { - let i = this.leftover; - this.buffer[i++] = 1; - for (; i < 16; i++) - this.buffer[i] = 0; - this.fin = 1; - this.blocks(this.buffer, 16, 0); - } - - // fully carry h - let c = this.h[1] >>> 13; - this.h[1] &= 0x1fff; - for (let i = 2; i < 10; i++) { - this.h[i] += c; - c = this.h[i] >>> 13; - this.h[i] &= 0x1fff; - } - this.h[0] += c * 5; - c = this.h[0] >>> 13; - this.h[0] &= 0x1fff; - this.h[1] += c; - c = this.h[1] >>> 13; - this.h[1] &= 0x1fff; - this.h[2] += c; - - // compute h + -p - g[0] = this.h[0] + 5; - c = g[0] >>> 13; - g[0] &= 0x1fff; - for (let i = 1; i < 10; i++) { - g[i] = this.h[i] + c; - c = g[i] >>> 13; - g[i] &= 0x1fff; - } - - // select h if h < p, or h + -p if h >= p - let mask = (c ^ 1) - 1; - for (let i = 0; i < 10; i++) - g[i] &= mask; - mask = ~mask; - for (let i = 0; i < 10; i++) - this.h[i] = (this.h[i] & mask) | g[i]; - - // h = h % (2^128) - this.h[0] = ((this.h[0]) | (this.h[1] << 13)) & 0xffff; - this.h[1] = ((this.h[1] >>> 3) | (this.h[2] << 10)) & 0xffff; - this.h[2] = ((this.h[2] >>> 6) | (this.h[3] << 7)) & 0xffff; - this.h[3] = ((this.h[3] >>> 9) | (this.h[4] << 4)) & 0xffff; - this.h[4] = ((this.h[4] >>> 12) - | (this.h[5] << 1) | (this.h[6] << 14)) & 0xffff; - this.h[5] = ((this.h[6] >>> 2) | (this.h[7] << 11)) & 0xffff; - this.h[6] = ((this.h[7] >>> 5) | (this.h[8] << 8)) & 0xffff; - this.h[7] = ((this.h[8] >>> 8) | (this.h[9] << 5)) & 0xffff; - - // mac = (h + pad) % (2^128) - let f = this.h[0] + this.pad[0]; - this.h[0] = f; - for (let i = 1; i < 8; i++) { - f = this.h[i] + this.pad[i] + (f >>> 16); - this.h[i] = f; - } - - for (let i = 0; i < 8; i++) - mac.writeUInt16LE(this.h[i], i * 2, true); - - // zero out the state - for (let i = 0; i < 10; i++) - this.h[i] = 0; - - for (let i = 0; i < 10; i++) - this.r[i] = 0; - - for (let i = 0; i < 8; i++) - this.pad[i] = 0; - - return mac; -}; - -/** - * Return a MAC for a message and key. - * @param {Buffer} msg - * @param {Buffer} key - * @returns {Buffer} MAC - */ - -Poly1305.auth = function auth(msg, key) { - const poly = new Poly1305(); - poly.init(key); - poly.update(msg); - return poly.finish(); -}; - -/** - * Compare two MACs in constant time. - * @param {Buffer} mac1 - * @param {Buffer} mac2 - * @returns {Boolean} - */ - -Poly1305.verify = function verify(mac1, mac2) { - let dif = 0; - - // Compare in constant time. - for (let i = 0; i < 16; i++) - dif |= mac1[i] ^ mac2[i]; - - dif = (dif - 1) >>> 31; - - return (dif & 1) !== 0; -}; - -if (native) - Poly1305 = native.Poly1305; - -/* - * Expose - */ - -module.exports = Poly1305; diff --git a/lib/crypto/random-browser.js b/lib/crypto/random-browser.js deleted file mode 100644 index 0f102b055..000000000 --- a/lib/crypto/random-browser.js +++ /dev/null @@ -1,63 +0,0 @@ -/*! - * random-browser.js - randomness for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module crypto.random-browser - * @ignore - */ - -const crypto = global.crypto || global.msCrypto || {}; - -/** - * Generate pseudo-random bytes. - * @param {Number} size - * @returns {Buffer} - */ - -exports.randomBytes = function randomBytes(size) { - const data = new Uint8Array(size); - crypto.getRandomValues(data); - return Buffer.from(data.buffer); -}; - -if (!crypto.getRandomValues) { - // Out of luck here. Use bad randomness for now. - exports.randomBytes = function randomBytes(size) { - const data = Buffer.allocUnsafe(size); - - for (let i = 0; i < data.length; i++) - data[i] = Math.floor(Math.random() * 256); - - return data; - }; -} - -/** - * Generate a random uint32. - * Probably more cryptographically sound than - * `Math.random()`. - * @returns {Number} - */ - -exports.randomInt = function randomInt() { - return exports.randomBytes(4).readUInt32LE(0, true); -}; - -/** - * Generate a random number within a range. - * Probably more cryptographically sound than - * `Math.random()`. - * @param {Number} min - Inclusive. - * @param {Number} max - Exclusive. - * @returns {Number} - */ - -exports.randomRange = function randomRange(min, max) { - const num = exports.randomInt(); - return Math.floor((num / 0x100000000) * (max - min) + min); -}; diff --git a/lib/crypto/random.js b/lib/crypto/random.js deleted file mode 100644 index e98c29198..000000000 --- a/lib/crypto/random.js +++ /dev/null @@ -1,47 +0,0 @@ -/*! - * random.js - randomness for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module crypto.random - */ - -const crypto = require('crypto'); - -/** - * Generate pseudo-random bytes. - * @function - * @param {Number} size - * @returns {Buffer} - */ - -exports.randomBytes = crypto.randomBytes; - -/** - * Generate a random uint32. - * Probably more cryptographically sound than - * `Math.random()`. - * @returns {Number} - */ - -exports.randomInt = function randomInt() { - return exports.randomBytes(4).readUInt32LE(0, true); -}; - -/** - * Generate a random number within a range. - * Probably more cryptographically sound than - * `Math.random()`. - * @param {Number} min - Inclusive. - * @param {Number} max - Exclusive. - * @returns {Number} - */ - -exports.randomRange = function randomRange(min, max) { - const num = exports.randomInt(); - return Math.floor((num / 0x100000000) * (max - min) + min); -}; diff --git a/lib/crypto/rsa-browser.js b/lib/crypto/rsa-browser.js deleted file mode 100644 index 6029fafc8..000000000 --- a/lib/crypto/rsa-browser.js +++ /dev/null @@ -1,186 +0,0 @@ -/*! - * rsa-browser.js - rsa for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const BN = require('./bn'); -const ASN1 = require('../utils/asn1'); -const digest = require('./digest'); -const ccmp = require('./ccmp'); - -/** - * @exports crypto/rsa - * @ignore - */ - -const rsa = exports; - -/** - * PKCS signature prefixes. - * @type {Object} - */ - -rsa.prefixes = { - md5: Buffer.from('3020300c06082a864886f70d020505000410', 'hex'), - sha1: Buffer.from('3021300906052b0e03021a05000414', 'hex'), - sha224: Buffer.from('302d300d06096086480165030402040500041c', 'hex'), - sha256: Buffer.from('3031300d060960864801650304020105000420', 'hex'), - sha384: Buffer.from('3041300d060960864801650304020205000430', 'hex'), - sha512: Buffer.from('3051300d060960864801650304020305000440', 'hex'), - ripemd160: Buffer.from('30203008060628cf060300310414', 'hex') -}; - -/** - * Verify RSA signature. - * @param {String} alg - Hash algorithm. - * @param {Buffer} msg - Signed message. - * @param {Buffer} sig - Signature. - * @param {Buffer} key - ASN1 serialized RSA key. - * @returns {Boolean} - */ - -rsa.verify = function verify(alg, msg, sig, key) { - assert(typeof alg === 'string', 'No algorithm selected.'); - assert(Buffer.isBuffer(msg)); - assert(Buffer.isBuffer(sig)); - assert(Buffer.isBuffer(key)); - - const prefix = rsa.prefixes[alg]; - - if (!prefix) - throw new Error('Unknown PKCS prefix.'); - - const hash = digest.hash(alg, msg); - const len = prefix.length + hash.length; - const pub = ASN1.parseRSAPublic(key); - - const N = new BN(pub.modulus); - const e = new BN(pub.publicExponent); - const k = Math.ceil(N.bitLength() / 8); - - if (k < len + 11) - throw new Error('Message too long.'); - - const m = rsa.encrypt(N, e, sig); - const em = leftpad(m, k); - - let ok = ceq(em[0], 0x00); - ok &= ceq(em[1], 0x01); - ok &= ccmp(em.slice(k - hash.length, k), hash); - ok &= ccmp(em.slice(k - len, k - hash.length), prefix); - ok &= ceq(em[k - len - 1], 0x00); - - for (let i = 2; i < k - len - 1; i++) - ok &= ceq(em[i], 0xff); - - return ok === 1; -}; - -/** - * Sign message with RSA key. - * @param {String} alg - Hash algorithm. - * @param {Buffer} msg - Signed message. - * @param {Buffer} key - ASN1 serialized RSA key. - * @returns {Buffer} Signature (DER) - */ - -rsa.sign = function sign(alg, msg, key) { - assert(typeof alg === 'string', 'No algorithm selected.'); - assert(Buffer.isBuffer(msg)); - assert(Buffer.isBuffer(key)); - - const prefix = rsa.prefixes[alg]; - - if (!prefix) - throw new Error('Unknown PKCS prefix.'); - - const hash = digest.hash(alg, msg); - const len = prefix.length + hash.length; - const priv = ASN1.parseRSAPrivate(key); - - const N = new BN(priv.modulus); - const D = new BN(priv.privateExponent); - const k = Math.ceil(N.bitLength() / 8); - - if (k < len + 11) - throw new Error('Message too long.'); - - const em = Buffer.allocUnsafe(k); - em.fill(0); - - em[1] = 0x01; - for (let i = 2; i < k - len - 1; i++) - em[i] = 0xff; - - prefix.copy(em, k - len); - hash.copy(em, k - hash.length); - - return rsa.decrypt(N, D, em); -}; - -/** - * Decrypt with modulus and exponent. - * @param {BN} N - * @param {BN} D - * @param {Buffer} m - * @returns {Buffer} - */ - -rsa.decrypt = function decrypt(N, D, m) { - const c = new BN(m); - - if (c.cmp(N) > 0) - throw new Error('Cannot decrypt.'); - - return c - .toRed(BN.red(N)) - .redPow(D) - .fromRed() - .toArrayLike(Buffer, 'be'); -}; - -/** - * Encrypt with modulus and exponent. - * @param {BN} N - * @param {BN} e - * @param {Buffer} m - * @returns {Buffer} - */ - -rsa.encrypt = function encrypt(N, e, m) { - return new BN(m) - .toRed(BN.red(N)) - .redPow(e) - .fromRed() - .toArrayLike(Buffer, 'be'); -}; - -/* - * Helpers - */ - -function leftpad(input, size) { - let n = input.length; - - if (n > size) - n = size; - - const out = Buffer.allocUnsafe(size); - out.fill(0); - - input.copy(out, out.length - n); - - return out; -} - -function ceq(a, b) { - let r = ~(a ^ b) & 0xff; - r &= r >>> 4; - r &= r >>> 2; - r &= r >>> 1; - return r === 1; -} diff --git a/lib/crypto/rsa.js b/lib/crypto/rsa.js deleted file mode 100644 index c8a3b57c2..000000000 --- a/lib/crypto/rsa.js +++ /dev/null @@ -1,72 +0,0 @@ -/*! - * rsa.js - RSA for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module crypto/rsa - */ - -const assert = require('assert'); -const crypto = require('crypto'); -const PEM = require('../utils/pem'); - -/** - * Verify RSA signature. - * @param {String} alg - Hash algorithm. - * @param {Buffer} msg - Signed message. - * @param {Buffer} sig - Signature. - * @param {Buffer} key - ASN1 serialized RSA key. - * @returns {Boolean} - */ - -exports.verify = function verify(alg, msg, sig, key) { - assert(typeof alg === 'string', 'No algorithm selected.'); - assert(Buffer.isBuffer(msg)); - assert(Buffer.isBuffer(sig)); - assert(Buffer.isBuffer(key)); - - const name = normalizeAlg('rsa', alg); - const pem = PEM.encode(key, 'rsa', 'public key'); - const ctx = crypto.createVerify(name); - - try { - ctx.update(msg); - return ctx.verify(pem, sig); - } catch (e) { - return false; - } -}; - -/** - * Sign message with RSA key. - * @param {String} alg - Hash algorithm. - * @param {Buffer} msg - Signed message. - * @param {Buffer} key - ASN1 serialized RSA key. - * @returns {Buffer} Signature (DER) - */ - -exports.sign = function sign(alg, msg, key) { - assert(typeof alg === 'string', 'No algorithm selected.'); - assert(Buffer.isBuffer(msg)); - assert(Buffer.isBuffer(key)); - - const name = normalizeAlg('rsa', alg); - const pem = PEM.encode(key, 'rsa', 'private key'); - const ctx = crypto.createSign(name); - - ctx.update(msg); - - return ctx.sign(pem); -}; - -/* - * Helpers - */ - -function normalizeAlg(alg, hash) { - return `${alg.toUpperCase()}-${hash.toUpperCase()}`; -} diff --git a/lib/crypto/schnorr.js b/lib/crypto/schnorr.js deleted file mode 100644 index c5c67be70..000000000 --- a/lib/crypto/schnorr.js +++ /dev/null @@ -1,342 +0,0 @@ -/*! - * schnorr.js - schnorr signatures for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const elliptic = require('elliptic'); -const Signature = require('elliptic/lib/elliptic/ec/signature'); -const BN = require('./bn'); -const HmacDRBG = require('./hmac-drbg'); -const sha256 = require('./digest').sha256; -const curve = elliptic.ec('secp256k1').curve; -const POOL64 = Buffer.allocUnsafe(64); - -/** - * @exports crypto/schnorr - */ - -const schnorr = exports; - -/** - * Hash (r | M). - * @param {Buffer} msg - * @param {BN} r - * @returns {Buffer} - */ - -schnorr.hash = function hash(msg, r) { - const R = r.toArrayLike(Buffer, 'be', 32); - const B = POOL64; - - R.copy(B, 0); - msg.copy(B, 32); - - return new BN(sha256(B)); -}; - -/** - * Sign message. - * @private - * @param {Buffer} msg - * @param {BN} priv - * @param {BN} k - * @param {Buffer} pn - * @returns {Signature|null} - */ - -schnorr.trySign = function trySign(msg, prv, k, pn) { - if (prv.isZero()) - throw new Error('Bad private key.'); - - if (prv.gte(curve.n)) - throw new Error('Bad private key.'); - - if (k.isZero()) - return null; - - if (k.gte(curve.n)) - return null; - - let r = curve.g.mul(k); - - if (pn) - r = r.add(pn); - - if (r.y.isOdd()) { - k = k.umod(curve.n); - k = curve.n.sub(k); - } - - const h = schnorr.hash(msg, r.getX()); - - if (h.isZero()) - return null; - - if (h.gte(curve.n)) - return null; - - let s = h.imul(prv); - s = k.isub(s); - s = s.umod(curve.n); - - if (s.isZero()) - return null; - - return new Signature({ r: r.getX(), s: s }); -}; - -/** - * Sign message. - * @param {Buffer} msg - * @param {Buffer} key - * @param {Buffer} pubNonce - * @returns {Signature} - */ - -schnorr.sign = function sign(msg, key, pubNonce) { - const prv = new BN(key); - const drbg = schnorr.drbg(msg, key, pubNonce); - const len = curve.n.byteLength(); - - let pn; - if (pubNonce) - pn = curve.decodePoint(pubNonce); - - let sig; - while (!sig) { - const k = new BN(drbg.generate(len)); - sig = schnorr.trySign(msg, prv, k, pn); - } - - return sig; -}; - -/** - * Verify signature. - * @param {Buffer} msg - * @param {Buffer} signature - * @param {Buffer} key - * @returns {Buffer} - */ - -schnorr.verify = function verify(msg, signature, key) { - const sig = new Signature(signature); - const h = schnorr.hash(msg, sig.r); - - if (h.gte(curve.n)) - throw new Error('Invalid hash.'); - - if (h.isZero()) - throw new Error('Invalid hash.'); - - if (sig.s.gte(curve.n)) - throw new Error('Invalid S value.'); - - if (sig.r.gt(curve.p)) - throw new Error('Invalid R value.'); - - const k = curve.decodePoint(key); - const l = k.mul(h); - const r = curve.g.mul(sig.s); - const rl = l.add(r); - - if (rl.y.isOdd()) - throw new Error('Odd R value.'); - - return rl.getX().eq(sig.r); -}; - -/** - * Recover public key. - * @param {Buffer} msg - * @param {Buffer} signature - * @returns {Buffer} - */ - -schnorr.recover = function recover(signature, msg) { - const sig = new Signature(signature); - const h = schnorr.hash(msg, sig.r); - - if (h.gte(curve.n)) - throw new Error('Invalid hash.'); - - if (h.isZero()) - throw new Error('Invalid hash.'); - - if (sig.s.gte(curve.n)) - throw new Error('Invalid S value.'); - - if (sig.r.gt(curve.p)) - throw new Error('Invalid R value.'); - - let hinv = h.invm(curve.n); - hinv = hinv.umod(curve.n); - - let s = sig.s; - s = curve.n.sub(s); - s = s.umod(curve.n); - - s = s.imul(hinv); - s = s.umod(curve.n); - - const R = curve.pointFromX(sig.r, false); - let l = R.mul(hinv); - let r = curve.g.mul(s); - const k = l.add(r); - - l = k.mul(h); - r = curve.g.mul(sig.s); - - const rl = l.add(r); - - if (rl.y.isOdd()) - throw new Error('Odd R value.'); - - if (!rl.getX().eq(sig.r)) - throw new Error('Could not recover pubkey.'); - - return Buffer.from(k.encode('array', true)); -}; - -/** - * Combine signatures. - * @param {Buffer[]} sigs - * @returns {Signature} - */ - -schnorr.combineSigs = function combineSigs(sigs) { - let s = new BN(0); - let r, last; - - for (let i = 0; i < sigs.length; i++) { - const sig = new Signature(sigs[i]); - - if (sig.s.isZero()) - throw new Error('Bad S value.'); - - if (sig.s.gte(curve.n)) - throw new Error('Bad S value.'); - - if (!r) - r = sig.r; - - if (last && !last.r.eq(sig.r)) - throw new Error('Bad signature combination.'); - - s = s.iadd(sig.s); - s = s.umod(curve.n); - - last = sig; - } - - if (s.isZero()) - throw new Error('Bad combined signature.'); - - return new Signature({ r: r, s: s }); -}; - -/** - * Combine public keys. - * @param {Buffer[]} keys - * @returns {Buffer} - */ - -schnorr.combineKeys = function combineKeys(keys) { - if (keys.length === 0) - throw new Error(); - - if (keys.length === 1) - return keys[0]; - - let point = curve.decodePoint(keys[0]); - - for (let i = 1; i < keys.length; i++) { - const key = curve.decodePoint(keys[i]); - point = point.add(key); - } - - return Buffer.from(point.encode('array', true)); -}; - -/** - * Partially sign. - * @param {Buffer} msg - * @param {Buffer} priv - * @param {Buffer} privNonce - * @param {Buffer} pubNonce - * @returns {Buffer} - */ - -schnorr.partialSign = function partialSign(msg, priv, privNonce, pubNonce) { - const prv = new BN(priv); - const k = new BN(privNonce); - const pn = curve.decodePoint(pubNonce); - const sig = schnorr.trySign(msg, prv, k, pn); - - if (!sig) - throw new Error('Bad K value.'); - - return sig; -}; - -/** - * Schnorr personalization string. - * @const {Buffer} - */ - -schnorr.alg = Buffer.from('Schnorr+SHA256 ', 'ascii'); - -/** - * Instantiate an HMAC-DRBG. - * @param {Buffer} msg - * @param {Buffer} priv - * @param {Buffer} data - * @returns {HmacDRBG} - */ - -schnorr.drbg = function drbg(msg, priv, data) { - const pers = Buffer.allocUnsafe(48); - - pers.fill(0); - - if (data) { - assert(data.length === 32); - data.copy(pers, 0); - } - - schnorr.alg.copy(pers, 32); - - return new HmacDRBG(priv, msg, pers); -}; - -/** - * Generate pub+priv nonce pair. - * @param {Buffer} msg - * @param {Buffer} priv - * @param {Buffer} data - * @returns {Buffer} - */ - -schnorr.generateNoncePair = function generateNoncePair(msg, priv, data) { - const drbg = schnorr.drbg(msg, priv, data); - const len = curve.n.byteLength(); - - let k; - for (;;) { - k = new BN(drbg.generate(len)); - - if (k.isZero()) - continue; - - if (k.gte(curve.n)) - continue; - - break; - } - - return Buffer.from(curve.g.mul(k).encode('array', true)); -}; diff --git a/lib/crypto/scrypt.js b/lib/crypto/scrypt.js deleted file mode 100644 index b07dedc60..000000000 --- a/lib/crypto/scrypt.js +++ /dev/null @@ -1,266 +0,0 @@ -/*! - * scrypt.js - scrypt for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - * - * Ported from: - * https://github.com/Tarsnap/scrypt/blob/master/lib/crypto/crypto_scrypt-ref.c - * - * Copyright 2009 Colin Percival - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -/* eslint camelcase: "off" */ - -'use strict'; - -/** - * @module crypto/scrypt - */ - -const co = require('../utils/co'); -const pbkdf2 = require('./pbkdf2'); -const native = require('../native').binding; - -/** - * Javascript scrypt implementation. Scrypt is - * used in bip38. Bcoin doesn't support bip38 - * yet, but here it is, just in case. - * @alias module:crypto/scrypt.derive - * @param {Buffer} passwd - * @param {Buffer} salt - * @param {Number} N - * @param {Number} r - * @param {Number} p - * @param {Number} len - * @returns {Buffer} - */ - -function derive(passwd, salt, N, r, p, len) { - if (r * p >= (1 << 30)) - throw new Error('EFBIG'); - - if ((N & (N - 1)) !== 0 || N === 0) - throw new Error('EINVAL'); - - if (N > 0xffffffff) - throw new Error('EINVAL'); - - const XY = Buffer.allocUnsafe(256 * r); - const V = Buffer.allocUnsafe(128 * r * N); - - const B = pbkdf2.derive(passwd, salt, 1, p * 128 * r, 'sha256'); - - for (let i = 0; i < p; i++) - smix(B, i * 128 * r, r, N, V, XY); - - return pbkdf2.derive(passwd, B, 1, len, 'sha256'); -} - -if (native) - derive = native.scrypt; - -/** - * Asynchronous scrypt implementation. - * @alias module:crypto/scrypt.deriveAsync - * @function - * @param {Buffer} passwd - * @param {Buffer} salt - * @param {Number} N - * @param {Number} r - * @param {Number} p - * @param {Number} len - * @returns {Promise} - */ - -async function deriveAsync(passwd, salt, N, r, p, len) { - if (r * p >= (1 << 30)) - throw new Error('EFBIG'); - - if ((N & (N - 1)) !== 0 || N === 0) - throw new Error('EINVAL'); - - if (N > 0xffffffff) - throw new Error('EINVAL'); - - const XY = Buffer.allocUnsafe(256 * r); - const V = Buffer.allocUnsafe(128 * r * N); - - const B = await pbkdf2.deriveAsync(passwd, salt, 1, p * 128 * r, 'sha256'); - - for (let i = 0; i < p; i++) - await smixAsync(B, i * 128 * r, r, N, V, XY); - - return await pbkdf2.deriveAsync(passwd, B, 1, len, 'sha256'); -} - -if (native) - deriveAsync = native.scryptAsync; - -/* - * Helpers - */ - -function salsa20_8(B) { - const B32 = new Uint32Array(16); - const x = new Uint32Array(16); - - for (let i = 0; i < 16; i++) - B32[i] = B.readUInt32LE(i * 4, true); - - for (let i = 0; i < 16; i++) - x[i] = B32[i]; - - for (let i = 0; i < 8; i += 2) { - x[4] ^= R(x[0] + x[12], 7); - x[8] ^= R(x[4] + x[0], 9); - x[12] ^= R(x[8] + x[4], 13); - x[0] ^= R(x[12] + x[8], 18); - - x[9] ^= R(x[5] + x[1], 7); - x[13] ^= R(x[9] + x[5], 9); - x[1] ^= R(x[13] + x[9], 13); - x[5] ^= R(x[1] + x[13], 18); - - x[14] ^= R(x[10] + x[6], 7); - x[2] ^= R(x[14] + x[10], 9); - x[6] ^= R(x[2] + x[14], 13); - x[10] ^= R(x[6] + x[2], 18); - - x[3] ^= R(x[15] + x[11], 7); - x[7] ^= R(x[3] + x[15], 9); - x[11] ^= R(x[7] + x[3], 13); - x[15] ^= R(x[11] + x[7], 18); - - x[1] ^= R(x[0] + x[3], 7); - x[2] ^= R(x[1] + x[0], 9); - x[3] ^= R(x[2] + x[1], 13); - x[0] ^= R(x[3] + x[2], 18); - - x[6] ^= R(x[5] + x[4], 7); - x[7] ^= R(x[6] + x[5], 9); - x[4] ^= R(x[7] + x[6], 13); - x[5] ^= R(x[4] + x[7], 18); - - x[11] ^= R(x[10] + x[9], 7); - x[8] ^= R(x[11] + x[10], 9); - x[9] ^= R(x[8] + x[11], 13); - x[10] ^= R(x[9] + x[8], 18); - - x[12] ^= R(x[15] + x[14], 7); - x[13] ^= R(x[12] + x[15], 9); - x[14] ^= R(x[13] + x[12], 13); - x[15] ^= R(x[14] + x[13], 18); - } - - for (let i = 0; i < 16; i++) - B32[i] += x[i]; - - for (let i = 0; i < 16; i++) - B.writeUInt32LE(B32[i], 4 * i, true); -} - -function R(a, b) { - return (a << b) | (a >>> (32 - b)); -} - -function blockmix_salsa8(B, Y, Yo, r) { - const X = Buffer.allocUnsafe(64); - - blkcpy(X, B, 0, (2 * r - 1) * 64, 64); - - for (let i = 0; i < 2 * r; i++) { - blkxor(X, B, 0, i * 64, 64); - salsa20_8(X); - blkcpy(Y, X, Yo + i * 64, 0, 64); - } - - for (let i = 0; i < r; i++) - blkcpy(B, Y, i * 64, Yo + (i * 2) * 64, 64); - - for (let i = 0; i < r; i++) - blkcpy(B, Y, (i + r) * 64, Yo + (i * 2 + 1) * 64, 64); -} - -function integerify(B, r) { - return B.readUInt32LE((2 * r - 1) * 64, true); -} - -function smix(B, Bo, r, N, V, XY) { - const X = XY; - const Y = XY; - - blkcpy(X, B, 0, Bo, 128 * r); - - for (let i = 0; i < N; i++) { - blkcpy(V, X, i * (128 * r), 0, 128 * r); - blockmix_salsa8(X, Y, 128 * r, r); - } - - for (let i = 0; i < N; i++) { - const j = integerify(X, r) & (N - 1); - blkxor(X, V, 0, j * (128 * r), 128 * r); - blockmix_salsa8(X, Y, 128 * r, r); - } - - blkcpy(B, X, Bo, 0, 128 * r); -} - -async function smixAsync(B, Bo, r, N, V, XY) { - const X = XY; - const Y = XY; - - blkcpy(X, B, 0, Bo, 128 * r); - - for (let i = 0; i < N; i++) { - blkcpy(V, X, i * (128 * r), 0, 128 * r); - blockmix_salsa8(X, Y, 128 * r, r); - await co.wait(); - } - - for (let i = 0; i < N; i++) { - const j = integerify(X, r) & (N - 1); - blkxor(X, V, 0, j * (128 * r), 128 * r); - blockmix_salsa8(X, Y, 128 * r, r); - await co.wait(); - } - - blkcpy(B, X, Bo, 0, 128 * r); -} - -function blkcpy(dest, src, s1, s2, len) { - src.copy(dest, s1, s2, s2 + len); -} - -function blkxor(dest, src, s1, s2, len) { - for (let i = 0; i < len; i++) - dest[s1 + i] ^= src[s2 + i]; -} - -/* - * Expose - */ - -exports.derive = derive; -exports.deriveAsync = deriveAsync; diff --git a/lib/crypto/secp256k1-browser.js b/lib/crypto/secp256k1-browser.js deleted file mode 100644 index d71dd6d69..000000000 --- a/lib/crypto/secp256k1-browser.js +++ /dev/null @@ -1,345 +0,0 @@ -/*! - * secp256k1-elliptic.js - wrapper for elliptic - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const elliptic = require('elliptic'); -const secp256k1 = elliptic.ec('secp256k1'); -const Signature = require('elliptic/lib/elliptic/ec/signature'); -const BN = require('./bn'); -const curve = secp256k1.curve; - -/** - * @exports crypto/secp256k1-elliptic - * @ignore - */ - -const ec = exports; - -/** - * Whether we're using native bindings. - * @const {Boolean} - */ - -ec.binding = false; - -/** - * Generate a private key. - * @returns {Buffer} Private key. - */ - -ec.generatePrivateKey = function generatePrivateKey() { - const key = secp256k1.genKeyPair(); - return key.getPrivate().toArrayLike(Buffer, 'be', 32); -}; - -/** - * Create a public key from a private key. - * @param {Buffer} priv - * @param {Boolean?} compress - * @returns {Buffer} - */ - -ec.publicKeyCreate = function publicKeyCreate(priv, compress) { - if (compress == null) - compress = true; - - assert(Buffer.isBuffer(priv)); - - const key = secp256k1.keyPair({ priv: priv }); - - return Buffer.from(key.getPublic(compress, 'array')); -}; - -/** - * Compress or decompress public key. - * @param {Buffer} pub - * @returns {Buffer} - */ - -ec.publicKeyConvert = function publicKeyConvert(key, compress) { - if (compress == null) - compress = true; - - const point = curve.decodePoint(key); - - return Buffer.from(point.encode('array', compress)); -}; - -/** - * ((tweak + key) % n) - * @param {Buffer} privateKey - * @param {Buffer} tweak - * @returns {Buffer} privateKey - */ - -ec.privateKeyTweakAdd = function privateKeyTweakAdd(privateKey, tweak) { - const key = new BN(tweak) - .add(new BN(privateKey)) - .mod(curve.n) - .toArrayLike(Buffer, 'be', 32); - - // Only a 1 in 2^127 chance of happening. - if (!ec.privateKeyVerify(key)) - throw new Error('Private key is invalid.'); - - return key; -}; - -/** - * ((g * tweak) + key) - * @param {Buffer} publicKey - * @param {Buffer} tweak - * @returns {Buffer} publicKey - */ - -ec.publicKeyTweakAdd = function publicKeyTweakAdd(publicKey, tweak, compress) { - if (compress == null) - compress = true; - - const key = curve.decodePoint(publicKey); - const point = curve.g.mul(new BN(tweak)).add(key); - const pub = Buffer.from(point.encode('array', compress)); - - if (!ec.publicKeyVerify(pub)) - throw new Error('Public key is invalid.'); - - return pub; -}; - -/** - * Create an ecdh. - * @param {Buffer} pub - * @param {Buffer} priv - * @returns {Buffer} - */ - -ec.ecdh = function ecdh(pub, priv) { - priv = secp256k1.keyPair({ priv: priv }); - pub = secp256k1.keyPair({ pub: pub }); - return priv.derive(pub.getPublic()).toArrayLike(Buffer, 'be', 32); -}; - -/** - * Recover a public key. - * @param {Buffer} msg - * @param {Buffer} sig - * @param {Number?} j - * @param {Boolean?} compress - * @returns {Buffer[]|Buffer|null} - */ - -ec.recover = function recover(msg, sig, j, compress) { - if (!j) - j = 0; - - if (compress == null) - compress = true; - - let point; - try { - point = secp256k1.recoverPubKey(msg, sig, j); - } catch (e) { - return null; - } - - return Buffer.from(point.encode('array', compress)); -}; - -/** - * Verify a signature. - * @param {Buffer} msg - * @param {Buffer} sig - DER formatted. - * @param {Buffer} key - * @returns {Boolean} - */ - -ec.verify = function verify(msg, sig, key) { - assert(Buffer.isBuffer(msg)); - assert(Buffer.isBuffer(sig)); - assert(Buffer.isBuffer(key)); - - if (sig.length === 0) - return false; - - if (key.length === 0) - return false; - - // Attempt to normalize the signature - // length before passing to elliptic. - // https://github.com/indutny/elliptic/issues/78 - sig = normalizeLength(sig); - - try { - return secp256k1.verify(msg, sig, key); - } catch (e) { - return false; - } -}; - -/** - * Validate a public key. - * @param {Buffer} key - * @returns {Boolean} True if buffer is a valid public key. - */ - -ec.publicKeyVerify = function publicKeyVerify(key) { - try { - const pub = secp256k1.keyPair({ pub: key }); - return pub.validate(); - } catch (e) { - return false; - } -}; - -/** - * Validate a private key. - * @param {Buffer} key - * @returns {Boolean} True if buffer is a valid private key. - */ - -ec.privateKeyVerify = function privateKeyVerify(key) { - if (key.length !== 32) - return false; - - key = new BN(key); - - return !key.isZero() && key.lt(curve.n); -}; - -/** - * Sign a message. - * @param {Buffer} msg - * @param {Buffer} key - Private key. - * @returns {Buffer} DER-formatted signature. - */ - -ec.sign = function sign(msg, key) { - assert(Buffer.isBuffer(msg)); - assert(Buffer.isBuffer(key)); - - // Sign message and ensure low S value - const sig = secp256k1.sign(msg, key, { canonical: true }); - - // Convert to DER - return Buffer.from(sig.toDER()); -}; - -/** - * Convert DER signature to R/S. - * @param {Buffer} raw - * @returns {Buffer} R/S-formatted signature. - */ - -ec.fromDER = function fromDER(raw) { - assert(Buffer.isBuffer(raw)); - - const sig = new Signature(raw); - const out = Buffer.allocUnsafe(64); - - sig.r.toArrayLike(Buffer, 'be', 32).copy(out, 0); - sig.s.toArrayLike(Buffer, 'be', 32).copy(out, 32); - - return out; -}; - -/** - * Convert R/S signature to DER. - * @param {Buffer} sig - * @returns {Buffer} DER-formatted signature. - */ - -ec.toDER = function toDER(raw) { - assert(Buffer.isBuffer(raw)); - - const sig = new Signature({ - r: new BN(raw.slice(0, 32), 'be'), - s: new BN(raw.slice(32, 64), 'be') - }); - - return Buffer.from(sig.toDER()); -}; - -/** - * Test whether a signature has a low S value. - * @param {Buffer} sig - * @returns {Boolean} - */ - -ec.isLowS = function isLowS(raw) { - let sig; - try { - sig = new Signature(raw); - } catch (e) { - return false; - } - - if (sig.s.isZero()) - return false; - - // If S is greater than half the order, - // it's too high. - if (sig.s.gt(secp256k1.nh)) - return false; - - return true; -}; - -/* - * Helpers - */ - -function normalizeLength(sig) { - let data = sig; - let pos = 0; - let len; - - if (data[pos++] !== 0x30) - return sig; - - [len, pos] = getLength(data, pos); - - if (data.length > len + pos) - data = data.slice(0, len + pos); - - if (data[pos++] !== 0x02) - return sig; - - // R length. - [len, pos] = getLength(data, pos); - - pos += len; - - if (data[pos++] !== 0x02) - return sig; - - // S length. - [len, pos] = getLength(data, pos); - - if (data.length > len + pos) - data = data.slice(0, len + pos); - - return data; -} - -function getLength(buf, pos) { - const initial = buf[pos++]; - - if (!(initial & 0x80)) - return [initial, pos]; - - const len = initial & 0xf; - let val = 0; - - for (let i = 0; i < len; i++) { - val <<= 8; - val |= buf[pos++]; - } - - return [val, pos]; -} diff --git a/lib/crypto/secp256k1-native.js b/lib/crypto/secp256k1-native.js deleted file mode 100644 index 8e2f58ce4..000000000 --- a/lib/crypto/secp256k1-native.js +++ /dev/null @@ -1,257 +0,0 @@ -/*! - * secp256k1-native.js - wrapper for secp256k1-node - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const secp256k1 = require('secp256k1'); -const random = require('./random'); - -/** - * @exports crypto/secp256k1 - */ - -const ec = exports; - -/* - * Constants - */ - -const ZERO_S = Buffer.from( - '0000000000000000000000000000000000000000000000000000000000000000', - 'hex' -); - -const HALF_ORDER = Buffer.from( - '7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0', - 'hex'); - -/** - * Whether we're using native bindings. - * @const {Boolean} - * @private - */ - -ec.binding = true; - -/** - * Generate a private key. - * @returns {Buffer} Private key. - */ - -ec.generatePrivateKey = function generatePrivateKey() { - let priv; - - do { - priv = random.randomBytes(32); - } while (!secp256k1.privateKeyVerify(priv)); - - return priv; -}; - -/** - * Create a public key from a private key. - * @param {Buffer} priv - * @param {Boolean?} compress - * @returns {Buffer} - */ - -ec.publicKeyCreate = function publicKeyCreate(priv, compress) { - assert(Buffer.isBuffer(priv)); - return secp256k1.publicKeyCreate(priv, compress); -}; - -/** - * Compress or decompress public key. - * @param {Buffer} pub - * @returns {Buffer} - */ - -ec.publicKeyConvert = function publicKeyConvert(key, compress) { - return secp256k1.publicKeyConvert(key, compress); -}; - -/** - * ((tweak + key) % n) - * @param {Buffer} privateKey - * @param {Buffer} tweak - * @returns {Buffer} privateKey - */ - -ec.privateKeyTweakAdd = function privateKeyTweakAdd(privateKey, tweak) { - return secp256k1.privateKeyTweakAdd(privateKey, tweak); -}; - -/** - * ((g * tweak) + key) - * @param {Buffer} publicKey - * @param {Buffer} tweak - * @returns {Buffer} publicKey - */ - -ec.publicKeyTweakAdd = function publicKeyTweakAdd(publicKey, tweak, compress) { - return secp256k1.publicKeyTweakAdd(publicKey, tweak, compress); -}; - -/** - * Create an ecdh. - * @param {Buffer} pub - * @param {Buffer} priv - * @returns {Buffer} - */ - -ec.ecdh = function ecdh(pub, priv) { - const point = secp256k1.ecdhUnsafe(pub, priv, true); - return point.slice(1, 33); -}; - -/** - * Recover a public key. - * @param {Buffer} msg - * @param {Buffer} sig - * @param {Number?} j - * @param {Boolean?} compress - * @returns {Buffer[]|Buffer|null} - */ - -ec.recover = function recover(msg, sig, j, compress) { - if (!j) - j = 0; - - try { - sig = secp256k1.signatureImport(sig); - } catch (e) { - return null; - } - - let key; - try { - key = secp256k1.recover(msg, sig, j, compress); - } catch (e) { - return null; - } - - return key; -}; - -/** - * Verify a signature. - * @param {Buffer} msg - * @param {Buffer} sig - DER formatted. - * @param {Buffer} key - * @returns {Boolean} - */ - -ec.verify = function verify(msg, sig, key) { - assert(Buffer.isBuffer(msg)); - assert(Buffer.isBuffer(sig)); - assert(Buffer.isBuffer(key)); - - if (sig.length === 0) - return false; - - if (key.length === 0) - return false; - - try { - sig = secp256k1.signatureImportLax(sig); - sig = secp256k1.signatureNormalize(sig); - return secp256k1.verify(msg, sig, key); - } catch (e) { - return false; - } -}; - -/** - * Validate a public key. - * @param {Buffer} key - * @returns {Boolean} True if buffer is a valid public key. - */ - -ec.publicKeyVerify = function publicKeyVerify(key) { - return secp256k1.publicKeyVerify(key); -}; - -/** - * Validate a private key. - * @param {Buffer} key - * @returns {Boolean} True if buffer is a valid private key. - */ - -ec.privateKeyVerify = function privateKeyVerify(key) { - return secp256k1.privateKeyVerify(key); -}; - -/** - * Sign a message. - * @param {Buffer} msg - * @param {Buffer} key - Private key. - * @returns {Buffer} DER-formatted signature. - */ - -ec.sign = function sign(msg, key) { - assert(Buffer.isBuffer(msg)); - assert(Buffer.isBuffer(key)); - - // Sign message - let sig = secp256k1.sign(msg, key); - - // Ensure low S value - sig = secp256k1.signatureNormalize(sig.signature); - - // Convert to DER - return secp256k1.signatureExport(sig); -}; - -/** - * Convert DER signature to R/S. - * @param {Buffer} sig - * @returns {Buffer} R/S-formatted signature. - */ - -ec.fromDER = function fromDER(sig) { - assert(Buffer.isBuffer(sig)); - return secp256k1.signatureImport(sig); -}; - -/** - * Convert R/S signature to DER. - * @param {Buffer} sig - * @returns {Buffer} DER-formatted signature. - */ - -ec.toDER = function toDER(sig) { - assert(Buffer.isBuffer(sig)); - return secp256k1.signatureExport(sig); -}; - -/** - * Test whether a signature has a low S value. - * @param {Buffer} sig - * @returns {Boolean} - */ - -ec.isLowS = function isLowS(sig) { - let s; - - try { - const rs = secp256k1.signatureImport(sig); - s = rs.slice(32, 64); - } catch (e) { - return false; - } - - if (s.equals(ZERO_S)) - return false; - - // If S is greater than half the order, - // it's too high. - if (s.compare(HALF_ORDER) > 0) - return false; - - return true; -}; diff --git a/lib/crypto/secp256k1.js b/lib/crypto/secp256k1.js deleted file mode 100644 index d2ee2f834..000000000 --- a/lib/crypto/secp256k1.js +++ /dev/null @@ -1,21 +0,0 @@ -/*! - * secp256k1.js - ecdsa wrapper for secp256k1 and elliptic - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -let native; - -if (Number(process.env.BCOIN_NO_SECP256K1) !== 1) { - try { - native = require('secp256k1/bindings'); - } catch (e) { - ; - } -} - -module.exports = native - ? require('./secp256k1-native') - : require('./secp256k1-browser'); diff --git a/lib/crypto/sha256.js b/lib/crypto/sha256.js deleted file mode 100644 index 9a25ade1e..000000000 --- a/lib/crypto/sha256.js +++ /dev/null @@ -1,399 +0,0 @@ -/*! - * sha256.js - SHA256 implementation for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - * Parts of this software based on hash.js. - */ - -'use strict'; - -/** - * @module crypto/sha256 - * @ignore - */ - -/* - * Constants - */ - -const DESC = Buffer.allocUnsafe(8); -const BUFFER64 = Buffer.allocUnsafe(64); -const PADDING = Buffer.allocUnsafe(64); - -const K = [ - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, - 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, - 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, - 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, - 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, - 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, - 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, - 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, - 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 -]; - -PADDING.fill(0); -PADDING[0] = 0x80; - -/** - * SHA256 - * @alias module:crypto/sha256.SHA256 - * @constructor - * @property {Number[]} s - * @property {Number[]} w - * @property {Buffer} block - * @property {Number} bytes - */ - -function SHA256() { - if (!(this instanceof SHA256)) - return new SHA256(); - - this.s = new Array(8); - this.w = new Array(64); - this.block = Buffer.allocUnsafe(64); - this.bytes = 0; -} - -/** - * Initialize SHA256 context. - */ - -SHA256.prototype.init = function init() { - this.s[0] = 0x6a09e667; - this.s[1] = 0xbb67ae85; - this.s[2] = 0x3c6ef372; - this.s[3] = 0xa54ff53a; - this.s[4] = 0x510e527f; - this.s[5] = 0x9b05688c; - this.s[6] = 0x1f83d9ab; - this.s[7] = 0x5be0cd19; - this.bytes = 0; -}; - -/** - * Update SHA256 context. - * @param {Buffer} data - */ - -SHA256.prototype.update = function update(data) { - return this._update(data, data.length); -}; - -/** - * Finalize SHA256 context. - * @returns {Buffer} - */ - -SHA256.prototype.finish = function finish() { - return this._finish(Buffer.allocUnsafe(32)); -}; - -/** - * Update SHA256 context. - * @private - * @param {Buffer} data - * @param {Number} len - */ - -SHA256.prototype._update = function _update(data, len) { - let size = this.bytes & 0x3f; - let pos = 0; - - this.bytes += len; - - if (size > 0) { - let want = 64 - size; - - if (want > len) - want = len; - - for (let i = 0; i < want; i++) - this.block[size + i] = data[i]; - - size += want; - len -= want; - pos += want; - - if (size < 64) - return; - - this.transform(this.block, 0); - } - - while (len >= 64) { - this.transform(data, pos); - pos += 64; - len -= 64; - } - - for (let i = 0; i < len; i++) - this.block[i] = data[pos + i]; -}; - -/** - * Finalize SHA256 context. - * @private - * @param {Buffer} out - * @returns {Buffer} - */ - -SHA256.prototype._finish = function _finish(out) { - writeU32(DESC, this.bytes >>> 29, 0); - writeU32(DESC, this.bytes << 3, 4); - - this._update(PADDING, 1 + ((119 - (this.bytes % 64)) % 64)); - this._update(DESC, 8); - - for (let i = 0; i < 8; i++) { - writeU32(out, this.s[i], i * 4); - this.s[i] = 0; - } - - return out; -}; - -/** - * Transform SHA256 block. - * @param {Buffer} chunk - * @param {Number} pos - */ - -SHA256.prototype.transform = function transform(chunk, pos) { - const w = this.w; - let a = this.s[0]; - let b = this.s[1]; - let c = this.s[2]; - let d = this.s[3]; - let e = this.s[4]; - let f = this.s[5]; - let g = this.s[6]; - let h = this.s[7]; - let i = 0; - - for (; i < 16; i++) - w[i] = readU32(chunk, pos + i * 4); - - for (; i < 64; i++) - w[i] = sigma1(w[i - 2]) + w[i - 7] + sigma0(w[i - 15]) + w[i - 16]; - - for (i = 0; i < 64; i++) { - let t1 = h + Sigma1(e); - t1 += Ch(e, f, g); - t1 += K[i] + w[i]; - - let t2 = Sigma0(a); - t2 += Maj(a, b, c); - - h = g; - g = f; - f = e; - - e = d + t1; - - d = c; - c = b; - b = a; - - a = t1 + t2; - } - - this.s[0] += a; - this.s[1] += b; - this.s[2] += c; - this.s[3] += d; - this.s[4] += e; - this.s[5] += f; - this.s[6] += g; - this.s[7] += h; - - this.s[0] >>>= 0; - this.s[1] >>>= 0; - this.s[2] >>>= 0; - this.s[3] >>>= 0; - this.s[4] >>>= 0; - this.s[5] >>>= 0; - this.s[6] >>>= 0; - this.s[7] >>>= 0; -}; - -/** - * SHA256Hmac - * @alias module:crypto/sha256.SHA256Hmac - * @constructor - * @property {SHA256} inner - * @property {SHA256} outer - */ - -function SHA256Hmac() { - if (!(this instanceof SHA256Hmac)) - return new SHA256Hmac(); - - this.inner = new SHA256(); - this.outer = new SHA256(); -} - -/** - * Initialize HMAC context. - * @param {Buffer} data - */ - -SHA256Hmac.prototype.init = function init(data) { - const key = BUFFER64; - - if (data.length > 64) { - this.inner.init(); - this.inner.update(data); - this.inner._finish(key); - key.fill(0, 32, 64); - } else { - data.copy(key, 0); - key.fill(0, data.length, 64); - } - - for (let i = 0; i < key.length; i++) - key[i] ^= 0x36; - - this.inner.init(); - this.inner.update(key); - - for (let i = 0; i < key.length; i++) - key[i] ^= 0x6a; - - this.outer.init(); - this.outer.update(key); -}; - -/** - * Update HMAC context. - * @param {Buffer} data - */ - -SHA256Hmac.prototype.update = function update(data) { - this.inner.update(data); -}; - -/** - * Finalize HMAC context. - * @returns {Buffer} - */ - -SHA256Hmac.prototype.finish = function finish() { - this.outer.update(this.inner.finish()); - return this.outer.finish(); -}; - -/* - * Helpers - * @see https://github.com/bitcoin-core/secp256k1/blob/master/src/hash_impl.h - */ - -function Sigma0(x) { - return (x >>> 2 | x << 30) ^ (x >>> 13 | x << 19) ^ (x >>> 22 | x << 10); -} - -function Sigma1(x) { - return (x >>> 6 | x << 26) ^ (x >>> 11 | x << 21) ^ (x >>> 25 | x << 7); -} - -function sigma0(x) { - return (x >>> 7 | x << 25) ^ (x >>> 18 | x << 14) ^ (x >>> 3); -} - -function sigma1(x) { - return (x >>> 17 | x << 15) ^ (x >>> 19 | x << 13) ^ (x >>> 10); -} - -function Ch(x, y, z) { - return z ^ (x & (y ^ z)); -} - -function Maj(x, y, z) { - return (x & y) | (z & (x | y)); -} - -function writeU32(buf, value, offset) { - buf[offset] = value >>> 24; - buf[offset + 1] = (value >> 16) & 0xff; - buf[offset + 2] = (value >> 8) & 0xff; - buf[offset + 3] = value & 0xff; -} - -function readU32(buf, offset) { - return ((buf[offset] & 0xff) * 0x1000000) - + ((buf[offset + 1] & 0xff) << 16) - | ((buf[offset + 2] & 0xff) << 8) - | (buf[offset + 3] & 0xff); -} - -/* - * Context Helpers - */ - -const ctx = new SHA256(); -const mctx = new SHA256Hmac(); - -/** - * Hash buffer with sha256. - * @alias module:crypto/sha256.sha256 - * @param {Buffer} data - * @returns {Buffer} - */ - -function sha256(data) { - ctx.init(); - ctx.update(data); - return ctx.finish(); -} - -/** - * Hash buffer with double sha256. - * @alias module:crypto/sha256.hash256 - * @param {Buffer} data - * @returns {Buffer} - */ - -function hash256(data) { - const out = Buffer.allocUnsafe(32); - ctx.init(); - ctx.update(data); - ctx._finish(out); - ctx.init(); - ctx.update(out); - ctx._finish(out); - return out; -} - -/** - * Create a sha256 HMAC from buffer and key. - * @alias module:crypto/sha256.hmac - * @param {Buffer} data - * @param {Buffer} key - * @returns {Buffer} - */ - -function hmac(data, key) { - mctx.init(key); - mctx.update(data); - return mctx.finish(); -} - -/* - * Expose - */ - -exports = SHA256; -exports.SHA256 = SHA256; -exports.SHA256Hmac = SHA256Hmac; -exports.digest = sha256; -exports.hmac = hmac; -exports.hash256 = hash256; - -module.exports = exports; diff --git a/lib/crypto/siphash.js b/lib/crypto/siphash.js deleted file mode 100644 index 8f45f5754..000000000 --- a/lib/crypto/siphash.js +++ /dev/null @@ -1,304 +0,0 @@ -/*! - * siphash.js - siphash for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - * - * Ported from: - * https://github.com/bitcoin/bitcoin/blob/master/src/hash.cpp - */ - -'use strict'; - -/** - * @module crypto/siphash - */ - -const native = require('../native').binding; - -/** - * Javascript siphash 2-4 implementation. - * @private - * @param {Buffer} data - * @param {Buffer} key - 128 bit key. - * @param {Number} shift - * @returns {Array} [hi, lo] - */ - -function _siphash(data, key, shift) { - const blocks = Math.floor(data.length / 8); - const c0 = U64(0x736f6d65, 0x70736575); - const c1 = U64(0x646f7261, 0x6e646f6d); - const c2 = U64(0x6c796765, 0x6e657261); - const c3 = U64(0x74656462, 0x79746573); - const f0 = U64(blocks << (shift - 32), 0); - const f1 = U64(0, 0xff); - const k0 = U64.fromRaw(key, 0); - const k1 = U64.fromRaw(key, 8); - - // Init - const v0 = c0.ixor(k0); - const v1 = c1.ixor(k1); - const v2 = c2.ixor(k0); - const v3 = c3.ixor(k1); - - // Blocks - let p = 0; - for (let i = 0; i < blocks; i++) { - const d = U64.fromRaw(data, p); - p += 8; - v3.ixor(d); - sipround(v0, v1, v2, v3); - sipround(v0, v1, v2, v3); - v0.ixor(d); - } - - switch (data.length & 7) { - case 7: - f0.hi |= data[p + 6] << 16; - case 6: - f0.hi |= data[p + 5] << 8; - case 5: - f0.hi |= data[p + 4]; - case 4: - f0.lo |= data[p + 3] << 24; - case 3: - f0.lo |= data[p + 2] << 16; - case 2: - f0.lo |= data[p + 1] << 8; - case 1: - f0.lo |= data[p]; - } - - // Finalization - v3.ixor(f0); - sipround(v0, v1, v2, v3); - sipround(v0, v1, v2, v3); - v0.ixor(f0); - v2.ixor(f1); - sipround(v0, v1, v2, v3); - sipround(v0, v1, v2, v3); - sipround(v0, v1, v2, v3); - sipround(v0, v1, v2, v3); - v0.ixor(v1); - v0.ixor(v2); - v0.ixor(v3); - - return [v0.hi, v0.lo]; -} - -/** - * Javascript siphash 2-4 implementation (64 bit ints). - * @private - * @param {Number} hi - * @param {Number} lo - * @param {Buffer} key - 128 bit key. - * @returns {Array} [hi, lo] - */ - -function _siphash64(hi, lo, key) { - const c0 = U64(0x736f6d65, 0x70736575); - const c1 = U64(0x646f7261, 0x6e646f6d); - const c2 = U64(0x6c796765, 0x6e657261); - const c3 = U64(0x74656462, 0x79746573); - const f0 = U64(hi, lo); - const f1 = U64(0, 0xff); - const k0 = U64.fromRaw(key, 0); - const k1 = U64.fromRaw(key, 8); - - // Init - const v0 = c0.ixor(k0); - const v1 = c1.ixor(k1); - const v2 = c2.ixor(k0); - const v3 = c3.ixor(k1); - - // Finalization - v3.ixor(f0); - sipround(v0, v1, v2, v3); - sipround(v0, v1, v2, v3); - v0.ixor(f0); - v2.ixor(f1); - sipround(v0, v1, v2, v3); - sipround(v0, v1, v2, v3); - sipround(v0, v1, v2, v3); - sipround(v0, v1, v2, v3); - v0.ixor(v1); - v0.ixor(v2); - v0.ixor(v3); - - return [v0.hi, v0.lo]; -} - -/** - * Javascript siphash 2-4 implementation (shift=56). - * @alias module:crypto/siphash.siphash - * @param {Buffer} data - * @param {Buffer} key - 128 bit key. - * @returns {Array} [hi, lo] - */ - -function siphash(data, key) { - return _siphash(data, key, 56); -} - -/** - * Javascript siphash 2-4 implementation (shift=59). - * @alias module:crypto/siphash.siphash256 - * @param {Buffer} data - * @param {Buffer} key - 128 bit key. - * @returns {Array} [hi, lo] - */ - -function siphash256(data, key) { - return _siphash(data, key, 59); -} - -/** - * Javascript siphash 2-4 implementation (32 bit ints). - * @alias module:crypto/siphash.siphash32 - * @param {Number} num - * @param {Buffer} key - 128 bit key. - * @returns {Number} - */ - -function siphash32(num, key) { - return _siphash64(0, num, key)[1]; -} - -/** - * Javascript siphash 2-4 implementation (64 bit ints). - * @alias module:crypto/siphash.siphash64 - * @param {Number} hi - * @param {Number} lo - * @param {Buffer} key - 128 bit key. - * @returns {Array} [hi, lo] - */ - -function siphash64(hi, lo, key) { - return _siphash64(hi, lo, key); -} - -if (native) { - siphash = native.siphash; - siphash256 = native.siphash256; - siphash32 = native.siphash32; - siphash64 = native.siphash64; -} - -/* - * U64 - * @constructor - * @ignore - */ - -function U64(hi, lo) { - if (!(this instanceof U64)) - return new U64(hi, lo); - - this.hi = hi | 0; - this.lo = lo | 0; -} - -U64.prototype.iadd = function iadd(b) { - const a = this; - - // Credit to @indutny for this method. - const lo = (a.lo + b.lo) | 0; - - const s = lo >> 31; - const as = a.lo >> 31; - const bs = b.lo >> 31; - - const c = ((as & bs) | (~s & (as ^ bs))) & 1; - - const hi = ((a.hi + b.hi) | 0) + c; - - a.hi = hi | 0; - a.lo = lo; - - return a; -}; - -U64.prototype.ixor = function ixor(b) { - this.hi ^= b.hi; - this.lo ^= b.lo; - return this; -}; - -U64.prototype.irotl = function irotl(bits) { - let ahi = this.hi; - let alo = this.lo; - let bhi = this.hi; - let blo = this.lo; - - // a = x << b - if (bits < 32) { - ahi <<= bits; - ahi |= alo >>> (32 - bits); - alo <<= bits; - } else { - ahi = alo << (bits - 32); - alo = 0; - } - - bits = 64 - bits; - - // b = x >> (64 - b) - if (bits < 32) { - blo >>>= bits; - blo |= bhi << (32 - bits); - bhi >>>= bits; - } else { - blo = bhi >>> (bits - 32); - bhi = 0; - } - - // a | b - this.hi = ahi | bhi; - this.lo = alo | blo; - - return this; -}; - -U64.fromRaw = function fromRaw(data, off) { - const lo = data.readUInt32LE(off, true); - const hi = data.readUInt32LE(off + 4, true); - return new U64(hi, lo); -}; - -/* - * Helpers - */ - -function sipround(v0, v1, v2, v3) { - v0.iadd(v1); - v1.irotl(13); - v1.ixor(v0); - - v0.irotl(32); - - v2.iadd(v3); - v3.irotl(16); - v3.ixor(v2); - - v0.iadd(v3); - v3.irotl(21); - v3.ixor(v0); - - v2.iadd(v1); - v1.irotl(17); - v1.ixor(v2); - - v2.irotl(32); -} - -/* - * Expose - */ - -exports = siphash; -exports.siphash = siphash; -exports.siphash256 = siphash256; -exports.siphash32 = siphash32; -exports.siphash64 = siphash64; - -module.exports = exports; diff --git a/lib/db/backends-browser.js b/lib/db/backends-browser.js deleted file mode 100644 index 62189e994..000000000 --- a/lib/db/backends-browser.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * backends-browser.js - database backends for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const level = require('./level'); -const MemDB = require('./memdb'); - -exports.get = function get(name) { - if (name === 'memory') - return MemDB; - return level; -}; diff --git a/lib/db/backends.js b/lib/db/backends.js deleted file mode 100644 index 0f589764e..000000000 --- a/lib/db/backends.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * backends.js - database backends for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -exports.get = function get(name) { - try { - switch (name) { - case 'leveldown': - return require('leveldown'); - case 'rocksdown': - return require('rocksdown'); - case 'lmdb': - return require('lmdb'); - case 'memory': - return require('./memdb'); - default: - throw new Error(`Database backend "${name}" not found.`); - } - } catch (e) { - if (e.code === 'MODULE_NOT_FOUND') - throw new Error(`Database backend "${name}" not found.`); - throw e; - } -}; diff --git a/lib/db/index.js b/lib/db/index.js deleted file mode 100644 index 492be96a3..000000000 --- a/lib/db/index.js +++ /dev/null @@ -1,16 +0,0 @@ -/*! - * db/index.js - data management for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module db - */ - -exports.backends = require('./backends'); -exports.LDB = require('./ldb'); -exports.LowlevelUp = require('./lowlevelup'); -exports.MemDB = require('./memdb'); diff --git a/lib/db/ldb.js b/lib/db/ldb.js deleted file mode 100644 index fc0294e6a..000000000 --- a/lib/db/ldb.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * ldb.js - database backend for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const LowlevelUp = require('./lowlevelup'); -const backends = require('./backends'); - -/** - * Create a database. - * @alias module:db.LDB - * @param {Object} options - * @returns {LowlevelUp} - */ - -function LDB(options) { - const result = LDB.getBackend(options); - const backend = result.backend; - const location = result.location; - - return new LowlevelUp(backend, location, options); -} - -/** - * Get database name and extension based on options. - * @param {String} db - * @returns {Object} - */ - -LDB.getName = function getName(db) { - let name, ext; - - if (!db) - db = 'memory'; - - switch (db) { - case 'ldb': - case 'leveldb': - case 'leveldown': - name = 'leveldown'; - ext = 'ldb'; - break; - case 'rdb': - case 'rocksdb': - case 'rocksdown': - name = 'rocksdown'; - ext = 'rdb'; - break; - case 'mdb': - case 'lmdb': - name = 'lmdb'; - ext = 'mdb'; - break; - case 'mem': - case 'memory': - case 'rbt': - name = 'memory'; - ext = 'mem'; - break; - default: - name = db; - ext = 'db'; - break; - } - - return [name, ext]; -}; - -/** - * Get target backend and location. - * @param {Object} options - * @returns {Object} - */ - -LDB.getBackend = function getBackend(options) { - const [name, ext] = LDB.getName(options.db); - const backend = backends.get(name); - let location = options.location; - - if (typeof location !== 'string') { - assert(name === 'memory', 'Location required.'); - location = 'memory'; - } - - return { - backend: backend, - location: `${location}.${ext}` - }; -}; - -/* - * Expose - */ - -module.exports = LDB; diff --git a/lib/db/level.js b/lib/db/level.js deleted file mode 100644 index 22c72021f..000000000 --- a/lib/db/level.js +++ /dev/null @@ -1,139 +0,0 @@ -'use strict'; - -const Level = require('level-js'); - -function DB(location) { - this.level = new Level(location); - this.bufferKeys = false; -} - -DB.prototype.open = function open(options, callback) { - this.bufferKeys = options.bufferKeys === true; - this.level.open(options, callback); -}; - -DB.prototype.close = function close(callback) { - this.level.close(callback); -}; - -DB.prototype.get = function get(key, options, callback) { - this.level.get(toHex(key), options, callback); -}; - -DB.prototype.put = function put(key, value, options, callback) { - this.level.put(toHex(key), value, options, callback); -}; - -DB.prototype.del = function del(key, options, callback) { - this.level.del(toHex(key), options, callback); -}; - -DB.prototype.batch = function batch() { - return new Batch(this); -}; - -DB.prototype.iterator = function iterator(options) { - return new Iterator(this, options); -}; - -DB.destroy = function destroy(db, callback) { - Level.destroy(db, callback); -}; - -function Batch(db) { - this.db = db; - this.batch = db.level.batch(); - this.hasOps = false; -} - -Batch.prototype.put = function put(key, value) { - this.batch.put(toHex(key), value); - this.hasOps = true; - return this; -}; - -Batch.prototype.del = function del(key) { - this.batch.del(toHex(key)); - this.hasOps = true; - return this; -}; - -Batch.prototype.write = function write(callback) { - if (!this.hasOps) - return callback(); - this.batch.write(callback); - return this; -}; - -Batch.prototype.clear = function clear() { - this.batch.clear(); - return this; -}; - -function Iterator(db, options) { - const opt = { - gt: toHex(options.gt), - gte: toHex(options.gte), - lt: toHex(options.lt), - lte: toHex(options.lte), - limit: options.limit, - reverse: options.reverse, - keys: options.keys, - values: options.values, - keyAsBuffer: false, - valueAsBuffer: true - }; - - this.db = db; - this.iter = db.level.iterator(opt); - this.ended = false; -} - -Iterator.prototype.next = function next(callback) { - this.iter.next((err, key, value) => { - // Hack for level-js: it doesn't actually - // end iterators -- it keeps streaming keys - // and values. - if (this.ended) - return; - - if (err) { - callback(err); - return; - } - - if (key === undefined && value === undefined) { - callback(err, key, value); - return; - } - - if (key && this.db.bufferKeys) - key = Buffer.from(key, 'hex'); - - if (value && !Buffer.isBuffer(value) && value.buffer) - value = Buffer.from(value.buffer); - - callback(err, key, value); - }); -}; - -Iterator.prototype.seek = function seek(key) { - this.iter.seek(toHex(key)); -}; - -Iterator.prototype.end = function end(callback) { - if (this.ended) { - callback(new Error('end() already called on iterator.')); - return; - } - this.ended = true; - this.iter.end(callback); -}; - -function toHex(key) { - if (Buffer.isBuffer(key)) - return key.toString('hex'); - return key; -} - -module.exports = DB; diff --git a/lib/db/lowlevelup.js b/lib/db/lowlevelup.js deleted file mode 100644 index 547219c51..000000000 --- a/lib/db/lowlevelup.js +++ /dev/null @@ -1,1073 +0,0 @@ -/*! - * lowlevelup.js - LevelUP module for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); - -const LOW = Buffer.from([0x00]); -const HIGH = Buffer.from([0xff]); - -let VERSION_ERROR; - -/** - * Extremely low-level version of levelup. - * - * This avoids pulling in extra deps and - * lowers memory usage. - * - * @alias module:db.LowlevelUp - * @constructor - * @param {Function} backend - Database backend. - * @param {String} location - File location. - * @param {Object?} options - Leveldown options. - */ - -function LowlevelUp(backend, location, options) { - if (!(this instanceof LowlevelUp)) - return new LowlevelUp(backend, location, options); - - assert(typeof backend === 'function', 'Backend is required.'); - assert(typeof location === 'string', 'Filename is required.'); - - this.options = new LLUOptions(options); - this.backend = backend; - this.location = location; - - this.loading = false; - this.closing = false; - this.loaded = false; - - this.binding = null; - this.leveldown = false; - - this.init(); -} - -/** - * Initialize the database. - * @method - * @private - */ - -LowlevelUp.prototype.init = function init() { - const Backend = this.backend; - - let db = new Backend(this.location); - - // Stay as close to the metal as possible. - // We want to make calls to C++ directly. - while (db.db) { - // Not a database. - if (typeof db.db.put !== 'function') - break; - - // Recursive. - if (db.db === db) - break; - - // Go deeper. - db = db.db; - } - - // A lower-level binding. - if (db.binding) { - this.binding = db.binding; - this.leveldown = db !== db.binding; - } else { - this.binding = db; - } -}; - -/** - * Open the database. - * @returns {Promise} - */ - -LowlevelUp.prototype.open = async function open() { - if (this.loaded) - throw new Error('Database is already open.'); - - assert(!this.loading); - assert(!this.closing); - - this.loading = true; - - try { - await this.load(); - } catch (e) { - this.loading = false; - throw e; - } - - this.loading = false; - this.loaded = true; -}; - -/** - * Close the database. - * @returns {Promise} - */ - -LowlevelUp.prototype.close = async function close() { - if (!this.loaded) - throw new Error('Database is already closed.'); - - assert(!this.loading); - assert(!this.closing); - - this.loaded = false; - this.closing = true; - - try { - await this.unload(); - } catch (e) { - this.loaded = true; - this.closing = false; - throw e; - } - - this.closing = false; -}; - -/** - * Open the database. - * @private - * @returns {Promise} - */ - -LowlevelUp.prototype.load = function load() { - return new Promise((resolve, reject) => { - this.binding.open(this.options, wrap(resolve, reject)); - }); -}; - -/** - * Close the database. - * @private - * @returns {Promise} - */ - -LowlevelUp.prototype.unload = function unload() { - return new Promise((resolve, reject) => { - this.binding.close(wrap(resolve, reject)); - }); -}; - -/** - * Destroy the database. - * @returns {Promise} - */ - -LowlevelUp.prototype.destroy = function destroy() { - return new Promise((resolve, reject) => { - if (this.loaded || this.closing) { - reject(new Error('Cannot destroy open database.')); - return; - } - - if (!this.backend.destroy) { - reject(new Error('Cannot destroy (method not available).')); - return; - } - - this.backend.destroy(this.location, wrap(resolve, reject)); - }); -}; - -/** - * Repair the database. - * @returns {Promise} - */ - -LowlevelUp.prototype.repair = function repair() { - return new Promise((resolve, reject) => { - if (this.loaded || this.closing) { - reject(new Error('Cannot repair open database.')); - return; - } - - if (!this.backend.repair) { - reject(new Error('Cannot repair (method not available).')); - return; - } - - this.backend.repair(this.location, wrap(resolve, reject)); - }); -}; - -/** - * Backup the database. - * @param {String} path - * @returns {Promise} - */ - -LowlevelUp.prototype.backup = function backup(path) { - if (!this.binding.backup) - return this.clone(path); - - return new Promise((resolve, reject) => { - if (!this.loaded) { - reject(new Error('Database is closed.')); - return; - } - this.binding.backup(path, wrap(resolve, reject)); - }); -}; - -/** - * Retrieve a record from the database. - * @param {String|Buffer} key - * @returns {Promise} - Returns Buffer. - */ - -LowlevelUp.prototype.get = function get(key) { - return new Promise((resolve, reject) => { - if (!this.loaded) { - reject(new Error('Database is closed.')); - return; - } - this.binding.get(key, (err, result) => { - if (err) { - if (isNotFound(err)) { - resolve(null); - return; - } - reject(err); - return; - } - resolve(result); - }); - }); -}; - -/** - * Store a record in the database. - * @param {String|Buffer} key - * @param {Buffer} value - * @returns {Promise} - */ - -LowlevelUp.prototype.put = function put(key, value) { - if (!value) - value = LOW; - - return new Promise((resolve, reject) => { - if (!this.loaded) { - reject(new Error('Database is closed.')); - return; - } - this.binding.put(key, value, wrap(resolve, reject)); - }); -}; - -/** - * Remove a record from the database. - * @param {String|Buffer} key - * @returns {Promise} - */ - -LowlevelUp.prototype.del = function del(key) { - return new Promise((resolve, reject) => { - if (!this.loaded) { - reject(new Error('Database is closed.')); - return; - } - this.binding.del(key, wrap(resolve, reject)); - }); -}; - -/** - * Create an atomic batch. - * @returns {Batch} - */ - -LowlevelUp.prototype.batch = function batch() { - if (!this.loaded) - throw new Error('Database is closed.'); - - return new Batch(this); -}; - -/** - * Create an iterator. - * @param {Object} options - * @returns {Iterator} - */ - -LowlevelUp.prototype.iterator = function iterator(options) { - if (!this.loaded) - throw new Error('Database is closed.'); - - return new Iterator(this, options); -}; - -/** - * Get a database property. - * @param {String} name - Property name. - * @returns {String} - */ - -LowlevelUp.prototype.getProperty = function getProperty(name) { - if (!this.loaded) - throw new Error('Database is closed.'); - - if (!this.binding.getProperty) - return ''; - - return this.binding.getProperty(name); -}; - -/** - * Calculate approximate database size. - * @param {String|Buffer} start - Start key. - * @param {String|Buffer} end - End key. - * @returns {Promise} - Returns Number. - */ - -LowlevelUp.prototype.approximateSize = function approximateSize(start, end) { - return new Promise((resolve, reject) => { - if (!this.loaded) { - reject(new Error('Database is closed.')); - return; - } - - if (!this.binding.approximateSize) { - reject(new Error('Cannot get size.')); - return; - } - - this.binding.approximateSize(start, end, wrap(resolve, reject)); - }); -}; - -/** - * Compact range of keys. - * @param {String|Buffer|null} start - Start key. - * @param {String|Buffer|null} end - End key. - * @returns {Promise} - */ - -LowlevelUp.prototype.compactRange = function compactRange(start, end) { - if (!start) - start = LOW; - - if (!end) - end = HIGH; - - return new Promise((resolve, reject) => { - if (!this.loaded) { - reject(new Error('Database is closed.')); - return; - } - - if (!this.binding.compactRange) { - resolve(); - return; - } - - this.binding.compactRange(start, end, wrap(resolve, reject)); - }); -}; - -/** - * Test whether a key exists. - * @method - * @param {String} key - * @returns {Promise} - Returns Boolean. - */ - -LowlevelUp.prototype.has = async function has(key) { - const value = await this.get(key); - return value != null; -}; - -/** - * Collect all keys from iterator options. - * @method - * @param {Object} options - Iterator options. - * @returns {Promise} - Returns Array. - */ - -LowlevelUp.prototype.range = async function range(options) { - const iter = this.iterator({ - gte: options.gte, - lte: options.lte, - keys: true, - values: true - }); - - const items = []; - - await iter.each((key, value) => { - if (options.parse) { - const item = options.parse(key, value); - if (item) - items.push(item); - } else { - items.push(new IteratorItem(key, value)); - } - }); - - return items; -}; - -/** - * Collect all keys from iterator options. - * @method - * @param {Object} options - Iterator options. - * @returns {Promise} - Returns Array. - */ - -LowlevelUp.prototype.keys = async function keys(options) { - const iter = this.iterator({ - gte: options.gte, - lte: options.lte, - keys: true, - values: false - }); - - const items = []; - - await iter.each((key) => { - if (options.parse) - key = options.parse(key); - items.push(key); - }); - - return items; -}; - -/** - * Collect all keys from iterator options. - * @method - * @param {Object} options - Iterator options. - * @returns {Promise} - Returns Array. - */ - -LowlevelUp.prototype.values = async function values(options) { - const iter = this.iterator({ - gte: options.gte, - lte: options.lte, - keys: false, - values: true - }); - - const items = []; - - await iter.each((value) => { - if (options.parse) - value = options.parse(value); - items.push(value); - }); - - return items; -}; - -/** - * Dump database (for debugging). - * @method - * @returns {Promise} - Returns Object. - */ - -LowlevelUp.prototype.dump = async function dump() { - const records = Object.create(null); - - const items = await this.range({ - gte: LOW, - lte: HIGH - }); - - for (const item of items) { - const key = item.key.toString('hex'); - const value = item.value.toString('hex'); - records[key] = value; - } - - return records; -}; - -/** - * Write and assert a version number for the database. - * @method - * @param {Number} version - * @returns {Promise} - */ - -LowlevelUp.prototype.checkVersion = async function checkVersion(key, version) { - const data = await this.get(key); - - if (!data) { - const value = Buffer.allocUnsafe(4); - value.writeUInt32LE(version, 0, true); - const batch = this.batch(); - batch.put(key, value); - await batch.write(); - return; - } - - const num = data.readUInt32LE(0, true); - - if (num !== version) - throw new Error(VERSION_ERROR); -}; - -/** - * Clone the database. - * @method - * @param {String} path - * @returns {Promise} - */ - -LowlevelUp.prototype.clone = async function clone(path) { - if (!this.loaded) - throw new Error('Database is closed.'); - - const hwm = 256 << 20; - - const options = new LLUOptions(this.options); - options.createIfMissing = true; - options.errorIfExists = true; - - const tmp = new LowlevelUp(this.backend, path, options); - - await tmp.open(); - - const iter = this.iterator({ - keys: true, - values: true - }); - - let batch = tmp.batch(); - let total = 0; - - while (await iter.next()) { - const {key, value} = iter; - - batch.put(key, value); - - total += key.length + 80; - total += value.length + 80; - - if (total >= hwm) { - total = 0; - - try { - await batch.write(); - } catch (e) { - await iter.end(); - await tmp.close(); - throw e; - } - - batch = tmp.batch(); - } - } - - try { - await batch.write(); - } finally { - await tmp.close(); - } -}; - -/** - * Batch - * @constructor - * @ignore - * @param {LowlevelUp} db - */ - -function Batch(db) { - this.batch = db.binding.batch(); -} - -/** - * Write a value to the batch. - * @param {String|Buffer} key - * @param {Buffer} value - */ - -Batch.prototype.put = function put(key, value) { - if (!value) - value = LOW; - - this.batch.put(key, value); - - return this; -}; - -/** - * Delete a value from the batch. - * @param {String|Buffer} key - */ - -Batch.prototype.del = function del(key) { - this.batch.del(key); - return this; -}; - -/** - * Write batch to database. - * @returns {Promise} - */ - -Batch.prototype.write = function write() { - return new Promise((resolve, reject) => { - this.batch.write(wrap(resolve, reject)); - }); -}; - -/** - * Clear the batch. - */ - -Batch.prototype.clear = function clear() { - this.batch.clear(); - return this; -}; - -/** - * Iterator - * @constructor - * @ignore - * @param {LowlevelUp} db - * @param {Object} options - */ - -function Iterator(db, options) { - this.options = new IteratorOptions(options); - this.options.keyAsBuffer = db.options.bufferKeys; - - this.iter = db.binding.iterator(this.options); - this.leveldown = db.leveldown; - - this.cache = []; - this.finished = false; - - this.key = null; - this.value = null; - this.valid = true; -} - -/** - * Clean up iterator. - * @private - */ - -Iterator.prototype.cleanup = function cleanup() { - this.cache = []; - this.finished = true; - this.key = null; - this.value = null; - this.valid = false; -}; - -/** - * For each. - * @returns {Promise} - */ - -Iterator.prototype.each = async function each(cb) { - assert(this.valid); - - const {keys, values} = this.options; - - while (!this.finished) { - await this.read(); - - while (this.cache.length > 0) { - const key = this.cache.pop(); - const value = this.cache.pop(); - - let result = null; - - try { - if (keys && values) - result = cb(key, value); - else if (keys) - result = cb(key); - else if (values) - result = cb(value); - else - assert(false); - - if (result instanceof Promise) - result = await result; - } catch (e) { - await this.end(); - throw e; - } - - if (result === false) { - await this.end(); - break; - } - } - } -}; - -/** - * Seek to the next key. - * @returns {Promise} - */ - -Iterator.prototype.next = async function next() { - assert(this.valid); - - if (!this.finished) { - if (this.cache.length === 0) - await this.read(); - } - - if (this.cache.length > 0) { - this.key = this.cache.pop(); - this.value = this.cache.pop(); - return true; - } - - assert(this.finished); - - this.cleanup(); - - return false; -}; - -/** - * Seek to the next key (buffer values). - * @private - * @returns {Promise} - */ - -Iterator.prototype.read = function read() { - return new Promise((resolve, reject) => { - if (!this.leveldown) { - this.iter.next((err, key, value) => { - if (err) { - this.cleanup(); - this.iter.end(() => reject(err)); - return; - } - - if (key === undefined && value === undefined) { - this.cleanup(); - this.iter.end(wrap(resolve, reject)); - return; - } - - this.cache = [value, key]; - - resolve(); - }); - return; - } - - this.iter.next((err, cache, finished) => { - if (err) { - this.cleanup(); - this.iter.end(() => reject(err)); - return; - } - - this.cache = cache; - this.finished = finished; - - resolve(); - }); - }); -}; - -/** - * Seek to an arbitrary key. - * @param {String|Buffer} key - */ - -Iterator.prototype.seek = function seek(key) { - assert(this.valid); - this.iter.seek(key); -}; - -/** - * End the iterator. - * @returns {Promise} - */ - -Iterator.prototype.end = function end() { - return new Promise((resolve, reject) => { - this.cleanup(); - this.iter.end(wrap(resolve, reject)); - }); -}; - -/** - * Iterator Item - * @ignore - * @constructor - * @param {String|Buffer} key - * @param {String|Buffer} value - * @property {String|Buffer} key - * @property {String|Buffer} value - */ - -function IteratorItem(key, value) { - this.key = key; - this.value = value; -} - -/** - * LowlevelUp Options - * @constructor - * @ignore - * @param {Object} options - */ - -function LLUOptions(options) { - this.createIfMissing = true; - this.errorIfExists = false; - this.compression = true; - this.cacheSize = 8 << 20; - this.writeBufferSize = 4 << 20; - this.maxOpenFiles = 64; - this.maxFileSize = 2 << 20; - this.paranoidChecks = false; - this.memory = false; - this.sync = false; - this.mapSize = 256 * (1024 << 20); - this.writeMap = false; - this.noSubdir = true; - this.bufferKeys = true; - - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options. - * @private - * @param {Object} options - * @returns {LLUOptions} - */ - -LLUOptions.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Options are required.'); - - if (options.createIfMissing != null) { - assert(typeof options.createIfMissing === 'boolean', - '`createIfMissing` must be a boolean.'); - this.createIfMissing = options.createIfMissing; - } - - if (options.errorIfExists != null) { - assert(typeof options.errorIfExists === 'boolean', - '`errorIfExists` must be a boolean.'); - this.errorIfExists = options.errorIfExists; - } - - if (options.compression != null) { - assert(typeof options.compression === 'boolean', - '`compression` must be a boolean.'); - this.compression = options.compression; - } - - if (options.cacheSize != null) { - assert(typeof options.cacheSize === 'number', - '`cacheSize` must be a number.'); - assert(options.cacheSize >= 0); - this.cacheSize = Math.floor(options.cacheSize / 2); - this.writeBufferSize = Math.floor(options.cacheSize / 4); - } - - if (options.maxFiles != null) { - assert(typeof options.maxFiles === 'number', - '`maxFiles` must be a number.'); - assert(options.maxFiles >= 0); - this.maxOpenFiles = options.maxFiles; - } - - if (options.maxFileSize != null) { - assert(typeof options.maxFileSize === 'number', - '`maxFileSize` must be a number.'); - assert(options.maxFileSize >= 0); - this.maxFileSize = options.maxFileSize; - } - - if (options.paranoidChecks != null) { - assert(typeof options.paranoidChecks === 'boolean', - '`paranoidChecks` must be a boolean.'); - this.paranoidChecks = options.paranoidChecks; - } - - if (options.memory != null) { - assert(typeof options.memory === 'boolean', - '`memory` must be a boolean.'); - this.memory = options.memory; - } - - if (options.sync != null) { - assert(typeof options.sync === 'boolean', - '`sync` must be a boolean.'); - this.sync = options.sync; - } - - if (options.mapSize != null) { - assert(typeof options.mapSize === 'number', - '`mapSize` must be a number.'); - assert(options.mapSize >= 0); - this.mapSize = options.mapSize; - } - - if (options.writeMap != null) { - assert(typeof options.writeMap === 'boolean', - '`writeMap` must be a boolean.'); - this.writeMap = options.writeMap; - } - - if (options.noSubdir != null) { - assert(typeof options.noSubdir === 'boolean', - '`noSubdir` must be a boolean.'); - this.noSubdir = options.noSubdir; - } - - if (options.bufferKeys != null) { - assert(typeof options.bufferKeys === 'boolean', - '`bufferKeys` must be a boolean.'); - this.bufferKeys = options.bufferKeys; - } - - return this; -}; - -/** - * Iterator Options - * @constructor - * @ignore - * @param {Object} options - */ - -function IteratorOptions(options) { - this.gte = null; - this.lte = null; - this.gt = null; - this.lt = null; - this.keys = true; - this.values = false; - this.fillCache = false; - this.keyAsBuffer = true; - this.valueAsBuffer = true; - this.reverse = false; - this.highWaterMark = 16 * 1024; - - // Note: do not add this property. - // this.limit = null; - - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options. - * @private - * @param {Object} options - * @returns {IteratorOptions} - */ - -IteratorOptions.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Options are required.'); - - if (options.gte != null) { - assert(Buffer.isBuffer(options.gte) || typeof options.gte === 'string'); - this.gte = options.gte; - } - - if (options.lte != null) { - assert(Buffer.isBuffer(options.lte) || typeof options.lte === 'string'); - this.lte = options.lte; - } - - if (options.gt != null) { - assert(Buffer.isBuffer(options.gt) || typeof options.gt === 'string'); - this.gt = options.gt; - } - - if (options.lt != null) { - assert(Buffer.isBuffer(options.lt) || typeof options.lt === 'string'); - this.lt = options.lt; - } - - if (options.keys != null) { - assert(typeof options.keys === 'boolean'); - this.keys = options.keys; - } - - if (options.values != null) { - assert(typeof options.values === 'boolean'); - this.values = options.values; - } - - if (options.fillCache != null) { - assert(typeof options.fillCache === 'boolean'); - this.fillCache = options.fillCache; - } - - if (options.keyAsBuffer != null) { - assert(typeof options.keyAsBuffer === 'boolean'); - this.keyAsBuffer = options.keyAsBuffer; - } - - if (options.reverse != null) { - assert(typeof options.reverse === 'boolean'); - this.reverse = options.reverse; - } - - if (options.limit != null) { - assert(typeof options.limit === 'number'); - assert(options.limit >= 0); - this.limit = options.limit; - } - - if (!this.keys && !this.values) - throw new Error('Keys and/or values must be chosen.'); - - return this; -}; - -/* - * Helpers - */ - -function isNotFound(err) { - if (!err) - return false; - - return err.notFound - || err.type === 'NotFoundError' - || /not\s*found/i.test(err.message); -} - -function wrap(resolve, reject) { - return function(err, result) { - if (err) { - reject(err); - return; - } - resolve(result); - }; -} - -VERSION_ERROR = 'Warning:' - + ' Your database does not match the current database version.' - + ' This is likely because the database layout or serialization' - + ' format has changed drastically. If you want to dump your' - + ' data, downgrade to your previous version first. If you do' - + ' not think you should be seeing this error, post an issue on' - + ' the repo.'; - -/* - * Expose - */ - -module.exports = LowlevelUp; diff --git a/lib/db/memdb.js b/lib/db/memdb.js deleted file mode 100644 index 4fc4dc53b..000000000 --- a/lib/db/memdb.js +++ /dev/null @@ -1,666 +0,0 @@ -/*! - * memdb.js - in-memory database for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const RBT = require('../utils/rbt'); -const DUMMY = Buffer.alloc(0); - -/** - * In memory database for bcoin - * using a red-black tree backend. - * @alias module:db.MemDB - * @constructor - * @param {String?} location - Phony location. - * @param {Object?} options - * @param {Function} options.compare - Comparator. - */ - -function MemDB(location) { - if (!(this instanceof MemDB)) - return new MemDB(location); - - this.location = location || 'memory'; - this.options = {}; - this.tree = new RBT(cmp, true); -} - -/** - * Do a key lookup. - * @private - * @param {Buffer|String} key - * @returns {Buffer?} value - */ - -MemDB.prototype.search = function search(key) { - if (typeof key === 'string') - key = Buffer.from(key, 'utf8'); - - assert(Buffer.isBuffer(key), 'Key must be a Buffer.'); - - const node = this.tree.search(key); - - if (!node) - return undefined; - - return node.value; -}; - -/** - * Insert a record. - * @private - * @param {Buffer|String} key - * @param {Buffer} value - */ - -MemDB.prototype.insert = function insert(key, value) { - if (typeof key === 'string') - key = Buffer.from(key, 'utf8'); - - if (typeof value === 'string') - value = Buffer.from(value, 'utf8'); - - if (value == null) - value = DUMMY; - - assert(Buffer.isBuffer(key), 'Key must be a Buffer.'); - assert(Buffer.isBuffer(value), 'Value must be a Buffer.'); - - return this.tree.insert(key, value) != null; -}; - -/** - * Remove a record. - * @private - * @param {Buffer|String} key - * @returns {Boolean} - */ - -MemDB.prototype.remove = function remove(key) { - if (typeof key === 'string') - key = Buffer.from(key, 'utf8'); - - assert(Buffer.isBuffer(key), 'Key must be a Buffer.'); - - return this.tree.remove(key) != null; -}; - -/** - * Traverse between a range of keys and collect records. - * @private - * @param {Buffer} min - * @param {Buffer} max - * @returns {RBTData[]} Records. - */ - -MemDB.prototype.range = function range(min, max) { - if (typeof min === 'string') - min = Buffer.from(min, 'utf8'); - - if (typeof max === 'string') - max = Buffer.from(max, 'utf8'); - - assert(!min || Buffer.isBuffer(min), 'Key must be a Buffer.'); - assert(!max || Buffer.isBuffer(max), 'Key must be a Buffer.'); - - return this.tree.range(min, max); -}; - -/** - * Open the database (leveldown method). - * @param {Object?} options - * @param {Function} callback - */ - -MemDB.prototype.open = function open(options, callback) { - if (!callback) { - callback = options; - options = null; - } - - if (!options) - options = {}; - - this.options = options; - - setImmediate(callback); -}; - -/** - * Close the database (leveldown method). - * @param {Function} callback - */ - -MemDB.prototype.close = function close(callback) { - setImmediate(callback); -}; - -/** - * Retrieve a record (leveldown method). - * @param {Buffer|String} key - * @param {Object?} options - * @param {Function} callback - Returns Buffer. - */ - -MemDB.prototype.get = function get(key, options, callback) { - if (!callback) { - callback = options; - options = null; - } - - if (!options) - options = {}; - - let value = this.search(key); - - if (!value) { - const err = new Error('MEMDB_NOTFOUND: Key not found.'); - err.notFound = true; - err.type = 'NotFoundError'; - setImmediate(() => callback(err)); - return; - } - - if (options.asBuffer === false) - value = value.toString('utf8'); - - setImmediate(() => callback(null, value)); -}; - -/** - * Insert a record (leveldown method). - * @param {Buffer|String} key - * @param {Buffer} value - * @param {Object?} options - * @param {Function} callback - */ - -MemDB.prototype.put = function put(key, value, options, callback) { - if (!callback) { - callback = options; - options = null; - } - - this.insert(key, value); - - setImmediate(callback); -}; - -/** - * Remove a record (leveldown method). - * @param {Buffer|String} key - * @param {Object?} options - * @param {Function} callback - */ - -MemDB.prototype.del = function del(key, options, callback) { - if (!callback) { - callback = options; - options = null; - } - - this.remove(key); - - setImmediate(callback); -}; - -/** - * Create an atomic batch (leveldown method). - * @see Leveldown.Batch - * @param {Object[]?} ops - * @param {Object?} options - * @param {Function} callback - */ - -MemDB.prototype.batch = function batch(ops, options, callback) { - if (!callback) { - callback = options; - options = null; - } - - const b = new Batch(this, options); - - if (ops) { - b.ops = ops; - b.write(callback); - return undefined; - } - - return b; -}; - -/** - * Create an iterator (leveldown method). - * @param {Object} options - See {Leveldown.Iterator}. - * @returns {Leveldown.Iterator}. - */ - -MemDB.prototype.iterator = function iterator(options) { - return new Iterator(this, options); -}; - -/** - * Get a database property (leveldown method) (NOP). - * @param {String} name - Property name. - * @returns {String} - */ - -MemDB.prototype.getProperty = function getProperty(name) { - return ''; -}; - -/** - * Calculate approximate database size (leveldown method). - * @param {Buffer|String} start - Start key. - * @param {Buffer|String} end - End key. - * @param {Function} callback - Returns Number. - */ - -MemDB.prototype.approximateSize = function approximateSize(start, end, callback) { - const items = this.range(start, end); - let size = 0; - - for (const item of items) { - size += item.key.length; - size += item.value.length; - } - - setImmediate(() => callback(null, size)); -}; - -/** - * Destroy the database (leveldown function) (NOP). - * @param {String} location - * @param {Function} callback - */ - -MemDB.destroy = function destroy(location, callback) { - setImmediate(callback); -}; - -/** - * Repair the database (leveldown function) (NOP). - * @param {String} location - * @param {Function} callback - */ - -MemDB.repair = function repair(location, callback) { - setImmediate(callback); -}; - -/** - * Batch - * @constructor - * @ignore - * @private - * @param {MemDB} db - * @param {Object?} options - */ - -function Batch(db, options) { - this.options = options || {}; - this.ops = []; - this.db = db; - this.written = false; -} - -/** - * Insert a record. - * @param {Buffer|String} key - * @param {Buffer} value - */ - -Batch.prototype.put = function put(key, value) { - assert(!this.written, 'Already written.'); - this.ops.push(new BatchOp('put', key, value)); - return this; -}; - -/** - * Remove a record. - * @param {Buffer|String} key - */ - -Batch.prototype.del = function del(key) { - assert(!this.written, 'Already written.'); - this.ops.push(new BatchOp('del', key)); - return this; -}; - -/** - * Commit the batch. - * @param {Function} callback - */ - -Batch.prototype.write = function write(callback) { - if (this.written) { - setImmediate(() => callback(new Error('Already written.'))); - return this; - } - - for (const op of this.ops) { - switch (op.type) { - case 'put': - this.db.insert(op.key, op.value); - break; - case 'del': - this.db.remove(op.key); - break; - default: - setImmediate(() => callback(new Error('Bad op.'))); - return this; - } - } - - this.ops = []; - this.written = true; - - setImmediate(callback); - - return this; -}; - -/** - * Clear batch of all ops. - */ - -Batch.prototype.clear = function clear() { - assert(!this.written, 'Already written.'); - this.ops = []; - return this; -}; - -/** - * Batch Operation - * @constructor - * @ignore - * @private - * @param {String} type - * @param {Buffer} key - * @param {Buffer|null} value - */ - -function BatchOp(type, key, value) { - this.type = type; - this.key = key; - this.value = value; -} - -/** - * Iterator - * @constructor - * @ignore - * @private - * @param {RBT} db - * @param {Object?} options - */ - -function Iterator(db, options) { - this.db = db; - this.options = new IteratorOptions(options); - this.iter = null; - this.ended = false; - this.total = 0; - this.init(); -} - -/** - * Initialize the iterator. - */ - -Iterator.prototype.init = function init() { - const snapshot = this.db.tree.snapshot(); - const iter = this.db.tree.iterator(snapshot); - - if (this.options.reverse) { - if (this.options.end) { - iter.seekMax(this.options.end); - if (this.options.lt && iter.valid()) { - if (iter.compare(this.options.end) === 0) - iter.prev(); - } - } else { - iter.seekLast(); - } - } else { - if (this.options.start) { - iter.seekMin(this.options.start); - if (this.options.gt && iter.valid()) { - if (iter.compare(this.options.start) === 0) - iter.next(); - } - } else { - iter.seekFirst(); - } - } - - this.iter = iter; -}; - -/** - * Seek to the next key. - * @param {Function} callback - */ - -Iterator.prototype.next = function next(callback) { - const options = this.options; - const iter = this.iter; - - if (!this.iter) { - setImmediate(() => callback(new Error('Cannot call next.'))); - return; - } - - let result; - if (options.reverse) { - result = iter.prev(); - - // Stop once we hit a key below our gte key. - if (result && options.start) { - if (options.gt) { - if (iter.compare(options.start) <= 0) - result = false; - } else { - if (iter.compare(options.start) < 0) - result = false; - } - } - } else { - result = iter.next(); - - // Stop once we hit a key above our lte key. - if (result && options.end) { - if (options.lt) { - if (iter.compare(options.end) >= 0) - result = false; - } else { - if (iter.compare(options.end) > 0) - result = false; - } - } - } - - if (!result) { - this.iter = null; - setImmediate(callback); - return; - } - - if (options.limit !== -1) { - if (this.total >= options.limit) { - this.iter = null; - setImmediate(callback); - return; - } - this.total += 1; - } - - let key = iter.key; - let value = iter.value; - - if (!options.keys) - key = DUMMY; - - if (!options.values) - value = DUMMY; - - if (!options.keyAsBuffer) - key = key.toString('utf8'); - - if (!options.valueAsBuffer) - value = value.toString('utf8'); - - setImmediate(() => callback(null, key, value)); -}; - -/** - * Seek to a key gte to `key`. - * @param {String|Buffer} key - */ - -Iterator.prototype.seek = function seek(key) { - assert(this.iter, 'Already ended.'); - - if (typeof key === 'string') - key = Buffer.from(key, 'utf8'); - - assert(Buffer.isBuffer(key), 'Key must be a Buffer.'); - - if (this.options.reverse) - this.iter.seekMax(key); - else - this.iter.seekMin(key); -}; - -/** - * End the iterator. Free up snapshot. - * @param {Function} callback - */ - -Iterator.prototype.end = function end(callback) { - if (this.ended) { - setImmediate(() => callback(new Error('Already ended.'))); - return; - } - - this.ended = true; - this.iter = null; - - setImmediate(callback); -}; - -/** - * Iterator Options - * @constructor - * @ignore - * @param {Object} options - */ - -function IteratorOptions(options) { - this.keys = true; - this.values = true; - this.start = null; - this.end = null; - this.gt = false; - this.lt = false; - this.keyAsBuffer = true; - this.valueAsBuffer = true; - this.reverse = false; - this.limit = -1; - - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options. - * @private - * @param {Object} options - * @returns {IteratorOptions} - */ - -IteratorOptions.prototype.fromOptions = function fromOptions(options) { - if (options.keys != null) { - assert(typeof options.keys === 'boolean'); - this.keys = options.keys; - } - - if (options.values != null) { - assert(typeof options.values === 'boolean'); - this.values = options.values; - } - - if (options.start != null) - this.start = options.start; - - if (options.end != null) - this.end = options.end; - - if (options.gte != null) - this.start = options.gte; - - if (options.lte != null) - this.end = options.lte; - - if (options.gt != null) { - this.gt = true; - this.start = options.gt; - } - - if (options.lt != null) { - this.lt = true; - this.end = options.lt; - } - - if (this.start != null) { - if (typeof this.start === 'string') - this.start = Buffer.from(this.start, 'utf8'); - assert(Buffer.isBuffer(this.start), '`start` must be a Buffer.'); - } - - if (this.end != null) { - if (typeof this.end === 'string') - this.end = Buffer.from(this.end, 'utf8'); - assert(Buffer.isBuffer(this.end), '`end` must be a Buffer.'); - } - - if (options.keyAsBuffer != null) { - assert(typeof options.keyAsBuffer === 'boolean'); - this.keyAsBuffer = options.keyAsBuffer; - } - - if (options.valueAsBuffer != null) { - assert(typeof options.valueAsBuffer === 'boolean'); - this.valueAsBuffer = options.valueAsBuffer; - } - - if (options.reverse != null) { - assert(typeof options.reverse === 'boolean'); - this.reverse = options.reverse; - } - - if (options.limit != null) { - assert(typeof options.limit === 'number'); - this.limit = options.limit; - } - - return this; -}; - -/* - * Helpers - */ - -function cmp(a, b) { - return a.compare(b); -} - -/* - * Expose - */ - -module.exports = MemDB; diff --git a/lib/hd/common.js b/lib/hd/common.js index 659a6f347..6541e1148 100644 --- a/lib/hd/common.js +++ b/lib/hd/common.js @@ -6,8 +6,8 @@ 'use strict'; -const assert = require('assert'); -const LRU = require('../utils/lru'); +const assert = require('bsert'); +const LRU = require('blru'); const common = exports; /** @@ -127,3 +127,11 @@ common.isAccount = function isAccount(key, account) { } return key.depth === 3 && (key.childIndex & common.HARDENED) !== 0; }; + +/** + * A compressed pubkey of all zeroes. + * @const {Buffer} + * @default + */ + +common.ZERO_KEY = Buffer.alloc(33, 0x00); diff --git a/lib/hd/hd.js b/lib/hd/hd.js index 68c953925..58643f0ec 100644 --- a/lib/hd/hd.js +++ b/lib/hd/hd.js @@ -6,7 +6,7 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const common = require('./common'); const Mnemonic = require('./mnemonic'); const HDPrivateKey = require('./private'); @@ -37,35 +37,32 @@ HD.fromBase58 = function fromBase58(xkey, network) { * @param {Object} options * @param {Buffer?} options.privateKey * @param {Buffer?} options.entropy - * @param {String?} network * @returns {HDPrivateKey} */ -HD.generate = function generate(network) { - return HDPrivateKey.generate(network); +HD.generate = function generate() { + return HDPrivateKey.generate(); }; /** * Generate an {@link HDPrivateKey} from a seed. * @param {Object|Mnemonic|Buffer} options - seed, * mnemonic, mnemonic options. - * @param {String?} network * @returns {HDPrivateKey} */ -HD.fromSeed = function fromSeed(options, network) { - return HDPrivateKey.fromSeed(options, network); +HD.fromSeed = function fromSeed(options) { + return HDPrivateKey.fromSeed(options); }; /** * Instantiate an hd private key from a mnemonic. * @param {Mnemonic|Object} mnemonic - * @param {String?} network * @returns {HDPrivateKey} */ -HD.fromMnemonic = function fromMnemonic(options, network) { - return HDPrivateKey.fromMnemonic(options, network); +HD.fromMnemonic = function fromMnemonic(options) { + return HDPrivateKey.fromMnemonic(options); }; /** @@ -115,7 +112,7 @@ HD.from = function from(options, network) { return HD.fromRaw(options, network); if (options && typeof options === 'object') - return HD.fromMnemonic(options, network); + return HD.fromMnemonic(options); throw new Error('Cannot create HD key from bad options.'); }; @@ -184,4 +181,6 @@ HD.HD = HD; HD.Mnemonic = Mnemonic; HD.PrivateKey = HDPrivateKey; HD.PublicKey = HDPublicKey; +HD.HDPrivateKey = HDPrivateKey; +HD.HDPublicKey = HDPublicKey; HD.wordlist = wordlist; diff --git a/lib/hd/mnemonic.js b/lib/hd/mnemonic.js index 49101e919..d5932aabb 100644 --- a/lib/hd/mnemonic.js +++ b/lib/hd/mnemonic.js @@ -6,18 +6,17 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); -const digest = require('../crypto/digest'); -const cleanse = require('../crypto/cleanse'); -const random = require('../crypto/random'); -const pbkdf2 = require('../crypto/pbkdf2'); -const StaticWriter = require('../utils/staticwriter'); -const BufferReader = require('../utils/reader'); -const encoding = require('../utils/encoding'); +const assert = require('bsert'); +const bio = require('bufio'); +const sha256 = require('bcrypto/lib/sha256'); +const cleanse = require('bcrypto/lib/cleanse'); +const random = require('bcrypto/lib/random'); +const pbkdf2 = require('bcrypto/lib/pbkdf2'); +const sha512 = require('bcrypto/lib/sha512'); const wordlist = require('./wordlist'); const common = require('./common'); -const nfkd = require('../utils/nfkd'); +const nfkd = require('./nfkd'); +const {inspectSymbol} = require('../utils'); /* * Constants @@ -28,549 +27,540 @@ const wordlistCache = Object.create(null); /** * HD Mnemonic * @alias module:hd.Mnemonic - * @constructor - * @param {Object} options - * @param {Number?} options.bit - Bits of entropy (Must - * be a multiple of 8) (default=128). - * @param {Buffer?} options.entropy - Entropy bytes. Will - * be generated with `options.bits` bits of entropy - * if not present. - * @param {String?} options.phrase - Mnemonic phrase (will - * be generated if not present). - * @param {String?} options.passphrase - Optional salt for - * key stretching (empty string if not present). - * @param {String?} options.language - Language. */ -function Mnemonic(options) { - if (!(this instanceof Mnemonic)) - return new Mnemonic(options); - - this.bits = common.MIN_ENTROPY; - this.language = 'english'; - this.entropy = null; - this.phrase = null; - this.passphrase = ''; +class Mnemonic { + /** + * Create a mnemonic. + * @constructor + * @param {Object} options + * @param {Number?} options.bit - Bits of entropy (Must + * be a multiple of 8) (default=128). + * @param {Buffer?} options.entropy - Entropy bytes. Will + * be generated with `options.bits` bits of entropy + * if not present. + * @param {String?} options.phrase - Mnemonic phrase (will + * be generated if not present). + * @param {String?} options.language - Language. + */ + + constructor(options) { + this.bits = common.MIN_ENTROPY; + this.language = 'english'; + this.entropy = null; + this.phrase = null; - if (options) - this.fromOptions(options); -} + if (options) + this.fromOptions(options); + } -/** - * List of languages. - * @const {String[]} - * @default - */ + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + if (typeof options === 'string') + options = { phrase: options }; + + if (options.bits != null) { + assert((options.bits & 0xffff) === options.bits); + assert(options.bits >= common.MIN_ENTROPY); + assert(options.bits <= common.MAX_ENTROPY); + assert(options.bits % 32 === 0); + this.bits = options.bits; + } -Mnemonic.languages = [ - 'simplified chinese', - 'traditional chinese', - 'english', - 'french', - 'italian', - 'japanese', - 'spanish' -]; + if (options.language) { + assert(typeof options.language === 'string'); + assert(Mnemonic.languages.indexOf(options.language) !== -1); + this.language = options.language; + } -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ + if (options.phrase) { + this.fromPhrase(options.phrase); + return this; + } -Mnemonic.prototype.fromOptions = function fromOptions(options) { - if (typeof options === 'string') - options = { phrase: options }; + if (options.entropy) { + this.fromEntropy(options.entropy); + return this; + } - if (options.bits != null) { - assert(util.isU16(options.bits)); - assert(options.bits >= common.MIN_ENTROPY); - assert(options.bits <= common.MAX_ENTROPY); - assert(options.bits % 32 === 0); - this.bits = options.bits; + return this; } - if (options.language) { - assert(typeof options.language === 'string'); - assert(Mnemonic.languages.indexOf(options.language) !== -1); - this.language = options.language; - } + /** + * Instantiate mnemonic from options. + * @param {Object} options + * @returns {Mnemonic} + */ - if (options.passphrase) { - assert(typeof options.passphrase === 'string'); - this.passphrase = options.passphrase; + static fromOptions(options) { + return new this().fromOptions(options); } - if (options.phrase) { - this.fromPhrase(options.phrase); - return this; - } + /** + * Destroy the mnemonic (zeroes entropy). + */ - if (options.entropy) { - this.fromEntropy(options.entropy); - return this; + destroy() { + this.bits = common.MIN_ENTROPY; + this.language = 'english'; + if (this.entropy) { + cleanse(this.entropy); + this.entropy = null; + } + this.phrase = null; } - return this; -}; + /** + * Generate the seed. + * @param {String?} passphrase + * @returns {Buffer} pbkdf2 seed. + */ -/** - * Instantiate mnemonic from options. - * @param {Object} options - * @returns {Mnemonic} - */ + toSeed(passphrase) { + if (!passphrase) + passphrase = ''; -Mnemonic.fromOptions = function fromOptions(options) { - return new Mnemonic().fromOptions(options); -}; + const phrase = nfkd(this.getPhrase()); + const passwd = nfkd(`mnemonic${passphrase}`); -/** - * Destroy the mnemonic (zeroes entropy). - */ - -Mnemonic.prototype.destroy = function destroy() { - this.bits = common.MIN_ENTROPY; - this.language = 'english'; - if (this.entropy) { - cleanse(this.entropy); - this.entropy = null; + return pbkdf2.derive(sha512, + Buffer.from(phrase, 'utf8'), + Buffer.from(passwd, 'utf8'), + 2048, 64); } - this.phrase = null; - this.passphrase = ''; -}; - -/** - * Generate the seed. - * @param {String?} passphrase - * @returns {Buffer} pbkdf2 seed. - */ -Mnemonic.prototype.toSeed = function toSeed(passphrase) { - if (!passphrase) - passphrase = this.passphrase; + /** + * Get or generate entropy. + * @returns {Buffer} + */ - this.passphrase = passphrase; + getEntropy() { + if (!this.entropy) + this.entropy = random.randomBytes(this.bits / 8); - const phrase = nfkd(this.getPhrase()); - const passwd = nfkd('mnemonic' + passphrase); - - return pbkdf2.derive( - Buffer.from(phrase, 'utf8'), - Buffer.from(passwd, 'utf8'), - 2048, 64, 'sha512'); -}; - -/** - * Get or generate entropy. - * @returns {Buffer} - */ + assert(this.bits / 8 === this.entropy.length); -Mnemonic.prototype.getEntropy = function getEntropy() { - if (!this.entropy) - this.entropy = random.randomBytes(this.bits / 8); - - assert(this.bits / 8 === this.entropy.length); - - return this.entropy; -}; - -/** - * Generate a mnemonic phrase from chosen language. - * @returns {String} - */ - -Mnemonic.prototype.getPhrase = function getPhrase() { - if (this.phrase) - return this.phrase; - - // Include the first `ENT / 32` bits - // of the hash (the checksum). - const wbits = this.bits + (this.bits / 32); - - // Get entropy and checksum. - const entropy = this.getEntropy(); - const chk = digest.sha256(entropy); - - // Append the hash to the entropy to - // make things easy when grabbing - // the checksum bits. - const size = Math.ceil(wbits / 8); - const data = Buffer.allocUnsafe(size); - entropy.copy(data, 0); - chk.copy(data, entropy.length); - - // Build the mnemonic by reading - // 11 bit indexes from the entropy. - const list = Mnemonic.getWordlist(this.language); - - let phrase = []; - for (let i = 0; i < wbits / 11; i++) { - let index = 0; - for (let j = 0; j < 11; j++) { - const pos = i * 11 + j; - const bit = pos % 8; - const oct = (pos - bit) / 8; - index <<= 1; - index |= (data[oct] >>> (7 - bit)) & 1; - } - phrase.push(list.words[index]); + return this.entropy; } - // Japanese likes double-width spaces. - if (this.language === 'japanese') - phrase = phrase.join('\u3000'); - else - phrase = phrase.join(' '); - - this.phrase = phrase; + /** + * Generate a mnemonic phrase from chosen language. + * @returns {String} + */ + + getPhrase() { + if (this.phrase) + return this.phrase; + + // Include the first `ENT / 32` bits + // of the hash (the checksum). + const wbits = this.bits + (this.bits / 32); + + // Get entropy and checksum. + const entropy = this.getEntropy(); + const chk = sha256.digest(entropy); + + // Append the hash to the entropy to + // make things easy when grabbing + // the checksum bits. + const size = Math.ceil(wbits / 8); + const data = Buffer.allocUnsafe(size); + entropy.copy(data, 0); + chk.copy(data, entropy.length); + + // Build the mnemonic by reading + // 11 bit indexes from the entropy. + const list = Mnemonic.getWordlist(this.language); + + let phrase = []; + for (let i = 0; i < wbits / 11; i++) { + let index = 0; + for (let j = 0; j < 11; j++) { + const pos = i * 11 + j; + const bit = pos % 8; + const oct = (pos - bit) / 8; + index <<= 1; + index |= (data[oct] >>> (7 - bit)) & 1; + } + phrase.push(list.words[index]); + } - return phrase; -}; + // Japanese likes double-width spaces. + if (this.language === 'japanese') + phrase = phrase.join('\u3000'); + else + phrase = phrase.join(' '); -/** - * Inject properties from phrase. - * @private - * @param {String} phrase - */ + this.phrase = phrase; -Mnemonic.prototype.fromPhrase = function fromPhrase(phrase) { - assert(typeof phrase === 'string'); - assert(phrase.length <= 1000); + return phrase; + } - const words = phrase.trim().split(/[\s\u3000]+/); - const wbits = words.length * 11; - const cbits = wbits % 32; + /** + * Inject properties from phrase. + * @private + * @param {String} phrase + */ - assert(cbits !== 0, 'Invalid checksum.'); + fromPhrase(phrase) { + assert(typeof phrase === 'string'); + assert(phrase.length <= 1000); - const bits = wbits - cbits; + const words = phrase.trim().split(/[\s\u3000]+/); + const wbits = words.length * 11; + const cbits = wbits % 32; - assert(bits >= common.MIN_ENTROPY); - assert(bits <= common.MAX_ENTROPY); - assert(bits % 32 === 0); + assert(cbits !== 0, 'Invalid checksum.'); - const size = Math.ceil(wbits / 8); - const data = Buffer.allocUnsafe(size); - data.fill(0); + const bits = wbits - cbits; - const lang = Mnemonic.getLanguage(words[0]); - const list = Mnemonic.getWordlist(lang); + assert(bits >= common.MIN_ENTROPY); + assert(bits <= common.MAX_ENTROPY); + assert(bits % 32 === 0); - // Rebuild entropy bytes. - for (let i = 0; i < words.length; i++) { - const word = words[i]; - const index = list.map[word]; + const size = Math.ceil(wbits / 8); + const data = Buffer.allocUnsafe(size); + data.fill(0); - if (index == null) - throw new Error('Could not find word.'); + const lang = Mnemonic.getLanguage(words[0]); + const list = Mnemonic.getWordlist(lang); - for (let j = 0; j < 11; j++) { - const pos = i * 11 + j; - const bit = pos % 8; - const oct = (pos - bit) / 8; - const val = (index >>> (10 - j)) & 1; - data[oct] |= val << (7 - bit); + // Rebuild entropy bytes. + for (let i = 0; i < words.length; i++) { + const word = words[i]; + const index = list.map[word]; + + if (index == null) + throw new Error('Could not find word.'); + + for (let j = 0; j < 11; j++) { + const pos = i * 11 + j; + const bit = pos % 8; + const oct = (pos - bit) / 8; + const val = (index >>> (10 - j)) & 1; + data[oct] |= val << (7 - bit); + } } - } - const cbytes = Math.ceil(cbits / 8); - const entropy = data.slice(0, data.length - cbytes); - const chk1 = data.slice(data.length - cbytes); - const chk2 = digest.sha256(entropy); - - // Verify checksum. - for (let i = 0; i < cbits; i++) { - const bit = i % 8; - const oct = (i - bit) / 8; - const b1 = (chk1[oct] >>> (7 - bit)) & 1; - const b2 = (chk2[oct] >>> (7 - bit)) & 1; - if (b1 !== b2) - throw new Error('Invalid checksum.'); - } + const cbytes = Math.ceil(cbits / 8); + const entropy = data.slice(0, data.length - cbytes); + const chk1 = data.slice(data.length - cbytes); + const chk2 = sha256.digest(entropy); + + // Verify checksum. + for (let i = 0; i < cbits; i++) { + const bit = i % 8; + const oct = (i - bit) / 8; + const b1 = (chk1[oct] >>> (7 - bit)) & 1; + const b2 = (chk2[oct] >>> (7 - bit)) & 1; + if (b1 !== b2) + throw new Error('Invalid checksum.'); + } - assert(bits / 8 === entropy.length); + assert(bits / 8 === entropy.length); - this.bits = bits; - this.language = lang; - this.entropy = entropy; - this.phrase = phrase; + this.bits = bits; + this.language = lang; + this.entropy = entropy; + this.phrase = phrase; - return this; -}; + return this; + } -/** - * Instantiate mnemonic from a phrase (validates checksum). - * @param {String} phrase - * @returns {Mnemonic} - * @throws on bad checksum - */ + /** + * Instantiate mnemonic from a phrase (validates checksum). + * @param {String} phrase + * @returns {Mnemonic} + * @throws on bad checksum + */ -Mnemonic.fromPhrase = function fromPhrase(phrase) { - return new Mnemonic().fromPhrase(phrase); -}; + static fromPhrase(phrase) { + return new this().fromPhrase(phrase); + } -/** - * Inject properties from entropy. - * @private - * @param {Buffer} entropy - * @param {String?} lang - */ + /** + * Inject properties from entropy. + * @private + * @param {Buffer} entropy + * @param {String?} lang + */ -Mnemonic.prototype.fromEntropy = function fromEntropy(entropy, lang) { - assert(Buffer.isBuffer(entropy)); - assert(entropy.length * 8 >= common.MIN_ENTROPY); - assert(entropy.length * 8 <= common.MAX_ENTROPY); - assert((entropy.length * 8) % 32 === 0); - assert(!lang || Mnemonic.languages.indexOf(lang) !== -1); + fromEntropy(entropy, lang) { + assert(Buffer.isBuffer(entropy)); + assert(entropy.length * 8 >= common.MIN_ENTROPY); + assert(entropy.length * 8 <= common.MAX_ENTROPY); + assert((entropy.length * 8) % 32 === 0); + assert(!lang || Mnemonic.languages.indexOf(lang) !== -1); - this.entropy = entropy; - this.bits = entropy.length * 8; + this.entropy = entropy; + this.bits = entropy.length * 8; - if (lang) - this.language = lang; + if (lang) + this.language = lang; - return this; -}; + return this; + } -/** - * Instantiate mnemonic from entropy. - * @param {Buffer} entropy - * @param {String?} lang - * @returns {Mnemonic} - */ + /** + * Instantiate mnemonic from entropy. + * @param {Buffer} entropy + * @param {String?} lang + * @returns {Mnemonic} + */ -Mnemonic.fromEntropy = function fromEntropy(entropy, lang) { - return new Mnemonic().fromEntropy(entropy, lang); -}; + static fromEntropy(entropy, lang) { + return new this().fromEntropy(entropy, lang); + } -/** - * Determine a single word's language. - * @param {String} word - * @returns {String} Language. - * @throws on not found. - */ + /** + * Determine a single word's language. + * @param {String} word + * @returns {String} Language. + * @throws on not found. + */ + + static getLanguage(word) { + for (const lang of Mnemonic.languages) { + const list = Mnemonic.getWordlist(lang); + if (list.map[word] != null) + return lang; + } -Mnemonic.getLanguage = function getLanguage(word) { - for (const lang of Mnemonic.languages) { - const list = Mnemonic.getWordlist(lang); - if (list.map[word] != null) - return lang; + throw new Error('Could not determine language.'); } - throw new Error('Could not determine language.'); -}; + /** + * Retrieve the wordlist for a language. + * @param {String} lang + * @returns {Object} + */ -/** - * Retrieve the wordlist for a language. - * @param {String} lang - * @returns {Object} - */ + static getWordlist(lang) { + const cache = wordlistCache[lang]; -Mnemonic.getWordlist = function getWordlist(lang) { - const cache = wordlistCache[lang]; + if (cache) + return cache; - if (cache) - return cache; + const words = wordlist.get(lang); + const list = new WordList(words); - const words = wordlist.get(lang); - const list = new WordList(words); + wordlistCache[lang] = list; - wordlistCache[lang] = list; + return list; + } - return list; -}; + /** + * Convert mnemonic to a json-friendly object. + * @returns {Object} + */ + + toJSON() { + return { + bits: this.bits, + language: this.language, + entropy: this.getEntropy().toString('hex'), + phrase: this.getPhrase() + }; + } -/** - * Convert mnemonic to a json-friendly object. - * @returns {Object} - */ + /** + * Inject properties from json object. + * @private + * @param {Object} json + */ + + fromJSON(json) { + assert(json); + assert((json.bits & 0xffff) === json.bits); + assert(typeof json.language === 'string'); + assert(typeof json.entropy === 'string'); + assert(typeof json.phrase === 'string'); + assert(json.bits >= common.MIN_ENTROPY); + assert(json.bits <= common.MAX_ENTROPY); + assert(json.bits % 32 === 0); + assert(json.bits / 8 === json.entropy.length / 2); + + this.bits = json.bits; + this.language = json.language; + this.entropy = Buffer.from(json.entropy, 'hex'); + this.phrase = json.phrase; -Mnemonic.prototype.toJSON = function toJSON() { - return { - bits: this.bits, - language: this.language, - entropy: this.getEntropy().toString('hex'), - phrase: this.getPhrase(), - passphrase: this.passphrase - }; -}; + return this; + } -/** - * Inject properties from json object. - * @private - * @param {Object} json - */ + /** + * Instantiate mnemonic from json object. + * @param {Object} json + * @returns {Mnemonic} + */ -Mnemonic.prototype.fromJSON = function fromJSON(json) { - assert(util.isU16(json.bits)); - assert(typeof json.language === 'string'); - assert(typeof json.entropy === 'string'); - assert(typeof json.phrase === 'string'); - assert(typeof json.passphrase === 'string'); - assert(json.bits >= common.MIN_ENTROPY); - assert(json.bits <= common.MAX_ENTROPY); - assert(json.bits % 32 === 0); - assert(json.bits / 8 === json.entropy.length / 2); - - this.bits = json.bits; - this.language = json.language; - this.entropy = Buffer.from(json.entropy, 'hex'); - this.phrase = json.phrase; - this.passphrase = json.passphrase; - - return this; -}; + static fromJSON(json) { + return new this().fromJSON(json); + } -/** - * Instantiate mnemonic from json object. - * @param {Object} json - * @returns {Mnemonic} - */ + /** + * Calculate serialization size. + * @returns {Number} + */ -Mnemonic.fromJSON = function fromJSON(json) { - return new Mnemonic().fromJSON(json); -}; + getSize() { + let size = 0; + size += 3; + size += this.getEntropy().length; + return size; + } -/** - * Calculate serialization size. - * @returns {Number} - */ + /** + * Write the mnemonic to a buffer writer. + * @params {BufferWriter} bw + */ -Mnemonic.prototype.getSize = function getSize() { - let size = 0; - size += 3; - size += this.getEntropy().length; - size += encoding.sizeVarString(this.getPhrase(), 'utf8'); - size += encoding.sizeVarString(this.passphrase, 'utf8'); - return size; -}; + toWriter(bw) { + const lang = Mnemonic.languages.indexOf(this.language); -/** - * Write the mnemonic to a buffer writer. - * @params {BufferWriter} bw - */ + assert(lang !== -1); -Mnemonic.prototype.toWriter = function toWriter(bw) { - const lang = Mnemonic.languages.indexOf(this.language); + bw.writeU16(this.bits); + bw.writeU8(lang); + bw.writeBytes(this.getEntropy()); - assert(lang !== -1); + return bw; + } - bw.writeU16(this.bits); - bw.writeU8(lang); - bw.writeBytes(this.getEntropy()); - bw.writeVarString(this.getPhrase(), 'utf8'); - bw.writeVarString(this.passphrase, 'utf8'); + /** + * Serialize mnemonic. + * @returns {Buffer} + */ - return bw; -}; + toRaw(writer) { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } -/** - * Serialize mnemonic. - * @returns {Buffer} - */ + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -Mnemonic.prototype.toRaw = function toRaw(writer) { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + fromReader(br) { + const bits = br.readU16(); -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + assert(bits >= common.MIN_ENTROPY); + assert(bits <= common.MAX_ENTROPY); + assert(bits % 32 === 0); -Mnemonic.prototype.fromReader = function fromReader(br) { - const bits = br.readU16(); + const language = Mnemonic.languages[br.readU8()]; + assert(language); - assert(bits >= common.MIN_ENTROPY); - assert(bits <= common.MAX_ENTROPY); - assert(bits % 32 === 0); + this.bits = bits; + this.language = language; + this.entropy = br.readBytes(bits / 8); - const language = Mnemonic.languages[br.readU8()]; - assert(language); + return this; + } - this.bits = bits; - this.language = language; - this.entropy = br.readBytes(bits / 8); - this.phrase = br.readVarString('utf8'); - this.passphrase = br.readVarString('utf8'); + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ - return this; -}; + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + /** + * Instantiate mnemonic from buffer reader. + * @param {BufferReader} br + * @returns {Mnemonic} + */ -Mnemonic.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + static fromReader(br) { + return new this().fromReader(br); + } -/** - * Instantiate mnemonic from buffer reader. - * @param {BufferReader} br - * @returns {Mnemonic} - */ + /** + * Instantiate mnemonic from serialized data. + * @param {Buffer} data + * @returns {Mnemonic} + */ -Mnemonic.fromReader = function fromReader(br) { - return new Mnemonic().fromReader(br); -}; + static fromRaw(data) { + return new this().fromRaw(data); + } -/** - * Instantiate mnemonic from serialized data. - * @param {Buffer} data - * @returns {Mnemonic} - */ + /** + * Convert the mnemonic to a string. + * @returns {String} + */ -Mnemonic.fromRaw = function fromRaw(data) { - return new Mnemonic().fromRaw(data); -}; + toString() { + return this.getPhrase(); + } -/** - * Convert the mnemonic to a string. - * @returns {String} - */ + /** + * Inspect the mnemonic. + * @returns {String} + */ -Mnemonic.prototype.toString = function toString() { - return this.getPhrase(); -}; + [inspectSymbol]() { + return ``; + } -/** - * Inspect the mnemonic. - * @returns {String} - */ + /** + * Test whether an object is a Mnemonic. + * @param {Object} obj + * @returns {Boolean} + */ -Mnemonic.prototype.inspect = function inspect() { - return ``; -}; + static isMnemonic(obj) { + return obj instanceof Mnemonic; + } +} /** - * Test whether an object is a Mnemonic. - * @param {Object} obj - * @returns {Boolean} + * List of languages. + * @const {String[]} + * @default */ -Mnemonic.isMnemonic = function isMnemonic(obj) { - return obj instanceof Mnemonic; -}; +Mnemonic.languages = [ + 'simplified chinese', + 'traditional chinese', + 'english', + 'french', + 'italian', + 'japanese', + 'spanish' +]; /** * Word List - * @constructor * @ignore - * @param {Array} words */ -function WordList(words) { - this.words = words; - this.map = Object.create(null); - - for (let i = 0; i < words.length; i++) { - const word = words[i]; - this.map[word] = i; +class WordList { + /** + * Create word list. + * @constructor + * @ignore + * @param {Array} words + */ + + constructor(words) { + this.words = words; + this.map = Object.create(null); + + for (let i = 0; i < words.length; i++) { + const word = words[i]; + this.map[word] = i; + } } } diff --git a/lib/utils/nfkd-browser.js b/lib/hd/nfkd-compat.js similarity index 73% rename from lib/utils/nfkd-browser.js rename to lib/hd/nfkd-compat.js index a4e2f846a..899c90b33 100644 --- a/lib/utils/nfkd-browser.js +++ b/lib/hd/nfkd-compat.js @@ -1,12 +1,12 @@ /*! - * nfkd-browser.js - unicode normalization for bcoin + * nfkd-compat.js - unicode normalization for bcoin * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ 'use strict'; -const unorm = require('../../vendor/unorm'); +const unorm = require('./unorm'); function nfkd(str) { if (str.normalize) diff --git a/lib/utils/nfkd.js b/lib/hd/nfkd.js similarity index 100% rename from lib/utils/nfkd.js rename to lib/hd/nfkd.js diff --git a/lib/hd/private.js b/lib/hd/private.js index 1711b31f4..27f3a19d9 100644 --- a/lib/hd/private.js +++ b/lib/hd/private.js @@ -6,17 +6,17 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); -const digest = require('../crypto/digest'); -const cleanse = require('../crypto/cleanse'); -const random = require('../crypto/random'); -const secp256k1 = require('../crypto/secp256k1'); +const assert = require('bsert'); +const bio = require('bufio'); +const {base58} = require('bstring'); +const sha512 = require('bcrypto/lib/sha512'); +const hash160 = require('bcrypto/lib/hash160'); +const hash256 = require('bcrypto/lib/hash256'); +const cleanse = require('bcrypto/lib/cleanse'); +const random = require('bcrypto/lib/random'); +const secp256k1 = require('bcrypto/lib/secp256k1'); const Network = require('../protocol/network'); -const StaticWriter = require('../utils/staticwriter'); -const BufferReader = require('../utils/reader'); -const base58 = require('../utils/base58'); -const encoding = require('../utils/encoding'); +const consensus = require('../protocol/consensus'); const common = require('./common'); const Mnemonic = require('./mnemonic'); const HDPublicKey = require('./public'); @@ -30,15 +30,6 @@ const SEED_SALT = Buffer.from('Bitcoin seed', 'ascii'); /** * HDPrivateKey * @alias module:hd.PrivateKey - * @constructor - * @param {Object|Base58String} options - * @param {Base58String?} options.xkey - Serialized base58 key. - * @param {Number?} options.depth - * @param {Number?} options.parentFingerPrint - * @param {Number?} options.childIndex - * @param {Buffer?} options.chainCode - * @param {Buffer?} options.privateKey - * @property {Network} network * @property {Number} depth * @property {Number} parentFingerPrint * @property {Number} childIndex @@ -46,704 +37,689 @@ const SEED_SALT = Buffer.from('Bitcoin seed', 'ascii'); * @property {Buffer} privateKey */ -function HDPrivateKey(options) { - if (!(this instanceof HDPrivateKey)) - return new HDPrivateKey(options); +class HDPrivateKey { + /** + * Create an hd private key. + * @constructor + * @param {Object|String} options + * @param {Number?} options.depth + * @param {Number?} options.parentFingerPrint + * @param {Number?} options.childIndex + * @param {Buffer?} options.chainCode + * @param {Buffer?} options.privateKey + */ + + constructor(options) { + this.depth = 0; + this.parentFingerPrint = 0; + this.childIndex = 0; + this.chainCode = consensus.ZERO_HASH; + this.privateKey = consensus.ZERO_HASH; + + this.publicKey = common.ZERO_KEY; + this.fingerPrint = -1; - this.network = Network.primary; - this.depth = 0; - this.parentFingerPrint = 0; - this.childIndex = 0; - this.chainCode = encoding.ZERO_HASH; - this.privateKey = encoding.ZERO_HASH; - - this.publicKey = encoding.ZERO_KEY; - this.fingerPrint = -1; - - this._xprivkey = null; - - this._hdPublicKey = null; - - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -HDPrivateKey.prototype.fromOptions = function fromOptions(options) { - assert(options, 'No options for HD private key.'); - assert(util.isU8(options.depth)); - assert(util.isU32(options.parentFingerPrint)); - assert(util.isU32(options.childIndex)); - assert(Buffer.isBuffer(options.chainCode)); - assert(Buffer.isBuffer(options.privateKey)); - - if (options.network) - this.network = Network.get(options.network); - - this.depth = options.depth; - this.parentFingerPrint = options.parentFingerPrint; - this.childIndex = options.childIndex; - this.chainCode = options.chainCode; - this.privateKey = options.privateKey; - this.publicKey = secp256k1.publicKeyCreate(options.privateKey, true); - - return this; -}; - -/** - * Instantiate HD private key from options object. - * @param {Object} options - * @returns {HDPrivateKey} - */ - -HDPrivateKey.fromOptions = function fromOptions(options) { - return new HDPrivateKey().fromOptions(options); -}; - -/** - * Get HD public key. - * @returns {HDPublicKey} - */ - -HDPrivateKey.prototype.toPublic = function toPublic() { - let key = this._hdPublicKey; + this._hdPublicKey = null; - if (!key) { - key = new HDPublicKey(); - key.network = this.network; - key.depth = this.depth; - key.parentFingerPrint = this.parentFingerPrint; - key.childIndex = this.childIndex; - key.chainCode = this.chainCode; - key.publicKey = this.publicKey; - this._hdPublicKey = key; + if (options) + this.fromOptions(options); } - return key; -}; - -/** - * Get cached base58 xprivkey. - * @returns {Base58String} - */ - -HDPrivateKey.prototype.xprivkey = function xprivkey() { - if (!this._xprivkey) - this._xprivkey = this.toBase58(); - return this._xprivkey; -}; + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + assert(options, 'No options for HD private key.'); + assert((options.depth & 0xff) === options.depth); + assert((options.parentFingerPrint >>> 0) === options.parentFingerPrint); + assert((options.childIndex >>> 0) === options.childIndex); + assert(Buffer.isBuffer(options.chainCode)); + assert(Buffer.isBuffer(options.privateKey)); + + this.depth = options.depth; + this.parentFingerPrint = options.parentFingerPrint; + this.childIndex = options.childIndex; + this.chainCode = options.chainCode; + this.privateKey = options.privateKey; + this.publicKey = secp256k1.publicKeyCreate(options.privateKey, true); + + return this; + } -/** - * Get cached base58 xpubkey. - * @returns {Base58String} - */ + /** + * Instantiate HD private key from options object. + * @param {Object} options + * @returns {HDPrivateKey} + */ -HDPrivateKey.prototype.xpubkey = function xpubkey() { - return this.toPublic().xpubkey(); -}; + static fromOptions(options) { + return new this().fromOptions(options); + } -/** - * Destroy the key (zeroes chain code, privkey, and pubkey). - * @param {Boolean} pub - Destroy hd public key as well. - */ + /** + * Get HD public key. + * @returns {HDPublicKey} + */ + + toPublic() { + let key = this._hdPublicKey; + + if (!key) { + key = new HDPublicKey(); + key.depth = this.depth; + key.parentFingerPrint = this.parentFingerPrint; + key.childIndex = this.childIndex; + key.chainCode = this.chainCode; + key.publicKey = this.publicKey; + this._hdPublicKey = key; + } + + return key; + } -HDPrivateKey.prototype.destroy = function destroy(pub) { - this.depth = 0; - this.childIndex = 0; - this.parentFingerPrint = 0; + /** + * Get cached base58 xprivkey. + * @returns {Base58String} + */ - cleanse(this.chainCode); - cleanse(this.privateKey); - cleanse(this.publicKey); + xprivkey(network) { + return this.toBase58(network); + } - this.fingerPrint = -1; + /** + * Get cached base58 xpubkey. + * @returns {Base58String} + */ - if (this._hdPublicKey) { - if (pub) - this._hdPublicKey.destroy(); - this._hdPublicKey = null; + xpubkey(network) { + return this.toPublic().xpubkey(network); } - this._xprivkey = null; -}; + /** + * Destroy the key (zeroes chain code, privkey, and pubkey). + * @param {Boolean} pub - Destroy hd public key as well. + */ -/** - * Derive a child key. - * @param {Number} index - Derivation index. - * @param {Boolean?} hardened - Whether the derivation should be hardened. - * @returns {HDPrivateKey} - */ + destroy(pub) { + this.depth = 0; + this.childIndex = 0; + this.parentFingerPrint = 0; -HDPrivateKey.prototype.derive = function derive(index, hardened) { - assert(typeof index === 'number'); + cleanse(this.chainCode); + cleanse(this.privateKey); + cleanse(this.publicKey); - if ((index >>> 0) !== index) - throw new Error('Index out of range.'); + this.fingerPrint = -1; - if (this.depth >= 0xff) - throw new Error('Depth too high.'); - - if (hardened) { - index |= common.HARDENED; - index >>>= 0; + if (this._hdPublicKey) { + if (pub) + this._hdPublicKey.destroy(); + this._hdPublicKey = null; + } } - const id = this.getID(index); - const cache = common.cache.get(id); - - if (cache) - return cache; - - const bw = StaticWriter.pool(37); - - if (index & common.HARDENED) { - bw.writeU8(0); - bw.writeBytes(this.privateKey); - bw.writeU32BE(index); - } else { - bw.writeBytes(this.publicKey); - bw.writeU32BE(index); + /** + * Derive a child key. + * @param {Number} index - Derivation index. + * @param {Boolean?} hardened - Whether the derivation should be hardened. + * @returns {HDPrivateKey} + */ + + derive(index, hardened) { + assert(typeof index === 'number'); + + if ((index >>> 0) !== index) + throw new Error('Index out of range.'); + + if (this.depth >= 0xff) + throw new Error('Depth too high.'); + + if (hardened) { + index |= common.HARDENED; + index >>>= 0; + } + + const id = this.getID(index); + const cache = common.cache.get(id); + + if (cache) + return cache; + + const bw = bio.pool(37); + + if (index & common.HARDENED) { + bw.writeU8(0); + bw.writeBytes(this.privateKey); + bw.writeU32BE(index); + } else { + bw.writeBytes(this.publicKey); + bw.writeU32BE(index); + } + + const data = bw.render(); + + const hash = sha512.mac(data, this.chainCode); + const left = hash.slice(0, 32); + const right = hash.slice(32, 64); + + let key; + try { + key = secp256k1.privateKeyTweakAdd(this.privateKey, left); + } catch (e) { + return this.derive(index + 1); + } + + if (this.fingerPrint === -1) { + const fp = hash160.digest(this.publicKey); + this.fingerPrint = fp.readUInt32BE(0, true); + } + + const child = new this.constructor(); + child.depth = this.depth + 1; + child.parentFingerPrint = this.fingerPrint; + child.childIndex = index; + child.chainCode = right; + child.privateKey = key; + child.publicKey = secp256k1.publicKeyCreate(key, true); + + common.cache.set(id, child); + + return child; } - const data = bw.render(); - - const hash = digest.hmac('sha512', data, this.chainCode); - const left = hash.slice(0, 32); - const right = hash.slice(32, 64); + /** + * Unique HD key ID. + * @private + * @param {Number} index + * @returns {String} + */ - let key; - try { - key = secp256k1.privateKeyTweakAdd(this.privateKey, left); - } catch (e) { - return this.derive(index + 1); + getID(index) { + return 'v' + this.publicKey.toString('hex') + index; } - if (this.fingerPrint === -1) { - const fp = digest.hash160(this.publicKey); - this.fingerPrint = fp.readUInt32BE(0, true); + /** + * Derive a BIP44 account key. + * @param {Number} purpose + * @param {Number} type + * @param {Number} account + * @returns {HDPrivateKey} + * @throws Error if key is not a master key. + */ + + deriveAccount(purpose, type, account) { + assert((purpose >>> 0) === purpose, 'Purpose must be a number.'); + assert((type >>> 0) === type, 'Account index must be a number.'); + assert((account >>> 0) === account, 'Account index must be a number.'); + assert(this.isMaster(), 'Cannot derive account index.'); + return this + .derive(purpose, true) + .derive(type, true) + .derive(account, true); } - const child = new HDPrivateKey(); - child.network = this.network; - child.depth = this.depth + 1; - child.parentFingerPrint = this.fingerPrint; - child.childIndex = index; - child.chainCode = right; - child.privateKey = key; - child.publicKey = secp256k1.publicKeyCreate(key, true); - - common.cache.set(id, child); - - return child; -}; - -/** - * Unique HD key ID. - * @private - * @param {Number} index - * @returns {String} - */ + /** + * Test whether the key is a master key. + * @returns {Boolean} + */ -HDPrivateKey.prototype.getID = function getID(index) { - return this.network.keyPrefix.xprivkey58 - + this.publicKey.toString('hex') - + index; -}; - -/** - * Derive a BIP44 account key. - * @param {Number} purpose - * @param {Number} account - * @returns {HDPrivateKey} - * @throws Error if key is not a master key. - */ - -HDPrivateKey.prototype.deriveAccount = function deriveAccount(purpose, account) { - assert(util.isU32(purpose), 'Purpose must be a number.'); - assert(util.isU32(account), 'Account index must be a number.'); - assert(this.isMaster(), 'Cannot derive account index.'); - return this - .derive(purpose, true) - .derive(this.network.keyPrefix.coinType, true) - .derive(account, true); -}; - -/** - * Test whether the key is a master key. - * @returns {Boolean} - */ - -HDPrivateKey.prototype.isMaster = function isMaster() { - return common.isMaster(this); -}; + isMaster() { + return common.isMaster(this); + } -/** - * Test whether the key is (most likely) a BIP44 account key. - * @param {Number?} account - * @returns {Boolean} - */ + /** + * Test whether the key is (most likely) a BIP44 account key. + * @param {Number?} account + * @returns {Boolean} + */ -HDPrivateKey.prototype.isAccount = function isAccount(account) { - return common.isAccount(this, account); -}; + isAccount(account) { + return common.isAccount(this, account); + } -/** - * Test whether an object is in the form of a base58 xprivkey. - * @param {String} data - * @param {Network?} network - * @returns {Boolean} - */ + /** + * Test whether an object is in the form of a base58 xprivkey. + * @param {String} data + * @param {Network?} network + * @returns {Boolean} + */ -HDPrivateKey.isBase58 = function isBase58(data, network) { - if (typeof data !== 'string') - return false; + static isBase58(data, network) { + if (typeof data !== 'string') + return false; - if (data.length < 4) - return false; + if (data.length < 4) + return false; - const prefix = data.substring(0, 4); + const prefix = data.substring(0, 4); - try { - Network.fromPrivate58(prefix, network); - return true; - } catch (e) { - return false; + try { + Network.fromPrivate58(prefix, network); + return true; + } catch (e) { + return false; + } } -}; -/** - * Test whether a buffer has a valid network prefix. - * @param {Buffer} data - * @param {Network?} network - * @returns {Boolean} - */ + /** + * Test whether a buffer has a valid network prefix. + * @param {Buffer} data + * @param {Network?} network + * @returns {Boolean} + */ -HDPrivateKey.isRaw = function isRaw(data, network) { - if (!Buffer.isBuffer(data)) - return false; + static isRaw(data, network) { + if (!Buffer.isBuffer(data)) + return false; - if (data.length < 4) - return false; + if (data.length < 4) + return false; - const version = data.readUInt32BE(0, true); + const version = data.readUInt32BE(0, true); - try { - Network.fromPrivate(version, network); - return true; - } catch (e) { - return false; + try { + Network.fromPrivate(version, network); + return true; + } catch (e) { + return false; + } } -}; -/** - * Test whether a string is a valid path. - * @param {String} path - * @returns {Boolean} - */ - -HDPrivateKey.isValidPath = function isValidPath(path) { - try { - common.parsePath(path, true); - return true; - } catch (e) { - return false; + /** + * Test whether a string is a valid path. + * @param {String} path + * @returns {Boolean} + */ + + static isValidPath(path) { + try { + common.parsePath(path, true); + return true; + } catch (e) { + return false; + } } -}; -/** - * Derive a key from a derivation path. - * @param {String} path - * @returns {HDPrivateKey} - * @throws Error if `path` is not a valid path. - */ + /** + * Derive a key from a derivation path. + * @param {String} path + * @returns {HDPrivateKey} + * @throws Error if `path` is not a valid path. + */ -HDPrivateKey.prototype.derivePath = function derivePath(path) { - const indexes = common.parsePath(path, true); + derivePath(path) { + const indexes = common.parsePath(path, true); - let key = this; + let key = this; - for (const index of indexes) - key = key.derive(index); + for (const index of indexes) + key = key.derive(index); - return key; -}; - -/** - * Compare a key against an object. - * @param {Object} obj - * @returns {Boolean} - */ + return key; + } -HDPrivateKey.prototype.equals = function equals(obj) { - assert(HDPrivateKey.isHDPrivateKey(obj)); + /** + * Compare a key against an object. + * @param {Object} obj + * @returns {Boolean} + */ - return this.network === obj.network - && this.depth === obj.depth - && this.parentFingerPrint === obj.parentFingerPrint - && this.childIndex === obj.childIndex - && this.chainCode.equals(obj.chainCode) - && this.privateKey.equals(obj.privateKey); -}; + equals(obj) { + assert(HDPrivateKey.isHDPrivateKey(obj)); -/** - * Compare a key against an object. - * @param {Object} obj - * @returns {Boolean} - */ + return this.depth === obj.depth + && this.parentFingerPrint === obj.parentFingerPrint + && this.childIndex === obj.childIndex + && this.chainCode.equals(obj.chainCode) + && this.privateKey.equals(obj.privateKey); + } -HDPrivateKey.prototype.compare = function compare(key) { - assert(HDPrivateKey.isHDPrivateKey(key)); + /** + * Compare a key against an object. + * @param {Object} obj + * @returns {Boolean} + */ - let cmp = this.depth - key.depth; + compare(key) { + assert(HDPrivateKey.isHDPrivateKey(key)); - if (cmp !== 0) - return cmp; + let cmp = this.depth - key.depth; - cmp = this.parentFingerPrint - key.parentFingerPrint; + if (cmp !== 0) + return cmp; - if (cmp !== 0) - return cmp; + cmp = this.parentFingerPrint - key.parentFingerPrint; - cmp = this.childIndex - key.childIndex; + if (cmp !== 0) + return cmp; - if (cmp !== 0) - return cmp; + cmp = this.childIndex - key.childIndex; - cmp = this.chainCode.compare(key.chainCode); + if (cmp !== 0) + return cmp; - if (cmp !== 0) - return cmp; + cmp = this.chainCode.compare(key.chainCode); - cmp = this.privateKey.compare(key.privateKey); + if (cmp !== 0) + return cmp; - if (cmp !== 0) - return cmp; + cmp = this.privateKey.compare(key.privateKey); - return 0; -}; + if (cmp !== 0) + return cmp; -/** - * Inject properties from seed. - * @private - * @param {Buffer} seed - * @param {(Network|NetworkType)?} network - */ + return 0; + } -HDPrivateKey.prototype.fromSeed = function fromSeed(seed, network) { - assert(Buffer.isBuffer(seed)); + /** + * Inject properties from seed. + * @private + * @param {Buffer} seed + */ - if (seed.length * 8 < common.MIN_ENTROPY - || seed.length * 8 > common.MAX_ENTROPY) { - throw new Error('Entropy not in range.'); - } + fromSeed(seed) { + assert(Buffer.isBuffer(seed)); - const hash = digest.hmac('sha512', seed, SEED_SALT); - const left = hash.slice(0, 32); - const right = hash.slice(32, 64); + if (seed.length * 8 < common.MIN_ENTROPY + || seed.length * 8 > common.MAX_ENTROPY) { + throw new Error('Entropy not in range.'); + } - // Only a 1 in 2^127 chance of happening. - if (!secp256k1.privateKeyVerify(left)) - throw new Error('Master private key is invalid.'); + const hash = sha512.mac(seed, SEED_SALT); + const left = hash.slice(0, 32); + const right = hash.slice(32, 64); - this.network = Network.get(network); - this.depth = 0; - this.parentFingerPrint = 0; - this.childIndex = 0; - this.chainCode = right; - this.privateKey = left; - this.publicKey = secp256k1.publicKeyCreate(left, true); + // Only a 1 in 2^127 chance of happening. + if (!secp256k1.privateKeyVerify(left)) + throw new Error('Master private key is invalid.'); - return this; -}; + this.depth = 0; + this.parentFingerPrint = 0; + this.childIndex = 0; + this.chainCode = right; + this.privateKey = left; + this.publicKey = secp256k1.publicKeyCreate(left, true); -/** - * Instantiate an hd private key from a 512 bit seed. - * @param {Buffer} seed - * @param {(Network|NetworkType)?} network - * @returns {HDPrivateKey} - */ + return this; + } -HDPrivateKey.fromSeed = function fromSeed(seed, network) { - return new HDPrivateKey().fromSeed(seed, network); -}; + /** + * Instantiate an hd private key from a 512 bit seed. + * @param {Buffer} seed + * @returns {HDPrivateKey} + */ -/** - * Inject properties from a mnemonic. - * @private - * @param {Mnemonic} mnemonic - * @param {(Network|NetworkType)?} network - */ + static fromSeed(seed) { + return new this().fromSeed(seed); + } -HDPrivateKey.prototype.fromMnemonic = function fromMnemonic(mnemonic, network) { - assert(mnemonic instanceof Mnemonic); - return this.fromSeed(mnemonic.toSeed(), network); -}; + /** + * Inject properties from a mnemonic. + * @private + * @param {Mnemonic} mnemonic + * @param {String?} passphrase + */ -/** - * Instantiate an hd private key from a mnemonic. - * @param {Mnemonic} mnemonic - * @param {(Network|NetworkType)?} network - * @returns {HDPrivateKey} - */ + fromMnemonic(mnemonic, passphrase) { + assert(mnemonic instanceof Mnemonic); + return this.fromSeed(mnemonic.toSeed(passphrase)); + } -HDPrivateKey.fromMnemonic = function fromMnemonic(mnemonic, network) { - return new HDPrivateKey().fromMnemonic(mnemonic, network); -}; + /** + * Instantiate an hd private key from a mnemonic. + * @param {Mnemonic} mnemonic + * @param {String?} passphrase + * @returns {HDPrivateKey} + */ -/** - * Inject properties from a mnemonic. - * @private - * @param {String} mnemonic - * @param {(Network|NetworkType)?} network - */ + static fromMnemonic(mnemonic, passphrase) { + return new this().fromMnemonic(mnemonic, passphrase); + } -HDPrivateKey.prototype.fromPhrase = function fromPhrase(phrase, network) { - const mnemonic = Mnemonic.fromPhrase(phrase); - this.fromMnemonic(mnemonic, network); - return this; -}; + /** + * Inject properties from a mnemonic. + * @private + * @param {String} mnemonic + */ -/** - * Instantiate an hd private key from a phrase. - * @param {String} phrase - * @param {(Network|NetworkType)?} network - * @returns {HDPrivateKey} - */ + fromPhrase(phrase) { + const mnemonic = Mnemonic.fromPhrase(phrase); + this.fromMnemonic(mnemonic); + return this; + } -HDPrivateKey.fromPhrase = function fromPhrase(phrase, network) { - return new HDPrivateKey().fromPhrase(phrase, network); -}; + /** + * Instantiate an hd private key from a phrase. + * @param {String} phrase + * @returns {HDPrivateKey} + */ -/** - * Inject properties from privateKey and entropy. - * @private - * @param {Buffer} key - * @param {Buffer} entropy - * @param {(Network|NetworkType)?} network - */ + static fromPhrase(phrase) { + return new this().fromPhrase(phrase); + } -HDPrivateKey.prototype.fromKey = function fromKey(key, entropy, network) { - assert(Buffer.isBuffer(key) && key.length === 32); - assert(Buffer.isBuffer(entropy) && entropy.length === 32); - this.network = Network.get(network); - this.depth = 0; - this.parentFingerPrint = 0; - this.childIndex = 0; - this.chainCode = entropy; - this.privateKey = key; - this.publicKey = secp256k1.publicKeyCreate(key, true); - return this; -}; + /** + * Inject properties from privateKey and entropy. + * @private + * @param {Buffer} key + * @param {Buffer} entropy + */ + + fromKey(key, entropy) { + assert(Buffer.isBuffer(key) && key.length === 32); + assert(Buffer.isBuffer(entropy) && entropy.length === 32); + this.depth = 0; + this.parentFingerPrint = 0; + this.childIndex = 0; + this.chainCode = entropy; + this.privateKey = key; + this.publicKey = secp256k1.publicKeyCreate(key, true); + return this; + } -/** - * Create an hd private key from a key and entropy bytes. - * @param {Buffer} key - * @param {Buffer} entropy - * @param {(Network|NetworkType)?} network - * @returns {HDPrivateKey} - */ + /** + * Create an hd private key from a key and entropy bytes. + * @param {Buffer} key + * @param {Buffer} entropy + * @returns {HDPrivateKey} + */ -HDPrivateKey.fromKey = function fromKey(key, entropy, network) { - return new HDPrivateKey().fromKey(key, entropy, network); -}; + static fromKey(key, entropy) { + return new this().fromKey(key, entropy); + } -/** - * Generate an hd private key. - * @param {(Network|NetworkType)?} network - * @returns {HDPrivateKey} - */ + /** + * Generate an hd private key. + * @returns {HDPrivateKey} + */ -HDPrivateKey.generate = function generate(network) { - const key = secp256k1.generatePrivateKey(); - const entropy = random.randomBytes(32); - return HDPrivateKey.fromKey(key, entropy, network); -}; + static generate() { + const key = secp256k1.privateKeyGenerate(); + const entropy = random.randomBytes(32); + return HDPrivateKey.fromKey(key, entropy); + } -/** - * Inject properties from base58 key. - * @private - * @param {Base58String} xkey - * @param {Network?} network - */ + /** + * Inject properties from base58 key. + * @private + * @param {Base58String} xkey + * @param {Network?} network + */ -HDPrivateKey.prototype.fromBase58 = function fromBase58(xkey, network) { - assert(typeof xkey === 'string'); - this._xprivkey = xkey; - return this.fromRaw(base58.decode(xkey), network); -}; + fromBase58(xkey, network) { + assert(typeof xkey === 'string'); + return this.fromRaw(base58.decode(xkey), network); + } -/** - * Inject properties from serialized data. - * @private - * @param {BufferReader} br - * @param {(Network|NetworkType)?} network - */ + /** + * Inject properties from serialized data. + * @private + * @param {BufferReader} br + * @param {(Network|NetworkType)?} network + */ -HDPrivateKey.prototype.fromReader = function fromReader(br, network) { - const version = br.readU32BE(); + fromReader(br, network) { + const version = br.readU32BE(); - this.network = Network.fromPrivate(version, network); - this.depth = br.readU8(); - this.parentFingerPrint = br.readU32BE(); - this.childIndex = br.readU32BE(); - this.chainCode = br.readBytes(32); - assert(br.readU8() === 0); - this.privateKey = br.readBytes(32); - this.publicKey = secp256k1.publicKeyCreate(this.privateKey, true); + Network.fromPrivate(version, network); - br.verifyChecksum(); + this.depth = br.readU8(); + this.parentFingerPrint = br.readU32BE(); + this.childIndex = br.readU32BE(); + this.chainCode = br.readBytes(32); + assert(br.readU8() === 0); + this.privateKey = br.readBytes(32); + this.publicKey = secp256k1.publicKeyCreate(this.privateKey, true); - return this; -}; + br.verifyChecksum(hash256.digest); -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @param {(Network|NetworkType)?} network - */ + return this; + } -HDPrivateKey.prototype.fromRaw = function fromRaw(data, network) { - return this.fromReader(new BufferReader(data), network); -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + * @param {(Network|NetworkType)?} network + */ -/** - * Serialize key to a base58 string. - * @param {(Network|NetworkType)?} network - * @returns {Base58String} - */ + fromRaw(data, network) { + return this.fromReader(bio.read(data), network); + } -HDPrivateKey.prototype.toBase58 = function toBase58(network) { - return base58.encode(this.toRaw(network)); -}; + /** + * Serialize key to a base58 string. + * @param {(Network|NetworkType)?} network + * @returns {Base58String} + */ -/** - * Calculate serialization size. - * @returns {Number} - */ + toBase58(network) { + return base58.encode(this.toRaw(network)); + } -HDPrivateKey.prototype.getSize = function getSize() { - return 82; -}; + /** + * Calculate serialization size. + * @returns {Number} + */ -/** - * Write the key to a buffer writer. - * @param {BufferWriter} bw - * @param {(Network|NetworkType)?} network - */ + getSize() { + return 82; + } -HDPrivateKey.prototype.toWriter = function toWriter(bw, network) { - if (!network) - network = this.network; + /** + * Write the key to a buffer writer. + * @param {BufferWriter} bw + * @param {(Network|NetworkType)?} network + */ - network = Network.get(network); + toWriter(bw, network) { + network = Network.get(network); - bw.writeU32BE(network.keyPrefix.xprivkey); - bw.writeU8(this.depth); - bw.writeU32BE(this.parentFingerPrint); - bw.writeU32BE(this.childIndex); - bw.writeBytes(this.chainCode); - bw.writeU8(0); - bw.writeBytes(this.privateKey); - bw.writeChecksum(); + bw.writeU32BE(network.keyPrefix.xprivkey); + bw.writeU8(this.depth); + bw.writeU32BE(this.parentFingerPrint); + bw.writeU32BE(this.childIndex); + bw.writeBytes(this.chainCode); + bw.writeU8(0); + bw.writeBytes(this.privateKey); + bw.writeChecksum(hash256.digest); - return bw; -}; + return bw; + } -/** - * Serialize the key. - * @param {(Network|NetworkType)?} network - * @returns {Buffer} - */ + /** + * Serialize the key. + * @param {(Network|NetworkType)?} network + * @returns {Buffer} + */ -HDPrivateKey.prototype.toRaw = function toRaw(network) { - return this.toWriter(new StaticWriter(82), network).render(); -}; + toRaw(network) { + return this.toWriter(bio.write(82), network).render(); + } -/** - * Instantiate an HD private key from a base58 string. - * @param {Base58String} xkey - * @param {Network?} network - * @returns {HDPrivateKey} - */ + /** + * Instantiate an HD private key from a base58 string. + * @param {Base58String} xkey + * @param {Network?} network + * @returns {HDPrivateKey} + */ -HDPrivateKey.fromBase58 = function fromBase58(xkey, network) { - return new HDPrivateKey().fromBase58(xkey, network); -}; + static fromBase58(xkey, network) { + return new this().fromBase58(xkey, network); + } -/** - * Instantiate key from buffer reader. - * @param {BufferReader} br - * @param {(Network|NetworkType)?} network - * @returns {HDPrivateKey} - */ + /** + * Instantiate key from buffer reader. + * @param {BufferReader} br + * @param {(Network|NetworkType)?} network + * @returns {HDPrivateKey} + */ -HDPrivateKey.fromReader = function fromReader(br, network) { - return new HDPrivateKey().fromReader(br, network); -}; + static fromReader(br, network) { + return new this().fromReader(br, network); + } -/** - * Instantiate key from serialized data. - * @param {Buffer} data - * @param {(Network|NetworkType)?} network - * @returns {HDPrivateKey} - */ + /** + * Instantiate key from serialized data. + * @param {Buffer} data + * @param {(Network|NetworkType)?} network + * @returns {HDPrivateKey} + */ -HDPrivateKey.fromRaw = function fromRaw(data, network) { - return new HDPrivateKey().fromRaw(data, network); -}; + static fromRaw(data, network) { + return new this().fromRaw(data, network); + } -/** - * Convert key to a more json-friendly object. - * @returns {Object} - */ + /** + * Convert key to a more json-friendly object. + * @returns {Object} + */ -HDPrivateKey.prototype.toJSON = function toJSON() { - return { - xprivkey: this.xprivkey() - }; -}; + toJSON(network) { + return { + xprivkey: this.xprivkey(network) + }; + } -/** - * Inject properties from json object. - * @private - * @param {Object} json - * @param {Network?} network - */ + /** + * Inject properties from json object. + * @private + * @param {Object} json + * @param {Network?} network + */ -HDPrivateKey.prototype.fromJSON = function fromJSON(json, network) { - assert(json.xprivkey, 'Could not handle key JSON.'); + fromJSON(json, network) { + assert(json.xprivkey, 'Could not handle key JSON.'); - this.fromBase58(json.xprivkey, network); + this.fromBase58(json.xprivkey, network); - return this; -}; + return this; + } -/** - * Instantiate an HDPrivateKey from a jsonified key object. - * @param {Object} json - The jsonified key object. - * @param {Network?} network - * @returns {HDPrivateKey} - */ + /** + * Instantiate an HDPrivateKey from a jsonified key object. + * @param {Object} json - The jsonified key object. + * @param {Network?} network + * @returns {HDPrivateKey} + */ -HDPrivateKey.fromJSON = function fromJSON(json, network) { - return new HDPrivateKey().fromJSON(json, network); -}; + static fromJSON(json, network) { + return new this().fromJSON(json, network); + } -/** - * Test whether an object is an HDPrivateKey. - * @param {Object} obj - * @returns {Boolean} - */ + /** + * Test whether an object is an HDPrivateKey. + * @param {Object} obj + * @returns {Boolean} + */ -HDPrivateKey.isHDPrivateKey = function isHDPrivateKey(obj) { - return obj instanceof HDPrivateKey; -}; + static isHDPrivateKey(obj) { + return obj instanceof HDPrivateKey; + } +} /* * Expose diff --git a/lib/hd/public.js b/lib/hd/public.js index e49286719..1bfd2fc3c 100644 --- a/lib/hd/public.js +++ b/lib/hd/public.js @@ -6,30 +6,21 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); -const digest = require('../crypto/digest'); -const cleanse = require('../crypto/cleanse'); -const secp256k1 = require('../crypto/secp256k1'); +const assert = require('bsert'); +const bio = require('bufio'); +const {base58} = require('bstring'); +const sha512 = require('bcrypto/lib/sha512'); +const hash160 = require('bcrypto/lib/hash160'); +const hash256 = require('bcrypto/lib/hash256'); +const cleanse = require('bcrypto/lib/cleanse'); +const secp256k1 = require('bcrypto/lib/secp256k1'); const Network = require('../protocol/network'); -const StaticWriter = require('../utils/staticwriter'); -const BufferReader = require('../utils/reader'); -const base58 = require('../utils/base58'); -const encoding = require('../utils/encoding'); +const consensus = require('../protocol/consensus'); const common = require('./common'); /** * HDPublicKey * @alias module:hd.PublicKey - * @constructor - * @param {Object|Base58String} options - * @param {Base58String?} options.xkey - Serialized base58 key. - * @param {Number?} options.depth - * @param {Number?} options.parentFingerPrint - * @param {Number?} options.childIndex - * @param {Buffer?} options.chainCode - * @param {Buffer?} options.publicKey - * @property {Network} network * @property {Number} depth * @property {Number} parentFingerPrint * @property {Number} childIndex @@ -37,532 +28,528 @@ const common = require('./common'); * @property {Buffer} publicKey */ -function HDPublicKey(options) { - if (!(this instanceof HDPublicKey)) - return new HDPublicKey(options); - - this.network = Network.primary; - this.depth = 0; - this.parentFingerPrint = 0; - this.childIndex = 0; - this.chainCode = encoding.ZERO_HASH; - this.publicKey = encoding.ZERO_KEY; - - this.fingerPrint = -1; - - this._xpubkey = null; +class HDPublicKey { + /** + * Create an HD public key. + * @constructor + * @param {Object|Base58String} options + * @param {Base58String?} options.xkey - Serialized base58 key. + * @param {Number?} options.depth + * @param {Number?} options.parentFingerPrint + * @param {Number?} options.childIndex + * @param {Buffer?} options.chainCode + * @param {Buffer?} options.publicKey + */ + + constructor(options) { + this.depth = 0; + this.parentFingerPrint = 0; + this.childIndex = 0; + this.chainCode = consensus.ZERO_HASH; + this.publicKey = common.ZERO_KEY; + + this.fingerPrint = -1; + + if (options) + this.fromOptions(options); + } - if (options) - this.fromOptions(options); -} + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + assert(options, 'No options for HDPublicKey'); + assert((options.depth & 0xff) === options.depth); + assert((options.parentFingerPrint >>> 0) === options.parentFingerPrint); + assert((options.childIndex >>> 0) === options.childIndex); + assert(Buffer.isBuffer(options.chainCode)); + assert(Buffer.isBuffer(options.publicKey)); + + this.depth = options.depth; + this.parentFingerPrint = options.parentFingerPrint; + this.childIndex = options.childIndex; + this.chainCode = options.chainCode; + this.publicKey = options.publicKey; + + return this; + } -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ + /** + * Instantiate HD public key from options object. + * @param {Object} options + * @returns {HDPublicKey} + */ -HDPublicKey.prototype.fromOptions = function fromOptions(options) { - assert(options, 'No options for HDPublicKey'); - assert(util.isU8(options.depth)); - assert(util.isU32(options.parentFingerPrint)); - assert(util.isU32(options.childIndex)); - assert(Buffer.isBuffer(options.chainCode)); - assert(Buffer.isBuffer(options.publicKey)); + static fromOptions(options) { + return new this().fromOptions(options); + } - if (options.network) - this.network = Network.get(options.network); + /** + * Get HD public key (self). + * @returns {HDPublicKey} + */ - this.depth = options.depth; - this.parentFingerPrint = options.parentFingerPrint; - this.childIndex = options.childIndex; - this.chainCode = options.chainCode; - this.publicKey = options.publicKey; + toPublic() { + return this; + } - return this; -}; + /** + * Get cached base58 xprivkey (always null here). + * @returns {null} + */ -/** - * Instantiate HD public key from options object. - * @param {Object} options - * @returns {HDPublicKey} - */ + xprivkey(network) { + return null; + } -HDPublicKey.fromOptions = function fromOptions(options) { - return new HDPublicKey().fromOptions(options); -}; + /** + * Get cached base58 xpubkey. + * @returns {Base58String} + */ -/** - * Get HD public key (self). - * @returns {HDPublicKey} - */ + xpubkey(network) { + return this.toBase58(network); + } -HDPublicKey.prototype.toPublic = function toPublic() { - return this; -}; + /** + * Destroy the key (zeroes chain code and pubkey). + */ -/** - * Get cached base58 xprivkey (always null here). - * @returns {null} - */ + destroy() { + this.depth = 0; + this.childIndex = 0; + this.parentFingerPrint = 0; -HDPublicKey.prototype.xprivkey = function xprivkey() { - return null; -}; + cleanse(this.chainCode); + cleanse(this.publicKey); -/** - * Get cached base58 xpubkey. - * @returns {Base58String} - */ + this.fingerPrint = -1; + } -HDPublicKey.prototype.xpubkey = function xpubkey() { - if (!this._xpubkey) - this._xpubkey = this.toBase58(); - return this._xpubkey; -}; + /** + * Derive a child key. + * @param {Number} index - Derivation index. + * @param {Boolean?} hardened - Whether the derivation + * should be hardened (throws if true). + * @returns {HDPrivateKey} + * @throws on `hardened` + */ -/** - * Destroy the key (zeroes chain code and pubkey). - */ + derive(index, hardened) { + assert(typeof index === 'number'); -HDPublicKey.prototype.destroy = function destroy() { - this.depth = 0; - this.childIndex = 0; - this.parentFingerPrint = 0; + if ((index >>> 0) !== index) + throw new Error('Index out of range.'); - cleanse(this.chainCode); - cleanse(this.publicKey); + if ((index & common.HARDENED) || hardened) + throw new Error('Cannot derive hardened.'); - this.fingerPrint = -1; + if (this.depth >= 0xff) + throw new Error('Depth too high.'); - this._xpubkey = null; -}; + const id = this.getID(index); + const cache = common.cache.get(id); -/** - * Derive a child key. - * @param {Number} index - Derivation index. - * @param {Boolean?} hardened - Whether the derivation - * should be hardened (throws if true). - * @returns {HDPrivateKey} - * @throws on `hardened` - */ + if (cache) + return cache; -HDPublicKey.prototype.derive = function derive(index, hardened) { - assert(typeof index === 'number'); + const bw = bio.pool(37); - if ((index >>> 0) !== index) - throw new Error('Index out of range.'); + bw.writeBytes(this.publicKey); + bw.writeU32BE(index); - if ((index & common.HARDENED) || hardened) - throw new Error('Cannot derive hardened.'); + const data = bw.render(); - if (this.depth >= 0xff) - throw new Error('Depth too high.'); + const hash = sha512.mac(data, this.chainCode); + const left = hash.slice(0, 32); + const right = hash.slice(32, 64); - const id = this.getID(index); - const cache = common.cache.get(id); + let key; + try { + key = secp256k1.publicKeyTweakAdd(this.publicKey, left, true); + } catch (e) { + return this.derive(index + 1); + } - if (cache) - return cache; + if (this.fingerPrint === -1) { + const fp = hash160.digest(this.publicKey); + this.fingerPrint = fp.readUInt32BE(0, true); + } - const bw = StaticWriter.pool(37); + const child = new this.constructor(); + child.depth = this.depth + 1; + child.parentFingerPrint = this.fingerPrint; + child.childIndex = index; + child.chainCode = right; + child.publicKey = key; - bw.writeBytes(this.publicKey); - bw.writeU32BE(index); + common.cache.set(id, child); - const data = bw.render(); + return child; + } - const hash = digest.hmac('sha512', data, this.chainCode); - const left = hash.slice(0, 32); - const right = hash.slice(32, 64); + /** + * Unique HD key ID. + * @private + * @param {Number} index + * @returns {String} + */ - let key; - try { - key = secp256k1.publicKeyTweakAdd(this.publicKey, left, true); - } catch (e) { - return this.derive(index + 1); + getID(index) { + return 'b' + this.publicKey.toString('hex') + index; } - if (this.fingerPrint === -1) { - const fp = digest.hash160(this.publicKey); - this.fingerPrint = fp.readUInt32BE(0, true); + /** + * Derive a BIP44 account key (does not derive, only ensures account key). + * @method + * @param {Number} purpose + * @param {Number} type + * @param {Number} account + * @returns {HDPublicKey} + * @throws Error if key is not already an account key. + */ + + deriveAccount(purpose, type, account) { + assert((purpose >>> 0) === purpose); + assert((type >>> 0) === type); + assert((account >>> 0) === account); + assert(this.isAccount(account), 'Cannot derive account index.'); + return this; } - const child = new HDPublicKey(); - child.network = this.network; - child.depth = this.depth + 1; - child.parentFingerPrint = this.fingerPrint; - child.childIndex = index; - child.chainCode = right; - child.publicKey = key; - - common.cache.set(id, child); + /** + * Test whether the key is a master key. + * @method + * @returns {Boolean} + */ - return child; -}; - -/** - * Unique HD key ID. - * @private - * @param {Number} index - * @returns {String} - */ - -HDPublicKey.prototype.getID = function getID(index) { - return this.network.keyPrefix.xpubkey58 - + this.publicKey.toString('hex') - + index; -}; - -/** - * Derive a BIP44 account key (does not derive, only ensures account key). - * @method - * @param {Number} purpose - * @param {Number} account - * @returns {HDPublicKey} - * @throws Error if key is not already an account key. - */ - -HDPublicKey.prototype.deriveAccount = function deriveAccount(purpose, account) { - assert(util.isU32(purpose)); - assert(util.isU32(account)); - assert(this.isAccount(account), 'Cannot derive account index.'); - return this; -}; - -/** - * Test whether the key is a master key. - * @method - * @returns {Boolean} - */ - -HDPublicKey.prototype.isMaster = function isMaster() { - return common.isMaster(this); -}; - -/** - * Test whether the key is (most likely) a BIP44 account key. - * @method - * @param {Number?} account - * @returns {Boolean} - */ + isMaster() { + return common.isMaster(this); + } -HDPublicKey.prototype.isAccount = function isAccount(account) { - return common.isAccount(this, account); -}; + /** + * Test whether the key is (most likely) a BIP44 account key. + * @method + * @param {Number?} account + * @returns {Boolean} + */ -/** - * Test whether a string is a valid path. - * @param {String} path - * @param {Boolean?} hardened - * @returns {Boolean} - */ - -HDPublicKey.isValidPath = function isValidPath(path) { - try { - common.parsePath(path, false); - return true; - } catch (e) { - return false; + isAccount(account) { + return common.isAccount(this, account); } -}; -/** - * Derive a key from a derivation path. - * @param {String} path - * @returns {HDPublicKey} - * @throws Error if `path` is not a valid path. - * @throws Error if hardened. - */ + /** + * Test whether a string is a valid path. + * @param {String} path + * @param {Boolean?} hardened + * @returns {Boolean} + */ + + static isValidPath(path) { + try { + common.parsePath(path, false); + return true; + } catch (e) { + return false; + } + } -HDPublicKey.prototype.derivePath = function derivePath(path) { - const indexes = common.parsePath(path, false); + /** + * Derive a key from a derivation path. + * @param {String} path + * @returns {HDPublicKey} + * @throws Error if `path` is not a valid path. + * @throws Error if hardened. + */ - let key = this; + derivePath(path) { + const indexes = common.parsePath(path, false); - for (const index of indexes) - key = key.derive(index); + let key = this; - return key; -}; + for (const index of indexes) + key = key.derive(index); -/** - * Compare a key against an object. - * @param {Object} obj - * @returns {Boolean} - */ + return key; + } -HDPublicKey.prototype.equals = function equals(obj) { - assert(HDPublicKey.isHDPublicKey(obj)); + /** + * Compare a key against an object. + * @param {Object} obj + * @returns {Boolean} + */ - return this.network === obj.network - && this.depth === obj.depth - && this.parentFingerPrint === obj.parentFingerPrint - && this.childIndex === obj.childIndex - && this.chainCode.equals(obj.chainCode) - && this.publicKey.equals(obj.publicKey); -}; + equals(obj) { + assert(HDPublicKey.isHDPublicKey(obj)); -/** - * Compare a key against an object. - * @param {Object} obj - * @returns {Boolean} - */ + return this.depth === obj.depth + && this.parentFingerPrint === obj.parentFingerPrint + && this.childIndex === obj.childIndex + && this.chainCode.equals(obj.chainCode) + && this.publicKey.equals(obj.publicKey); + } -HDPublicKey.prototype.compare = function compare(key) { - assert(HDPublicKey.isHDPublicKey(key)); + /** + * Compare a key against an object. + * @param {Object} obj + * @returns {Boolean} + */ - let cmp = this.depth - key.depth; + compare(key) { + assert(HDPublicKey.isHDPublicKey(key)); - if (cmp !== 0) - return cmp; + let cmp = this.depth - key.depth; - cmp = this.parentFingerPrint - key.parentFingerPrint; + if (cmp !== 0) + return cmp; - if (cmp !== 0) - return cmp; + cmp = this.parentFingerPrint - key.parentFingerPrint; - cmp = this.childIndex - key.childIndex; + if (cmp !== 0) + return cmp; - if (cmp !== 0) - return cmp; + cmp = this.childIndex - key.childIndex; - cmp = this.chainCode.compare(key.chainCode); + if (cmp !== 0) + return cmp; - if (cmp !== 0) - return cmp; + cmp = this.chainCode.compare(key.chainCode); - cmp = this.publicKey.compare(key.publicKey); + if (cmp !== 0) + return cmp; - if (cmp !== 0) - return cmp; + cmp = this.publicKey.compare(key.publicKey); - return 0; -}; + if (cmp !== 0) + return cmp; -/** - * Convert key to a more json-friendly object. - * @returns {Object} - */ + return 0; + } -HDPublicKey.prototype.toJSON = function toJSON() { - return { - xpubkey: this.xpubkey() - }; -}; + /** + * Convert key to a more json-friendly object. + * @returns {Object} + */ -/** - * Inject properties from json object. - * @private - * @param {Object} json - * @param {Network?} network - */ + toJSON(network) { + return { + xpubkey: this.xpubkey(network) + }; + } -HDPublicKey.prototype.fromJSON = function fromJSON(json, network) { - assert(json.xpubkey, 'Could not handle HD key JSON.'); - this.fromBase58(json.xpubkey, network); - return this; -}; + /** + * Inject properties from json object. + * @private + * @param {Object} json + * @param {Network?} network + */ + + fromJSON(json, network) { + assert(json.xpubkey, 'Could not handle HD key JSON.'); + this.fromBase58(json.xpubkey, network); + return this; + } -/** - * Instantiate an HDPublicKey from a jsonified key object. - * @param {Object} json - The jsonified transaction object. - * @param {Network?} network - * @returns {HDPrivateKey} - */ + /** + * Instantiate an HDPublicKey from a jsonified key object. + * @param {Object} json - The jsonified transaction object. + * @param {Network?} network + * @returns {HDPrivateKey} + */ -HDPublicKey.fromJSON = function fromJSON(json, network) { - return new HDPublicKey().fromJSON(json, network); -}; + static fromJSON(json, network) { + return new this().fromJSON(json, network); + } -/** - * Test whether an object is in the form of a base58 xpubkey. - * @param {String} data - * @param {(Network|NetworkType)?} network - * @returns {Boolean} - */ + /** + * Test whether an object is in the form of a base58 xpubkey. + * @param {String} data + * @param {(Network|NetworkType)?} network + * @returns {Boolean} + */ -HDPublicKey.isBase58 = function isBase58(data, network) { - if (typeof data !== 'string') - return false; + static isBase58(data, network) { + if (typeof data !== 'string') + return false; - if (data.length < 4) - return false; + if (data.length < 4) + return false; - const prefix = data.substring(0, 4); + const prefix = data.substring(0, 4); - try { - Network.fromPublic58(prefix, network); - return true; - } catch (e) { - return false; + try { + Network.fromPublic58(prefix, network); + return true; + } catch (e) { + return false; + } } -}; -/** - * Test whether a buffer has a valid network prefix. - * @param {Buffer} data - * @param {(Network|NetworkType)?} network - * @returns {NetworkType} - */ + /** + * Test whether a buffer has a valid network prefix. + * @param {Buffer} data + * @param {(Network|NetworkType)?} network + * @returns {NetworkType} + */ -HDPublicKey.isRaw = function isRaw(data, network) { - if (!Buffer.isBuffer(data)) - return false; + static isRaw(data, network) { + if (!Buffer.isBuffer(data)) + return false; - if (data.length < 4) - return false; + if (data.length < 4) + return false; - const version = data.readUInt32BE(0, true); + const version = data.readUInt32BE(0, true); - try { - Network.fromPublic(version, network); - return true; - } catch (e) { - return false; + try { + Network.fromPublic(version, network); + return true; + } catch (e) { + return false; + } } -}; -/** - * Inject properties from a base58 key. - * @private - * @param {Base58String} xkey - * @param {Network?} network - */ + /** + * Inject properties from a base58 key. + * @private + * @param {Base58String} xkey + * @param {Network?} network + */ -HDPublicKey.prototype.fromBase58 = function fromBase58(xkey, network) { - assert(typeof xkey === 'string'); - this._xpubkey = xkey; - return this.fromRaw(base58.decode(xkey), network); -}; + fromBase58(xkey, network) { + assert(typeof xkey === 'string'); + return this.fromRaw(base58.decode(xkey), network); + } -/** - * Inject properties from serialized data. - * @private - * @param {BufferReader} br - * @param {(Network|NetworkType)?} network - */ + /** + * Inject properties from serialized data. + * @private + * @param {BufferReader} br + * @param {(Network|NetworkType)?} network + */ -HDPublicKey.prototype.fromReader = function fromReader(br, network) { - const version = br.readU32BE(); + fromReader(br, network) { + const version = br.readU32BE(); - this.network = Network.fromPublic(version, network); - this.depth = br.readU8(); - this.parentFingerPrint = br.readU32BE(); - this.childIndex = br.readU32BE(); - this.chainCode = br.readBytes(32); - this.publicKey = br.readBytes(33); + Network.fromPublic(version, network); - br.verifyChecksum(); + this.depth = br.readU8(); + this.parentFingerPrint = br.readU32BE(); + this.childIndex = br.readU32BE(); + this.chainCode = br.readBytes(32); + this.publicKey = br.readBytes(33); - return this; -}; + br.verifyChecksum(hash256.digest); -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @param {(Network|NetworkType)?} network - */ + return this; + } -HDPublicKey.prototype.fromRaw = function fromRaw(data, network) { - return this.fromReader(new BufferReader(data), network); -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + * @param {(Network|NetworkType)?} network + */ -/** - * Serialize key data to base58 extended key. - * @param {(Network|NetworkType)?} network - * @returns {Base58String} - */ + fromRaw(data, network) { + return this.fromReader(bio.read(data), network); + } -HDPublicKey.prototype.toBase58 = function toBase58(network) { - return base58.encode(this.toRaw(network)); -}; + /** + * Serialize key data to base58 extended key. + * @param {(Network|NetworkType)?} network + * @returns {Base58String} + */ -/** - * Write the key to a buffer writer. - * @param {BufferWriter} bw - * @param {(Network|NetworkType)?} network - */ + toBase58(network) { + return base58.encode(this.toRaw(network)); + } -HDPublicKey.prototype.toWriter = function toWriter(bw, network) { - if (!network) - network = this.network; + /** + * Write the key to a buffer writer. + * @param {BufferWriter} bw + * @param {(Network|NetworkType)?} network + */ - network = Network.get(network); + toWriter(bw, network) { + network = Network.get(network); - bw.writeU32BE(network.keyPrefix.xpubkey); - bw.writeU8(this.depth); - bw.writeU32BE(this.parentFingerPrint); - bw.writeU32BE(this.childIndex); - bw.writeBytes(this.chainCode); - bw.writeBytes(this.publicKey); - bw.writeChecksum(); + bw.writeU32BE(network.keyPrefix.xpubkey); + bw.writeU8(this.depth); + bw.writeU32BE(this.parentFingerPrint); + bw.writeU32BE(this.childIndex); + bw.writeBytes(this.chainCode); + bw.writeBytes(this.publicKey); + bw.writeChecksum(hash256.digest); - return bw; -}; + return bw; + } -/** - * Calculate serialization size. - * @returns {Number} - */ + /** + * Calculate serialization size. + * @returns {Number} + */ -HDPublicKey.prototype.getSize = function getSize() { - return 82; -}; + getSize() { + return 82; + } -/** - * Serialize the key. - * @param {(Network|NetworkType)?} network - * @returns {Buffer} - */ + /** + * Serialize the key. + * @param {(Network|NetworkType)?} network + * @returns {Buffer} + */ -HDPublicKey.prototype.toRaw = function toRaw(network) { - return this.toWriter(new StaticWriter(82), network).render(); -}; + toRaw(network) { + return this.toWriter(bio.write(82), network).render(); + } -/** - * Instantiate an HD public key from a base58 string. - * @param {Base58String} xkey - * @param {Network?} network - * @returns {HDPublicKey} - */ + /** + * Instantiate an HD public key from a base58 string. + * @param {Base58String} xkey + * @param {Network?} network + * @returns {HDPublicKey} + */ -HDPublicKey.fromBase58 = function fromBase58(xkey, network) { - return new HDPublicKey().fromBase58(xkey, network); -}; + static fromBase58(xkey, network) { + return new this().fromBase58(xkey, network); + } -/** - * Instantiate key from serialized data. - * @param {BufferReader} br - * @param {(Network|NetworkType)?} network - * @returns {HDPublicKey} - */ + /** + * Instantiate key from serialized data. + * @param {BufferReader} br + * @param {(Network|NetworkType)?} network + * @returns {HDPublicKey} + */ -HDPublicKey.fromReader = function fromReader(br, network) { - return new HDPublicKey().fromReader(br, network); -}; + static fromReader(br, network) { + return new this().fromReader(br, network); + } -/** - * Instantiate key from serialized data. - * @param {Buffer} data - * @param {(Network|NetworkType)?} network - * @returns {HDPublicKey} - */ + /** + * Instantiate key from serialized data. + * @param {Buffer} data + * @param {(Network|NetworkType)?} network + * @returns {HDPublicKey} + */ -HDPublicKey.fromRaw = function fromRaw(data, network) { - return new HDPublicKey().fromRaw(data, network); -}; + static fromRaw(data, network) { + return new this().fromRaw(data, network); + } -/** - * Test whether an object is a HDPublicKey. - * @param {Object} obj - * @returns {Boolean} - */ + /** + * Test whether an object is a HDPublicKey. + * @param {Object} obj + * @returns {Boolean} + */ -HDPublicKey.isHDPublicKey = function isHDPublicKey(obj) { - return obj instanceof HDPublicKey; -}; + static isHDPublicKey(obj) { + return obj instanceof HDPublicKey; + } +} /* * Expose diff --git a/lib/hd/udata.json b/lib/hd/udata.json new file mode 100644 index 000000000..5bc5fbb1d --- /dev/null +++ b/lib/hd/udata.json @@ -0,0 +1 @@ +{"0":{"60":[null,null,{"824":8814}],"61":[null,null,{"824":8800}],"62":[null,null,{"824":8815}],"65":[null,null,{"768":192,"769":193,"770":194,"771":195,"772":256,"774":258,"775":550,"776":196,"777":7842,"778":197,"780":461,"783":512,"785":514,"803":7840,"805":7680,"808":260}],"66":[null,null,{"775":7682,"803":7684,"817":7686}],"67":[null,null,{"769":262,"770":264,"775":266,"780":268,"807":199}],"68":[null,null,{"775":7690,"780":270,"803":7692,"807":7696,"813":7698,"817":7694}],"69":[null,null,{"768":200,"769":201,"770":202,"771":7868,"772":274,"774":276,"775":278,"776":203,"777":7866,"780":282,"783":516,"785":518,"803":7864,"807":552,"808":280,"813":7704,"816":7706}],"70":[null,null,{"775":7710}],"71":[null,null,{"769":500,"770":284,"772":7712,"774":286,"775":288,"780":486,"807":290}],"72":[null,null,{"770":292,"775":7714,"776":7718,"780":542,"803":7716,"807":7720,"814":7722}],"73":[null,null,{"768":204,"769":205,"770":206,"771":296,"772":298,"774":300,"775":304,"776":207,"777":7880,"780":463,"783":520,"785":522,"803":7882,"808":302,"816":7724}],"74":[null,null,{"770":308}],"75":[null,null,{"769":7728,"780":488,"803":7730,"807":310,"817":7732}],"76":[null,null,{"769":313,"780":317,"803":7734,"807":315,"813":7740,"817":7738}],"77":[null,null,{"769":7742,"775":7744,"803":7746}],"78":[null,null,{"768":504,"769":323,"771":209,"775":7748,"780":327,"803":7750,"807":325,"813":7754,"817":7752}],"79":[null,null,{"768":210,"769":211,"770":212,"771":213,"772":332,"774":334,"775":558,"776":214,"777":7886,"779":336,"780":465,"783":524,"785":526,"795":416,"803":7884,"808":490}],"80":[null,null,{"769":7764,"775":7766}],"82":[null,null,{"769":340,"775":7768,"780":344,"783":528,"785":530,"803":7770,"807":342,"817":7774}],"83":[null,null,{"769":346,"770":348,"775":7776,"780":352,"803":7778,"806":536,"807":350}],"84":[null,null,{"775":7786,"780":356,"803":7788,"806":538,"807":354,"813":7792,"817":7790}],"85":[null,null,{"768":217,"769":218,"770":219,"771":360,"772":362,"774":364,"776":220,"777":7910,"778":366,"779":368,"780":467,"783":532,"785":534,"795":431,"803":7908,"804":7794,"808":370,"813":7798,"816":7796}],"86":[null,null,{"771":7804,"803":7806}],"87":[null,null,{"768":7808,"769":7810,"770":372,"775":7814,"776":7812,"803":7816}],"88":[null,null,{"775":7818,"776":7820}],"89":[null,null,{"768":7922,"769":221,"770":374,"771":7928,"772":562,"775":7822,"776":376,"777":7926,"803":7924}],"90":[null,null,{"769":377,"770":7824,"775":379,"780":381,"803":7826,"817":7828}],"97":[null,null,{"768":224,"769":225,"770":226,"771":227,"772":257,"774":259,"775":551,"776":228,"777":7843,"778":229,"780":462,"783":513,"785":515,"803":7841,"805":7681,"808":261}],"98":[null,null,{"775":7683,"803":7685,"817":7687}],"99":[null,null,{"769":263,"770":265,"775":267,"780":269,"807":231}],"100":[null,null,{"775":7691,"780":271,"803":7693,"807":7697,"813":7699,"817":7695}],"101":[null,null,{"768":232,"769":233,"770":234,"771":7869,"772":275,"774":277,"775":279,"776":235,"777":7867,"780":283,"783":517,"785":519,"803":7865,"807":553,"808":281,"813":7705,"816":7707}],"102":[null,null,{"775":7711}],"103":[null,null,{"769":501,"770":285,"772":7713,"774":287,"775":289,"780":487,"807":291}],"104":[null,null,{"770":293,"775":7715,"776":7719,"780":543,"803":7717,"807":7721,"814":7723,"817":7830}],"105":[null,null,{"768":236,"769":237,"770":238,"771":297,"772":299,"774":301,"776":239,"777":7881,"780":464,"783":521,"785":523,"803":7883,"808":303,"816":7725}],"106":[null,null,{"770":309,"780":496}],"107":[null,null,{"769":7729,"780":489,"803":7731,"807":311,"817":7733}],"108":[null,null,{"769":314,"780":318,"803":7735,"807":316,"813":7741,"817":7739}],"109":[null,null,{"769":7743,"775":7745,"803":7747}],"110":[null,null,{"768":505,"769":324,"771":241,"775":7749,"780":328,"803":7751,"807":326,"813":7755,"817":7753}],"111":[null,null,{"768":242,"769":243,"770":244,"771":245,"772":333,"774":335,"775":559,"776":246,"777":7887,"779":337,"780":466,"783":525,"785":527,"795":417,"803":7885,"808":491}],"112":[null,null,{"769":7765,"775":7767}],"114":[null,null,{"769":341,"775":7769,"780":345,"783":529,"785":531,"803":7771,"807":343,"817":7775}],"115":[null,null,{"769":347,"770":349,"775":7777,"780":353,"803":7779,"806":537,"807":351}],"116":[null,null,{"775":7787,"776":7831,"780":357,"803":7789,"806":539,"807":355,"813":7793,"817":7791}],"117":[null,null,{"768":249,"769":250,"770":251,"771":361,"772":363,"774":365,"776":252,"777":7911,"778":367,"779":369,"780":468,"783":533,"785":535,"795":432,"803":7909,"804":7795,"808":371,"813":7799,"816":7797}],"118":[null,null,{"771":7805,"803":7807}],"119":[null,null,{"768":7809,"769":7811,"770":373,"775":7815,"776":7813,"778":7832,"803":7817}],"120":[null,null,{"775":7819,"776":7821}],"121":[null,null,{"768":7923,"769":253,"770":375,"771":7929,"772":563,"775":7823,"776":255,"777":7927,"778":7833,"803":7925}],"122":[null,null,{"769":378,"770":7825,"775":380,"780":382,"803":7827,"817":7829}],"160":[[32],256],"168":[[32,776],256,{"768":8173,"769":901,"834":8129}],"170":[[97],256],"175":[[32,772],256],"178":[[50],256],"179":[[51],256],"180":[[32,769],256],"181":[[956],256],"184":[[32,807],256],"185":[[49],256],"186":[[111],256],"188":[[49,8260,52],256],"189":[[49,8260,50],256],"190":[[51,8260,52],256],"192":[[65,768]],"193":[[65,769]],"194":[[65,770],null,{"768":7846,"769":7844,"771":7850,"777":7848}],"195":[[65,771]],"196":[[65,776],null,{"772":478}],"197":[[65,778],null,{"769":506}],"198":[null,null,{"769":508,"772":482}],"199":[[67,807],null,{"769":7688}],"200":[[69,768]],"201":[[69,769]],"202":[[69,770],null,{"768":7872,"769":7870,"771":7876,"777":7874}],"203":[[69,776]],"204":[[73,768]],"205":[[73,769]],"206":[[73,770]],"207":[[73,776],null,{"769":7726}],"209":[[78,771]],"210":[[79,768]],"211":[[79,769]],"212":[[79,770],null,{"768":7890,"769":7888,"771":7894,"777":7892}],"213":[[79,771],null,{"769":7756,"772":556,"776":7758}],"214":[[79,776],null,{"772":554}],"216":[null,null,{"769":510}],"217":[[85,768]],"218":[[85,769]],"219":[[85,770]],"220":[[85,776],null,{"768":475,"769":471,"772":469,"780":473}],"221":[[89,769]],"224":[[97,768]],"225":[[97,769]],"226":[[97,770],null,{"768":7847,"769":7845,"771":7851,"777":7849}],"227":[[97,771]],"228":[[97,776],null,{"772":479}],"229":[[97,778],null,{"769":507}],"230":[null,null,{"769":509,"772":483}],"231":[[99,807],null,{"769":7689}],"232":[[101,768]],"233":[[101,769]],"234":[[101,770],null,{"768":7873,"769":7871,"771":7877,"777":7875}],"235":[[101,776]],"236":[[105,768]],"237":[[105,769]],"238":[[105,770]],"239":[[105,776],null,{"769":7727}],"241":[[110,771]],"242":[[111,768]],"243":[[111,769]],"244":[[111,770],null,{"768":7891,"769":7889,"771":7895,"777":7893}],"245":[[111,771],null,{"769":7757,"772":557,"776":7759}],"246":[[111,776],null,{"772":555}],"248":[null,null,{"769":511}],"249":[[117,768]],"250":[[117,769]],"251":[[117,770]],"252":[[117,776],null,{"768":476,"769":472,"772":470,"780":474}],"253":[[121,769]],"255":[[121,776]]},"256":{"256":[[65,772]],"257":[[97,772]],"258":[[65,774],null,{"768":7856,"769":7854,"771":7860,"777":7858}],"259":[[97,774],null,{"768":7857,"769":7855,"771":7861,"777":7859}],"260":[[65,808]],"261":[[97,808]],"262":[[67,769]],"263":[[99,769]],"264":[[67,770]],"265":[[99,770]],"266":[[67,775]],"267":[[99,775]],"268":[[67,780]],"269":[[99,780]],"270":[[68,780]],"271":[[100,780]],"274":[[69,772],null,{"768":7700,"769":7702}],"275":[[101,772],null,{"768":7701,"769":7703}],"276":[[69,774]],"277":[[101,774]],"278":[[69,775]],"279":[[101,775]],"280":[[69,808]],"281":[[101,808]],"282":[[69,780]],"283":[[101,780]],"284":[[71,770]],"285":[[103,770]],"286":[[71,774]],"287":[[103,774]],"288":[[71,775]],"289":[[103,775]],"290":[[71,807]],"291":[[103,807]],"292":[[72,770]],"293":[[104,770]],"296":[[73,771]],"297":[[105,771]],"298":[[73,772]],"299":[[105,772]],"300":[[73,774]],"301":[[105,774]],"302":[[73,808]],"303":[[105,808]],"304":[[73,775]],"306":[[73,74],256],"307":[[105,106],256],"308":[[74,770]],"309":[[106,770]],"310":[[75,807]],"311":[[107,807]],"313":[[76,769]],"314":[[108,769]],"315":[[76,807]],"316":[[108,807]],"317":[[76,780]],"318":[[108,780]],"319":[[76,183],256],"320":[[108,183],256],"323":[[78,769]],"324":[[110,769]],"325":[[78,807]],"326":[[110,807]],"327":[[78,780]],"328":[[110,780]],"329":[[700,110],256],"332":[[79,772],null,{"768":7760,"769":7762}],"333":[[111,772],null,{"768":7761,"769":7763}],"334":[[79,774]],"335":[[111,774]],"336":[[79,779]],"337":[[111,779]],"340":[[82,769]],"341":[[114,769]],"342":[[82,807]],"343":[[114,807]],"344":[[82,780]],"345":[[114,780]],"346":[[83,769],null,{"775":7780}],"347":[[115,769],null,{"775":7781}],"348":[[83,770]],"349":[[115,770]],"350":[[83,807]],"351":[[115,807]],"352":[[83,780],null,{"775":7782}],"353":[[115,780],null,{"775":7783}],"354":[[84,807]],"355":[[116,807]],"356":[[84,780]],"357":[[116,780]],"360":[[85,771],null,{"769":7800}],"361":[[117,771],null,{"769":7801}],"362":[[85,772],null,{"776":7802}],"363":[[117,772],null,{"776":7803}],"364":[[85,774]],"365":[[117,774]],"366":[[85,778]],"367":[[117,778]],"368":[[85,779]],"369":[[117,779]],"370":[[85,808]],"371":[[117,808]],"372":[[87,770]],"373":[[119,770]],"374":[[89,770]],"375":[[121,770]],"376":[[89,776]],"377":[[90,769]],"378":[[122,769]],"379":[[90,775]],"380":[[122,775]],"381":[[90,780]],"382":[[122,780]],"383":[[115],256,{"775":7835}],"416":[[79,795],null,{"768":7900,"769":7898,"771":7904,"777":7902,"803":7906}],"417":[[111,795],null,{"768":7901,"769":7899,"771":7905,"777":7903,"803":7907}],"431":[[85,795],null,{"768":7914,"769":7912,"771":7918,"777":7916,"803":7920}],"432":[[117,795],null,{"768":7915,"769":7913,"771":7919,"777":7917,"803":7921}],"439":[null,null,{"780":494}],"452":[[68,381],256],"453":[[68,382],256],"454":[[100,382],256],"455":[[76,74],256],"456":[[76,106],256],"457":[[108,106],256],"458":[[78,74],256],"459":[[78,106],256],"460":[[110,106],256],"461":[[65,780]],"462":[[97,780]],"463":[[73,780]],"464":[[105,780]],"465":[[79,780]],"466":[[111,780]],"467":[[85,780]],"468":[[117,780]],"469":[[220,772]],"470":[[252,772]],"471":[[220,769]],"472":[[252,769]],"473":[[220,780]],"474":[[252,780]],"475":[[220,768]],"476":[[252,768]],"478":[[196,772]],"479":[[228,772]],"480":[[550,772]],"481":[[551,772]],"482":[[198,772]],"483":[[230,772]],"486":[[71,780]],"487":[[103,780]],"488":[[75,780]],"489":[[107,780]],"490":[[79,808],null,{"772":492}],"491":[[111,808],null,{"772":493}],"492":[[490,772]],"493":[[491,772]],"494":[[439,780]],"495":[[658,780]],"496":[[106,780]],"497":[[68,90],256],"498":[[68,122],256],"499":[[100,122],256],"500":[[71,769]],"501":[[103,769]],"504":[[78,768]],"505":[[110,768]],"506":[[197,769]],"507":[[229,769]],"508":[[198,769]],"509":[[230,769]],"510":[[216,769]],"511":[[248,769]],"66045":[null,220]},"512":{"512":[[65,783]],"513":[[97,783]],"514":[[65,785]],"515":[[97,785]],"516":[[69,783]],"517":[[101,783]],"518":[[69,785]],"519":[[101,785]],"520":[[73,783]],"521":[[105,783]],"522":[[73,785]],"523":[[105,785]],"524":[[79,783]],"525":[[111,783]],"526":[[79,785]],"527":[[111,785]],"528":[[82,783]],"529":[[114,783]],"530":[[82,785]],"531":[[114,785]],"532":[[85,783]],"533":[[117,783]],"534":[[85,785]],"535":[[117,785]],"536":[[83,806]],"537":[[115,806]],"538":[[84,806]],"539":[[116,806]],"542":[[72,780]],"543":[[104,780]],"550":[[65,775],null,{"772":480}],"551":[[97,775],null,{"772":481}],"552":[[69,807],null,{"774":7708}],"553":[[101,807],null,{"774":7709}],"554":[[214,772]],"555":[[246,772]],"556":[[213,772]],"557":[[245,772]],"558":[[79,775],null,{"772":560}],"559":[[111,775],null,{"772":561}],"560":[[558,772]],"561":[[559,772]],"562":[[89,772]],"563":[[121,772]],"658":[null,null,{"780":495}],"688":[[104],256],"689":[[614],256],"690":[[106],256],"691":[[114],256],"692":[[633],256],"693":[[635],256],"694":[[641],256],"695":[[119],256],"696":[[121],256],"728":[[32,774],256],"729":[[32,775],256],"730":[[32,778],256],"731":[[32,808],256],"732":[[32,771],256],"733":[[32,779],256],"736":[[611],256],"737":[[108],256],"738":[[115],256],"739":[[120],256],"740":[[661],256],"66272":[null,220]},"768":{"768":[null,230],"769":[null,230],"770":[null,230],"771":[null,230],"772":[null,230],"773":[null,230],"774":[null,230],"775":[null,230],"776":[null,230,{"769":836}],"777":[null,230],"778":[null,230],"779":[null,230],"780":[null,230],"781":[null,230],"782":[null,230],"783":[null,230],"784":[null,230],"785":[null,230],"786":[null,230],"787":[null,230],"788":[null,230],"789":[null,232],"790":[null,220],"791":[null,220],"792":[null,220],"793":[null,220],"794":[null,232],"795":[null,216],"796":[null,220],"797":[null,220],"798":[null,220],"799":[null,220],"800":[null,220],"801":[null,202],"802":[null,202],"803":[null,220],"804":[null,220],"805":[null,220],"806":[null,220],"807":[null,202],"808":[null,202],"809":[null,220],"810":[null,220],"811":[null,220],"812":[null,220],"813":[null,220],"814":[null,220],"815":[null,220],"816":[null,220],"817":[null,220],"818":[null,220],"819":[null,220],"820":[null,1],"821":[null,1],"822":[null,1],"823":[null,1],"824":[null,1],"825":[null,220],"826":[null,220],"827":[null,220],"828":[null,220],"829":[null,230],"830":[null,230],"831":[null,230],"832":[[768],230],"833":[[769],230],"834":[null,230],"835":[[787],230],"836":[[776,769],230],"837":[null,240],"838":[null,230],"839":[null,220],"840":[null,220],"841":[null,220],"842":[null,230],"843":[null,230],"844":[null,230],"845":[null,220],"846":[null,220],"848":[null,230],"849":[null,230],"850":[null,230],"851":[null,220],"852":[null,220],"853":[null,220],"854":[null,220],"855":[null,230],"856":[null,232],"857":[null,220],"858":[null,220],"859":[null,230],"860":[null,233],"861":[null,234],"862":[null,234],"863":[null,233],"864":[null,234],"865":[null,234],"866":[null,233],"867":[null,230],"868":[null,230],"869":[null,230],"870":[null,230],"871":[null,230],"872":[null,230],"873":[null,230],"874":[null,230],"875":[null,230],"876":[null,230],"877":[null,230],"878":[null,230],"879":[null,230],"884":[[697]],"890":[[32,837],256],"894":[[59]],"900":[[32,769],256],"901":[[168,769]],"902":[[913,769]],"903":[[183]],"904":[[917,769]],"905":[[919,769]],"906":[[921,769]],"908":[[927,769]],"910":[[933,769]],"911":[[937,769]],"912":[[970,769]],"913":[null,null,{"768":8122,"769":902,"772":8121,"774":8120,"787":7944,"788":7945,"837":8124}],"917":[null,null,{"768":8136,"769":904,"787":7960,"788":7961}],"919":[null,null,{"768":8138,"769":905,"787":7976,"788":7977,"837":8140}],"921":[null,null,{"768":8154,"769":906,"772":8153,"774":8152,"776":938,"787":7992,"788":7993}],"927":[null,null,{"768":8184,"769":908,"787":8008,"788":8009}],"929":[null,null,{"788":8172}],"933":[null,null,{"768":8170,"769":910,"772":8169,"774":8168,"776":939,"788":8025}],"937":[null,null,{"768":8186,"769":911,"787":8040,"788":8041,"837":8188}],"938":[[921,776]],"939":[[933,776]],"940":[[945,769],null,{"837":8116}],"941":[[949,769]],"942":[[951,769],null,{"837":8132}],"943":[[953,769]],"944":[[971,769]],"945":[null,null,{"768":8048,"769":940,"772":8113,"774":8112,"787":7936,"788":7937,"834":8118,"837":8115}],"949":[null,null,{"768":8050,"769":941,"787":7952,"788":7953}],"951":[null,null,{"768":8052,"769":942,"787":7968,"788":7969,"834":8134,"837":8131}],"953":[null,null,{"768":8054,"769":943,"772":8145,"774":8144,"776":970,"787":7984,"788":7985,"834":8150}],"959":[null,null,{"768":8056,"769":972,"787":8000,"788":8001}],"961":[null,null,{"787":8164,"788":8165}],"965":[null,null,{"768":8058,"769":973,"772":8161,"774":8160,"776":971,"787":8016,"788":8017,"834":8166}],"969":[null,null,{"768":8060,"769":974,"787":8032,"788":8033,"834":8182,"837":8179}],"970":[[953,776],null,{"768":8146,"769":912,"834":8151}],"971":[[965,776],null,{"768":8162,"769":944,"834":8167}],"972":[[959,769]],"973":[[965,769]],"974":[[969,769],null,{"837":8180}],"976":[[946],256],"977":[[952],256],"978":[[933],256,{"769":979,"776":980}],"979":[[978,769]],"980":[[978,776]],"981":[[966],256],"982":[[960],256],"1008":[[954],256],"1009":[[961],256],"1010":[[962],256],"1012":[[920],256],"1013":[[949],256],"1017":[[931],256],"66422":[null,230],"66423":[null,230],"66424":[null,230],"66425":[null,230],"66426":[null,230]},"1024":{"1024":[[1045,768]],"1025":[[1045,776]],"1027":[[1043,769]],"1030":[null,null,{"776":1031}],"1031":[[1030,776]],"1036":[[1050,769]],"1037":[[1048,768]],"1038":[[1059,774]],"1040":[null,null,{"774":1232,"776":1234}],"1043":[null,null,{"769":1027}],"1045":[null,null,{"768":1024,"774":1238,"776":1025}],"1046":[null,null,{"774":1217,"776":1244}],"1047":[null,null,{"776":1246}],"1048":[null,null,{"768":1037,"772":1250,"774":1049,"776":1252}],"1049":[[1048,774]],"1050":[null,null,{"769":1036}],"1054":[null,null,{"776":1254}],"1059":[null,null,{"772":1262,"774":1038,"776":1264,"779":1266}],"1063":[null,null,{"776":1268}],"1067":[null,null,{"776":1272}],"1069":[null,null,{"776":1260}],"1072":[null,null,{"774":1233,"776":1235}],"1075":[null,null,{"769":1107}],"1077":[null,null,{"768":1104,"774":1239,"776":1105}],"1078":[null,null,{"774":1218,"776":1245}],"1079":[null,null,{"776":1247}],"1080":[null,null,{"768":1117,"772":1251,"774":1081,"776":1253}],"1081":[[1080,774]],"1082":[null,null,{"769":1116}],"1086":[null,null,{"776":1255}],"1091":[null,null,{"772":1263,"774":1118,"776":1265,"779":1267}],"1095":[null,null,{"776":1269}],"1099":[null,null,{"776":1273}],"1101":[null,null,{"776":1261}],"1104":[[1077,768]],"1105":[[1077,776]],"1107":[[1075,769]],"1110":[null,null,{"776":1111}],"1111":[[1110,776]],"1116":[[1082,769]],"1117":[[1080,768]],"1118":[[1091,774]],"1140":[null,null,{"783":1142}],"1141":[null,null,{"783":1143}],"1142":[[1140,783]],"1143":[[1141,783]],"1155":[null,230],"1156":[null,230],"1157":[null,230],"1158":[null,230],"1159":[null,230],"1217":[[1046,774]],"1218":[[1078,774]],"1232":[[1040,774]],"1233":[[1072,774]],"1234":[[1040,776]],"1235":[[1072,776]],"1238":[[1045,774]],"1239":[[1077,774]],"1240":[null,null,{"776":1242}],"1241":[null,null,{"776":1243}],"1242":[[1240,776]],"1243":[[1241,776]],"1244":[[1046,776]],"1245":[[1078,776]],"1246":[[1047,776]],"1247":[[1079,776]],"1250":[[1048,772]],"1251":[[1080,772]],"1252":[[1048,776]],"1253":[[1080,776]],"1254":[[1054,776]],"1255":[[1086,776]],"1256":[null,null,{"776":1258}],"1257":[null,null,{"776":1259}],"1258":[[1256,776]],"1259":[[1257,776]],"1260":[[1069,776]],"1261":[[1101,776]],"1262":[[1059,772]],"1263":[[1091,772]],"1264":[[1059,776]],"1265":[[1091,776]],"1266":[[1059,779]],"1267":[[1091,779]],"1268":[[1063,776]],"1269":[[1095,776]],"1272":[[1067,776]],"1273":[[1099,776]]},"1280":{"1415":[[1381,1410],256],"1425":[null,220],"1426":[null,230],"1427":[null,230],"1428":[null,230],"1429":[null,230],"1430":[null,220],"1431":[null,230],"1432":[null,230],"1433":[null,230],"1434":[null,222],"1435":[null,220],"1436":[null,230],"1437":[null,230],"1438":[null,230],"1439":[null,230],"1440":[null,230],"1441":[null,230],"1442":[null,220],"1443":[null,220],"1444":[null,220],"1445":[null,220],"1446":[null,220],"1447":[null,220],"1448":[null,230],"1449":[null,230],"1450":[null,220],"1451":[null,230],"1452":[null,230],"1453":[null,222],"1454":[null,228],"1455":[null,230],"1456":[null,10],"1457":[null,11],"1458":[null,12],"1459":[null,13],"1460":[null,14],"1461":[null,15],"1462":[null,16],"1463":[null,17],"1464":[null,18],"1465":[null,19],"1466":[null,19],"1467":[null,20],"1468":[null,21],"1469":[null,22],"1471":[null,23],"1473":[null,24],"1474":[null,25],"1476":[null,230],"1477":[null,220],"1479":[null,18]},"1536":{"1552":[null,230],"1553":[null,230],"1554":[null,230],"1555":[null,230],"1556":[null,230],"1557":[null,230],"1558":[null,230],"1559":[null,230],"1560":[null,30],"1561":[null,31],"1562":[null,32],"1570":[[1575,1619]],"1571":[[1575,1620]],"1572":[[1608,1620]],"1573":[[1575,1621]],"1574":[[1610,1620]],"1575":[null,null,{"1619":1570,"1620":1571,"1621":1573}],"1608":[null,null,{"1620":1572}],"1610":[null,null,{"1620":1574}],"1611":[null,27],"1612":[null,28],"1613":[null,29],"1614":[null,30],"1615":[null,31],"1616":[null,32],"1617":[null,33],"1618":[null,34],"1619":[null,230],"1620":[null,230],"1621":[null,220],"1622":[null,220],"1623":[null,230],"1624":[null,230],"1625":[null,230],"1626":[null,230],"1627":[null,230],"1628":[null,220],"1629":[null,230],"1630":[null,230],"1631":[null,220],"1648":[null,35],"1653":[[1575,1652],256],"1654":[[1608,1652],256],"1655":[[1735,1652],256],"1656":[[1610,1652],256],"1728":[[1749,1620]],"1729":[null,null,{"1620":1730}],"1730":[[1729,1620]],"1746":[null,null,{"1620":1747}],"1747":[[1746,1620]],"1749":[null,null,{"1620":1728}],"1750":[null,230],"1751":[null,230],"1752":[null,230],"1753":[null,230],"1754":[null,230],"1755":[null,230],"1756":[null,230],"1759":[null,230],"1760":[null,230],"1761":[null,230],"1762":[null,230],"1763":[null,220],"1764":[null,230],"1767":[null,230],"1768":[null,230],"1770":[null,220],"1771":[null,230],"1772":[null,230],"1773":[null,220]},"1792":{"1809":[null,36],"1840":[null,230],"1841":[null,220],"1842":[null,230],"1843":[null,230],"1844":[null,220],"1845":[null,230],"1846":[null,230],"1847":[null,220],"1848":[null,220],"1849":[null,220],"1850":[null,230],"1851":[null,220],"1852":[null,220],"1853":[null,230],"1854":[null,220],"1855":[null,230],"1856":[null,230],"1857":[null,230],"1858":[null,220],"1859":[null,230],"1860":[null,220],"1861":[null,230],"1862":[null,220],"1863":[null,230],"1864":[null,220],"1865":[null,230],"1866":[null,230],"2027":[null,230],"2028":[null,230],"2029":[null,230],"2030":[null,230],"2031":[null,230],"2032":[null,230],"2033":[null,230],"2034":[null,220],"2035":[null,230]},"2048":{"2070":[null,230],"2071":[null,230],"2072":[null,230],"2073":[null,230],"2075":[null,230],"2076":[null,230],"2077":[null,230],"2078":[null,230],"2079":[null,230],"2080":[null,230],"2081":[null,230],"2082":[null,230],"2083":[null,230],"2085":[null,230],"2086":[null,230],"2087":[null,230],"2089":[null,230],"2090":[null,230],"2091":[null,230],"2092":[null,230],"2093":[null,230],"2137":[null,220],"2138":[null,220],"2139":[null,220],"2276":[null,230],"2277":[null,230],"2278":[null,220],"2279":[null,230],"2280":[null,230],"2281":[null,220],"2282":[null,230],"2283":[null,230],"2284":[null,230],"2285":[null,220],"2286":[null,220],"2287":[null,220],"2288":[null,27],"2289":[null,28],"2290":[null,29],"2291":[null,230],"2292":[null,230],"2293":[null,230],"2294":[null,220],"2295":[null,230],"2296":[null,230],"2297":[null,220],"2298":[null,220],"2299":[null,230],"2300":[null,230],"2301":[null,230],"2302":[null,230],"2303":[null,230]},"2304":{"2344":[null,null,{"2364":2345}],"2345":[[2344,2364]],"2352":[null,null,{"2364":2353}],"2353":[[2352,2364]],"2355":[null,null,{"2364":2356}],"2356":[[2355,2364]],"2364":[null,7],"2381":[null,9],"2385":[null,230],"2386":[null,220],"2387":[null,230],"2388":[null,230],"2392":[[2325,2364],512],"2393":[[2326,2364],512],"2394":[[2327,2364],512],"2395":[[2332,2364],512],"2396":[[2337,2364],512],"2397":[[2338,2364],512],"2398":[[2347,2364],512],"2399":[[2351,2364],512],"2492":[null,7],"2503":[null,null,{"2494":2507,"2519":2508}],"2507":[[2503,2494]],"2508":[[2503,2519]],"2509":[null,9],"2524":[[2465,2492],512],"2525":[[2466,2492],512],"2527":[[2479,2492],512]},"2560":{"2611":[[2610,2620],512],"2614":[[2616,2620],512],"2620":[null,7],"2637":[null,9],"2649":[[2582,2620],512],"2650":[[2583,2620],512],"2651":[[2588,2620],512],"2654":[[2603,2620],512],"2748":[null,7],"2765":[null,9],"68109":[null,220],"68111":[null,230],"68152":[null,230],"68153":[null,1],"68154":[null,220],"68159":[null,9],"68325":[null,230],"68326":[null,220]},"2816":{"2876":[null,7],"2887":[null,null,{"2878":2891,"2902":2888,"2903":2892}],"2888":[[2887,2902]],"2891":[[2887,2878]],"2892":[[2887,2903]],"2893":[null,9],"2908":[[2849,2876],512],"2909":[[2850,2876],512],"2962":[null,null,{"3031":2964}],"2964":[[2962,3031]],"3014":[null,null,{"3006":3018,"3031":3020}],"3015":[null,null,{"3006":3019}],"3018":[[3014,3006]],"3019":[[3015,3006]],"3020":[[3014,3031]],"3021":[null,9]},"3072":{"3142":[null,null,{"3158":3144}],"3144":[[3142,3158]],"3149":[null,9],"3157":[null,84],"3158":[null,91],"3260":[null,7],"3263":[null,null,{"3285":3264}],"3264":[[3263,3285]],"3270":[null,null,{"3266":3274,"3285":3271,"3286":3272}],"3271":[[3270,3285]],"3272":[[3270,3286]],"3274":[[3270,3266],null,{"3285":3275}],"3275":[[3274,3285]],"3277":[null,9]},"3328":{"3398":[null,null,{"3390":3402,"3415":3404}],"3399":[null,null,{"3390":3403}],"3402":[[3398,3390]],"3403":[[3399,3390]],"3404":[[3398,3415]],"3405":[null,9],"3530":[null,9],"3545":[null,null,{"3530":3546,"3535":3548,"3551":3550}],"3546":[[3545,3530]],"3548":[[3545,3535],null,{"3530":3549}],"3549":[[3548,3530]],"3550":[[3545,3551]]},"3584":{"3635":[[3661,3634],256],"3640":[null,103],"3641":[null,103],"3642":[null,9],"3656":[null,107],"3657":[null,107],"3658":[null,107],"3659":[null,107],"3763":[[3789,3762],256],"3768":[null,118],"3769":[null,118],"3784":[null,122],"3785":[null,122],"3786":[null,122],"3787":[null,122],"3804":[[3755,3737],256],"3805":[[3755,3745],256]},"3840":{"3852":[[3851],256],"3864":[null,220],"3865":[null,220],"3893":[null,220],"3895":[null,220],"3897":[null,216],"3907":[[3906,4023],512],"3917":[[3916,4023],512],"3922":[[3921,4023],512],"3927":[[3926,4023],512],"3932":[[3931,4023],512],"3945":[[3904,4021],512],"3953":[null,129],"3954":[null,130],"3955":[[3953,3954],512],"3956":[null,132],"3957":[[3953,3956],512],"3958":[[4018,3968],512],"3959":[[4018,3969],256],"3960":[[4019,3968],512],"3961":[[4019,3969],256],"3962":[null,130],"3963":[null,130],"3964":[null,130],"3965":[null,130],"3968":[null,130],"3969":[[3953,3968],512],"3970":[null,230],"3971":[null,230],"3972":[null,9],"3974":[null,230],"3975":[null,230],"3987":[[3986,4023],512],"3997":[[3996,4023],512],"4002":[[4001,4023],512],"4007":[[4006,4023],512],"4012":[[4011,4023],512],"4025":[[3984,4021],512],"4038":[null,220]},"4096":{"4133":[null,null,{"4142":4134}],"4134":[[4133,4142]],"4151":[null,7],"4153":[null,9],"4154":[null,9],"4237":[null,220],"4348":[[4316],256],"69702":[null,9],"69759":[null,9],"69785":[null,null,{"69818":69786}],"69786":[[69785,69818]],"69787":[null,null,{"69818":69788}],"69788":[[69787,69818]],"69797":[null,null,{"69818":69803}],"69803":[[69797,69818]],"69817":[null,9],"69818":[null,7]},"4352":{"69888":[null,230],"69889":[null,230],"69890":[null,230],"69934":[[69937,69927]],"69935":[[69938,69927]],"69937":[null,null,{"69927":69934}],"69938":[null,null,{"69927":69935}],"69939":[null,9],"69940":[null,9],"70003":[null,7],"70080":[null,9]},"4608":{"70197":[null,9],"70198":[null,7],"70377":[null,7],"70378":[null,9]},"4864":{"4957":[null,230],"4958":[null,230],"4959":[null,230],"70460":[null,7],"70471":[null,null,{"70462":70475,"70487":70476}],"70475":[[70471,70462]],"70476":[[70471,70487]],"70477":[null,9],"70502":[null,230],"70503":[null,230],"70504":[null,230],"70505":[null,230],"70506":[null,230],"70507":[null,230],"70508":[null,230],"70512":[null,230],"70513":[null,230],"70514":[null,230],"70515":[null,230],"70516":[null,230]},"5120":{"70841":[null,null,{"70832":70844,"70842":70843,"70845":70846}],"70843":[[70841,70842]],"70844":[[70841,70832]],"70846":[[70841,70845]],"70850":[null,9],"70851":[null,7]},"5376":{"71096":[null,null,{"71087":71098}],"71097":[null,null,{"71087":71099}],"71098":[[71096,71087]],"71099":[[71097,71087]],"71103":[null,9],"71104":[null,7]},"5632":{"71231":[null,9],"71350":[null,9],"71351":[null,7]},"5888":{"5908":[null,9],"5940":[null,9],"6098":[null,9],"6109":[null,230]},"6144":{"6313":[null,228]},"6400":{"6457":[null,222],"6458":[null,230],"6459":[null,220]},"6656":{"6679":[null,230],"6680":[null,220],"6752":[null,9],"6773":[null,230],"6774":[null,230],"6775":[null,230],"6776":[null,230],"6777":[null,230],"6778":[null,230],"6779":[null,230],"6780":[null,230],"6783":[null,220],"6832":[null,230],"6833":[null,230],"6834":[null,230],"6835":[null,230],"6836":[null,230],"6837":[null,220],"6838":[null,220],"6839":[null,220],"6840":[null,220],"6841":[null,220],"6842":[null,220],"6843":[null,230],"6844":[null,230],"6845":[null,220]},"6912":{"6917":[null,null,{"6965":6918}],"6918":[[6917,6965]],"6919":[null,null,{"6965":6920}],"6920":[[6919,6965]],"6921":[null,null,{"6965":6922}],"6922":[[6921,6965]],"6923":[null,null,{"6965":6924}],"6924":[[6923,6965]],"6925":[null,null,{"6965":6926}],"6926":[[6925,6965]],"6929":[null,null,{"6965":6930}],"6930":[[6929,6965]],"6964":[null,7],"6970":[null,null,{"6965":6971}],"6971":[[6970,6965]],"6972":[null,null,{"6965":6973}],"6973":[[6972,6965]],"6974":[null,null,{"6965":6976}],"6975":[null,null,{"6965":6977}],"6976":[[6974,6965]],"6977":[[6975,6965]],"6978":[null,null,{"6965":6979}],"6979":[[6978,6965]],"6980":[null,9],"7019":[null,230],"7020":[null,220],"7021":[null,230],"7022":[null,230],"7023":[null,230],"7024":[null,230],"7025":[null,230],"7026":[null,230],"7027":[null,230],"7082":[null,9],"7083":[null,9],"7142":[null,7],"7154":[null,9],"7155":[null,9]},"7168":{"7223":[null,7],"7376":[null,230],"7377":[null,230],"7378":[null,230],"7380":[null,1],"7381":[null,220],"7382":[null,220],"7383":[null,220],"7384":[null,220],"7385":[null,220],"7386":[null,230],"7387":[null,230],"7388":[null,220],"7389":[null,220],"7390":[null,220],"7391":[null,220],"7392":[null,230],"7394":[null,1],"7395":[null,1],"7396":[null,1],"7397":[null,1],"7398":[null,1],"7399":[null,1],"7400":[null,1],"7405":[null,220],"7412":[null,230],"7416":[null,230],"7417":[null,230]},"7424":{"7468":[[65],256],"7469":[[198],256],"7470":[[66],256],"7472":[[68],256],"7473":[[69],256],"7474":[[398],256],"7475":[[71],256],"7476":[[72],256],"7477":[[73],256],"7478":[[74],256],"7479":[[75],256],"7480":[[76],256],"7481":[[77],256],"7482":[[78],256],"7484":[[79],256],"7485":[[546],256],"7486":[[80],256],"7487":[[82],256],"7488":[[84],256],"7489":[[85],256],"7490":[[87],256],"7491":[[97],256],"7492":[[592],256],"7493":[[593],256],"7494":[[7426],256],"7495":[[98],256],"7496":[[100],256],"7497":[[101],256],"7498":[[601],256],"7499":[[603],256],"7500":[[604],256],"7501":[[103],256],"7503":[[107],256],"7504":[[109],256],"7505":[[331],256],"7506":[[111],256],"7507":[[596],256],"7508":[[7446],256],"7509":[[7447],256],"7510":[[112],256],"7511":[[116],256],"7512":[[117],256],"7513":[[7453],256],"7514":[[623],256],"7515":[[118],256],"7516":[[7461],256],"7517":[[946],256],"7518":[[947],256],"7519":[[948],256],"7520":[[966],256],"7521":[[967],256],"7522":[[105],256],"7523":[[114],256],"7524":[[117],256],"7525":[[118],256],"7526":[[946],256],"7527":[[947],256],"7528":[[961],256],"7529":[[966],256],"7530":[[967],256],"7544":[[1085],256],"7579":[[594],256],"7580":[[99],256],"7581":[[597],256],"7582":[[240],256],"7583":[[604],256],"7584":[[102],256],"7585":[[607],256],"7586":[[609],256],"7587":[[613],256],"7588":[[616],256],"7589":[[617],256],"7590":[[618],256],"7591":[[7547],256],"7592":[[669],256],"7593":[[621],256],"7594":[[7557],256],"7595":[[671],256],"7596":[[625],256],"7597":[[624],256],"7598":[[626],256],"7599":[[627],256],"7600":[[628],256],"7601":[[629],256],"7602":[[632],256],"7603":[[642],256],"7604":[[643],256],"7605":[[427],256],"7606":[[649],256],"7607":[[650],256],"7608":[[7452],256],"7609":[[651],256],"7610":[[652],256],"7611":[[122],256],"7612":[[656],256],"7613":[[657],256],"7614":[[658],256],"7615":[[952],256],"7616":[null,230],"7617":[null,230],"7618":[null,220],"7619":[null,230],"7620":[null,230],"7621":[null,230],"7622":[null,230],"7623":[null,230],"7624":[null,230],"7625":[null,230],"7626":[null,220],"7627":[null,230],"7628":[null,230],"7629":[null,234],"7630":[null,214],"7631":[null,220],"7632":[null,202],"7633":[null,230],"7634":[null,230],"7635":[null,230],"7636":[null,230],"7637":[null,230],"7638":[null,230],"7639":[null,230],"7640":[null,230],"7641":[null,230],"7642":[null,230],"7643":[null,230],"7644":[null,230],"7645":[null,230],"7646":[null,230],"7647":[null,230],"7648":[null,230],"7649":[null,230],"7650":[null,230],"7651":[null,230],"7652":[null,230],"7653":[null,230],"7654":[null,230],"7655":[null,230],"7656":[null,230],"7657":[null,230],"7658":[null,230],"7659":[null,230],"7660":[null,230],"7661":[null,230],"7662":[null,230],"7663":[null,230],"7664":[null,230],"7665":[null,230],"7666":[null,230],"7667":[null,230],"7668":[null,230],"7669":[null,230],"7676":[null,233],"7677":[null,220],"7678":[null,230],"7679":[null,220]},"7680":{"7680":[[65,805]],"7681":[[97,805]],"7682":[[66,775]],"7683":[[98,775]],"7684":[[66,803]],"7685":[[98,803]],"7686":[[66,817]],"7687":[[98,817]],"7688":[[199,769]],"7689":[[231,769]],"7690":[[68,775]],"7691":[[100,775]],"7692":[[68,803]],"7693":[[100,803]],"7694":[[68,817]],"7695":[[100,817]],"7696":[[68,807]],"7697":[[100,807]],"7698":[[68,813]],"7699":[[100,813]],"7700":[[274,768]],"7701":[[275,768]],"7702":[[274,769]],"7703":[[275,769]],"7704":[[69,813]],"7705":[[101,813]],"7706":[[69,816]],"7707":[[101,816]],"7708":[[552,774]],"7709":[[553,774]],"7710":[[70,775]],"7711":[[102,775]],"7712":[[71,772]],"7713":[[103,772]],"7714":[[72,775]],"7715":[[104,775]],"7716":[[72,803]],"7717":[[104,803]],"7718":[[72,776]],"7719":[[104,776]],"7720":[[72,807]],"7721":[[104,807]],"7722":[[72,814]],"7723":[[104,814]],"7724":[[73,816]],"7725":[[105,816]],"7726":[[207,769]],"7727":[[239,769]],"7728":[[75,769]],"7729":[[107,769]],"7730":[[75,803]],"7731":[[107,803]],"7732":[[75,817]],"7733":[[107,817]],"7734":[[76,803],null,{"772":7736}],"7735":[[108,803],null,{"772":7737}],"7736":[[7734,772]],"7737":[[7735,772]],"7738":[[76,817]],"7739":[[108,817]],"7740":[[76,813]],"7741":[[108,813]],"7742":[[77,769]],"7743":[[109,769]],"7744":[[77,775]],"7745":[[109,775]],"7746":[[77,803]],"7747":[[109,803]],"7748":[[78,775]],"7749":[[110,775]],"7750":[[78,803]],"7751":[[110,803]],"7752":[[78,817]],"7753":[[110,817]],"7754":[[78,813]],"7755":[[110,813]],"7756":[[213,769]],"7757":[[245,769]],"7758":[[213,776]],"7759":[[245,776]],"7760":[[332,768]],"7761":[[333,768]],"7762":[[332,769]],"7763":[[333,769]],"7764":[[80,769]],"7765":[[112,769]],"7766":[[80,775]],"7767":[[112,775]],"7768":[[82,775]],"7769":[[114,775]],"7770":[[82,803],null,{"772":7772}],"7771":[[114,803],null,{"772":7773}],"7772":[[7770,772]],"7773":[[7771,772]],"7774":[[82,817]],"7775":[[114,817]],"7776":[[83,775]],"7777":[[115,775]],"7778":[[83,803],null,{"775":7784}],"7779":[[115,803],null,{"775":7785}],"7780":[[346,775]],"7781":[[347,775]],"7782":[[352,775]],"7783":[[353,775]],"7784":[[7778,775]],"7785":[[7779,775]],"7786":[[84,775]],"7787":[[116,775]],"7788":[[84,803]],"7789":[[116,803]],"7790":[[84,817]],"7791":[[116,817]],"7792":[[84,813]],"7793":[[116,813]],"7794":[[85,804]],"7795":[[117,804]],"7796":[[85,816]],"7797":[[117,816]],"7798":[[85,813]],"7799":[[117,813]],"7800":[[360,769]],"7801":[[361,769]],"7802":[[362,776]],"7803":[[363,776]],"7804":[[86,771]],"7805":[[118,771]],"7806":[[86,803]],"7807":[[118,803]],"7808":[[87,768]],"7809":[[119,768]],"7810":[[87,769]],"7811":[[119,769]],"7812":[[87,776]],"7813":[[119,776]],"7814":[[87,775]],"7815":[[119,775]],"7816":[[87,803]],"7817":[[119,803]],"7818":[[88,775]],"7819":[[120,775]],"7820":[[88,776]],"7821":[[120,776]],"7822":[[89,775]],"7823":[[121,775]],"7824":[[90,770]],"7825":[[122,770]],"7826":[[90,803]],"7827":[[122,803]],"7828":[[90,817]],"7829":[[122,817]],"7830":[[104,817]],"7831":[[116,776]],"7832":[[119,778]],"7833":[[121,778]],"7834":[[97,702],256],"7835":[[383,775]],"7840":[[65,803],null,{"770":7852,"774":7862}],"7841":[[97,803],null,{"770":7853,"774":7863}],"7842":[[65,777]],"7843":[[97,777]],"7844":[[194,769]],"7845":[[226,769]],"7846":[[194,768]],"7847":[[226,768]],"7848":[[194,777]],"7849":[[226,777]],"7850":[[194,771]],"7851":[[226,771]],"7852":[[7840,770]],"7853":[[7841,770]],"7854":[[258,769]],"7855":[[259,769]],"7856":[[258,768]],"7857":[[259,768]],"7858":[[258,777]],"7859":[[259,777]],"7860":[[258,771]],"7861":[[259,771]],"7862":[[7840,774]],"7863":[[7841,774]],"7864":[[69,803],null,{"770":7878}],"7865":[[101,803],null,{"770":7879}],"7866":[[69,777]],"7867":[[101,777]],"7868":[[69,771]],"7869":[[101,771]],"7870":[[202,769]],"7871":[[234,769]],"7872":[[202,768]],"7873":[[234,768]],"7874":[[202,777]],"7875":[[234,777]],"7876":[[202,771]],"7877":[[234,771]],"7878":[[7864,770]],"7879":[[7865,770]],"7880":[[73,777]],"7881":[[105,777]],"7882":[[73,803]],"7883":[[105,803]],"7884":[[79,803],null,{"770":7896}],"7885":[[111,803],null,{"770":7897}],"7886":[[79,777]],"7887":[[111,777]],"7888":[[212,769]],"7889":[[244,769]],"7890":[[212,768]],"7891":[[244,768]],"7892":[[212,777]],"7893":[[244,777]],"7894":[[212,771]],"7895":[[244,771]],"7896":[[7884,770]],"7897":[[7885,770]],"7898":[[416,769]],"7899":[[417,769]],"7900":[[416,768]],"7901":[[417,768]],"7902":[[416,777]],"7903":[[417,777]],"7904":[[416,771]],"7905":[[417,771]],"7906":[[416,803]],"7907":[[417,803]],"7908":[[85,803]],"7909":[[117,803]],"7910":[[85,777]],"7911":[[117,777]],"7912":[[431,769]],"7913":[[432,769]],"7914":[[431,768]],"7915":[[432,768]],"7916":[[431,777]],"7917":[[432,777]],"7918":[[431,771]],"7919":[[432,771]],"7920":[[431,803]],"7921":[[432,803]],"7922":[[89,768]],"7923":[[121,768]],"7924":[[89,803]],"7925":[[121,803]],"7926":[[89,777]],"7927":[[121,777]],"7928":[[89,771]],"7929":[[121,771]]},"7936":{"7936":[[945,787],null,{"768":7938,"769":7940,"834":7942,"837":8064}],"7937":[[945,788],null,{"768":7939,"769":7941,"834":7943,"837":8065}],"7938":[[7936,768],null,{"837":8066}],"7939":[[7937,768],null,{"837":8067}],"7940":[[7936,769],null,{"837":8068}],"7941":[[7937,769],null,{"837":8069}],"7942":[[7936,834],null,{"837":8070}],"7943":[[7937,834],null,{"837":8071}],"7944":[[913,787],null,{"768":7946,"769":7948,"834":7950,"837":8072}],"7945":[[913,788],null,{"768":7947,"769":7949,"834":7951,"837":8073}],"7946":[[7944,768],null,{"837":8074}],"7947":[[7945,768],null,{"837":8075}],"7948":[[7944,769],null,{"837":8076}],"7949":[[7945,769],null,{"837":8077}],"7950":[[7944,834],null,{"837":8078}],"7951":[[7945,834],null,{"837":8079}],"7952":[[949,787],null,{"768":7954,"769":7956}],"7953":[[949,788],null,{"768":7955,"769":7957}],"7954":[[7952,768]],"7955":[[7953,768]],"7956":[[7952,769]],"7957":[[7953,769]],"7960":[[917,787],null,{"768":7962,"769":7964}],"7961":[[917,788],null,{"768":7963,"769":7965}],"7962":[[7960,768]],"7963":[[7961,768]],"7964":[[7960,769]],"7965":[[7961,769]],"7968":[[951,787],null,{"768":7970,"769":7972,"834":7974,"837":8080}],"7969":[[951,788],null,{"768":7971,"769":7973,"834":7975,"837":8081}],"7970":[[7968,768],null,{"837":8082}],"7971":[[7969,768],null,{"837":8083}],"7972":[[7968,769],null,{"837":8084}],"7973":[[7969,769],null,{"837":8085}],"7974":[[7968,834],null,{"837":8086}],"7975":[[7969,834],null,{"837":8087}],"7976":[[919,787],null,{"768":7978,"769":7980,"834":7982,"837":8088}],"7977":[[919,788],null,{"768":7979,"769":7981,"834":7983,"837":8089}],"7978":[[7976,768],null,{"837":8090}],"7979":[[7977,768],null,{"837":8091}],"7980":[[7976,769],null,{"837":8092}],"7981":[[7977,769],null,{"837":8093}],"7982":[[7976,834],null,{"837":8094}],"7983":[[7977,834],null,{"837":8095}],"7984":[[953,787],null,{"768":7986,"769":7988,"834":7990}],"7985":[[953,788],null,{"768":7987,"769":7989,"834":7991}],"7986":[[7984,768]],"7987":[[7985,768]],"7988":[[7984,769]],"7989":[[7985,769]],"7990":[[7984,834]],"7991":[[7985,834]],"7992":[[921,787],null,{"768":7994,"769":7996,"834":7998}],"7993":[[921,788],null,{"768":7995,"769":7997,"834":7999}],"7994":[[7992,768]],"7995":[[7993,768]],"7996":[[7992,769]],"7997":[[7993,769]],"7998":[[7992,834]],"7999":[[7993,834]],"8000":[[959,787],null,{"768":8002,"769":8004}],"8001":[[959,788],null,{"768":8003,"769":8005}],"8002":[[8000,768]],"8003":[[8001,768]],"8004":[[8000,769]],"8005":[[8001,769]],"8008":[[927,787],null,{"768":8010,"769":8012}],"8009":[[927,788],null,{"768":8011,"769":8013}],"8010":[[8008,768]],"8011":[[8009,768]],"8012":[[8008,769]],"8013":[[8009,769]],"8016":[[965,787],null,{"768":8018,"769":8020,"834":8022}],"8017":[[965,788],null,{"768":8019,"769":8021,"834":8023}],"8018":[[8016,768]],"8019":[[8017,768]],"8020":[[8016,769]],"8021":[[8017,769]],"8022":[[8016,834]],"8023":[[8017,834]],"8025":[[933,788],null,{"768":8027,"769":8029,"834":8031}],"8027":[[8025,768]],"8029":[[8025,769]],"8031":[[8025,834]],"8032":[[969,787],null,{"768":8034,"769":8036,"834":8038,"837":8096}],"8033":[[969,788],null,{"768":8035,"769":8037,"834":8039,"837":8097}],"8034":[[8032,768],null,{"837":8098}],"8035":[[8033,768],null,{"837":8099}],"8036":[[8032,769],null,{"837":8100}],"8037":[[8033,769],null,{"837":8101}],"8038":[[8032,834],null,{"837":8102}],"8039":[[8033,834],null,{"837":8103}],"8040":[[937,787],null,{"768":8042,"769":8044,"834":8046,"837":8104}],"8041":[[937,788],null,{"768":8043,"769":8045,"834":8047,"837":8105}],"8042":[[8040,768],null,{"837":8106}],"8043":[[8041,768],null,{"837":8107}],"8044":[[8040,769],null,{"837":8108}],"8045":[[8041,769],null,{"837":8109}],"8046":[[8040,834],null,{"837":8110}],"8047":[[8041,834],null,{"837":8111}],"8048":[[945,768],null,{"837":8114}],"8049":[[940]],"8050":[[949,768]],"8051":[[941]],"8052":[[951,768],null,{"837":8130}],"8053":[[942]],"8054":[[953,768]],"8055":[[943]],"8056":[[959,768]],"8057":[[972]],"8058":[[965,768]],"8059":[[973]],"8060":[[969,768],null,{"837":8178}],"8061":[[974]],"8064":[[7936,837]],"8065":[[7937,837]],"8066":[[7938,837]],"8067":[[7939,837]],"8068":[[7940,837]],"8069":[[7941,837]],"8070":[[7942,837]],"8071":[[7943,837]],"8072":[[7944,837]],"8073":[[7945,837]],"8074":[[7946,837]],"8075":[[7947,837]],"8076":[[7948,837]],"8077":[[7949,837]],"8078":[[7950,837]],"8079":[[7951,837]],"8080":[[7968,837]],"8081":[[7969,837]],"8082":[[7970,837]],"8083":[[7971,837]],"8084":[[7972,837]],"8085":[[7973,837]],"8086":[[7974,837]],"8087":[[7975,837]],"8088":[[7976,837]],"8089":[[7977,837]],"8090":[[7978,837]],"8091":[[7979,837]],"8092":[[7980,837]],"8093":[[7981,837]],"8094":[[7982,837]],"8095":[[7983,837]],"8096":[[8032,837]],"8097":[[8033,837]],"8098":[[8034,837]],"8099":[[8035,837]],"8100":[[8036,837]],"8101":[[8037,837]],"8102":[[8038,837]],"8103":[[8039,837]],"8104":[[8040,837]],"8105":[[8041,837]],"8106":[[8042,837]],"8107":[[8043,837]],"8108":[[8044,837]],"8109":[[8045,837]],"8110":[[8046,837]],"8111":[[8047,837]],"8112":[[945,774]],"8113":[[945,772]],"8114":[[8048,837]],"8115":[[945,837]],"8116":[[940,837]],"8118":[[945,834],null,{"837":8119}],"8119":[[8118,837]],"8120":[[913,774]],"8121":[[913,772]],"8122":[[913,768]],"8123":[[902]],"8124":[[913,837]],"8125":[[32,787],256],"8126":[[953]],"8127":[[32,787],256,{"768":8141,"769":8142,"834":8143}],"8128":[[32,834],256],"8129":[[168,834]],"8130":[[8052,837]],"8131":[[951,837]],"8132":[[942,837]],"8134":[[951,834],null,{"837":8135}],"8135":[[8134,837]],"8136":[[917,768]],"8137":[[904]],"8138":[[919,768]],"8139":[[905]],"8140":[[919,837]],"8141":[[8127,768]],"8142":[[8127,769]],"8143":[[8127,834]],"8144":[[953,774]],"8145":[[953,772]],"8146":[[970,768]],"8147":[[912]],"8150":[[953,834]],"8151":[[970,834]],"8152":[[921,774]],"8153":[[921,772]],"8154":[[921,768]],"8155":[[906]],"8157":[[8190,768]],"8158":[[8190,769]],"8159":[[8190,834]],"8160":[[965,774]],"8161":[[965,772]],"8162":[[971,768]],"8163":[[944]],"8164":[[961,787]],"8165":[[961,788]],"8166":[[965,834]],"8167":[[971,834]],"8168":[[933,774]],"8169":[[933,772]],"8170":[[933,768]],"8171":[[910]],"8172":[[929,788]],"8173":[[168,768]],"8174":[[901]],"8175":[[96]],"8178":[[8060,837]],"8179":[[969,837]],"8180":[[974,837]],"8182":[[969,834],null,{"837":8183}],"8183":[[8182,837]],"8184":[[927,768]],"8185":[[908]],"8186":[[937,768]],"8187":[[911]],"8188":[[937,837]],"8189":[[180]],"8190":[[32,788],256,{"768":8157,"769":8158,"834":8159}]},"8192":{"8192":[[8194]],"8193":[[8195]],"8194":[[32],256],"8195":[[32],256],"8196":[[32],256],"8197":[[32],256],"8198":[[32],256],"8199":[[32],256],"8200":[[32],256],"8201":[[32],256],"8202":[[32],256],"8209":[[8208],256],"8215":[[32,819],256],"8228":[[46],256],"8229":[[46,46],256],"8230":[[46,46,46],256],"8239":[[32],256],"8243":[[8242,8242],256],"8244":[[8242,8242,8242],256],"8246":[[8245,8245],256],"8247":[[8245,8245,8245],256],"8252":[[33,33],256],"8254":[[32,773],256],"8263":[[63,63],256],"8264":[[63,33],256],"8265":[[33,63],256],"8279":[[8242,8242,8242,8242],256],"8287":[[32],256],"8304":[[48],256],"8305":[[105],256],"8308":[[52],256],"8309":[[53],256],"8310":[[54],256],"8311":[[55],256],"8312":[[56],256],"8313":[[57],256],"8314":[[43],256],"8315":[[8722],256],"8316":[[61],256],"8317":[[40],256],"8318":[[41],256],"8319":[[110],256],"8320":[[48],256],"8321":[[49],256],"8322":[[50],256],"8323":[[51],256],"8324":[[52],256],"8325":[[53],256],"8326":[[54],256],"8327":[[55],256],"8328":[[56],256],"8329":[[57],256],"8330":[[43],256],"8331":[[8722],256],"8332":[[61],256],"8333":[[40],256],"8334":[[41],256],"8336":[[97],256],"8337":[[101],256],"8338":[[111],256],"8339":[[120],256],"8340":[[601],256],"8341":[[104],256],"8342":[[107],256],"8343":[[108],256],"8344":[[109],256],"8345":[[110],256],"8346":[[112],256],"8347":[[115],256],"8348":[[116],256],"8360":[[82,115],256],"8400":[null,230],"8401":[null,230],"8402":[null,1],"8403":[null,1],"8404":[null,230],"8405":[null,230],"8406":[null,230],"8407":[null,230],"8408":[null,1],"8409":[null,1],"8410":[null,1],"8411":[null,230],"8412":[null,230],"8417":[null,230],"8421":[null,1],"8422":[null,1],"8423":[null,230],"8424":[null,220],"8425":[null,230],"8426":[null,1],"8427":[null,1],"8428":[null,220],"8429":[null,220],"8430":[null,220],"8431":[null,220],"8432":[null,230]},"8448":{"8448":[[97,47,99],256],"8449":[[97,47,115],256],"8450":[[67],256],"8451":[[176,67],256],"8453":[[99,47,111],256],"8454":[[99,47,117],256],"8455":[[400],256],"8457":[[176,70],256],"8458":[[103],256],"8459":[[72],256],"8460":[[72],256],"8461":[[72],256],"8462":[[104],256],"8463":[[295],256],"8464":[[73],256],"8465":[[73],256],"8466":[[76],256],"8467":[[108],256],"8469":[[78],256],"8470":[[78,111],256],"8473":[[80],256],"8474":[[81],256],"8475":[[82],256],"8476":[[82],256],"8477":[[82],256],"8480":[[83,77],256],"8481":[[84,69,76],256],"8482":[[84,77],256],"8484":[[90],256],"8486":[[937]],"8488":[[90],256],"8490":[[75]],"8491":[[197]],"8492":[[66],256],"8493":[[67],256],"8495":[[101],256],"8496":[[69],256],"8497":[[70],256],"8499":[[77],256],"8500":[[111],256],"8501":[[1488],256],"8502":[[1489],256],"8503":[[1490],256],"8504":[[1491],256],"8505":[[105],256],"8507":[[70,65,88],256],"8508":[[960],256],"8509":[[947],256],"8510":[[915],256],"8511":[[928],256],"8512":[[8721],256],"8517":[[68],256],"8518":[[100],256],"8519":[[101],256],"8520":[[105],256],"8521":[[106],256],"8528":[[49,8260,55],256],"8529":[[49,8260,57],256],"8530":[[49,8260,49,48],256],"8531":[[49,8260,51],256],"8532":[[50,8260,51],256],"8533":[[49,8260,53],256],"8534":[[50,8260,53],256],"8535":[[51,8260,53],256],"8536":[[52,8260,53],256],"8537":[[49,8260,54],256],"8538":[[53,8260,54],256],"8539":[[49,8260,56],256],"8540":[[51,8260,56],256],"8541":[[53,8260,56],256],"8542":[[55,8260,56],256],"8543":[[49,8260],256],"8544":[[73],256],"8545":[[73,73],256],"8546":[[73,73,73],256],"8547":[[73,86],256],"8548":[[86],256],"8549":[[86,73],256],"8550":[[86,73,73],256],"8551":[[86,73,73,73],256],"8552":[[73,88],256],"8553":[[88],256],"8554":[[88,73],256],"8555":[[88,73,73],256],"8556":[[76],256],"8557":[[67],256],"8558":[[68],256],"8559":[[77],256],"8560":[[105],256],"8561":[[105,105],256],"8562":[[105,105,105],256],"8563":[[105,118],256],"8564":[[118],256],"8565":[[118,105],256],"8566":[[118,105,105],256],"8567":[[118,105,105,105],256],"8568":[[105,120],256],"8569":[[120],256],"8570":[[120,105],256],"8571":[[120,105,105],256],"8572":[[108],256],"8573":[[99],256],"8574":[[100],256],"8575":[[109],256],"8585":[[48,8260,51],256],"8592":[null,null,{"824":8602}],"8594":[null,null,{"824":8603}],"8596":[null,null,{"824":8622}],"8602":[[8592,824]],"8603":[[8594,824]],"8622":[[8596,824]],"8653":[[8656,824]],"8654":[[8660,824]],"8655":[[8658,824]],"8656":[null,null,{"824":8653}],"8658":[null,null,{"824":8655}],"8660":[null,null,{"824":8654}]},"8704":{"8707":[null,null,{"824":8708}],"8708":[[8707,824]],"8712":[null,null,{"824":8713}],"8713":[[8712,824]],"8715":[null,null,{"824":8716}],"8716":[[8715,824]],"8739":[null,null,{"824":8740}],"8740":[[8739,824]],"8741":[null,null,{"824":8742}],"8742":[[8741,824]],"8748":[[8747,8747],256],"8749":[[8747,8747,8747],256],"8751":[[8750,8750],256],"8752":[[8750,8750,8750],256],"8764":[null,null,{"824":8769}],"8769":[[8764,824]],"8771":[null,null,{"824":8772}],"8772":[[8771,824]],"8773":[null,null,{"824":8775}],"8775":[[8773,824]],"8776":[null,null,{"824":8777}],"8777":[[8776,824]],"8781":[null,null,{"824":8813}],"8800":[[61,824]],"8801":[null,null,{"824":8802}],"8802":[[8801,824]],"8804":[null,null,{"824":8816}],"8805":[null,null,{"824":8817}],"8813":[[8781,824]],"8814":[[60,824]],"8815":[[62,824]],"8816":[[8804,824]],"8817":[[8805,824]],"8818":[null,null,{"824":8820}],"8819":[null,null,{"824":8821}],"8820":[[8818,824]],"8821":[[8819,824]],"8822":[null,null,{"824":8824}],"8823":[null,null,{"824":8825}],"8824":[[8822,824]],"8825":[[8823,824]],"8826":[null,null,{"824":8832}],"8827":[null,null,{"824":8833}],"8828":[null,null,{"824":8928}],"8829":[null,null,{"824":8929}],"8832":[[8826,824]],"8833":[[8827,824]],"8834":[null,null,{"824":8836}],"8835":[null,null,{"824":8837}],"8836":[[8834,824]],"8837":[[8835,824]],"8838":[null,null,{"824":8840}],"8839":[null,null,{"824":8841}],"8840":[[8838,824]],"8841":[[8839,824]],"8849":[null,null,{"824":8930}],"8850":[null,null,{"824":8931}],"8866":[null,null,{"824":8876}],"8872":[null,null,{"824":8877}],"8873":[null,null,{"824":8878}],"8875":[null,null,{"824":8879}],"8876":[[8866,824]],"8877":[[8872,824]],"8878":[[8873,824]],"8879":[[8875,824]],"8882":[null,null,{"824":8938}],"8883":[null,null,{"824":8939}],"8884":[null,null,{"824":8940}],"8885":[null,null,{"824":8941}],"8928":[[8828,824]],"8929":[[8829,824]],"8930":[[8849,824]],"8931":[[8850,824]],"8938":[[8882,824]],"8939":[[8883,824]],"8940":[[8884,824]],"8941":[[8885,824]]},"8960":{"9001":[[12296]],"9002":[[12297]]},"9216":{"9312":[[49],256],"9313":[[50],256],"9314":[[51],256],"9315":[[52],256],"9316":[[53],256],"9317":[[54],256],"9318":[[55],256],"9319":[[56],256],"9320":[[57],256],"9321":[[49,48],256],"9322":[[49,49],256],"9323":[[49,50],256],"9324":[[49,51],256],"9325":[[49,52],256],"9326":[[49,53],256],"9327":[[49,54],256],"9328":[[49,55],256],"9329":[[49,56],256],"9330":[[49,57],256],"9331":[[50,48],256],"9332":[[40,49,41],256],"9333":[[40,50,41],256],"9334":[[40,51,41],256],"9335":[[40,52,41],256],"9336":[[40,53,41],256],"9337":[[40,54,41],256],"9338":[[40,55,41],256],"9339":[[40,56,41],256],"9340":[[40,57,41],256],"9341":[[40,49,48,41],256],"9342":[[40,49,49,41],256],"9343":[[40,49,50,41],256],"9344":[[40,49,51,41],256],"9345":[[40,49,52,41],256],"9346":[[40,49,53,41],256],"9347":[[40,49,54,41],256],"9348":[[40,49,55,41],256],"9349":[[40,49,56,41],256],"9350":[[40,49,57,41],256],"9351":[[40,50,48,41],256],"9352":[[49,46],256],"9353":[[50,46],256],"9354":[[51,46],256],"9355":[[52,46],256],"9356":[[53,46],256],"9357":[[54,46],256],"9358":[[55,46],256],"9359":[[56,46],256],"9360":[[57,46],256],"9361":[[49,48,46],256],"9362":[[49,49,46],256],"9363":[[49,50,46],256],"9364":[[49,51,46],256],"9365":[[49,52,46],256],"9366":[[49,53,46],256],"9367":[[49,54,46],256],"9368":[[49,55,46],256],"9369":[[49,56,46],256],"9370":[[49,57,46],256],"9371":[[50,48,46],256],"9372":[[40,97,41],256],"9373":[[40,98,41],256],"9374":[[40,99,41],256],"9375":[[40,100,41],256],"9376":[[40,101,41],256],"9377":[[40,102,41],256],"9378":[[40,103,41],256],"9379":[[40,104,41],256],"9380":[[40,105,41],256],"9381":[[40,106,41],256],"9382":[[40,107,41],256],"9383":[[40,108,41],256],"9384":[[40,109,41],256],"9385":[[40,110,41],256],"9386":[[40,111,41],256],"9387":[[40,112,41],256],"9388":[[40,113,41],256],"9389":[[40,114,41],256],"9390":[[40,115,41],256],"9391":[[40,116,41],256],"9392":[[40,117,41],256],"9393":[[40,118,41],256],"9394":[[40,119,41],256],"9395":[[40,120,41],256],"9396":[[40,121,41],256],"9397":[[40,122,41],256],"9398":[[65],256],"9399":[[66],256],"9400":[[67],256],"9401":[[68],256],"9402":[[69],256],"9403":[[70],256],"9404":[[71],256],"9405":[[72],256],"9406":[[73],256],"9407":[[74],256],"9408":[[75],256],"9409":[[76],256],"9410":[[77],256],"9411":[[78],256],"9412":[[79],256],"9413":[[80],256],"9414":[[81],256],"9415":[[82],256],"9416":[[83],256],"9417":[[84],256],"9418":[[85],256],"9419":[[86],256],"9420":[[87],256],"9421":[[88],256],"9422":[[89],256],"9423":[[90],256],"9424":[[97],256],"9425":[[98],256],"9426":[[99],256],"9427":[[100],256],"9428":[[101],256],"9429":[[102],256],"9430":[[103],256],"9431":[[104],256],"9432":[[105],256],"9433":[[106],256],"9434":[[107],256],"9435":[[108],256],"9436":[[109],256],"9437":[[110],256],"9438":[[111],256],"9439":[[112],256],"9440":[[113],256],"9441":[[114],256],"9442":[[115],256],"9443":[[116],256],"9444":[[117],256],"9445":[[118],256],"9446":[[119],256],"9447":[[120],256],"9448":[[121],256],"9449":[[122],256],"9450":[[48],256]},"10752":{"10764":[[8747,8747,8747,8747],256],"10868":[[58,58,61],256],"10869":[[61,61],256],"10870":[[61,61,61],256],"10972":[[10973,824],512]},"11264":{"11388":[[106],256],"11389":[[86],256],"11503":[null,230],"11504":[null,230],"11505":[null,230]},"11520":{"11631":[[11617],256],"11647":[null,9],"11744":[null,230],"11745":[null,230],"11746":[null,230],"11747":[null,230],"11748":[null,230],"11749":[null,230],"11750":[null,230],"11751":[null,230],"11752":[null,230],"11753":[null,230],"11754":[null,230],"11755":[null,230],"11756":[null,230],"11757":[null,230],"11758":[null,230],"11759":[null,230],"11760":[null,230],"11761":[null,230],"11762":[null,230],"11763":[null,230],"11764":[null,230],"11765":[null,230],"11766":[null,230],"11767":[null,230],"11768":[null,230],"11769":[null,230],"11770":[null,230],"11771":[null,230],"11772":[null,230],"11773":[null,230],"11774":[null,230],"11775":[null,230]},"11776":{"11935":[[27597],256],"12019":[[40863],256]},"12032":{"12032":[[19968],256],"12033":[[20008],256],"12034":[[20022],256],"12035":[[20031],256],"12036":[[20057],256],"12037":[[20101],256],"12038":[[20108],256],"12039":[[20128],256],"12040":[[20154],256],"12041":[[20799],256],"12042":[[20837],256],"12043":[[20843],256],"12044":[[20866],256],"12045":[[20886],256],"12046":[[20907],256],"12047":[[20960],256],"12048":[[20981],256],"12049":[[20992],256],"12050":[[21147],256],"12051":[[21241],256],"12052":[[21269],256],"12053":[[21274],256],"12054":[[21304],256],"12055":[[21313],256],"12056":[[21340],256],"12057":[[21353],256],"12058":[[21378],256],"12059":[[21430],256],"12060":[[21448],256],"12061":[[21475],256],"12062":[[22231],256],"12063":[[22303],256],"12064":[[22763],256],"12065":[[22786],256],"12066":[[22794],256],"12067":[[22805],256],"12068":[[22823],256],"12069":[[22899],256],"12070":[[23376],256],"12071":[[23424],256],"12072":[[23544],256],"12073":[[23567],256],"12074":[[23586],256],"12075":[[23608],256],"12076":[[23662],256],"12077":[[23665],256],"12078":[[24027],256],"12079":[[24037],256],"12080":[[24049],256],"12081":[[24062],256],"12082":[[24178],256],"12083":[[24186],256],"12084":[[24191],256],"12085":[[24308],256],"12086":[[24318],256],"12087":[[24331],256],"12088":[[24339],256],"12089":[[24400],256],"12090":[[24417],256],"12091":[[24435],256],"12092":[[24515],256],"12093":[[25096],256],"12094":[[25142],256],"12095":[[25163],256],"12096":[[25903],256],"12097":[[25908],256],"12098":[[25991],256],"12099":[[26007],256],"12100":[[26020],256],"12101":[[26041],256],"12102":[[26080],256],"12103":[[26085],256],"12104":[[26352],256],"12105":[[26376],256],"12106":[[26408],256],"12107":[[27424],256],"12108":[[27490],256],"12109":[[27513],256],"12110":[[27571],256],"12111":[[27595],256],"12112":[[27604],256],"12113":[[27611],256],"12114":[[27663],256],"12115":[[27668],256],"12116":[[27700],256],"12117":[[28779],256],"12118":[[29226],256],"12119":[[29238],256],"12120":[[29243],256],"12121":[[29247],256],"12122":[[29255],256],"12123":[[29273],256],"12124":[[29275],256],"12125":[[29356],256],"12126":[[29572],256],"12127":[[29577],256],"12128":[[29916],256],"12129":[[29926],256],"12130":[[29976],256],"12131":[[29983],256],"12132":[[29992],256],"12133":[[30000],256],"12134":[[30091],256],"12135":[[30098],256],"12136":[[30326],256],"12137":[[30333],256],"12138":[[30382],256],"12139":[[30399],256],"12140":[[30446],256],"12141":[[30683],256],"12142":[[30690],256],"12143":[[30707],256],"12144":[[31034],256],"12145":[[31160],256],"12146":[[31166],256],"12147":[[31348],256],"12148":[[31435],256],"12149":[[31481],256],"12150":[[31859],256],"12151":[[31992],256],"12152":[[32566],256],"12153":[[32593],256],"12154":[[32650],256],"12155":[[32701],256],"12156":[[32769],256],"12157":[[32780],256],"12158":[[32786],256],"12159":[[32819],256],"12160":[[32895],256],"12161":[[32905],256],"12162":[[33251],256],"12163":[[33258],256],"12164":[[33267],256],"12165":[[33276],256],"12166":[[33292],256],"12167":[[33307],256],"12168":[[33311],256],"12169":[[33390],256],"12170":[[33394],256],"12171":[[33400],256],"12172":[[34381],256],"12173":[[34411],256],"12174":[[34880],256],"12175":[[34892],256],"12176":[[34915],256],"12177":[[35198],256],"12178":[[35211],256],"12179":[[35282],256],"12180":[[35328],256],"12181":[[35895],256],"12182":[[35910],256],"12183":[[35925],256],"12184":[[35960],256],"12185":[[35997],256],"12186":[[36196],256],"12187":[[36208],256],"12188":[[36275],256],"12189":[[36523],256],"12190":[[36554],256],"12191":[[36763],256],"12192":[[36784],256],"12193":[[36789],256],"12194":[[37009],256],"12195":[[37193],256],"12196":[[37318],256],"12197":[[37324],256],"12198":[[37329],256],"12199":[[38263],256],"12200":[[38272],256],"12201":[[38428],256],"12202":[[38582],256],"12203":[[38585],256],"12204":[[38632],256],"12205":[[38737],256],"12206":[[38750],256],"12207":[[38754],256],"12208":[[38761],256],"12209":[[38859],256],"12210":[[38893],256],"12211":[[38899],256],"12212":[[38913],256],"12213":[[39080],256],"12214":[[39131],256],"12215":[[39135],256],"12216":[[39318],256],"12217":[[39321],256],"12218":[[39340],256],"12219":[[39592],256],"12220":[[39640],256],"12221":[[39647],256],"12222":[[39717],256],"12223":[[39727],256],"12224":[[39730],256],"12225":[[39740],256],"12226":[[39770],256],"12227":[[40165],256],"12228":[[40565],256],"12229":[[40575],256],"12230":[[40613],256],"12231":[[40635],256],"12232":[[40643],256],"12233":[[40653],256],"12234":[[40657],256],"12235":[[40697],256],"12236":[[40701],256],"12237":[[40718],256],"12238":[[40723],256],"12239":[[40736],256],"12240":[[40763],256],"12241":[[40778],256],"12242":[[40786],256],"12243":[[40845],256],"12244":[[40860],256],"12245":[[40864],256]},"12288":{"12288":[[32],256],"12330":[null,218],"12331":[null,228],"12332":[null,232],"12333":[null,222],"12334":[null,224],"12335":[null,224],"12342":[[12306],256],"12344":[[21313],256],"12345":[[21316],256],"12346":[[21317],256],"12358":[null,null,{"12441":12436}],"12363":[null,null,{"12441":12364}],"12364":[[12363,12441]],"12365":[null,null,{"12441":12366}],"12366":[[12365,12441]],"12367":[null,null,{"12441":12368}],"12368":[[12367,12441]],"12369":[null,null,{"12441":12370}],"12370":[[12369,12441]],"12371":[null,null,{"12441":12372}],"12372":[[12371,12441]],"12373":[null,null,{"12441":12374}],"12374":[[12373,12441]],"12375":[null,null,{"12441":12376}],"12376":[[12375,12441]],"12377":[null,null,{"12441":12378}],"12378":[[12377,12441]],"12379":[null,null,{"12441":12380}],"12380":[[12379,12441]],"12381":[null,null,{"12441":12382}],"12382":[[12381,12441]],"12383":[null,null,{"12441":12384}],"12384":[[12383,12441]],"12385":[null,null,{"12441":12386}],"12386":[[12385,12441]],"12388":[null,null,{"12441":12389}],"12389":[[12388,12441]],"12390":[null,null,{"12441":12391}],"12391":[[12390,12441]],"12392":[null,null,{"12441":12393}],"12393":[[12392,12441]],"12399":[null,null,{"12441":12400,"12442":12401}],"12400":[[12399,12441]],"12401":[[12399,12442]],"12402":[null,null,{"12441":12403,"12442":12404}],"12403":[[12402,12441]],"12404":[[12402,12442]],"12405":[null,null,{"12441":12406,"12442":12407}],"12406":[[12405,12441]],"12407":[[12405,12442]],"12408":[null,null,{"12441":12409,"12442":12410}],"12409":[[12408,12441]],"12410":[[12408,12442]],"12411":[null,null,{"12441":12412,"12442":12413}],"12412":[[12411,12441]],"12413":[[12411,12442]],"12436":[[12358,12441]],"12441":[null,8],"12442":[null,8],"12443":[[32,12441],256],"12444":[[32,12442],256],"12445":[null,null,{"12441":12446}],"12446":[[12445,12441]],"12447":[[12424,12426],256],"12454":[null,null,{"12441":12532}],"12459":[null,null,{"12441":12460}],"12460":[[12459,12441]],"12461":[null,null,{"12441":12462}],"12462":[[12461,12441]],"12463":[null,null,{"12441":12464}],"12464":[[12463,12441]],"12465":[null,null,{"12441":12466}],"12466":[[12465,12441]],"12467":[null,null,{"12441":12468}],"12468":[[12467,12441]],"12469":[null,null,{"12441":12470}],"12470":[[12469,12441]],"12471":[null,null,{"12441":12472}],"12472":[[12471,12441]],"12473":[null,null,{"12441":12474}],"12474":[[12473,12441]],"12475":[null,null,{"12441":12476}],"12476":[[12475,12441]],"12477":[null,null,{"12441":12478}],"12478":[[12477,12441]],"12479":[null,null,{"12441":12480}],"12480":[[12479,12441]],"12481":[null,null,{"12441":12482}],"12482":[[12481,12441]],"12484":[null,null,{"12441":12485}],"12485":[[12484,12441]],"12486":[null,null,{"12441":12487}],"12487":[[12486,12441]],"12488":[null,null,{"12441":12489}],"12489":[[12488,12441]],"12495":[null,null,{"12441":12496,"12442":12497}],"12496":[[12495,12441]],"12497":[[12495,12442]],"12498":[null,null,{"12441":12499,"12442":12500}],"12499":[[12498,12441]],"12500":[[12498,12442]],"12501":[null,null,{"12441":12502,"12442":12503}],"12502":[[12501,12441]],"12503":[[12501,12442]],"12504":[null,null,{"12441":12505,"12442":12506}],"12505":[[12504,12441]],"12506":[[12504,12442]],"12507":[null,null,{"12441":12508,"12442":12509}],"12508":[[12507,12441]],"12509":[[12507,12442]],"12527":[null,null,{"12441":12535}],"12528":[null,null,{"12441":12536}],"12529":[null,null,{"12441":12537}],"12530":[null,null,{"12441":12538}],"12532":[[12454,12441]],"12535":[[12527,12441]],"12536":[[12528,12441]],"12537":[[12529,12441]],"12538":[[12530,12441]],"12541":[null,null,{"12441":12542}],"12542":[[12541,12441]],"12543":[[12467,12488],256]},"12544":{"12593":[[4352],256],"12594":[[4353],256],"12595":[[4522],256],"12596":[[4354],256],"12597":[[4524],256],"12598":[[4525],256],"12599":[[4355],256],"12600":[[4356],256],"12601":[[4357],256],"12602":[[4528],256],"12603":[[4529],256],"12604":[[4530],256],"12605":[[4531],256],"12606":[[4532],256],"12607":[[4533],256],"12608":[[4378],256],"12609":[[4358],256],"12610":[[4359],256],"12611":[[4360],256],"12612":[[4385],256],"12613":[[4361],256],"12614":[[4362],256],"12615":[[4363],256],"12616":[[4364],256],"12617":[[4365],256],"12618":[[4366],256],"12619":[[4367],256],"12620":[[4368],256],"12621":[[4369],256],"12622":[[4370],256],"12623":[[4449],256],"12624":[[4450],256],"12625":[[4451],256],"12626":[[4452],256],"12627":[[4453],256],"12628":[[4454],256],"12629":[[4455],256],"12630":[[4456],256],"12631":[[4457],256],"12632":[[4458],256],"12633":[[4459],256],"12634":[[4460],256],"12635":[[4461],256],"12636":[[4462],256],"12637":[[4463],256],"12638":[[4464],256],"12639":[[4465],256],"12640":[[4466],256],"12641":[[4467],256],"12642":[[4468],256],"12643":[[4469],256],"12644":[[4448],256],"12645":[[4372],256],"12646":[[4373],256],"12647":[[4551],256],"12648":[[4552],256],"12649":[[4556],256],"12650":[[4558],256],"12651":[[4563],256],"12652":[[4567],256],"12653":[[4569],256],"12654":[[4380],256],"12655":[[4573],256],"12656":[[4575],256],"12657":[[4381],256],"12658":[[4382],256],"12659":[[4384],256],"12660":[[4386],256],"12661":[[4387],256],"12662":[[4391],256],"12663":[[4393],256],"12664":[[4395],256],"12665":[[4396],256],"12666":[[4397],256],"12667":[[4398],256],"12668":[[4399],256],"12669":[[4402],256],"12670":[[4406],256],"12671":[[4416],256],"12672":[[4423],256],"12673":[[4428],256],"12674":[[4593],256],"12675":[[4594],256],"12676":[[4439],256],"12677":[[4440],256],"12678":[[4441],256],"12679":[[4484],256],"12680":[[4485],256],"12681":[[4488],256],"12682":[[4497],256],"12683":[[4498],256],"12684":[[4500],256],"12685":[[4510],256],"12686":[[4513],256],"12690":[[19968],256],"12691":[[20108],256],"12692":[[19977],256],"12693":[[22235],256],"12694":[[19978],256],"12695":[[20013],256],"12696":[[19979],256],"12697":[[30002],256],"12698":[[20057],256],"12699":[[19993],256],"12700":[[19969],256],"12701":[[22825],256],"12702":[[22320],256],"12703":[[20154],256]},"12800":{"12800":[[40,4352,41],256],"12801":[[40,4354,41],256],"12802":[[40,4355,41],256],"12803":[[40,4357,41],256],"12804":[[40,4358,41],256],"12805":[[40,4359,41],256],"12806":[[40,4361,41],256],"12807":[[40,4363,41],256],"12808":[[40,4364,41],256],"12809":[[40,4366,41],256],"12810":[[40,4367,41],256],"12811":[[40,4368,41],256],"12812":[[40,4369,41],256],"12813":[[40,4370,41],256],"12814":[[40,4352,4449,41],256],"12815":[[40,4354,4449,41],256],"12816":[[40,4355,4449,41],256],"12817":[[40,4357,4449,41],256],"12818":[[40,4358,4449,41],256],"12819":[[40,4359,4449,41],256],"12820":[[40,4361,4449,41],256],"12821":[[40,4363,4449,41],256],"12822":[[40,4364,4449,41],256],"12823":[[40,4366,4449,41],256],"12824":[[40,4367,4449,41],256],"12825":[[40,4368,4449,41],256],"12826":[[40,4369,4449,41],256],"12827":[[40,4370,4449,41],256],"12828":[[40,4364,4462,41],256],"12829":[[40,4363,4457,4364,4453,4523,41],256],"12830":[[40,4363,4457,4370,4462,41],256],"12832":[[40,19968,41],256],"12833":[[40,20108,41],256],"12834":[[40,19977,41],256],"12835":[[40,22235,41],256],"12836":[[40,20116,41],256],"12837":[[40,20845,41],256],"12838":[[40,19971,41],256],"12839":[[40,20843,41],256],"12840":[[40,20061,41],256],"12841":[[40,21313,41],256],"12842":[[40,26376,41],256],"12843":[[40,28779,41],256],"12844":[[40,27700,41],256],"12845":[[40,26408,41],256],"12846":[[40,37329,41],256],"12847":[[40,22303,41],256],"12848":[[40,26085,41],256],"12849":[[40,26666,41],256],"12850":[[40,26377,41],256],"12851":[[40,31038,41],256],"12852":[[40,21517,41],256],"12853":[[40,29305,41],256],"12854":[[40,36001,41],256],"12855":[[40,31069,41],256],"12856":[[40,21172,41],256],"12857":[[40,20195,41],256],"12858":[[40,21628,41],256],"12859":[[40,23398,41],256],"12860":[[40,30435,41],256],"12861":[[40,20225,41],256],"12862":[[40,36039,41],256],"12863":[[40,21332,41],256],"12864":[[40,31085,41],256],"12865":[[40,20241,41],256],"12866":[[40,33258,41],256],"12867":[[40,33267,41],256],"12868":[[21839],256],"12869":[[24188],256],"12870":[[25991],256],"12871":[[31631],256],"12880":[[80,84,69],256],"12881":[[50,49],256],"12882":[[50,50],256],"12883":[[50,51],256],"12884":[[50,52],256],"12885":[[50,53],256],"12886":[[50,54],256],"12887":[[50,55],256],"12888":[[50,56],256],"12889":[[50,57],256],"12890":[[51,48],256],"12891":[[51,49],256],"12892":[[51,50],256],"12893":[[51,51],256],"12894":[[51,52],256],"12895":[[51,53],256],"12896":[[4352],256],"12897":[[4354],256],"12898":[[4355],256],"12899":[[4357],256],"12900":[[4358],256],"12901":[[4359],256],"12902":[[4361],256],"12903":[[4363],256],"12904":[[4364],256],"12905":[[4366],256],"12906":[[4367],256],"12907":[[4368],256],"12908":[[4369],256],"12909":[[4370],256],"12910":[[4352,4449],256],"12911":[[4354,4449],256],"12912":[[4355,4449],256],"12913":[[4357,4449],256],"12914":[[4358,4449],256],"12915":[[4359,4449],256],"12916":[[4361,4449],256],"12917":[[4363,4449],256],"12918":[[4364,4449],256],"12919":[[4366,4449],256],"12920":[[4367,4449],256],"12921":[[4368,4449],256],"12922":[[4369,4449],256],"12923":[[4370,4449],256],"12924":[[4366,4449,4535,4352,4457],256],"12925":[[4364,4462,4363,4468],256],"12926":[[4363,4462],256],"12928":[[19968],256],"12929":[[20108],256],"12930":[[19977],256],"12931":[[22235],256],"12932":[[20116],256],"12933":[[20845],256],"12934":[[19971],256],"12935":[[20843],256],"12936":[[20061],256],"12937":[[21313],256],"12938":[[26376],256],"12939":[[28779],256],"12940":[[27700],256],"12941":[[26408],256],"12942":[[37329],256],"12943":[[22303],256],"12944":[[26085],256],"12945":[[26666],256],"12946":[[26377],256],"12947":[[31038],256],"12948":[[21517],256],"12949":[[29305],256],"12950":[[36001],256],"12951":[[31069],256],"12952":[[21172],256],"12953":[[31192],256],"12954":[[30007],256],"12955":[[22899],256],"12956":[[36969],256],"12957":[[20778],256],"12958":[[21360],256],"12959":[[27880],256],"12960":[[38917],256],"12961":[[20241],256],"12962":[[20889],256],"12963":[[27491],256],"12964":[[19978],256],"12965":[[20013],256],"12966":[[19979],256],"12967":[[24038],256],"12968":[[21491],256],"12969":[[21307],256],"12970":[[23447],256],"12971":[[23398],256],"12972":[[30435],256],"12973":[[20225],256],"12974":[[36039],256],"12975":[[21332],256],"12976":[[22812],256],"12977":[[51,54],256],"12978":[[51,55],256],"12979":[[51,56],256],"12980":[[51,57],256],"12981":[[52,48],256],"12982":[[52,49],256],"12983":[[52,50],256],"12984":[[52,51],256],"12985":[[52,52],256],"12986":[[52,53],256],"12987":[[52,54],256],"12988":[[52,55],256],"12989":[[52,56],256],"12990":[[52,57],256],"12991":[[53,48],256],"12992":[[49,26376],256],"12993":[[50,26376],256],"12994":[[51,26376],256],"12995":[[52,26376],256],"12996":[[53,26376],256],"12997":[[54,26376],256],"12998":[[55,26376],256],"12999":[[56,26376],256],"13000":[[57,26376],256],"13001":[[49,48,26376],256],"13002":[[49,49,26376],256],"13003":[[49,50,26376],256],"13004":[[72,103],256],"13005":[[101,114,103],256],"13006":[[101,86],256],"13007":[[76,84,68],256],"13008":[[12450],256],"13009":[[12452],256],"13010":[[12454],256],"13011":[[12456],256],"13012":[[12458],256],"13013":[[12459],256],"13014":[[12461],256],"13015":[[12463],256],"13016":[[12465],256],"13017":[[12467],256],"13018":[[12469],256],"13019":[[12471],256],"13020":[[12473],256],"13021":[[12475],256],"13022":[[12477],256],"13023":[[12479],256],"13024":[[12481],256],"13025":[[12484],256],"13026":[[12486],256],"13027":[[12488],256],"13028":[[12490],256],"13029":[[12491],256],"13030":[[12492],256],"13031":[[12493],256],"13032":[[12494],256],"13033":[[12495],256],"13034":[[12498],256],"13035":[[12501],256],"13036":[[12504],256],"13037":[[12507],256],"13038":[[12510],256],"13039":[[12511],256],"13040":[[12512],256],"13041":[[12513],256],"13042":[[12514],256],"13043":[[12516],256],"13044":[[12518],256],"13045":[[12520],256],"13046":[[12521],256],"13047":[[12522],256],"13048":[[12523],256],"13049":[[12524],256],"13050":[[12525],256],"13051":[[12527],256],"13052":[[12528],256],"13053":[[12529],256],"13054":[[12530],256]},"13056":{"13056":[[12450,12497,12540,12488],256],"13057":[[12450,12523,12501,12449],256],"13058":[[12450,12531,12506,12450],256],"13059":[[12450,12540,12523],256],"13060":[[12452,12491,12531,12464],256],"13061":[[12452,12531,12481],256],"13062":[[12454,12457,12531],256],"13063":[[12456,12473,12463,12540,12489],256],"13064":[[12456,12540,12459,12540],256],"13065":[[12458,12531,12473],256],"13066":[[12458,12540,12512],256],"13067":[[12459,12452,12522],256],"13068":[[12459,12521,12483,12488],256],"13069":[[12459,12525,12522,12540],256],"13070":[[12460,12525,12531],256],"13071":[[12460,12531,12510],256],"13072":[[12462,12460],256],"13073":[[12462,12491,12540],256],"13074":[[12461,12517,12522,12540],256],"13075":[[12462,12523,12480,12540],256],"13076":[[12461,12525],256],"13077":[[12461,12525,12464,12521,12512],256],"13078":[[12461,12525,12513,12540,12488,12523],256],"13079":[[12461,12525,12527,12483,12488],256],"13080":[[12464,12521,12512],256],"13081":[[12464,12521,12512,12488,12531],256],"13082":[[12463,12523,12476,12452,12525],256],"13083":[[12463,12525,12540,12493],256],"13084":[[12465,12540,12473],256],"13085":[[12467,12523,12490],256],"13086":[[12467,12540,12509],256],"13087":[[12469,12452,12463,12523],256],"13088":[[12469,12531,12481,12540,12512],256],"13089":[[12471,12522,12531,12464],256],"13090":[[12475,12531,12481],256],"13091":[[12475,12531,12488],256],"13092":[[12480,12540,12473],256],"13093":[[12487,12471],256],"13094":[[12489,12523],256],"13095":[[12488,12531],256],"13096":[[12490,12494],256],"13097":[[12494,12483,12488],256],"13098":[[12495,12452,12484],256],"13099":[[12497,12540,12475,12531,12488],256],"13100":[[12497,12540,12484],256],"13101":[[12496,12540,12524,12523],256],"13102":[[12500,12450,12473,12488,12523],256],"13103":[[12500,12463,12523],256],"13104":[[12500,12467],256],"13105":[[12499,12523],256],"13106":[[12501,12449,12521,12483,12489],256],"13107":[[12501,12451,12540,12488],256],"13108":[[12502,12483,12471,12455,12523],256],"13109":[[12501,12521,12531],256],"13110":[[12504,12463,12479,12540,12523],256],"13111":[[12506,12477],256],"13112":[[12506,12491,12498],256],"13113":[[12504,12523,12484],256],"13114":[[12506,12531,12473],256],"13115":[[12506,12540,12472],256],"13116":[[12505,12540,12479],256],"13117":[[12509,12452,12531,12488],256],"13118":[[12508,12523,12488],256],"13119":[[12507,12531],256],"13120":[[12509,12531,12489],256],"13121":[[12507,12540,12523],256],"13122":[[12507,12540,12531],256],"13123":[[12510,12452,12463,12525],256],"13124":[[12510,12452,12523],256],"13125":[[12510,12483,12495],256],"13126":[[12510,12523,12463],256],"13127":[[12510,12531,12471,12519,12531],256],"13128":[[12511,12463,12525,12531],256],"13129":[[12511,12522],256],"13130":[[12511,12522,12496,12540,12523],256],"13131":[[12513,12460],256],"13132":[[12513,12460,12488,12531],256],"13133":[[12513,12540,12488,12523],256],"13134":[[12516,12540,12489],256],"13135":[[12516,12540,12523],256],"13136":[[12518,12450,12531],256],"13137":[[12522,12483,12488,12523],256],"13138":[[12522,12521],256],"13139":[[12523,12500,12540],256],"13140":[[12523,12540,12502,12523],256],"13141":[[12524,12512],256],"13142":[[12524,12531,12488,12466,12531],256],"13143":[[12527,12483,12488],256],"13144":[[48,28857],256],"13145":[[49,28857],256],"13146":[[50,28857],256],"13147":[[51,28857],256],"13148":[[52,28857],256],"13149":[[53,28857],256],"13150":[[54,28857],256],"13151":[[55,28857],256],"13152":[[56,28857],256],"13153":[[57,28857],256],"13154":[[49,48,28857],256],"13155":[[49,49,28857],256],"13156":[[49,50,28857],256],"13157":[[49,51,28857],256],"13158":[[49,52,28857],256],"13159":[[49,53,28857],256],"13160":[[49,54,28857],256],"13161":[[49,55,28857],256],"13162":[[49,56,28857],256],"13163":[[49,57,28857],256],"13164":[[50,48,28857],256],"13165":[[50,49,28857],256],"13166":[[50,50,28857],256],"13167":[[50,51,28857],256],"13168":[[50,52,28857],256],"13169":[[104,80,97],256],"13170":[[100,97],256],"13171":[[65,85],256],"13172":[[98,97,114],256],"13173":[[111,86],256],"13174":[[112,99],256],"13175":[[100,109],256],"13176":[[100,109,178],256],"13177":[[100,109,179],256],"13178":[[73,85],256],"13179":[[24179,25104],256],"13180":[[26157,21644],256],"13181":[[22823,27491],256],"13182":[[26126,27835],256],"13183":[[26666,24335,20250,31038],256],"13184":[[112,65],256],"13185":[[110,65],256],"13186":[[956,65],256],"13187":[[109,65],256],"13188":[[107,65],256],"13189":[[75,66],256],"13190":[[77,66],256],"13191":[[71,66],256],"13192":[[99,97,108],256],"13193":[[107,99,97,108],256],"13194":[[112,70],256],"13195":[[110,70],256],"13196":[[956,70],256],"13197":[[956,103],256],"13198":[[109,103],256],"13199":[[107,103],256],"13200":[[72,122],256],"13201":[[107,72,122],256],"13202":[[77,72,122],256],"13203":[[71,72,122],256],"13204":[[84,72,122],256],"13205":[[956,8467],256],"13206":[[109,8467],256],"13207":[[100,8467],256],"13208":[[107,8467],256],"13209":[[102,109],256],"13210":[[110,109],256],"13211":[[956,109],256],"13212":[[109,109],256],"13213":[[99,109],256],"13214":[[107,109],256],"13215":[[109,109,178],256],"13216":[[99,109,178],256],"13217":[[109,178],256],"13218":[[107,109,178],256],"13219":[[109,109,179],256],"13220":[[99,109,179],256],"13221":[[109,179],256],"13222":[[107,109,179],256],"13223":[[109,8725,115],256],"13224":[[109,8725,115,178],256],"13225":[[80,97],256],"13226":[[107,80,97],256],"13227":[[77,80,97],256],"13228":[[71,80,97],256],"13229":[[114,97,100],256],"13230":[[114,97,100,8725,115],256],"13231":[[114,97,100,8725,115,178],256],"13232":[[112,115],256],"13233":[[110,115],256],"13234":[[956,115],256],"13235":[[109,115],256],"13236":[[112,86],256],"13237":[[110,86],256],"13238":[[956,86],256],"13239":[[109,86],256],"13240":[[107,86],256],"13241":[[77,86],256],"13242":[[112,87],256],"13243":[[110,87],256],"13244":[[956,87],256],"13245":[[109,87],256],"13246":[[107,87],256],"13247":[[77,87],256],"13248":[[107,937],256],"13249":[[77,937],256],"13250":[[97,46,109,46],256],"13251":[[66,113],256],"13252":[[99,99],256],"13253":[[99,100],256],"13254":[[67,8725,107,103],256],"13255":[[67,111,46],256],"13256":[[100,66],256],"13257":[[71,121],256],"13258":[[104,97],256],"13259":[[72,80],256],"13260":[[105,110],256],"13261":[[75,75],256],"13262":[[75,77],256],"13263":[[107,116],256],"13264":[[108,109],256],"13265":[[108,110],256],"13266":[[108,111,103],256],"13267":[[108,120],256],"13268":[[109,98],256],"13269":[[109,105,108],256],"13270":[[109,111,108],256],"13271":[[80,72],256],"13272":[[112,46,109,46],256],"13273":[[80,80,77],256],"13274":[[80,82],256],"13275":[[115,114],256],"13276":[[83,118],256],"13277":[[87,98],256],"13278":[[86,8725,109],256],"13279":[[65,8725,109],256],"13280":[[49,26085],256],"13281":[[50,26085],256],"13282":[[51,26085],256],"13283":[[52,26085],256],"13284":[[53,26085],256],"13285":[[54,26085],256],"13286":[[55,26085],256],"13287":[[56,26085],256],"13288":[[57,26085],256],"13289":[[49,48,26085],256],"13290":[[49,49,26085],256],"13291":[[49,50,26085],256],"13292":[[49,51,26085],256],"13293":[[49,52,26085],256],"13294":[[49,53,26085],256],"13295":[[49,54,26085],256],"13296":[[49,55,26085],256],"13297":[[49,56,26085],256],"13298":[[49,57,26085],256],"13299":[[50,48,26085],256],"13300":[[50,49,26085],256],"13301":[[50,50,26085],256],"13302":[[50,51,26085],256],"13303":[[50,52,26085],256],"13304":[[50,53,26085],256],"13305":[[50,54,26085],256],"13306":[[50,55,26085],256],"13307":[[50,56,26085],256],"13308":[[50,57,26085],256],"13309":[[51,48,26085],256],"13310":[[51,49,26085],256],"13311":[[103,97,108],256]},"27136":{"92912":[null,1],"92913":[null,1],"92914":[null,1],"92915":[null,1],"92916":[null,1]},"27392":{"92976":[null,230],"92977":[null,230],"92978":[null,230],"92979":[null,230],"92980":[null,230],"92981":[null,230],"92982":[null,230]},"42496":{"42607":[null,230],"42612":[null,230],"42613":[null,230],"42614":[null,230],"42615":[null,230],"42616":[null,230],"42617":[null,230],"42618":[null,230],"42619":[null,230],"42620":[null,230],"42621":[null,230],"42652":[[1098],256],"42653":[[1100],256],"42655":[null,230],"42736":[null,230],"42737":[null,230]},"42752":{"42864":[[42863],256],"43000":[[294],256],"43001":[[339],256]},"43008":{"43014":[null,9],"43204":[null,9],"43232":[null,230],"43233":[null,230],"43234":[null,230],"43235":[null,230],"43236":[null,230],"43237":[null,230],"43238":[null,230],"43239":[null,230],"43240":[null,230],"43241":[null,230],"43242":[null,230],"43243":[null,230],"43244":[null,230],"43245":[null,230],"43246":[null,230],"43247":[null,230],"43248":[null,230],"43249":[null,230]},"43264":{"43307":[null,220],"43308":[null,220],"43309":[null,220],"43347":[null,9],"43443":[null,7],"43456":[null,9]},"43520":{"43696":[null,230],"43698":[null,230],"43699":[null,230],"43700":[null,220],"43703":[null,230],"43704":[null,230],"43710":[null,230],"43711":[null,230],"43713":[null,230],"43766":[null,9]},"43776":{"43868":[[42791],256],"43869":[[43831],256],"43870":[[619],256],"43871":[[43858],256],"44013":[null,9]},"48128":{"113822":[null,1]},"53504":{"119134":[[119127,119141],512],"119135":[[119128,119141],512],"119136":[[119135,119150],512],"119137":[[119135,119151],512],"119138":[[119135,119152],512],"119139":[[119135,119153],512],"119140":[[119135,119154],512],"119141":[null,216],"119142":[null,216],"119143":[null,1],"119144":[null,1],"119145":[null,1],"119149":[null,226],"119150":[null,216],"119151":[null,216],"119152":[null,216],"119153":[null,216],"119154":[null,216],"119163":[null,220],"119164":[null,220],"119165":[null,220],"119166":[null,220],"119167":[null,220],"119168":[null,220],"119169":[null,220],"119170":[null,220],"119173":[null,230],"119174":[null,230],"119175":[null,230],"119176":[null,230],"119177":[null,230],"119178":[null,220],"119179":[null,220],"119210":[null,230],"119211":[null,230],"119212":[null,230],"119213":[null,230],"119227":[[119225,119141],512],"119228":[[119226,119141],512],"119229":[[119227,119150],512],"119230":[[119228,119150],512],"119231":[[119227,119151],512],"119232":[[119228,119151],512]},"53760":{"119362":[null,230],"119363":[null,230],"119364":[null,230]},"54272":{"119808":[[65],256],"119809":[[66],256],"119810":[[67],256],"119811":[[68],256],"119812":[[69],256],"119813":[[70],256],"119814":[[71],256],"119815":[[72],256],"119816":[[73],256],"119817":[[74],256],"119818":[[75],256],"119819":[[76],256],"119820":[[77],256],"119821":[[78],256],"119822":[[79],256],"119823":[[80],256],"119824":[[81],256],"119825":[[82],256],"119826":[[83],256],"119827":[[84],256],"119828":[[85],256],"119829":[[86],256],"119830":[[87],256],"119831":[[88],256],"119832":[[89],256],"119833":[[90],256],"119834":[[97],256],"119835":[[98],256],"119836":[[99],256],"119837":[[100],256],"119838":[[101],256],"119839":[[102],256],"119840":[[103],256],"119841":[[104],256],"119842":[[105],256],"119843":[[106],256],"119844":[[107],256],"119845":[[108],256],"119846":[[109],256],"119847":[[110],256],"119848":[[111],256],"119849":[[112],256],"119850":[[113],256],"119851":[[114],256],"119852":[[115],256],"119853":[[116],256],"119854":[[117],256],"119855":[[118],256],"119856":[[119],256],"119857":[[120],256],"119858":[[121],256],"119859":[[122],256],"119860":[[65],256],"119861":[[66],256],"119862":[[67],256],"119863":[[68],256],"119864":[[69],256],"119865":[[70],256],"119866":[[71],256],"119867":[[72],256],"119868":[[73],256],"119869":[[74],256],"119870":[[75],256],"119871":[[76],256],"119872":[[77],256],"119873":[[78],256],"119874":[[79],256],"119875":[[80],256],"119876":[[81],256],"119877":[[82],256],"119878":[[83],256],"119879":[[84],256],"119880":[[85],256],"119881":[[86],256],"119882":[[87],256],"119883":[[88],256],"119884":[[89],256],"119885":[[90],256],"119886":[[97],256],"119887":[[98],256],"119888":[[99],256],"119889":[[100],256],"119890":[[101],256],"119891":[[102],256],"119892":[[103],256],"119894":[[105],256],"119895":[[106],256],"119896":[[107],256],"119897":[[108],256],"119898":[[109],256],"119899":[[110],256],"119900":[[111],256],"119901":[[112],256],"119902":[[113],256],"119903":[[114],256],"119904":[[115],256],"119905":[[116],256],"119906":[[117],256],"119907":[[118],256],"119908":[[119],256],"119909":[[120],256],"119910":[[121],256],"119911":[[122],256],"119912":[[65],256],"119913":[[66],256],"119914":[[67],256],"119915":[[68],256],"119916":[[69],256],"119917":[[70],256],"119918":[[71],256],"119919":[[72],256],"119920":[[73],256],"119921":[[74],256],"119922":[[75],256],"119923":[[76],256],"119924":[[77],256],"119925":[[78],256],"119926":[[79],256],"119927":[[80],256],"119928":[[81],256],"119929":[[82],256],"119930":[[83],256],"119931":[[84],256],"119932":[[85],256],"119933":[[86],256],"119934":[[87],256],"119935":[[88],256],"119936":[[89],256],"119937":[[90],256],"119938":[[97],256],"119939":[[98],256],"119940":[[99],256],"119941":[[100],256],"119942":[[101],256],"119943":[[102],256],"119944":[[103],256],"119945":[[104],256],"119946":[[105],256],"119947":[[106],256],"119948":[[107],256],"119949":[[108],256],"119950":[[109],256],"119951":[[110],256],"119952":[[111],256],"119953":[[112],256],"119954":[[113],256],"119955":[[114],256],"119956":[[115],256],"119957":[[116],256],"119958":[[117],256],"119959":[[118],256],"119960":[[119],256],"119961":[[120],256],"119962":[[121],256],"119963":[[122],256],"119964":[[65],256],"119966":[[67],256],"119967":[[68],256],"119970":[[71],256],"119973":[[74],256],"119974":[[75],256],"119977":[[78],256],"119978":[[79],256],"119979":[[80],256],"119980":[[81],256],"119982":[[83],256],"119983":[[84],256],"119984":[[85],256],"119985":[[86],256],"119986":[[87],256],"119987":[[88],256],"119988":[[89],256],"119989":[[90],256],"119990":[[97],256],"119991":[[98],256],"119992":[[99],256],"119993":[[100],256],"119995":[[102],256],"119997":[[104],256],"119998":[[105],256],"119999":[[106],256],"120000":[[107],256],"120001":[[108],256],"120002":[[109],256],"120003":[[110],256],"120005":[[112],256],"120006":[[113],256],"120007":[[114],256],"120008":[[115],256],"120009":[[116],256],"120010":[[117],256],"120011":[[118],256],"120012":[[119],256],"120013":[[120],256],"120014":[[121],256],"120015":[[122],256],"120016":[[65],256],"120017":[[66],256],"120018":[[67],256],"120019":[[68],256],"120020":[[69],256],"120021":[[70],256],"120022":[[71],256],"120023":[[72],256],"120024":[[73],256],"120025":[[74],256],"120026":[[75],256],"120027":[[76],256],"120028":[[77],256],"120029":[[78],256],"120030":[[79],256],"120031":[[80],256],"120032":[[81],256],"120033":[[82],256],"120034":[[83],256],"120035":[[84],256],"120036":[[85],256],"120037":[[86],256],"120038":[[87],256],"120039":[[88],256],"120040":[[89],256],"120041":[[90],256],"120042":[[97],256],"120043":[[98],256],"120044":[[99],256],"120045":[[100],256],"120046":[[101],256],"120047":[[102],256],"120048":[[103],256],"120049":[[104],256],"120050":[[105],256],"120051":[[106],256],"120052":[[107],256],"120053":[[108],256],"120054":[[109],256],"120055":[[110],256],"120056":[[111],256],"120057":[[112],256],"120058":[[113],256],"120059":[[114],256],"120060":[[115],256],"120061":[[116],256],"120062":[[117],256],"120063":[[118],256]},"54528":{"120064":[[119],256],"120065":[[120],256],"120066":[[121],256],"120067":[[122],256],"120068":[[65],256],"120069":[[66],256],"120071":[[68],256],"120072":[[69],256],"120073":[[70],256],"120074":[[71],256],"120077":[[74],256],"120078":[[75],256],"120079":[[76],256],"120080":[[77],256],"120081":[[78],256],"120082":[[79],256],"120083":[[80],256],"120084":[[81],256],"120086":[[83],256],"120087":[[84],256],"120088":[[85],256],"120089":[[86],256],"120090":[[87],256],"120091":[[88],256],"120092":[[89],256],"120094":[[97],256],"120095":[[98],256],"120096":[[99],256],"120097":[[100],256],"120098":[[101],256],"120099":[[102],256],"120100":[[103],256],"120101":[[104],256],"120102":[[105],256],"120103":[[106],256],"120104":[[107],256],"120105":[[108],256],"120106":[[109],256],"120107":[[110],256],"120108":[[111],256],"120109":[[112],256],"120110":[[113],256],"120111":[[114],256],"120112":[[115],256],"120113":[[116],256],"120114":[[117],256],"120115":[[118],256],"120116":[[119],256],"120117":[[120],256],"120118":[[121],256],"120119":[[122],256],"120120":[[65],256],"120121":[[66],256],"120123":[[68],256],"120124":[[69],256],"120125":[[70],256],"120126":[[71],256],"120128":[[73],256],"120129":[[74],256],"120130":[[75],256],"120131":[[76],256],"120132":[[77],256],"120134":[[79],256],"120138":[[83],256],"120139":[[84],256],"120140":[[85],256],"120141":[[86],256],"120142":[[87],256],"120143":[[88],256],"120144":[[89],256],"120146":[[97],256],"120147":[[98],256],"120148":[[99],256],"120149":[[100],256],"120150":[[101],256],"120151":[[102],256],"120152":[[103],256],"120153":[[104],256],"120154":[[105],256],"120155":[[106],256],"120156":[[107],256],"120157":[[108],256],"120158":[[109],256],"120159":[[110],256],"120160":[[111],256],"120161":[[112],256],"120162":[[113],256],"120163":[[114],256],"120164":[[115],256],"120165":[[116],256],"120166":[[117],256],"120167":[[118],256],"120168":[[119],256],"120169":[[120],256],"120170":[[121],256],"120171":[[122],256],"120172":[[65],256],"120173":[[66],256],"120174":[[67],256],"120175":[[68],256],"120176":[[69],256],"120177":[[70],256],"120178":[[71],256],"120179":[[72],256],"120180":[[73],256],"120181":[[74],256],"120182":[[75],256],"120183":[[76],256],"120184":[[77],256],"120185":[[78],256],"120186":[[79],256],"120187":[[80],256],"120188":[[81],256],"120189":[[82],256],"120190":[[83],256],"120191":[[84],256],"120192":[[85],256],"120193":[[86],256],"120194":[[87],256],"120195":[[88],256],"120196":[[89],256],"120197":[[90],256],"120198":[[97],256],"120199":[[98],256],"120200":[[99],256],"120201":[[100],256],"120202":[[101],256],"120203":[[102],256],"120204":[[103],256],"120205":[[104],256],"120206":[[105],256],"120207":[[106],256],"120208":[[107],256],"120209":[[108],256],"120210":[[109],256],"120211":[[110],256],"120212":[[111],256],"120213":[[112],256],"120214":[[113],256],"120215":[[114],256],"120216":[[115],256],"120217":[[116],256],"120218":[[117],256],"120219":[[118],256],"120220":[[119],256],"120221":[[120],256],"120222":[[121],256],"120223":[[122],256],"120224":[[65],256],"120225":[[66],256],"120226":[[67],256],"120227":[[68],256],"120228":[[69],256],"120229":[[70],256],"120230":[[71],256],"120231":[[72],256],"120232":[[73],256],"120233":[[74],256],"120234":[[75],256],"120235":[[76],256],"120236":[[77],256],"120237":[[78],256],"120238":[[79],256],"120239":[[80],256],"120240":[[81],256],"120241":[[82],256],"120242":[[83],256],"120243":[[84],256],"120244":[[85],256],"120245":[[86],256],"120246":[[87],256],"120247":[[88],256],"120248":[[89],256],"120249":[[90],256],"120250":[[97],256],"120251":[[98],256],"120252":[[99],256],"120253":[[100],256],"120254":[[101],256],"120255":[[102],256],"120256":[[103],256],"120257":[[104],256],"120258":[[105],256],"120259":[[106],256],"120260":[[107],256],"120261":[[108],256],"120262":[[109],256],"120263":[[110],256],"120264":[[111],256],"120265":[[112],256],"120266":[[113],256],"120267":[[114],256],"120268":[[115],256],"120269":[[116],256],"120270":[[117],256],"120271":[[118],256],"120272":[[119],256],"120273":[[120],256],"120274":[[121],256],"120275":[[122],256],"120276":[[65],256],"120277":[[66],256],"120278":[[67],256],"120279":[[68],256],"120280":[[69],256],"120281":[[70],256],"120282":[[71],256],"120283":[[72],256],"120284":[[73],256],"120285":[[74],256],"120286":[[75],256],"120287":[[76],256],"120288":[[77],256],"120289":[[78],256],"120290":[[79],256],"120291":[[80],256],"120292":[[81],256],"120293":[[82],256],"120294":[[83],256],"120295":[[84],256],"120296":[[85],256],"120297":[[86],256],"120298":[[87],256],"120299":[[88],256],"120300":[[89],256],"120301":[[90],256],"120302":[[97],256],"120303":[[98],256],"120304":[[99],256],"120305":[[100],256],"120306":[[101],256],"120307":[[102],256],"120308":[[103],256],"120309":[[104],256],"120310":[[105],256],"120311":[[106],256],"120312":[[107],256],"120313":[[108],256],"120314":[[109],256],"120315":[[110],256],"120316":[[111],256],"120317":[[112],256],"120318":[[113],256],"120319":[[114],256]},"54784":{"120320":[[115],256],"120321":[[116],256],"120322":[[117],256],"120323":[[118],256],"120324":[[119],256],"120325":[[120],256],"120326":[[121],256],"120327":[[122],256],"120328":[[65],256],"120329":[[66],256],"120330":[[67],256],"120331":[[68],256],"120332":[[69],256],"120333":[[70],256],"120334":[[71],256],"120335":[[72],256],"120336":[[73],256],"120337":[[74],256],"120338":[[75],256],"120339":[[76],256],"120340":[[77],256],"120341":[[78],256],"120342":[[79],256],"120343":[[80],256],"120344":[[81],256],"120345":[[82],256],"120346":[[83],256],"120347":[[84],256],"120348":[[85],256],"120349":[[86],256],"120350":[[87],256],"120351":[[88],256],"120352":[[89],256],"120353":[[90],256],"120354":[[97],256],"120355":[[98],256],"120356":[[99],256],"120357":[[100],256],"120358":[[101],256],"120359":[[102],256],"120360":[[103],256],"120361":[[104],256],"120362":[[105],256],"120363":[[106],256],"120364":[[107],256],"120365":[[108],256],"120366":[[109],256],"120367":[[110],256],"120368":[[111],256],"120369":[[112],256],"120370":[[113],256],"120371":[[114],256],"120372":[[115],256],"120373":[[116],256],"120374":[[117],256],"120375":[[118],256],"120376":[[119],256],"120377":[[120],256],"120378":[[121],256],"120379":[[122],256],"120380":[[65],256],"120381":[[66],256],"120382":[[67],256],"120383":[[68],256],"120384":[[69],256],"120385":[[70],256],"120386":[[71],256],"120387":[[72],256],"120388":[[73],256],"120389":[[74],256],"120390":[[75],256],"120391":[[76],256],"120392":[[77],256],"120393":[[78],256],"120394":[[79],256],"120395":[[80],256],"120396":[[81],256],"120397":[[82],256],"120398":[[83],256],"120399":[[84],256],"120400":[[85],256],"120401":[[86],256],"120402":[[87],256],"120403":[[88],256],"120404":[[89],256],"120405":[[90],256],"120406":[[97],256],"120407":[[98],256],"120408":[[99],256],"120409":[[100],256],"120410":[[101],256],"120411":[[102],256],"120412":[[103],256],"120413":[[104],256],"120414":[[105],256],"120415":[[106],256],"120416":[[107],256],"120417":[[108],256],"120418":[[109],256],"120419":[[110],256],"120420":[[111],256],"120421":[[112],256],"120422":[[113],256],"120423":[[114],256],"120424":[[115],256],"120425":[[116],256],"120426":[[117],256],"120427":[[118],256],"120428":[[119],256],"120429":[[120],256],"120430":[[121],256],"120431":[[122],256],"120432":[[65],256],"120433":[[66],256],"120434":[[67],256],"120435":[[68],256],"120436":[[69],256],"120437":[[70],256],"120438":[[71],256],"120439":[[72],256],"120440":[[73],256],"120441":[[74],256],"120442":[[75],256],"120443":[[76],256],"120444":[[77],256],"120445":[[78],256],"120446":[[79],256],"120447":[[80],256],"120448":[[81],256],"120449":[[82],256],"120450":[[83],256],"120451":[[84],256],"120452":[[85],256],"120453":[[86],256],"120454":[[87],256],"120455":[[88],256],"120456":[[89],256],"120457":[[90],256],"120458":[[97],256],"120459":[[98],256],"120460":[[99],256],"120461":[[100],256],"120462":[[101],256],"120463":[[102],256],"120464":[[103],256],"120465":[[104],256],"120466":[[105],256],"120467":[[106],256],"120468":[[107],256],"120469":[[108],256],"120470":[[109],256],"120471":[[110],256],"120472":[[111],256],"120473":[[112],256],"120474":[[113],256],"120475":[[114],256],"120476":[[115],256],"120477":[[116],256],"120478":[[117],256],"120479":[[118],256],"120480":[[119],256],"120481":[[120],256],"120482":[[121],256],"120483":[[122],256],"120484":[[305],256],"120485":[[567],256],"120488":[[913],256],"120489":[[914],256],"120490":[[915],256],"120491":[[916],256],"120492":[[917],256],"120493":[[918],256],"120494":[[919],256],"120495":[[920],256],"120496":[[921],256],"120497":[[922],256],"120498":[[923],256],"120499":[[924],256],"120500":[[925],256],"120501":[[926],256],"120502":[[927],256],"120503":[[928],256],"120504":[[929],256],"120505":[[1012],256],"120506":[[931],256],"120507":[[932],256],"120508":[[933],256],"120509":[[934],256],"120510":[[935],256],"120511":[[936],256],"120512":[[937],256],"120513":[[8711],256],"120514":[[945],256],"120515":[[946],256],"120516":[[947],256],"120517":[[948],256],"120518":[[949],256],"120519":[[950],256],"120520":[[951],256],"120521":[[952],256],"120522":[[953],256],"120523":[[954],256],"120524":[[955],256],"120525":[[956],256],"120526":[[957],256],"120527":[[958],256],"120528":[[959],256],"120529":[[960],256],"120530":[[961],256],"120531":[[962],256],"120532":[[963],256],"120533":[[964],256],"120534":[[965],256],"120535":[[966],256],"120536":[[967],256],"120537":[[968],256],"120538":[[969],256],"120539":[[8706],256],"120540":[[1013],256],"120541":[[977],256],"120542":[[1008],256],"120543":[[981],256],"120544":[[1009],256],"120545":[[982],256],"120546":[[913],256],"120547":[[914],256],"120548":[[915],256],"120549":[[916],256],"120550":[[917],256],"120551":[[918],256],"120552":[[919],256],"120553":[[920],256],"120554":[[921],256],"120555":[[922],256],"120556":[[923],256],"120557":[[924],256],"120558":[[925],256],"120559":[[926],256],"120560":[[927],256],"120561":[[928],256],"120562":[[929],256],"120563":[[1012],256],"120564":[[931],256],"120565":[[932],256],"120566":[[933],256],"120567":[[934],256],"120568":[[935],256],"120569":[[936],256],"120570":[[937],256],"120571":[[8711],256],"120572":[[945],256],"120573":[[946],256],"120574":[[947],256],"120575":[[948],256]},"55040":{"120576":[[949],256],"120577":[[950],256],"120578":[[951],256],"120579":[[952],256],"120580":[[953],256],"120581":[[954],256],"120582":[[955],256],"120583":[[956],256],"120584":[[957],256],"120585":[[958],256],"120586":[[959],256],"120587":[[960],256],"120588":[[961],256],"120589":[[962],256],"120590":[[963],256],"120591":[[964],256],"120592":[[965],256],"120593":[[966],256],"120594":[[967],256],"120595":[[968],256],"120596":[[969],256],"120597":[[8706],256],"120598":[[1013],256],"120599":[[977],256],"120600":[[1008],256],"120601":[[981],256],"120602":[[1009],256],"120603":[[982],256],"120604":[[913],256],"120605":[[914],256],"120606":[[915],256],"120607":[[916],256],"120608":[[917],256],"120609":[[918],256],"120610":[[919],256],"120611":[[920],256],"120612":[[921],256],"120613":[[922],256],"120614":[[923],256],"120615":[[924],256],"120616":[[925],256],"120617":[[926],256],"120618":[[927],256],"120619":[[928],256],"120620":[[929],256],"120621":[[1012],256],"120622":[[931],256],"120623":[[932],256],"120624":[[933],256],"120625":[[934],256],"120626":[[935],256],"120627":[[936],256],"120628":[[937],256],"120629":[[8711],256],"120630":[[945],256],"120631":[[946],256],"120632":[[947],256],"120633":[[948],256],"120634":[[949],256],"120635":[[950],256],"120636":[[951],256],"120637":[[952],256],"120638":[[953],256],"120639":[[954],256],"120640":[[955],256],"120641":[[956],256],"120642":[[957],256],"120643":[[958],256],"120644":[[959],256],"120645":[[960],256],"120646":[[961],256],"120647":[[962],256],"120648":[[963],256],"120649":[[964],256],"120650":[[965],256],"120651":[[966],256],"120652":[[967],256],"120653":[[968],256],"120654":[[969],256],"120655":[[8706],256],"120656":[[1013],256],"120657":[[977],256],"120658":[[1008],256],"120659":[[981],256],"120660":[[1009],256],"120661":[[982],256],"120662":[[913],256],"120663":[[914],256],"120664":[[915],256],"120665":[[916],256],"120666":[[917],256],"120667":[[918],256],"120668":[[919],256],"120669":[[920],256],"120670":[[921],256],"120671":[[922],256],"120672":[[923],256],"120673":[[924],256],"120674":[[925],256],"120675":[[926],256],"120676":[[927],256],"120677":[[928],256],"120678":[[929],256],"120679":[[1012],256],"120680":[[931],256],"120681":[[932],256],"120682":[[933],256],"120683":[[934],256],"120684":[[935],256],"120685":[[936],256],"120686":[[937],256],"120687":[[8711],256],"120688":[[945],256],"120689":[[946],256],"120690":[[947],256],"120691":[[948],256],"120692":[[949],256],"120693":[[950],256],"120694":[[951],256],"120695":[[952],256],"120696":[[953],256],"120697":[[954],256],"120698":[[955],256],"120699":[[956],256],"120700":[[957],256],"120701":[[958],256],"120702":[[959],256],"120703":[[960],256],"120704":[[961],256],"120705":[[962],256],"120706":[[963],256],"120707":[[964],256],"120708":[[965],256],"120709":[[966],256],"120710":[[967],256],"120711":[[968],256],"120712":[[969],256],"120713":[[8706],256],"120714":[[1013],256],"120715":[[977],256],"120716":[[1008],256],"120717":[[981],256],"120718":[[1009],256],"120719":[[982],256],"120720":[[913],256],"120721":[[914],256],"120722":[[915],256],"120723":[[916],256],"120724":[[917],256],"120725":[[918],256],"120726":[[919],256],"120727":[[920],256],"120728":[[921],256],"120729":[[922],256],"120730":[[923],256],"120731":[[924],256],"120732":[[925],256],"120733":[[926],256],"120734":[[927],256],"120735":[[928],256],"120736":[[929],256],"120737":[[1012],256],"120738":[[931],256],"120739":[[932],256],"120740":[[933],256],"120741":[[934],256],"120742":[[935],256],"120743":[[936],256],"120744":[[937],256],"120745":[[8711],256],"120746":[[945],256],"120747":[[946],256],"120748":[[947],256],"120749":[[948],256],"120750":[[949],256],"120751":[[950],256],"120752":[[951],256],"120753":[[952],256],"120754":[[953],256],"120755":[[954],256],"120756":[[955],256],"120757":[[956],256],"120758":[[957],256],"120759":[[958],256],"120760":[[959],256],"120761":[[960],256],"120762":[[961],256],"120763":[[962],256],"120764":[[963],256],"120765":[[964],256],"120766":[[965],256],"120767":[[966],256],"120768":[[967],256],"120769":[[968],256],"120770":[[969],256],"120771":[[8706],256],"120772":[[1013],256],"120773":[[977],256],"120774":[[1008],256],"120775":[[981],256],"120776":[[1009],256],"120777":[[982],256],"120778":[[988],256],"120779":[[989],256],"120782":[[48],256],"120783":[[49],256],"120784":[[50],256],"120785":[[51],256],"120786":[[52],256],"120787":[[53],256],"120788":[[54],256],"120789":[[55],256],"120790":[[56],256],"120791":[[57],256],"120792":[[48],256],"120793":[[49],256],"120794":[[50],256],"120795":[[51],256],"120796":[[52],256],"120797":[[53],256],"120798":[[54],256],"120799":[[55],256],"120800":[[56],256],"120801":[[57],256],"120802":[[48],256],"120803":[[49],256],"120804":[[50],256],"120805":[[51],256],"120806":[[52],256],"120807":[[53],256],"120808":[[54],256],"120809":[[55],256],"120810":[[56],256],"120811":[[57],256],"120812":[[48],256],"120813":[[49],256],"120814":[[50],256],"120815":[[51],256],"120816":[[52],256],"120817":[[53],256],"120818":[[54],256],"120819":[[55],256],"120820":[[56],256],"120821":[[57],256],"120822":[[48],256],"120823":[[49],256],"120824":[[50],256],"120825":[[51],256],"120826":[[52],256],"120827":[[53],256],"120828":[[54],256],"120829":[[55],256],"120830":[[56],256],"120831":[[57],256]},"59392":{"125136":[null,220],"125137":[null,220],"125138":[null,220],"125139":[null,220],"125140":[null,220],"125141":[null,220],"125142":[null,220]},"60928":{"126464":[[1575],256],"126465":[[1576],256],"126466":[[1580],256],"126467":[[1583],256],"126469":[[1608],256],"126470":[[1586],256],"126471":[[1581],256],"126472":[[1591],256],"126473":[[1610],256],"126474":[[1603],256],"126475":[[1604],256],"126476":[[1605],256],"126477":[[1606],256],"126478":[[1587],256],"126479":[[1593],256],"126480":[[1601],256],"126481":[[1589],256],"126482":[[1602],256],"126483":[[1585],256],"126484":[[1588],256],"126485":[[1578],256],"126486":[[1579],256],"126487":[[1582],256],"126488":[[1584],256],"126489":[[1590],256],"126490":[[1592],256],"126491":[[1594],256],"126492":[[1646],256],"126493":[[1722],256],"126494":[[1697],256],"126495":[[1647],256],"126497":[[1576],256],"126498":[[1580],256],"126500":[[1607],256],"126503":[[1581],256],"126505":[[1610],256],"126506":[[1603],256],"126507":[[1604],256],"126508":[[1605],256],"126509":[[1606],256],"126510":[[1587],256],"126511":[[1593],256],"126512":[[1601],256],"126513":[[1589],256],"126514":[[1602],256],"126516":[[1588],256],"126517":[[1578],256],"126518":[[1579],256],"126519":[[1582],256],"126521":[[1590],256],"126523":[[1594],256],"126530":[[1580],256],"126535":[[1581],256],"126537":[[1610],256],"126539":[[1604],256],"126541":[[1606],256],"126542":[[1587],256],"126543":[[1593],256],"126545":[[1589],256],"126546":[[1602],256],"126548":[[1588],256],"126551":[[1582],256],"126553":[[1590],256],"126555":[[1594],256],"126557":[[1722],256],"126559":[[1647],256],"126561":[[1576],256],"126562":[[1580],256],"126564":[[1607],256],"126567":[[1581],256],"126568":[[1591],256],"126569":[[1610],256],"126570":[[1603],256],"126572":[[1605],256],"126573":[[1606],256],"126574":[[1587],256],"126575":[[1593],256],"126576":[[1601],256],"126577":[[1589],256],"126578":[[1602],256],"126580":[[1588],256],"126581":[[1578],256],"126582":[[1579],256],"126583":[[1582],256],"126585":[[1590],256],"126586":[[1592],256],"126587":[[1594],256],"126588":[[1646],256],"126590":[[1697],256],"126592":[[1575],256],"126593":[[1576],256],"126594":[[1580],256],"126595":[[1583],256],"126596":[[1607],256],"126597":[[1608],256],"126598":[[1586],256],"126599":[[1581],256],"126600":[[1591],256],"126601":[[1610],256],"126603":[[1604],256],"126604":[[1605],256],"126605":[[1606],256],"126606":[[1587],256],"126607":[[1593],256],"126608":[[1601],256],"126609":[[1589],256],"126610":[[1602],256],"126611":[[1585],256],"126612":[[1588],256],"126613":[[1578],256],"126614":[[1579],256],"126615":[[1582],256],"126616":[[1584],256],"126617":[[1590],256],"126618":[[1592],256],"126619":[[1594],256],"126625":[[1576],256],"126626":[[1580],256],"126627":[[1583],256],"126629":[[1608],256],"126630":[[1586],256],"126631":[[1581],256],"126632":[[1591],256],"126633":[[1610],256],"126635":[[1604],256],"126636":[[1605],256],"126637":[[1606],256],"126638":[[1587],256],"126639":[[1593],256],"126640":[[1601],256],"126641":[[1589],256],"126642":[[1602],256],"126643":[[1585],256],"126644":[[1588],256],"126645":[[1578],256],"126646":[[1579],256],"126647":[[1582],256],"126648":[[1584],256],"126649":[[1590],256],"126650":[[1592],256],"126651":[[1594],256]},"61696":{"127232":[[48,46],256],"127233":[[48,44],256],"127234":[[49,44],256],"127235":[[50,44],256],"127236":[[51,44],256],"127237":[[52,44],256],"127238":[[53,44],256],"127239":[[54,44],256],"127240":[[55,44],256],"127241":[[56,44],256],"127242":[[57,44],256],"127248":[[40,65,41],256],"127249":[[40,66,41],256],"127250":[[40,67,41],256],"127251":[[40,68,41],256],"127252":[[40,69,41],256],"127253":[[40,70,41],256],"127254":[[40,71,41],256],"127255":[[40,72,41],256],"127256":[[40,73,41],256],"127257":[[40,74,41],256],"127258":[[40,75,41],256],"127259":[[40,76,41],256],"127260":[[40,77,41],256],"127261":[[40,78,41],256],"127262":[[40,79,41],256],"127263":[[40,80,41],256],"127264":[[40,81,41],256],"127265":[[40,82,41],256],"127266":[[40,83,41],256],"127267":[[40,84,41],256],"127268":[[40,85,41],256],"127269":[[40,86,41],256],"127270":[[40,87,41],256],"127271":[[40,88,41],256],"127272":[[40,89,41],256],"127273":[[40,90,41],256],"127274":[[12308,83,12309],256],"127275":[[67],256],"127276":[[82],256],"127277":[[67,68],256],"127278":[[87,90],256],"127280":[[65],256],"127281":[[66],256],"127282":[[67],256],"127283":[[68],256],"127284":[[69],256],"127285":[[70],256],"127286":[[71],256],"127287":[[72],256],"127288":[[73],256],"127289":[[74],256],"127290":[[75],256],"127291":[[76],256],"127292":[[77],256],"127293":[[78],256],"127294":[[79],256],"127295":[[80],256],"127296":[[81],256],"127297":[[82],256],"127298":[[83],256],"127299":[[84],256],"127300":[[85],256],"127301":[[86],256],"127302":[[87],256],"127303":[[88],256],"127304":[[89],256],"127305":[[90],256],"127306":[[72,86],256],"127307":[[77,86],256],"127308":[[83,68],256],"127309":[[83,83],256],"127310":[[80,80,86],256],"127311":[[87,67],256],"127338":[[77,67],256],"127339":[[77,68],256],"127376":[[68,74],256]},"61952":{"127488":[[12411,12363],256],"127489":[[12467,12467],256],"127490":[[12469],256],"127504":[[25163],256],"127505":[[23383],256],"127506":[[21452],256],"127507":[[12487],256],"127508":[[20108],256],"127509":[[22810],256],"127510":[[35299],256],"127511":[[22825],256],"127512":[[20132],256],"127513":[[26144],256],"127514":[[28961],256],"127515":[[26009],256],"127516":[[21069],256],"127517":[[24460],256],"127518":[[20877],256],"127519":[[26032],256],"127520":[[21021],256],"127521":[[32066],256],"127522":[[29983],256],"127523":[[36009],256],"127524":[[22768],256],"127525":[[21561],256],"127526":[[28436],256],"127527":[[25237],256],"127528":[[25429],256],"127529":[[19968],256],"127530":[[19977],256],"127531":[[36938],256],"127532":[[24038],256],"127533":[[20013],256],"127534":[[21491],256],"127535":[[25351],256],"127536":[[36208],256],"127537":[[25171],256],"127538":[[31105],256],"127539":[[31354],256],"127540":[[21512],256],"127541":[[28288],256],"127542":[[26377],256],"127543":[[26376],256],"127544":[[30003],256],"127545":[[21106],256],"127546":[[21942],256],"127552":[[12308,26412,12309],256],"127553":[[12308,19977,12309],256],"127554":[[12308,20108,12309],256],"127555":[[12308,23433,12309],256],"127556":[[12308,28857,12309],256],"127557":[[12308,25171,12309],256],"127558":[[12308,30423,12309],256],"127559":[[12308,21213,12309],256],"127560":[[12308,25943,12309],256],"127568":[[24471],256],"127569":[[21487],256]},"63488":{"194560":[[20029]],"194561":[[20024]],"194562":[[20033]],"194563":[[131362]],"194564":[[20320]],"194565":[[20398]],"194566":[[20411]],"194567":[[20482]],"194568":[[20602]],"194569":[[20633]],"194570":[[20711]],"194571":[[20687]],"194572":[[13470]],"194573":[[132666]],"194574":[[20813]],"194575":[[20820]],"194576":[[20836]],"194577":[[20855]],"194578":[[132380]],"194579":[[13497]],"194580":[[20839]],"194581":[[20877]],"194582":[[132427]],"194583":[[20887]],"194584":[[20900]],"194585":[[20172]],"194586":[[20908]],"194587":[[20917]],"194588":[[168415]],"194589":[[20981]],"194590":[[20995]],"194591":[[13535]],"194592":[[21051]],"194593":[[21062]],"194594":[[21106]],"194595":[[21111]],"194596":[[13589]],"194597":[[21191]],"194598":[[21193]],"194599":[[21220]],"194600":[[21242]],"194601":[[21253]],"194602":[[21254]],"194603":[[21271]],"194604":[[21321]],"194605":[[21329]],"194606":[[21338]],"194607":[[21363]],"194608":[[21373]],"194609":[[21375]],"194610":[[21375]],"194611":[[21375]],"194612":[[133676]],"194613":[[28784]],"194614":[[21450]],"194615":[[21471]],"194616":[[133987]],"194617":[[21483]],"194618":[[21489]],"194619":[[21510]],"194620":[[21662]],"194621":[[21560]],"194622":[[21576]],"194623":[[21608]],"194624":[[21666]],"194625":[[21750]],"194626":[[21776]],"194627":[[21843]],"194628":[[21859]],"194629":[[21892]],"194630":[[21892]],"194631":[[21913]],"194632":[[21931]],"194633":[[21939]],"194634":[[21954]],"194635":[[22294]],"194636":[[22022]],"194637":[[22295]],"194638":[[22097]],"194639":[[22132]],"194640":[[20999]],"194641":[[22766]],"194642":[[22478]],"194643":[[22516]],"194644":[[22541]],"194645":[[22411]],"194646":[[22578]],"194647":[[22577]],"194648":[[22700]],"194649":[[136420]],"194650":[[22770]],"194651":[[22775]],"194652":[[22790]],"194653":[[22810]],"194654":[[22818]],"194655":[[22882]],"194656":[[136872]],"194657":[[136938]],"194658":[[23020]],"194659":[[23067]],"194660":[[23079]],"194661":[[23000]],"194662":[[23142]],"194663":[[14062]],"194664":[[14076]],"194665":[[23304]],"194666":[[23358]],"194667":[[23358]],"194668":[[137672]],"194669":[[23491]],"194670":[[23512]],"194671":[[23527]],"194672":[[23539]],"194673":[[138008]],"194674":[[23551]],"194675":[[23558]],"194676":[[24403]],"194677":[[23586]],"194678":[[14209]],"194679":[[23648]],"194680":[[23662]],"194681":[[23744]],"194682":[[23693]],"194683":[[138724]],"194684":[[23875]],"194685":[[138726]],"194686":[[23918]],"194687":[[23915]],"194688":[[23932]],"194689":[[24033]],"194690":[[24034]],"194691":[[14383]],"194692":[[24061]],"194693":[[24104]],"194694":[[24125]],"194695":[[24169]],"194696":[[14434]],"194697":[[139651]],"194698":[[14460]],"194699":[[24240]],"194700":[[24243]],"194701":[[24246]],"194702":[[24266]],"194703":[[172946]],"194704":[[24318]],"194705":[[140081]],"194706":[[140081]],"194707":[[33281]],"194708":[[24354]],"194709":[[24354]],"194710":[[14535]],"194711":[[144056]],"194712":[[156122]],"194713":[[24418]],"194714":[[24427]],"194715":[[14563]],"194716":[[24474]],"194717":[[24525]],"194718":[[24535]],"194719":[[24569]],"194720":[[24705]],"194721":[[14650]],"194722":[[14620]],"194723":[[24724]],"194724":[[141012]],"194725":[[24775]],"194726":[[24904]],"194727":[[24908]],"194728":[[24910]],"194729":[[24908]],"194730":[[24954]],"194731":[[24974]],"194732":[[25010]],"194733":[[24996]],"194734":[[25007]],"194735":[[25054]],"194736":[[25074]],"194737":[[25078]],"194738":[[25104]],"194739":[[25115]],"194740":[[25181]],"194741":[[25265]],"194742":[[25300]],"194743":[[25424]],"194744":[[142092]],"194745":[[25405]],"194746":[[25340]],"194747":[[25448]],"194748":[[25475]],"194749":[[25572]],"194750":[[142321]],"194751":[[25634]],"194752":[[25541]],"194753":[[25513]],"194754":[[14894]],"194755":[[25705]],"194756":[[25726]],"194757":[[25757]],"194758":[[25719]],"194759":[[14956]],"194760":[[25935]],"194761":[[25964]],"194762":[[143370]],"194763":[[26083]],"194764":[[26360]],"194765":[[26185]],"194766":[[15129]],"194767":[[26257]],"194768":[[15112]],"194769":[[15076]],"194770":[[20882]],"194771":[[20885]],"194772":[[26368]],"194773":[[26268]],"194774":[[32941]],"194775":[[17369]],"194776":[[26391]],"194777":[[26395]],"194778":[[26401]],"194779":[[26462]],"194780":[[26451]],"194781":[[144323]],"194782":[[15177]],"194783":[[26618]],"194784":[[26501]],"194785":[[26706]],"194786":[[26757]],"194787":[[144493]],"194788":[[26766]],"194789":[[26655]],"194790":[[26900]],"194791":[[15261]],"194792":[[26946]],"194793":[[27043]],"194794":[[27114]],"194795":[[27304]],"194796":[[145059]],"194797":[[27355]],"194798":[[15384]],"194799":[[27425]],"194800":[[145575]],"194801":[[27476]],"194802":[[15438]],"194803":[[27506]],"194804":[[27551]],"194805":[[27578]],"194806":[[27579]],"194807":[[146061]],"194808":[[138507]],"194809":[[146170]],"194810":[[27726]],"194811":[[146620]],"194812":[[27839]],"194813":[[27853]],"194814":[[27751]],"194815":[[27926]]},"63744":{"63744":[[35912]],"63745":[[26356]],"63746":[[36554]],"63747":[[36040]],"63748":[[28369]],"63749":[[20018]],"63750":[[21477]],"63751":[[40860]],"63752":[[40860]],"63753":[[22865]],"63754":[[37329]],"63755":[[21895]],"63756":[[22856]],"63757":[[25078]],"63758":[[30313]],"63759":[[32645]],"63760":[[34367]],"63761":[[34746]],"63762":[[35064]],"63763":[[37007]],"63764":[[27138]],"63765":[[27931]],"63766":[[28889]],"63767":[[29662]],"63768":[[33853]],"63769":[[37226]],"63770":[[39409]],"63771":[[20098]],"63772":[[21365]],"63773":[[27396]],"63774":[[29211]],"63775":[[34349]],"63776":[[40478]],"63777":[[23888]],"63778":[[28651]],"63779":[[34253]],"63780":[[35172]],"63781":[[25289]],"63782":[[33240]],"63783":[[34847]],"63784":[[24266]],"63785":[[26391]],"63786":[[28010]],"63787":[[29436]],"63788":[[37070]],"63789":[[20358]],"63790":[[20919]],"63791":[[21214]],"63792":[[25796]],"63793":[[27347]],"63794":[[29200]],"63795":[[30439]],"63796":[[32769]],"63797":[[34310]],"63798":[[34396]],"63799":[[36335]],"63800":[[38706]],"63801":[[39791]],"63802":[[40442]],"63803":[[30860]],"63804":[[31103]],"63805":[[32160]],"63806":[[33737]],"63807":[[37636]],"63808":[[40575]],"63809":[[35542]],"63810":[[22751]],"63811":[[24324]],"63812":[[31840]],"63813":[[32894]],"63814":[[29282]],"63815":[[30922]],"63816":[[36034]],"63817":[[38647]],"63818":[[22744]],"63819":[[23650]],"63820":[[27155]],"63821":[[28122]],"63822":[[28431]],"63823":[[32047]],"63824":[[32311]],"63825":[[38475]],"63826":[[21202]],"63827":[[32907]],"63828":[[20956]],"63829":[[20940]],"63830":[[31260]],"63831":[[32190]],"63832":[[33777]],"63833":[[38517]],"63834":[[35712]],"63835":[[25295]],"63836":[[27138]],"63837":[[35582]],"63838":[[20025]],"63839":[[23527]],"63840":[[24594]],"63841":[[29575]],"63842":[[30064]],"63843":[[21271]],"63844":[[30971]],"63845":[[20415]],"63846":[[24489]],"63847":[[19981]],"63848":[[27852]],"63849":[[25976]],"63850":[[32034]],"63851":[[21443]],"63852":[[22622]],"63853":[[30465]],"63854":[[33865]],"63855":[[35498]],"63856":[[27578]],"63857":[[36784]],"63858":[[27784]],"63859":[[25342]],"63860":[[33509]],"63861":[[25504]],"63862":[[30053]],"63863":[[20142]],"63864":[[20841]],"63865":[[20937]],"63866":[[26753]],"63867":[[31975]],"63868":[[33391]],"63869":[[35538]],"63870":[[37327]],"63871":[[21237]],"63872":[[21570]],"63873":[[22899]],"63874":[[24300]],"63875":[[26053]],"63876":[[28670]],"63877":[[31018]],"63878":[[38317]],"63879":[[39530]],"63880":[[40599]],"63881":[[40654]],"63882":[[21147]],"63883":[[26310]],"63884":[[27511]],"63885":[[36706]],"63886":[[24180]],"63887":[[24976]],"63888":[[25088]],"63889":[[25754]],"63890":[[28451]],"63891":[[29001]],"63892":[[29833]],"63893":[[31178]],"63894":[[32244]],"63895":[[32879]],"63896":[[36646]],"63897":[[34030]],"63898":[[36899]],"63899":[[37706]],"63900":[[21015]],"63901":[[21155]],"63902":[[21693]],"63903":[[28872]],"63904":[[35010]],"63905":[[35498]],"63906":[[24265]],"63907":[[24565]],"63908":[[25467]],"63909":[[27566]],"63910":[[31806]],"63911":[[29557]],"63912":[[20196]],"63913":[[22265]],"63914":[[23527]],"63915":[[23994]],"63916":[[24604]],"63917":[[29618]],"63918":[[29801]],"63919":[[32666]],"63920":[[32838]],"63921":[[37428]],"63922":[[38646]],"63923":[[38728]],"63924":[[38936]],"63925":[[20363]],"63926":[[31150]],"63927":[[37300]],"63928":[[38584]],"63929":[[24801]],"63930":[[20102]],"63931":[[20698]],"63932":[[23534]],"63933":[[23615]],"63934":[[26009]],"63935":[[27138]],"63936":[[29134]],"63937":[[30274]],"63938":[[34044]],"63939":[[36988]],"63940":[[40845]],"63941":[[26248]],"63942":[[38446]],"63943":[[21129]],"63944":[[26491]],"63945":[[26611]],"63946":[[27969]],"63947":[[28316]],"63948":[[29705]],"63949":[[30041]],"63950":[[30827]],"63951":[[32016]],"63952":[[39006]],"63953":[[20845]],"63954":[[25134]],"63955":[[38520]],"63956":[[20523]],"63957":[[23833]],"63958":[[28138]],"63959":[[36650]],"63960":[[24459]],"63961":[[24900]],"63962":[[26647]],"63963":[[29575]],"63964":[[38534]],"63965":[[21033]],"63966":[[21519]],"63967":[[23653]],"63968":[[26131]],"63969":[[26446]],"63970":[[26792]],"63971":[[27877]],"63972":[[29702]],"63973":[[30178]],"63974":[[32633]],"63975":[[35023]],"63976":[[35041]],"63977":[[37324]],"63978":[[38626]],"63979":[[21311]],"63980":[[28346]],"63981":[[21533]],"63982":[[29136]],"63983":[[29848]],"63984":[[34298]],"63985":[[38563]],"63986":[[40023]],"63987":[[40607]],"63988":[[26519]],"63989":[[28107]],"63990":[[33256]],"63991":[[31435]],"63992":[[31520]],"63993":[[31890]],"63994":[[29376]],"63995":[[28825]],"63996":[[35672]],"63997":[[20160]],"63998":[[33590]],"63999":[[21050]],"194816":[[27966]],"194817":[[28023]],"194818":[[27969]],"194819":[[28009]],"194820":[[28024]],"194821":[[28037]],"194822":[[146718]],"194823":[[27956]],"194824":[[28207]],"194825":[[28270]],"194826":[[15667]],"194827":[[28363]],"194828":[[28359]],"194829":[[147153]],"194830":[[28153]],"194831":[[28526]],"194832":[[147294]],"194833":[[147342]],"194834":[[28614]],"194835":[[28729]],"194836":[[28702]],"194837":[[28699]],"194838":[[15766]],"194839":[[28746]],"194840":[[28797]],"194841":[[28791]],"194842":[[28845]],"194843":[[132389]],"194844":[[28997]],"194845":[[148067]],"194846":[[29084]],"194847":[[148395]],"194848":[[29224]],"194849":[[29237]],"194850":[[29264]],"194851":[[149000]],"194852":[[29312]],"194853":[[29333]],"194854":[[149301]],"194855":[[149524]],"194856":[[29562]],"194857":[[29579]],"194858":[[16044]],"194859":[[29605]],"194860":[[16056]],"194861":[[16056]],"194862":[[29767]],"194863":[[29788]],"194864":[[29809]],"194865":[[29829]],"194866":[[29898]],"194867":[[16155]],"194868":[[29988]],"194869":[[150582]],"194870":[[30014]],"194871":[[150674]],"194872":[[30064]],"194873":[[139679]],"194874":[[30224]],"194875":[[151457]],"194876":[[151480]],"194877":[[151620]],"194878":[[16380]],"194879":[[16392]],"194880":[[30452]],"194881":[[151795]],"194882":[[151794]],"194883":[[151833]],"194884":[[151859]],"194885":[[30494]],"194886":[[30495]],"194887":[[30495]],"194888":[[30538]],"194889":[[16441]],"194890":[[30603]],"194891":[[16454]],"194892":[[16534]],"194893":[[152605]],"194894":[[30798]],"194895":[[30860]],"194896":[[30924]],"194897":[[16611]],"194898":[[153126]],"194899":[[31062]],"194900":[[153242]],"194901":[[153285]],"194902":[[31119]],"194903":[[31211]],"194904":[[16687]],"194905":[[31296]],"194906":[[31306]],"194907":[[31311]],"194908":[[153980]],"194909":[[154279]],"194910":[[154279]],"194911":[[31470]],"194912":[[16898]],"194913":[[154539]],"194914":[[31686]],"194915":[[31689]],"194916":[[16935]],"194917":[[154752]],"194918":[[31954]],"194919":[[17056]],"194920":[[31976]],"194921":[[31971]],"194922":[[32000]],"194923":[[155526]],"194924":[[32099]],"194925":[[17153]],"194926":[[32199]],"194927":[[32258]],"194928":[[32325]],"194929":[[17204]],"194930":[[156200]],"194931":[[156231]],"194932":[[17241]],"194933":[[156377]],"194934":[[32634]],"194935":[[156478]],"194936":[[32661]],"194937":[[32762]],"194938":[[32773]],"194939":[[156890]],"194940":[[156963]],"194941":[[32864]],"194942":[[157096]],"194943":[[32880]],"194944":[[144223]],"194945":[[17365]],"194946":[[32946]],"194947":[[33027]],"194948":[[17419]],"194949":[[33086]],"194950":[[23221]],"194951":[[157607]],"194952":[[157621]],"194953":[[144275]],"194954":[[144284]],"194955":[[33281]],"194956":[[33284]],"194957":[[36766]],"194958":[[17515]],"194959":[[33425]],"194960":[[33419]],"194961":[[33437]],"194962":[[21171]],"194963":[[33457]],"194964":[[33459]],"194965":[[33469]],"194966":[[33510]],"194967":[[158524]],"194968":[[33509]],"194969":[[33565]],"194970":[[33635]],"194971":[[33709]],"194972":[[33571]],"194973":[[33725]],"194974":[[33767]],"194975":[[33879]],"194976":[[33619]],"194977":[[33738]],"194978":[[33740]],"194979":[[33756]],"194980":[[158774]],"194981":[[159083]],"194982":[[158933]],"194983":[[17707]],"194984":[[34033]],"194985":[[34035]],"194986":[[34070]],"194987":[[160714]],"194988":[[34148]],"194989":[[159532]],"194990":[[17757]],"194991":[[17761]],"194992":[[159665]],"194993":[[159954]],"194994":[[17771]],"194995":[[34384]],"194996":[[34396]],"194997":[[34407]],"194998":[[34409]],"194999":[[34473]],"195000":[[34440]],"195001":[[34574]],"195002":[[34530]],"195003":[[34681]],"195004":[[34600]],"195005":[[34667]],"195006":[[34694]],"195007":[[17879]],"195008":[[34785]],"195009":[[34817]],"195010":[[17913]],"195011":[[34912]],"195012":[[34915]],"195013":[[161383]],"195014":[[35031]],"195015":[[35038]],"195016":[[17973]],"195017":[[35066]],"195018":[[13499]],"195019":[[161966]],"195020":[[162150]],"195021":[[18110]],"195022":[[18119]],"195023":[[35488]],"195024":[[35565]],"195025":[[35722]],"195026":[[35925]],"195027":[[162984]],"195028":[[36011]],"195029":[[36033]],"195030":[[36123]],"195031":[[36215]],"195032":[[163631]],"195033":[[133124]],"195034":[[36299]],"195035":[[36284]],"195036":[[36336]],"195037":[[133342]],"195038":[[36564]],"195039":[[36664]],"195040":[[165330]],"195041":[[165357]],"195042":[[37012]],"195043":[[37105]],"195044":[[37137]],"195045":[[165678]],"195046":[[37147]],"195047":[[37432]],"195048":[[37591]],"195049":[[37592]],"195050":[[37500]],"195051":[[37881]],"195052":[[37909]],"195053":[[166906]],"195054":[[38283]],"195055":[[18837]],"195056":[[38327]],"195057":[[167287]],"195058":[[18918]],"195059":[[38595]],"195060":[[23986]],"195061":[[38691]],"195062":[[168261]],"195063":[[168474]],"195064":[[19054]],"195065":[[19062]],"195066":[[38880]],"195067":[[168970]],"195068":[[19122]],"195069":[[169110]],"195070":[[38923]],"195071":[[38923]]},"64000":{"64000":[[20999]],"64001":[[24230]],"64002":[[25299]],"64003":[[31958]],"64004":[[23429]],"64005":[[27934]],"64006":[[26292]],"64007":[[36667]],"64008":[[34892]],"64009":[[38477]],"64010":[[35211]],"64011":[[24275]],"64012":[[20800]],"64013":[[21952]],"64016":[[22618]],"64018":[[26228]],"64021":[[20958]],"64022":[[29482]],"64023":[[30410]],"64024":[[31036]],"64025":[[31070]],"64026":[[31077]],"64027":[[31119]],"64028":[[38742]],"64029":[[31934]],"64030":[[32701]],"64032":[[34322]],"64034":[[35576]],"64037":[[36920]],"64038":[[37117]],"64042":[[39151]],"64043":[[39164]],"64044":[[39208]],"64045":[[40372]],"64046":[[37086]],"64047":[[38583]],"64048":[[20398]],"64049":[[20711]],"64050":[[20813]],"64051":[[21193]],"64052":[[21220]],"64053":[[21329]],"64054":[[21917]],"64055":[[22022]],"64056":[[22120]],"64057":[[22592]],"64058":[[22696]],"64059":[[23652]],"64060":[[23662]],"64061":[[24724]],"64062":[[24936]],"64063":[[24974]],"64064":[[25074]],"64065":[[25935]],"64066":[[26082]],"64067":[[26257]],"64068":[[26757]],"64069":[[28023]],"64070":[[28186]],"64071":[[28450]],"64072":[[29038]],"64073":[[29227]],"64074":[[29730]],"64075":[[30865]],"64076":[[31038]],"64077":[[31049]],"64078":[[31048]],"64079":[[31056]],"64080":[[31062]],"64081":[[31069]],"64082":[[31117]],"64083":[[31118]],"64084":[[31296]],"64085":[[31361]],"64086":[[31680]],"64087":[[32244]],"64088":[[32265]],"64089":[[32321]],"64090":[[32626]],"64091":[[32773]],"64092":[[33261]],"64093":[[33401]],"64094":[[33401]],"64095":[[33879]],"64096":[[35088]],"64097":[[35222]],"64098":[[35585]],"64099":[[35641]],"64100":[[36051]],"64101":[[36104]],"64102":[[36790]],"64103":[[36920]],"64104":[[38627]],"64105":[[38911]],"64106":[[38971]],"64107":[[24693]],"64108":[[148206]],"64109":[[33304]],"64112":[[20006]],"64113":[[20917]],"64114":[[20840]],"64115":[[20352]],"64116":[[20805]],"64117":[[20864]],"64118":[[21191]],"64119":[[21242]],"64120":[[21917]],"64121":[[21845]],"64122":[[21913]],"64123":[[21986]],"64124":[[22618]],"64125":[[22707]],"64126":[[22852]],"64127":[[22868]],"64128":[[23138]],"64129":[[23336]],"64130":[[24274]],"64131":[[24281]],"64132":[[24425]],"64133":[[24493]],"64134":[[24792]],"64135":[[24910]],"64136":[[24840]],"64137":[[24974]],"64138":[[24928]],"64139":[[25074]],"64140":[[25140]],"64141":[[25540]],"64142":[[25628]],"64143":[[25682]],"64144":[[25942]],"64145":[[26228]],"64146":[[26391]],"64147":[[26395]],"64148":[[26454]],"64149":[[27513]],"64150":[[27578]],"64151":[[27969]],"64152":[[28379]],"64153":[[28363]],"64154":[[28450]],"64155":[[28702]],"64156":[[29038]],"64157":[[30631]],"64158":[[29237]],"64159":[[29359]],"64160":[[29482]],"64161":[[29809]],"64162":[[29958]],"64163":[[30011]],"64164":[[30237]],"64165":[[30239]],"64166":[[30410]],"64167":[[30427]],"64168":[[30452]],"64169":[[30538]],"64170":[[30528]],"64171":[[30924]],"64172":[[31409]],"64173":[[31680]],"64174":[[31867]],"64175":[[32091]],"64176":[[32244]],"64177":[[32574]],"64178":[[32773]],"64179":[[33618]],"64180":[[33775]],"64181":[[34681]],"64182":[[35137]],"64183":[[35206]],"64184":[[35222]],"64185":[[35519]],"64186":[[35576]],"64187":[[35531]],"64188":[[35585]],"64189":[[35582]],"64190":[[35565]],"64191":[[35641]],"64192":[[35722]],"64193":[[36104]],"64194":[[36664]],"64195":[[36978]],"64196":[[37273]],"64197":[[37494]],"64198":[[38524]],"64199":[[38627]],"64200":[[38742]],"64201":[[38875]],"64202":[[38911]],"64203":[[38923]],"64204":[[38971]],"64205":[[39698]],"64206":[[40860]],"64207":[[141386]],"64208":[[141380]],"64209":[[144341]],"64210":[[15261]],"64211":[[16408]],"64212":[[16441]],"64213":[[152137]],"64214":[[154832]],"64215":[[163539]],"64216":[[40771]],"64217":[[40846]],"195072":[[38953]],"195073":[[169398]],"195074":[[39138]],"195075":[[19251]],"195076":[[39209]],"195077":[[39335]],"195078":[[39362]],"195079":[[39422]],"195080":[[19406]],"195081":[[170800]],"195082":[[39698]],"195083":[[40000]],"195084":[[40189]],"195085":[[19662]],"195086":[[19693]],"195087":[[40295]],"195088":[[172238]],"195089":[[19704]],"195090":[[172293]],"195091":[[172558]],"195092":[[172689]],"195093":[[40635]],"195094":[[19798]],"195095":[[40697]],"195096":[[40702]],"195097":[[40709]],"195098":[[40719]],"195099":[[40726]],"195100":[[40763]],"195101":[[173568]]},"64256":{"64256":[[102,102],256],"64257":[[102,105],256],"64258":[[102,108],256],"64259":[[102,102,105],256],"64260":[[102,102,108],256],"64261":[[383,116],256],"64262":[[115,116],256],"64275":[[1396,1398],256],"64276":[[1396,1381],256],"64277":[[1396,1387],256],"64278":[[1406,1398],256],"64279":[[1396,1389],256],"64285":[[1497,1460],512],"64286":[null,26],"64287":[[1522,1463],512],"64288":[[1506],256],"64289":[[1488],256],"64290":[[1491],256],"64291":[[1492],256],"64292":[[1499],256],"64293":[[1500],256],"64294":[[1501],256],"64295":[[1512],256],"64296":[[1514],256],"64297":[[43],256],"64298":[[1513,1473],512],"64299":[[1513,1474],512],"64300":[[64329,1473],512],"64301":[[64329,1474],512],"64302":[[1488,1463],512],"64303":[[1488,1464],512],"64304":[[1488,1468],512],"64305":[[1489,1468],512],"64306":[[1490,1468],512],"64307":[[1491,1468],512],"64308":[[1492,1468],512],"64309":[[1493,1468],512],"64310":[[1494,1468],512],"64312":[[1496,1468],512],"64313":[[1497,1468],512],"64314":[[1498,1468],512],"64315":[[1499,1468],512],"64316":[[1500,1468],512],"64318":[[1502,1468],512],"64320":[[1504,1468],512],"64321":[[1505,1468],512],"64323":[[1507,1468],512],"64324":[[1508,1468],512],"64326":[[1510,1468],512],"64327":[[1511,1468],512],"64328":[[1512,1468],512],"64329":[[1513,1468],512],"64330":[[1514,1468],512],"64331":[[1493,1465],512],"64332":[[1489,1471],512],"64333":[[1499,1471],512],"64334":[[1508,1471],512],"64335":[[1488,1500],256],"64336":[[1649],256],"64337":[[1649],256],"64338":[[1659],256],"64339":[[1659],256],"64340":[[1659],256],"64341":[[1659],256],"64342":[[1662],256],"64343":[[1662],256],"64344":[[1662],256],"64345":[[1662],256],"64346":[[1664],256],"64347":[[1664],256],"64348":[[1664],256],"64349":[[1664],256],"64350":[[1658],256],"64351":[[1658],256],"64352":[[1658],256],"64353":[[1658],256],"64354":[[1663],256],"64355":[[1663],256],"64356":[[1663],256],"64357":[[1663],256],"64358":[[1657],256],"64359":[[1657],256],"64360":[[1657],256],"64361":[[1657],256],"64362":[[1700],256],"64363":[[1700],256],"64364":[[1700],256],"64365":[[1700],256],"64366":[[1702],256],"64367":[[1702],256],"64368":[[1702],256],"64369":[[1702],256],"64370":[[1668],256],"64371":[[1668],256],"64372":[[1668],256],"64373":[[1668],256],"64374":[[1667],256],"64375":[[1667],256],"64376":[[1667],256],"64377":[[1667],256],"64378":[[1670],256],"64379":[[1670],256],"64380":[[1670],256],"64381":[[1670],256],"64382":[[1671],256],"64383":[[1671],256],"64384":[[1671],256],"64385":[[1671],256],"64386":[[1677],256],"64387":[[1677],256],"64388":[[1676],256],"64389":[[1676],256],"64390":[[1678],256],"64391":[[1678],256],"64392":[[1672],256],"64393":[[1672],256],"64394":[[1688],256],"64395":[[1688],256],"64396":[[1681],256],"64397":[[1681],256],"64398":[[1705],256],"64399":[[1705],256],"64400":[[1705],256],"64401":[[1705],256],"64402":[[1711],256],"64403":[[1711],256],"64404":[[1711],256],"64405":[[1711],256],"64406":[[1715],256],"64407":[[1715],256],"64408":[[1715],256],"64409":[[1715],256],"64410":[[1713],256],"64411":[[1713],256],"64412":[[1713],256],"64413":[[1713],256],"64414":[[1722],256],"64415":[[1722],256],"64416":[[1723],256],"64417":[[1723],256],"64418":[[1723],256],"64419":[[1723],256],"64420":[[1728],256],"64421":[[1728],256],"64422":[[1729],256],"64423":[[1729],256],"64424":[[1729],256],"64425":[[1729],256],"64426":[[1726],256],"64427":[[1726],256],"64428":[[1726],256],"64429":[[1726],256],"64430":[[1746],256],"64431":[[1746],256],"64432":[[1747],256],"64433":[[1747],256],"64467":[[1709],256],"64468":[[1709],256],"64469":[[1709],256],"64470":[[1709],256],"64471":[[1735],256],"64472":[[1735],256],"64473":[[1734],256],"64474":[[1734],256],"64475":[[1736],256],"64476":[[1736],256],"64477":[[1655],256],"64478":[[1739],256],"64479":[[1739],256],"64480":[[1733],256],"64481":[[1733],256],"64482":[[1737],256],"64483":[[1737],256],"64484":[[1744],256],"64485":[[1744],256],"64486":[[1744],256],"64487":[[1744],256],"64488":[[1609],256],"64489":[[1609],256],"64490":[[1574,1575],256],"64491":[[1574,1575],256],"64492":[[1574,1749],256],"64493":[[1574,1749],256],"64494":[[1574,1608],256],"64495":[[1574,1608],256],"64496":[[1574,1735],256],"64497":[[1574,1735],256],"64498":[[1574,1734],256],"64499":[[1574,1734],256],"64500":[[1574,1736],256],"64501":[[1574,1736],256],"64502":[[1574,1744],256],"64503":[[1574,1744],256],"64504":[[1574,1744],256],"64505":[[1574,1609],256],"64506":[[1574,1609],256],"64507":[[1574,1609],256],"64508":[[1740],256],"64509":[[1740],256],"64510":[[1740],256],"64511":[[1740],256]},"64512":{"64512":[[1574,1580],256],"64513":[[1574,1581],256],"64514":[[1574,1605],256],"64515":[[1574,1609],256],"64516":[[1574,1610],256],"64517":[[1576,1580],256],"64518":[[1576,1581],256],"64519":[[1576,1582],256],"64520":[[1576,1605],256],"64521":[[1576,1609],256],"64522":[[1576,1610],256],"64523":[[1578,1580],256],"64524":[[1578,1581],256],"64525":[[1578,1582],256],"64526":[[1578,1605],256],"64527":[[1578,1609],256],"64528":[[1578,1610],256],"64529":[[1579,1580],256],"64530":[[1579,1605],256],"64531":[[1579,1609],256],"64532":[[1579,1610],256],"64533":[[1580,1581],256],"64534":[[1580,1605],256],"64535":[[1581,1580],256],"64536":[[1581,1605],256],"64537":[[1582,1580],256],"64538":[[1582,1581],256],"64539":[[1582,1605],256],"64540":[[1587,1580],256],"64541":[[1587,1581],256],"64542":[[1587,1582],256],"64543":[[1587,1605],256],"64544":[[1589,1581],256],"64545":[[1589,1605],256],"64546":[[1590,1580],256],"64547":[[1590,1581],256],"64548":[[1590,1582],256],"64549":[[1590,1605],256],"64550":[[1591,1581],256],"64551":[[1591,1605],256],"64552":[[1592,1605],256],"64553":[[1593,1580],256],"64554":[[1593,1605],256],"64555":[[1594,1580],256],"64556":[[1594,1605],256],"64557":[[1601,1580],256],"64558":[[1601,1581],256],"64559":[[1601,1582],256],"64560":[[1601,1605],256],"64561":[[1601,1609],256],"64562":[[1601,1610],256],"64563":[[1602,1581],256],"64564":[[1602,1605],256],"64565":[[1602,1609],256],"64566":[[1602,1610],256],"64567":[[1603,1575],256],"64568":[[1603,1580],256],"64569":[[1603,1581],256],"64570":[[1603,1582],256],"64571":[[1603,1604],256],"64572":[[1603,1605],256],"64573":[[1603,1609],256],"64574":[[1603,1610],256],"64575":[[1604,1580],256],"64576":[[1604,1581],256],"64577":[[1604,1582],256],"64578":[[1604,1605],256],"64579":[[1604,1609],256],"64580":[[1604,1610],256],"64581":[[1605,1580],256],"64582":[[1605,1581],256],"64583":[[1605,1582],256],"64584":[[1605,1605],256],"64585":[[1605,1609],256],"64586":[[1605,1610],256],"64587":[[1606,1580],256],"64588":[[1606,1581],256],"64589":[[1606,1582],256],"64590":[[1606,1605],256],"64591":[[1606,1609],256],"64592":[[1606,1610],256],"64593":[[1607,1580],256],"64594":[[1607,1605],256],"64595":[[1607,1609],256],"64596":[[1607,1610],256],"64597":[[1610,1580],256],"64598":[[1610,1581],256],"64599":[[1610,1582],256],"64600":[[1610,1605],256],"64601":[[1610,1609],256],"64602":[[1610,1610],256],"64603":[[1584,1648],256],"64604":[[1585,1648],256],"64605":[[1609,1648],256],"64606":[[32,1612,1617],256],"64607":[[32,1613,1617],256],"64608":[[32,1614,1617],256],"64609":[[32,1615,1617],256],"64610":[[32,1616,1617],256],"64611":[[32,1617,1648],256],"64612":[[1574,1585],256],"64613":[[1574,1586],256],"64614":[[1574,1605],256],"64615":[[1574,1606],256],"64616":[[1574,1609],256],"64617":[[1574,1610],256],"64618":[[1576,1585],256],"64619":[[1576,1586],256],"64620":[[1576,1605],256],"64621":[[1576,1606],256],"64622":[[1576,1609],256],"64623":[[1576,1610],256],"64624":[[1578,1585],256],"64625":[[1578,1586],256],"64626":[[1578,1605],256],"64627":[[1578,1606],256],"64628":[[1578,1609],256],"64629":[[1578,1610],256],"64630":[[1579,1585],256],"64631":[[1579,1586],256],"64632":[[1579,1605],256],"64633":[[1579,1606],256],"64634":[[1579,1609],256],"64635":[[1579,1610],256],"64636":[[1601,1609],256],"64637":[[1601,1610],256],"64638":[[1602,1609],256],"64639":[[1602,1610],256],"64640":[[1603,1575],256],"64641":[[1603,1604],256],"64642":[[1603,1605],256],"64643":[[1603,1609],256],"64644":[[1603,1610],256],"64645":[[1604,1605],256],"64646":[[1604,1609],256],"64647":[[1604,1610],256],"64648":[[1605,1575],256],"64649":[[1605,1605],256],"64650":[[1606,1585],256],"64651":[[1606,1586],256],"64652":[[1606,1605],256],"64653":[[1606,1606],256],"64654":[[1606,1609],256],"64655":[[1606,1610],256],"64656":[[1609,1648],256],"64657":[[1610,1585],256],"64658":[[1610,1586],256],"64659":[[1610,1605],256],"64660":[[1610,1606],256],"64661":[[1610,1609],256],"64662":[[1610,1610],256],"64663":[[1574,1580],256],"64664":[[1574,1581],256],"64665":[[1574,1582],256],"64666":[[1574,1605],256],"64667":[[1574,1607],256],"64668":[[1576,1580],256],"64669":[[1576,1581],256],"64670":[[1576,1582],256],"64671":[[1576,1605],256],"64672":[[1576,1607],256],"64673":[[1578,1580],256],"64674":[[1578,1581],256],"64675":[[1578,1582],256],"64676":[[1578,1605],256],"64677":[[1578,1607],256],"64678":[[1579,1605],256],"64679":[[1580,1581],256],"64680":[[1580,1605],256],"64681":[[1581,1580],256],"64682":[[1581,1605],256],"64683":[[1582,1580],256],"64684":[[1582,1605],256],"64685":[[1587,1580],256],"64686":[[1587,1581],256],"64687":[[1587,1582],256],"64688":[[1587,1605],256],"64689":[[1589,1581],256],"64690":[[1589,1582],256],"64691":[[1589,1605],256],"64692":[[1590,1580],256],"64693":[[1590,1581],256],"64694":[[1590,1582],256],"64695":[[1590,1605],256],"64696":[[1591,1581],256],"64697":[[1592,1605],256],"64698":[[1593,1580],256],"64699":[[1593,1605],256],"64700":[[1594,1580],256],"64701":[[1594,1605],256],"64702":[[1601,1580],256],"64703":[[1601,1581],256],"64704":[[1601,1582],256],"64705":[[1601,1605],256],"64706":[[1602,1581],256],"64707":[[1602,1605],256],"64708":[[1603,1580],256],"64709":[[1603,1581],256],"64710":[[1603,1582],256],"64711":[[1603,1604],256],"64712":[[1603,1605],256],"64713":[[1604,1580],256],"64714":[[1604,1581],256],"64715":[[1604,1582],256],"64716":[[1604,1605],256],"64717":[[1604,1607],256],"64718":[[1605,1580],256],"64719":[[1605,1581],256],"64720":[[1605,1582],256],"64721":[[1605,1605],256],"64722":[[1606,1580],256],"64723":[[1606,1581],256],"64724":[[1606,1582],256],"64725":[[1606,1605],256],"64726":[[1606,1607],256],"64727":[[1607,1580],256],"64728":[[1607,1605],256],"64729":[[1607,1648],256],"64730":[[1610,1580],256],"64731":[[1610,1581],256],"64732":[[1610,1582],256],"64733":[[1610,1605],256],"64734":[[1610,1607],256],"64735":[[1574,1605],256],"64736":[[1574,1607],256],"64737":[[1576,1605],256],"64738":[[1576,1607],256],"64739":[[1578,1605],256],"64740":[[1578,1607],256],"64741":[[1579,1605],256],"64742":[[1579,1607],256],"64743":[[1587,1605],256],"64744":[[1587,1607],256],"64745":[[1588,1605],256],"64746":[[1588,1607],256],"64747":[[1603,1604],256],"64748":[[1603,1605],256],"64749":[[1604,1605],256],"64750":[[1606,1605],256],"64751":[[1606,1607],256],"64752":[[1610,1605],256],"64753":[[1610,1607],256],"64754":[[1600,1614,1617],256],"64755":[[1600,1615,1617],256],"64756":[[1600,1616,1617],256],"64757":[[1591,1609],256],"64758":[[1591,1610],256],"64759":[[1593,1609],256],"64760":[[1593,1610],256],"64761":[[1594,1609],256],"64762":[[1594,1610],256],"64763":[[1587,1609],256],"64764":[[1587,1610],256],"64765":[[1588,1609],256],"64766":[[1588,1610],256],"64767":[[1581,1609],256]},"64768":{"64768":[[1581,1610],256],"64769":[[1580,1609],256],"64770":[[1580,1610],256],"64771":[[1582,1609],256],"64772":[[1582,1610],256],"64773":[[1589,1609],256],"64774":[[1589,1610],256],"64775":[[1590,1609],256],"64776":[[1590,1610],256],"64777":[[1588,1580],256],"64778":[[1588,1581],256],"64779":[[1588,1582],256],"64780":[[1588,1605],256],"64781":[[1588,1585],256],"64782":[[1587,1585],256],"64783":[[1589,1585],256],"64784":[[1590,1585],256],"64785":[[1591,1609],256],"64786":[[1591,1610],256],"64787":[[1593,1609],256],"64788":[[1593,1610],256],"64789":[[1594,1609],256],"64790":[[1594,1610],256],"64791":[[1587,1609],256],"64792":[[1587,1610],256],"64793":[[1588,1609],256],"64794":[[1588,1610],256],"64795":[[1581,1609],256],"64796":[[1581,1610],256],"64797":[[1580,1609],256],"64798":[[1580,1610],256],"64799":[[1582,1609],256],"64800":[[1582,1610],256],"64801":[[1589,1609],256],"64802":[[1589,1610],256],"64803":[[1590,1609],256],"64804":[[1590,1610],256],"64805":[[1588,1580],256],"64806":[[1588,1581],256],"64807":[[1588,1582],256],"64808":[[1588,1605],256],"64809":[[1588,1585],256],"64810":[[1587,1585],256],"64811":[[1589,1585],256],"64812":[[1590,1585],256],"64813":[[1588,1580],256],"64814":[[1588,1581],256],"64815":[[1588,1582],256],"64816":[[1588,1605],256],"64817":[[1587,1607],256],"64818":[[1588,1607],256],"64819":[[1591,1605],256],"64820":[[1587,1580],256],"64821":[[1587,1581],256],"64822":[[1587,1582],256],"64823":[[1588,1580],256],"64824":[[1588,1581],256],"64825":[[1588,1582],256],"64826":[[1591,1605],256],"64827":[[1592,1605],256],"64828":[[1575,1611],256],"64829":[[1575,1611],256],"64848":[[1578,1580,1605],256],"64849":[[1578,1581,1580],256],"64850":[[1578,1581,1580],256],"64851":[[1578,1581,1605],256],"64852":[[1578,1582,1605],256],"64853":[[1578,1605,1580],256],"64854":[[1578,1605,1581],256],"64855":[[1578,1605,1582],256],"64856":[[1580,1605,1581],256],"64857":[[1580,1605,1581],256],"64858":[[1581,1605,1610],256],"64859":[[1581,1605,1609],256],"64860":[[1587,1581,1580],256],"64861":[[1587,1580,1581],256],"64862":[[1587,1580,1609],256],"64863":[[1587,1605,1581],256],"64864":[[1587,1605,1581],256],"64865":[[1587,1605,1580],256],"64866":[[1587,1605,1605],256],"64867":[[1587,1605,1605],256],"64868":[[1589,1581,1581],256],"64869":[[1589,1581,1581],256],"64870":[[1589,1605,1605],256],"64871":[[1588,1581,1605],256],"64872":[[1588,1581,1605],256],"64873":[[1588,1580,1610],256],"64874":[[1588,1605,1582],256],"64875":[[1588,1605,1582],256],"64876":[[1588,1605,1605],256],"64877":[[1588,1605,1605],256],"64878":[[1590,1581,1609],256],"64879":[[1590,1582,1605],256],"64880":[[1590,1582,1605],256],"64881":[[1591,1605,1581],256],"64882":[[1591,1605,1581],256],"64883":[[1591,1605,1605],256],"64884":[[1591,1605,1610],256],"64885":[[1593,1580,1605],256],"64886":[[1593,1605,1605],256],"64887":[[1593,1605,1605],256],"64888":[[1593,1605,1609],256],"64889":[[1594,1605,1605],256],"64890":[[1594,1605,1610],256],"64891":[[1594,1605,1609],256],"64892":[[1601,1582,1605],256],"64893":[[1601,1582,1605],256],"64894":[[1602,1605,1581],256],"64895":[[1602,1605,1605],256],"64896":[[1604,1581,1605],256],"64897":[[1604,1581,1610],256],"64898":[[1604,1581,1609],256],"64899":[[1604,1580,1580],256],"64900":[[1604,1580,1580],256],"64901":[[1604,1582,1605],256],"64902":[[1604,1582,1605],256],"64903":[[1604,1605,1581],256],"64904":[[1604,1605,1581],256],"64905":[[1605,1581,1580],256],"64906":[[1605,1581,1605],256],"64907":[[1605,1581,1610],256],"64908":[[1605,1580,1581],256],"64909":[[1605,1580,1605],256],"64910":[[1605,1582,1580],256],"64911":[[1605,1582,1605],256],"64914":[[1605,1580,1582],256],"64915":[[1607,1605,1580],256],"64916":[[1607,1605,1605],256],"64917":[[1606,1581,1605],256],"64918":[[1606,1581,1609],256],"64919":[[1606,1580,1605],256],"64920":[[1606,1580,1605],256],"64921":[[1606,1580,1609],256],"64922":[[1606,1605,1610],256],"64923":[[1606,1605,1609],256],"64924":[[1610,1605,1605],256],"64925":[[1610,1605,1605],256],"64926":[[1576,1582,1610],256],"64927":[[1578,1580,1610],256],"64928":[[1578,1580,1609],256],"64929":[[1578,1582,1610],256],"64930":[[1578,1582,1609],256],"64931":[[1578,1605,1610],256],"64932":[[1578,1605,1609],256],"64933":[[1580,1605,1610],256],"64934":[[1580,1581,1609],256],"64935":[[1580,1605,1609],256],"64936":[[1587,1582,1609],256],"64937":[[1589,1581,1610],256],"64938":[[1588,1581,1610],256],"64939":[[1590,1581,1610],256],"64940":[[1604,1580,1610],256],"64941":[[1604,1605,1610],256],"64942":[[1610,1581,1610],256],"64943":[[1610,1580,1610],256],"64944":[[1610,1605,1610],256],"64945":[[1605,1605,1610],256],"64946":[[1602,1605,1610],256],"64947":[[1606,1581,1610],256],"64948":[[1602,1605,1581],256],"64949":[[1604,1581,1605],256],"64950":[[1593,1605,1610],256],"64951":[[1603,1605,1610],256],"64952":[[1606,1580,1581],256],"64953":[[1605,1582,1610],256],"64954":[[1604,1580,1605],256],"64955":[[1603,1605,1605],256],"64956":[[1604,1580,1605],256],"64957":[[1606,1580,1581],256],"64958":[[1580,1581,1610],256],"64959":[[1581,1580,1610],256],"64960":[[1605,1580,1610],256],"64961":[[1601,1605,1610],256],"64962":[[1576,1581,1610],256],"64963":[[1603,1605,1605],256],"64964":[[1593,1580,1605],256],"64965":[[1589,1605,1605],256],"64966":[[1587,1582,1610],256],"64967":[[1606,1580,1610],256],"65008":[[1589,1604,1746],256],"65009":[[1602,1604,1746],256],"65010":[[1575,1604,1604,1607],256],"65011":[[1575,1603,1576,1585],256],"65012":[[1605,1581,1605,1583],256],"65013":[[1589,1604,1593,1605],256],"65014":[[1585,1587,1608,1604],256],"65015":[[1593,1604,1610,1607],256],"65016":[[1608,1587,1604,1605],256],"65017":[[1589,1604,1609],256],"65018":[[1589,1604,1609,32,1575,1604,1604,1607,32,1593,1604,1610,1607,32,1608,1587,1604,1605],256],"65019":[[1580,1604,32,1580,1604,1575,1604,1607],256],"65020":[[1585,1740,1575,1604],256]},"65024":{"65040":[[44],256],"65041":[[12289],256],"65042":[[12290],256],"65043":[[58],256],"65044":[[59],256],"65045":[[33],256],"65046":[[63],256],"65047":[[12310],256],"65048":[[12311],256],"65049":[[8230],256],"65056":[null,230],"65057":[null,230],"65058":[null,230],"65059":[null,230],"65060":[null,230],"65061":[null,230],"65062":[null,230],"65063":[null,220],"65064":[null,220],"65065":[null,220],"65066":[null,220],"65067":[null,220],"65068":[null,220],"65069":[null,220],"65072":[[8229],256],"65073":[[8212],256],"65074":[[8211],256],"65075":[[95],256],"65076":[[95],256],"65077":[[40],256],"65078":[[41],256],"65079":[[123],256],"65080":[[125],256],"65081":[[12308],256],"65082":[[12309],256],"65083":[[12304],256],"65084":[[12305],256],"65085":[[12298],256],"65086":[[12299],256],"65087":[[12296],256],"65088":[[12297],256],"65089":[[12300],256],"65090":[[12301],256],"65091":[[12302],256],"65092":[[12303],256],"65095":[[91],256],"65096":[[93],256],"65097":[[8254],256],"65098":[[8254],256],"65099":[[8254],256],"65100":[[8254],256],"65101":[[95],256],"65102":[[95],256],"65103":[[95],256],"65104":[[44],256],"65105":[[12289],256],"65106":[[46],256],"65108":[[59],256],"65109":[[58],256],"65110":[[63],256],"65111":[[33],256],"65112":[[8212],256],"65113":[[40],256],"65114":[[41],256],"65115":[[123],256],"65116":[[125],256],"65117":[[12308],256],"65118":[[12309],256],"65119":[[35],256],"65120":[[38],256],"65121":[[42],256],"65122":[[43],256],"65123":[[45],256],"65124":[[60],256],"65125":[[62],256],"65126":[[61],256],"65128":[[92],256],"65129":[[36],256],"65130":[[37],256],"65131":[[64],256],"65136":[[32,1611],256],"65137":[[1600,1611],256],"65138":[[32,1612],256],"65140":[[32,1613],256],"65142":[[32,1614],256],"65143":[[1600,1614],256],"65144":[[32,1615],256],"65145":[[1600,1615],256],"65146":[[32,1616],256],"65147":[[1600,1616],256],"65148":[[32,1617],256],"65149":[[1600,1617],256],"65150":[[32,1618],256],"65151":[[1600,1618],256],"65152":[[1569],256],"65153":[[1570],256],"65154":[[1570],256],"65155":[[1571],256],"65156":[[1571],256],"65157":[[1572],256],"65158":[[1572],256],"65159":[[1573],256],"65160":[[1573],256],"65161":[[1574],256],"65162":[[1574],256],"65163":[[1574],256],"65164":[[1574],256],"65165":[[1575],256],"65166":[[1575],256],"65167":[[1576],256],"65168":[[1576],256],"65169":[[1576],256],"65170":[[1576],256],"65171":[[1577],256],"65172":[[1577],256],"65173":[[1578],256],"65174":[[1578],256],"65175":[[1578],256],"65176":[[1578],256],"65177":[[1579],256],"65178":[[1579],256],"65179":[[1579],256],"65180":[[1579],256],"65181":[[1580],256],"65182":[[1580],256],"65183":[[1580],256],"65184":[[1580],256],"65185":[[1581],256],"65186":[[1581],256],"65187":[[1581],256],"65188":[[1581],256],"65189":[[1582],256],"65190":[[1582],256],"65191":[[1582],256],"65192":[[1582],256],"65193":[[1583],256],"65194":[[1583],256],"65195":[[1584],256],"65196":[[1584],256],"65197":[[1585],256],"65198":[[1585],256],"65199":[[1586],256],"65200":[[1586],256],"65201":[[1587],256],"65202":[[1587],256],"65203":[[1587],256],"65204":[[1587],256],"65205":[[1588],256],"65206":[[1588],256],"65207":[[1588],256],"65208":[[1588],256],"65209":[[1589],256],"65210":[[1589],256],"65211":[[1589],256],"65212":[[1589],256],"65213":[[1590],256],"65214":[[1590],256],"65215":[[1590],256],"65216":[[1590],256],"65217":[[1591],256],"65218":[[1591],256],"65219":[[1591],256],"65220":[[1591],256],"65221":[[1592],256],"65222":[[1592],256],"65223":[[1592],256],"65224":[[1592],256],"65225":[[1593],256],"65226":[[1593],256],"65227":[[1593],256],"65228":[[1593],256],"65229":[[1594],256],"65230":[[1594],256],"65231":[[1594],256],"65232":[[1594],256],"65233":[[1601],256],"65234":[[1601],256],"65235":[[1601],256],"65236":[[1601],256],"65237":[[1602],256],"65238":[[1602],256],"65239":[[1602],256],"65240":[[1602],256],"65241":[[1603],256],"65242":[[1603],256],"65243":[[1603],256],"65244":[[1603],256],"65245":[[1604],256],"65246":[[1604],256],"65247":[[1604],256],"65248":[[1604],256],"65249":[[1605],256],"65250":[[1605],256],"65251":[[1605],256],"65252":[[1605],256],"65253":[[1606],256],"65254":[[1606],256],"65255":[[1606],256],"65256":[[1606],256],"65257":[[1607],256],"65258":[[1607],256],"65259":[[1607],256],"65260":[[1607],256],"65261":[[1608],256],"65262":[[1608],256],"65263":[[1609],256],"65264":[[1609],256],"65265":[[1610],256],"65266":[[1610],256],"65267":[[1610],256],"65268":[[1610],256],"65269":[[1604,1570],256],"65270":[[1604,1570],256],"65271":[[1604,1571],256],"65272":[[1604,1571],256],"65273":[[1604,1573],256],"65274":[[1604,1573],256],"65275":[[1604,1575],256],"65276":[[1604,1575],256]},"65280":{"65281":[[33],256],"65282":[[34],256],"65283":[[35],256],"65284":[[36],256],"65285":[[37],256],"65286":[[38],256],"65287":[[39],256],"65288":[[40],256],"65289":[[41],256],"65290":[[42],256],"65291":[[43],256],"65292":[[44],256],"65293":[[45],256],"65294":[[46],256],"65295":[[47],256],"65296":[[48],256],"65297":[[49],256],"65298":[[50],256],"65299":[[51],256],"65300":[[52],256],"65301":[[53],256],"65302":[[54],256],"65303":[[55],256],"65304":[[56],256],"65305":[[57],256],"65306":[[58],256],"65307":[[59],256],"65308":[[60],256],"65309":[[61],256],"65310":[[62],256],"65311":[[63],256],"65312":[[64],256],"65313":[[65],256],"65314":[[66],256],"65315":[[67],256],"65316":[[68],256],"65317":[[69],256],"65318":[[70],256],"65319":[[71],256],"65320":[[72],256],"65321":[[73],256],"65322":[[74],256],"65323":[[75],256],"65324":[[76],256],"65325":[[77],256],"65326":[[78],256],"65327":[[79],256],"65328":[[80],256],"65329":[[81],256],"65330":[[82],256],"65331":[[83],256],"65332":[[84],256],"65333":[[85],256],"65334":[[86],256],"65335":[[87],256],"65336":[[88],256],"65337":[[89],256],"65338":[[90],256],"65339":[[91],256],"65340":[[92],256],"65341":[[93],256],"65342":[[94],256],"65343":[[95],256],"65344":[[96],256],"65345":[[97],256],"65346":[[98],256],"65347":[[99],256],"65348":[[100],256],"65349":[[101],256],"65350":[[102],256],"65351":[[103],256],"65352":[[104],256],"65353":[[105],256],"65354":[[106],256],"65355":[[107],256],"65356":[[108],256],"65357":[[109],256],"65358":[[110],256],"65359":[[111],256],"65360":[[112],256],"65361":[[113],256],"65362":[[114],256],"65363":[[115],256],"65364":[[116],256],"65365":[[117],256],"65366":[[118],256],"65367":[[119],256],"65368":[[120],256],"65369":[[121],256],"65370":[[122],256],"65371":[[123],256],"65372":[[124],256],"65373":[[125],256],"65374":[[126],256],"65375":[[10629],256],"65376":[[10630],256],"65377":[[12290],256],"65378":[[12300],256],"65379":[[12301],256],"65380":[[12289],256],"65381":[[12539],256],"65382":[[12530],256],"65383":[[12449],256],"65384":[[12451],256],"65385":[[12453],256],"65386":[[12455],256],"65387":[[12457],256],"65388":[[12515],256],"65389":[[12517],256],"65390":[[12519],256],"65391":[[12483],256],"65392":[[12540],256],"65393":[[12450],256],"65394":[[12452],256],"65395":[[12454],256],"65396":[[12456],256],"65397":[[12458],256],"65398":[[12459],256],"65399":[[12461],256],"65400":[[12463],256],"65401":[[12465],256],"65402":[[12467],256],"65403":[[12469],256],"65404":[[12471],256],"65405":[[12473],256],"65406":[[12475],256],"65407":[[12477],256],"65408":[[12479],256],"65409":[[12481],256],"65410":[[12484],256],"65411":[[12486],256],"65412":[[12488],256],"65413":[[12490],256],"65414":[[12491],256],"65415":[[12492],256],"65416":[[12493],256],"65417":[[12494],256],"65418":[[12495],256],"65419":[[12498],256],"65420":[[12501],256],"65421":[[12504],256],"65422":[[12507],256],"65423":[[12510],256],"65424":[[12511],256],"65425":[[12512],256],"65426":[[12513],256],"65427":[[12514],256],"65428":[[12516],256],"65429":[[12518],256],"65430":[[12520],256],"65431":[[12521],256],"65432":[[12522],256],"65433":[[12523],256],"65434":[[12524],256],"65435":[[12525],256],"65436":[[12527],256],"65437":[[12531],256],"65438":[[12441],256],"65439":[[12442],256],"65440":[[12644],256],"65441":[[12593],256],"65442":[[12594],256],"65443":[[12595],256],"65444":[[12596],256],"65445":[[12597],256],"65446":[[12598],256],"65447":[[12599],256],"65448":[[12600],256],"65449":[[12601],256],"65450":[[12602],256],"65451":[[12603],256],"65452":[[12604],256],"65453":[[12605],256],"65454":[[12606],256],"65455":[[12607],256],"65456":[[12608],256],"65457":[[12609],256],"65458":[[12610],256],"65459":[[12611],256],"65460":[[12612],256],"65461":[[12613],256],"65462":[[12614],256],"65463":[[12615],256],"65464":[[12616],256],"65465":[[12617],256],"65466":[[12618],256],"65467":[[12619],256],"65468":[[12620],256],"65469":[[12621],256],"65470":[[12622],256],"65474":[[12623],256],"65475":[[12624],256],"65476":[[12625],256],"65477":[[12626],256],"65478":[[12627],256],"65479":[[12628],256],"65482":[[12629],256],"65483":[[12630],256],"65484":[[12631],256],"65485":[[12632],256],"65486":[[12633],256],"65487":[[12634],256],"65490":[[12635],256],"65491":[[12636],256],"65492":[[12637],256],"65493":[[12638],256],"65494":[[12639],256],"65495":[[12640],256],"65498":[[12641],256],"65499":[[12642],256],"65500":[[12643],256],"65504":[[162],256],"65505":[[163],256],"65506":[[172],256],"65507":[[175],256],"65508":[[166],256],"65509":[[165],256],"65510":[[8361],256],"65512":[[9474],256],"65513":[[8592],256],"65514":[[8593],256],"65515":[[8594],256],"65516":[[8595],256],"65517":[[9632],256],"65518":[[9675],256]}} diff --git a/lib/hd/unorm.js b/lib/hd/unorm.js new file mode 100644 index 000000000..1f730e0fe --- /dev/null +++ b/lib/hd/unorm.js @@ -0,0 +1,430 @@ +/*! + * Unorm + * https://github.com/walling/unorm + * + * The software dual licensed under the MIT and GPL licenses. MIT license: + * + * Copyright (c) 2008-2013 + * Matsuza , + * Bjarke Walling + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 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. + * + * GPL notice (please read the [full GPL license] online): + * + * Copyright (C) 2008-2013 + * Matsuza , + * Bjarke Walling + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * [full GPL license]: http://www.gnu.org/licenses/gpl-2.0-standalone.html +*/ + +'use strict'; + +const udata = require('./udata.json'); +const DEFAULT_FEATURE = [null, 0, {}]; +const CACHE_THRESHOLD = 10; +const SBase = 0xac00; +const LBase = 0x1100; +const VBase = 0x1161; +const TBase = 0x11a7; +const LCount = 19; +const VCount = 21; +const TCount = 28; +const NCount = VCount * TCount; // 588 +const SCount = LCount * NCount; // 11172 + +const cache = {}; +const cacheCounter = []; + +for (let i = 0; i <= 0xff; i++) + cacheCounter[i] = 0; + +let fromCharCode = null; + +class UChar { + constructor(cp, feature) { + this.codepoint = cp; + this.feature = feature; + } + + static isHighSurrogate(cp) { + return cp >= 0xd800 && cp <= 0xdbff; + } + + static isLowSurrogate(cp) { + return cp >= 0xdc00 && cp <= 0xdfff; + } + + prepFeature() { + if (!this.feature) + this.feature = fromCharCode(this.codepoint, true).feature; + } + + toString() { + if (this.codepoint < 0x10000) + return String.fromCharCode(this.codepoint); + + const x = this.codepoint - 0x10000; + + return String.fromCharCode( + Math.floor(x / 0x400) + 0xd800, + x % 0x400 + 0xdc00 + ); + } + + getDecomp() { + this.prepFeature(); + return this.feature[0] || null; + } + + isCompatibility() { + this.prepFeature(); + return Boolean(this.feature[1]) && (this.feature[1] & (1 << 8)) !== 0; + } + + isExclude() { + this.prepFeature(); + return Boolean(this.feature[1]) && (this.feature[1] & (1 << 9)) !== 0; + } + + getCanonicalClass() { + this.prepFeature(); + return this.feature[1] ? (this.feature[1] & 0xff) : 0; + } + + getComposite(following) { + this.prepFeature(); + + if (!this.feature[2]) + return null; + + const cp = this.feature[2][following.codepoint]; + + return cp ? fromCharCode(cp) : null; + } +} + +function fromCache(next, cp, needFeature) { + let ret = cache[cp]; + + if (!ret) { + ret = next(cp, needFeature); + if (ret.feature && ++cacheCounter[(cp >> 8) & 0xff] > CACHE_THRESHOLD) + cache[cp] = ret; + } + + return ret; +} + +function fromData(next, cp, needFeature) { + const hash = cp & 0xff00; + const dunit = udata[hash] || {}; + const f = dunit[cp]; + return f ? new UChar(cp, f) : new UChar(cp, DEFAULT_FEATURE); +} + +function fromCpOnly(next, cp, needFeature) { + return needFeature ? next(cp, needFeature) : new UChar(cp, null); +} + +function fromRuleBasedJamo(next, cp, needFeature) { + if (cp < LBase + || (LBase + LCount <= cp && cp < SBase) + || (SBase + SCount < cp)) { + return next(cp, needFeature); + } + + if (LBase <= cp && cp < LBase + LCount) { + const c = {}; + const base = (cp - LBase) * VCount; + + for (let j = 0; j < VCount; j++) + c[VBase + j] = SBase + TCount * (j + base); + + return new UChar(cp, [null, null, c]); + } + + const SIndex = cp - SBase; + const TIndex = SIndex % TCount; + const feature = []; + + if (TIndex !== 0) { + feature[0] = [SBase + SIndex - TIndex, TBase + TIndex]; + } else { + feature[0] = [ + LBase + Math.floor(SIndex / NCount), + VBase + Math.floor((SIndex % NCount) / TCount) + ]; + feature[2] = {}; + for (let j = 1; j < TCount; j++) + feature[2][TBase + j] = cp + j; + } + + return new UChar(cp, feature); +} + +function fromCpFilter(next, cp, needFeature) { + return cp < 60 || 13311 < cp && cp < 42607 + ? new UChar(cp, DEFAULT_FEATURE) + : next(cp, needFeature); +} + +const strategies = [ + fromCpFilter, + fromCache, + fromCpOnly, + fromRuleBasedJamo, + fromData +]; + +fromCharCode = strategies.reduceRight((next, strategy) => { + return (cp, needFeature) => { + return strategy(next, cp, needFeature); + }; +}, null); + +class UCharIterator { + constructor(str) { + this.str = str; + this.cursor = 0; + } + next() { + if (this.str && this.cursor < this.str.length) { + let cp = this.str.charCodeAt(this.cursor++); + + if (UChar.isHighSurrogate(cp) && this.cursor < this.str.length) { + const d = this.str.charCodeAt(this.cursor); + if (UChar.isLowSurrogate(d)) { + cp = (cp - 0xd800) * 0x400 + (d - 0xdc00) + 0x10000; + this.cursor += 1; + } + } + + return fromCharCode(cp); + } + + this.str = null; + return null; + } +} + +class RecursDecompIterator { + constructor(it, cano) { + this.it = it; + this.canonical = cano; + this.resBuf = []; + } + + recursiveDecomp(uchar) { + const cano = this.canonical; + const decomp = uchar.getDecomp(); + + if (decomp && !(cano && uchar.isCompatibility())) { + let ret = []; + for (let i = 0; i < decomp.length; i++) { + const a = this.recursiveDecomp(fromCharCode(decomp[i])); + ret = ret.concat(a); + } + return ret; + } + + return [uchar]; + } + + next() { + if (this.resBuf.length === 0) { + const uchar = this.it.next(); + + if (!uchar) + return null; + + this.resBuf = this.recursiveDecomp(uchar); + } + + return this.resBuf.shift(); + } +} + +class DecompIterator { + constructor(it) { + this.it = it; + this.resBuf = []; + } + + next() { + if (this.resBuf.length === 0) { + for (;;) { + const uchar = this.it.next(); + + if (!uchar) + break; + + const cc = uchar.getCanonicalClass(); + + let inspt = this.resBuf.length; + + if (cc !== 0) { + while (inspt > 0) { + const uchar2 = this.resBuf[inspt - 1]; + const cc2 = uchar2.getCanonicalClass(); + if (cc2 <= cc) + break; + inspt -= 1; + } + } + + this.resBuf.splice(inspt, 0, uchar); + + if (cc === 0) + break; + } + } + + return this.resBuf.shift(); + } +} + +class CompIterator { + constructor(it) { + this.it = it; + this.procBuf = []; + this.resBuf = []; + this.lastClass = null; + } + next() { + while (this.resBuf.length === 0) { + const uchar = this.it.next(); + + if (!uchar) { + this.resBuf = this.procBuf; + this.procBuf = []; + break; + } + + if (this.procBuf.length === 0) { + this.lastClass = uchar.getCanonicalClass(); + this.procBuf.push(uchar); + continue; + } + + const starter = this.procBuf[0]; + const composite = starter.getComposite(uchar); + const cc = uchar.getCanonicalClass(); + + if (composite && (this.lastClass < cc || this.lastClass === 0)) { + this.procBuf[0] = composite; + continue; + } + + if (cc === 0) { + this.resBuf = this.procBuf; + this.procBuf = []; + } + + this.lastClass = cc; + this.procBuf.push(uchar); + } + + return this.resBuf.shift(); + } +} + +function createIterator(mode, str) { + switch (mode) { + case 'NFD': { + const it1 = new UCharIterator(str); + const it2 = new RecursDecompIterator(it1, true); + return new DecompIterator(it2); + } + case 'NFKD': { + const it1 = new UCharIterator(str); + const it2 = new RecursDecompIterator(it1, false); + return new DecompIterator(it2); + } + case 'NFC': { + const it1 = new UCharIterator(str); + const it2 = new RecursDecompIterator(it1, true); + const it3 = new DecompIterator(it2); + return new CompIterator(it3); + } + case 'NFKC': { + const it1 = new UCharIterator(str); + const it2 = new RecursDecompIterator(it1, false); + const it3 = new DecompIterator(it2); + return new CompIterator(it3); + } + } + + throw new Error(`${mode} is invalid.`); +} + +function normalize(mode, str) { + const it = createIterator(mode, str); + + let ret = ''; + let uchar; + + for (;;) { + uchar = it.next(); + + if (!uchar) + break; + + ret += uchar.toString(); + } + + return ret; +}; + +function nfd(str) { + return normalize('NFD', str); +} + +function nfkd(str) { + return normalize('NFKD', str); +} + +function nfc(str) { + return normalize('NFC', str); +} + +function nfkc(str) { + return normalize('NFKC', str); +} + +exports.nfc = nfc; +exports.nfd = nfd; +exports.nfkc = nfkc; +exports.nfkd = nfkd; diff --git a/lib/http/base-browser.js b/lib/http/base-browser.js deleted file mode 100644 index 212272704..000000000 --- a/lib/http/base-browser.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -exports.unsupported = true; diff --git a/lib/http/base.js b/lib/http/base.js index 677695aa9..e69de29bb 100644 --- a/lib/http/base.js +++ b/lib/http/base.js @@ -1,1749 +0,0 @@ -/*! - * http.js - http server for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const path = require('path'); -const EventEmitter = require('events'); -const URL = require('url'); -const {StringDecoder} = require('string_decoder'); -const AsyncObject = require('../utils/asyncobject'); -const util = require('../utils/util'); -const co = require('../utils/co'); -const Validator = require('../utils/validator'); -const {List, ListItem} = require('../utils/list'); -const fs = require('../utils/fs'); -const digest = require('../crypto/digest'); -const ccmp = require('../crypto/ccmp'); - -/** - * HTTPBase - * @alias module:http.Base - * @constructor - * @param {Object?} options - * @emits HTTPBase#socket - */ - -function HTTPBase(options) { - if (!(this instanceof HTTPBase)) - return new HTTPBase(options); - - AsyncObject.call(this); - - this.config = new HTTPBaseOptions(options); - this.config.load(); - - this.server = null; - this.io = null; - this.sockets = new List(); - this.channels = new Map(); - this.routes = new Routes(); - this.mounts = []; - this.stack = []; - this.hooks = []; - - this._init(); -} - -Object.setPrototypeOf(HTTPBase.prototype, AsyncObject.prototype); - -/** - * Initialize server. - * @private - */ - -HTTPBase.prototype._init = function _init() { - const backend = this.config.getBackend(); - const options = this.config.toHTTP(); - - this.server = backend.createServer(options); - - this._initRouter(); - this._initSockets(); - - this.server.on('connection', (socket) => { - socket.on('error', (err) => { - if (err.message === 'Parse Error') { - let msg = 'http_parser.execute failure'; - msg += ` (parsed=${err.bytesParsed || -1}`; - msg += ` code=${err.code})`; - err = new Error(msg); - } - - this.emit('error', err); - - try { - socket.destroy(); - } catch (e) { - ; - } - }); - }); - - this.server.on('error', (err) => { - this.emit('error', err); - }); -}; - -/** - * Initialize router. - * @private - */ - -HTTPBase.prototype._initRouter = function _initRouter() { - this.server.on('request', async (hreq, hres) => { - const req = new Request(hreq, hres, hreq.url); - const res = new Response(hreq, hres); - - req.on('error', () => {}); - - try { - req.pause(); - await this.handleRequest(req, res); - } catch (e) { - res.error(e.statusCode || 500, e); - this.emit('error', e); - } - }); -}; - -/** - * Handle a request. - * @private - * @param {ServerRequest} req - * @param {ServerResponse} res - * @returns {Promise} - */ - -HTTPBase.prototype.handleRequest = async function handleRequest(req, res) { - if (await this.handleMounts(req, res)) - return; - - this.emit('request', req, res); - - if (await this.handleStack(req, res)) - return; - - const routes = this.routes.getHandlers(req.method); - - if (!routes) - throw new Error(`No routes found for method: ${req.method}.`); - - for (const route of routes) { - const params = route.match(req.pathname); - - if (!params) - continue; - - req.params = params; - - if (await this.handleHooks(req, res)) - return; - - if (await route.call(req, res)) - return; - } - - throw new Error(`No routes found for path: ${req.pathname}.`); -}; - -/** - * CORS middleware. - * @returns {Function} - */ - -HTTPBase.prototype.cors = function cors() { - return async (req, res) => { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Credentials', 'true'); - res.setHeader( - 'Access-Control-Allow-Methods', - 'GET,HEAD,PUT,PATCH,POST,DELETE'); - res.setHeader('Access-Control-Allow-Headers', 'Authorization'); - - if (req.method === 'OPTIONS') { - res.setStatus(200); - res.end(); - return; - } - }; -}; - -/** - * Basic auth middleware. - * @param {Object} options - * @returns {Function} - */ - -HTTPBase.prototype.basicAuth = function basicAuth(options) { - assert(options, 'Basic auth requires options.'); - - let user = options.username; - let pass = options.password; - let realm = options.realm; - - if (user != null) { - assert(typeof user === 'string'); - assert(user.length <= 255, 'Username too long.'); - assert(util.isAscii(user), 'Username must be ASCII.'); - user = digest.hash256(Buffer.from(user, 'ascii')); - } - - assert(typeof pass === 'string'); - assert(pass.length <= 255, 'Password too long.'); - assert(util.isAscii(pass), 'Password must be ASCII.'); - pass = digest.hash256(Buffer.from(pass, 'ascii')); - - if (!realm) - realm = 'server'; - - assert(typeof realm === 'string'); - - const fail = (res) => { - res.setHeader('WWW-Authenticate', `Basic realm="${realm}"`); - res.setStatus(401); - res.end(); - }; - - return async (req, res) => { - const hdr = req.headers['authorization']; - - if (!hdr) { - fail(res); - return; - } - - if (hdr.length > 674) { - fail(res); - return; - } - - const parts = hdr.split(' '); - - if (parts.length !== 2) { - fail(res); - return; - } - - const [type, b64] = parts; - - if (type !== 'Basic') { - fail(res); - return; - } - - const auth = Buffer.from(b64, 'base64').toString('ascii'); - const items = auth.split(':'); - - const username = items.shift(); - const password = items.join(':'); - - if (user) { - if (username.length > 255) { - fail(res); - return; - } - - const raw = Buffer.from(username, 'ascii'); - const hash = digest.hash256(raw); - - if (!ccmp(hash, user)) { - fail(res); - return; - } - } - - if (password.length > 255) { - fail(res); - return; - } - - const raw = Buffer.from(password, 'ascii'); - const hash = digest.hash256(raw); - - if (!ccmp(hash, pass)) { - fail(res); - return; - } - - req.username = username; - }; -}; - -/** - * Body parser middleware. - * @param {Object} options - * @returns {Function} - */ - -HTTPBase.prototype.bodyParser = function bodyParser(options) { - const opt = new BodyParserOptions(options); - - return async (req, res) => { - if (req.hasBody) - return; - - try { - req.resume(); - req.body = await this.parseBody(req, opt); - } finally { - req.pause(); - } - - req.hasBody = true; - }; -}; - -/** - * Parse request body. - * @private - * @param {ServerRequest} req - * @param {Object} options - * @returns {Promise} - */ - -HTTPBase.prototype.parseBody = async function parseBody(req, options) { - let body = Object.create(null); - - if (req.method === 'GET') - return body; - - let type = req.contentType; - - if (options.contentType) - type = options.contentType; - - if (type === 'bin') - return body; - - const data = await this.readBody(req, 'utf8', options); - - if (!data) - return body; - - switch (type) { - case 'json': - body = JSON.parse(data); - if (!body || !(typeof body === 'object' || Array.isArray(body))) - throw new Error('JSON body must be an object or array.'); - break; - case 'form': - body = parsePairs(data, options.keyLimit); - break; - } - - return body; -}; - -/** - * Read and buffer request body. - * @param {ServerRequest} req - * @param {String} enc - * @param {Object} options - * @returns {Promise} - */ - -HTTPBase.prototype.readBody = function readBody(req, enc, options) { - return new Promise((resolve, reject) => { - return this._readBody(req, enc, options, resolve, reject); - }); -}; - -/** - * Read and buffer request body. - * @private - * @param {ServerRequest} req - * @param {String} enc - * @param {Object} options - * @param {Function} resolve - * @param {Function} reject - */ - -HTTPBase.prototype._readBody = function _readBody(req, enc, options, resolve, reject) { - const decode = new StringDecoder(enc); - - let hasData = false; - let total = 0; - let body = ''; - - const cleanup = () => { - /* eslint-disable */ - req.removeListener('data', onData); - req.removeListener('error', onError); - req.removeListener('end', onEnd); - - if (timer != null) { - timer = null; - clearTimeout(timer); - } - /* eslint-enable */ - }; - - const onData = (data) => { - total += data.length; - hasData = true; - - if (total > options.bodyLimit) { - reject(new Error('Request body overflow.')); - return; - } - - body += decode.write(data); - }; - - const onError = (err) => { - cleanup(); - reject(err); - }; - - const onEnd = () => { - cleanup(); - - if (hasData) { - resolve(body); - return; - } - - resolve(null); - }; - - let timer = setTimeout(() => { - timer = null; - cleanup(); - reject(new Error('Request body timed out.')); - }, options.timeout); - - req.on('data', onData); - req.on('error', onError); - req.on('end', onEnd); -}; - -/** - * JSON rpc middleware. - * @param {RPCBase} rpc - * @returns {Function} - */ - -HTTPBase.prototype.jsonRPC = function jsonRPC(rpc) { - return async (req, res) => { - if (req.method !== 'POST') - return; - - if (req.pathname !== '/') - return; - - if (req.body instanceof Array) { - for (const request of req.body) { - if (typeof request.method !== 'string') - return; - } - } else { - if (typeof req.body.method !== 'string') - return; - } - - let json = await rpc.call(req.body, req.query); - - json = JSON.stringify(json); - json += '\n'; - - res.setHeader('X-Long-Polling', '/?longpoll=1'); - - res.send(200, json, 'json'); - }; -}; - -/** - * Handle mount stack. - * @private - * @param {HTTPRequest} req - * @param {HTTPResponse} res - * @returns {Promise} - */ - -HTTPBase.prototype.handleMounts = async function handleMounts(req, res) { - let url = req.url; - - for (const route of this.mounts) { - const server = route.handler; - - if (!route.hasPrefix(req.pathname)) - continue; - - assert(url.indexOf(route.path) === 0); - - url = url.substring(route.path.length); - req = req.rewrite(url); - - await server.handleRequest(req, res); - - return true; - } - - return false; -}; - -/** - * Handle middleware stack. - * @private - * @param {HTTPRequest} req - * @param {HTTPResponse} res - * @returns {Promise} - */ - -HTTPBase.prototype.handleStack = async function handleStack(req, res) { - for (const route of this.stack) { - if (!route.hasPrefix(req.pathname)) - continue; - - if (await route.call(req, res)) - return true; - } - - return false; -}; - -/** - * Handle hook stack. - * @private - * @param {HTTPRequest} req - * @param {HTTPResponse} res - * @returns {Promise} - */ - -HTTPBase.prototype.handleHooks = async function handleHooks(req, res) { - for (const route of this.hooks) { - if (!route.hasPrefix(req.pathname)) - continue; - - if (await route.call(req, res)) - return true; - } - - return false; -}; - -/** - * Initialize websockets. - * @private - */ - -HTTPBase.prototype._initSockets = function _initSockets() { - if (!this.config.sockets) - return; - - let IOServer; - try { - IOServer = require('socket.io'); - } catch (e) { - ; - } - - if (!IOServer) - return; - - this.io = new IOServer({ - transports: ['websocket'], - serveClient: false - }); - - this.io.attach(this.server); - - this.io.on('connection', (ws) => { - this.addSocket(ws); - }); -}; - -/** - * Broadcast event to channel. - * @param {String} name - * @param {String} type - * @param {...Object} args - */ - -HTTPBase.prototype.to = function to(name, ...args) { - const list = this.channels.get(name); - - if (!list) - return; - - assert(list.size > 0); - - for (let item = list.head; item; item = item.next) { - const socket = item.value; - socket.emit(...args); - } -}; - -/** - * Broadcast event to all connections. - * @param {String} channel - * @param {String} type - * @param {...Object} args - */ - -HTTPBase.prototype.all = function all() { - const list = this.sockets; - - for (let socket = list.head; socket; socket = socket.next) - socket.emit.apply(socket, arguments); -}; - -/** - * Add and initialize a websocket. - * @private - * @param {SocketIO.Socket} ws - */ - -HTTPBase.prototype.addSocket = function addSocket(ws) { - const socket = new WebSocket(ws, this); - - socket.on('error', (err) => { - this.emit('error', err); - }); - - socket.on('close', () => { - this.removeSocket(socket); - }); - - socket.on('join channel', (name) => { - this.joinChannel(socket, name); - }); - - socket.on('leave channel', (name) => { - this.leaveChannel(socket, name); - }); - - this.sockets.push(socket); - - for (const route of this.mounts) - route.handler.addSocket(ws); - - this.emit('socket', socket); -}; - -/** - * Remove a socket from lists. - * @private - * @param {WebSocket} socket - */ - -HTTPBase.prototype.removeSocket = function removeSocket(socket) { - for (const key of socket.channels.keys()) - this.leaveChannel(socket, key); - - assert(this.sockets.remove(socket)); -}; - -/** - * Add a socket to channel list. - * @private - * @param {WebSocket} socket - * @param {String} name - */ - -HTTPBase.prototype.joinChannel = function joinChannel(socket, name) { - let item = socket.channels.get(name); - - if (item) - return; - - let list = this.channels.get(name); - - if (!list) { - list = new List(); - this.channels.set(name, list); - } - - item = new ListItem(socket); - list.push(item); - - socket.channels.set(name, item); -}; - -/** - * Remove a socket from channel list. - * @private - * @param {WebSocket} socket - * @param {String} name - */ - -HTTPBase.prototype.leaveChannel = function leaveChannel(socket, name) { - const item = socket.channels.get(name); - - if (!item) - return; - - const list = this.channels.get(name); - - assert(list); - assert(list.remove(item)); - - if (list.size === 0) - this.channels.delete(name); - - socket.channels.delete(name); -}; - -/** - * Get channel list. - * @private - * @param {String} name - */ - -HTTPBase.prototype.channel = function channel(name) { - const list = this.channels.get(name); - - if (!list) - return null; - - assert(list.size > 0); - - return list; -}; - -/** - * Open the server. - * @alias HTTPBase#open - * @returns {Promise} - */ - -HTTPBase.prototype._open = function _open() { - return this.listen(this.config.port, this.config.host); -}; - -/** - * Close the server. - * @alias HTTPBase#close - * @returns {Promise} - */ - -HTTPBase.prototype._close = function _close() { - return new Promise((resolve, reject) => { - if (this.io) { - this.server.once('close', resolve); - this.io.close(); - return; - } - - this.server.close((err) => { - if (err) { - reject(err); - return; - } - resolve(); - }); - }); -}; - -/** - * Mount a server. - * @param {String?} path - * @param {HTTPBase} server - * @param {Object?} ctx - */ - -HTTPBase.prototype.mount = function mount(path, server, ctx) { - if (!server) { - server = path; - path = null; - } - this.mounts.push(new Route(ctx || this, path, server)); -}; - -/** - * Add a middleware to the stack. - * @param {String?} path - * @param {Function} handler - * @param {Object?} ctx - */ - -HTTPBase.prototype.use = function use(path, handler, ctx) { - if (!handler) { - handler = path; - path = null; - } - this.stack.push(new Route(ctx || this, path, handler)); -}; - -/** - * Add a hook to the stack. - * @param {String?} path - * @param {Function} handler - * @param {Object?} ctx - */ - -HTTPBase.prototype.hook = function hook(path, handler, ctx) { - if (!handler) { - handler = path; - path = null; - } - this.hooks.push(new Route(ctx || this, path, handler)); -}; - -/** - * Add a GET route. - * @param {String} path - * @param {Function} handler - * @param {Object?} ctx - */ - -HTTPBase.prototype.get = function get(path, handler, ctx) { - this.routes.get.push(new Route(ctx || this, path, handler)); -}; - -/** - * Add a POST route. - * @param {String} path - * @param {Function} handler - * @param {Object?} ctx - */ - -HTTPBase.prototype.post = function post(path, handler, ctx) { - this.routes.post.push(new Route(ctx || this, path, handler)); -}; - -/** - * Add a PUT route. - * @param {String} path - * @param {Function} handler - * @param {Object?} ctx - */ - -HTTPBase.prototype.put = function put(path, handler, ctx) { - this.routes.put.push(new Route(ctx || this, path, handler)); -}; - -/** - * Add a DELETE route. - * @param {String} path - * @param {Function} handler - * @param {Object?} ctx - */ - -HTTPBase.prototype.del = function del(path, handler, ctx) { - this.routes.del.push(new Route(ctx || this, path, handler)); -}; - -/** - * Get server address. - * @returns {Object} - */ - -HTTPBase.prototype.address = function address() { - return this.server.address(); -}; - -/** - * Listen on port and host. - * @param {Number} port - * @param {String} host - * @returns {Promise} - */ - -HTTPBase.prototype.listen = function listen(port, host) { - return new Promise((resolve, reject) => { - this.server.once('error', reject); - this.server.listen(port, host, () => { - const addr = this.address(); - - this.emit('listening', addr); - - this.server.removeListener('error', reject); - resolve(addr); - }); - }); -}; - -/** - * HTTP Base Options - * @alias module:http.HTTPBaseOptions - * @constructor - * @param {Object} options - */ - -function HTTPBaseOptions(options) { - if (!(this instanceof HTTPBaseOptions)) - return new HTTPBaseOptions(options); - - this.host = '127.0.0.1'; - this.port = 8080; - this.sockets = true; - - this.ssl = false; - this.keyFile = null; - this.certFile = null; - this.key = null; - this.cert = null; - this.ca = null; - - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from object. - * @private - * @param {Object} options - * @returns {HTTPBaseOptions} - */ - -HTTPBaseOptions.prototype.fromOptions = function fromOptions(options) { - assert(options); - - if (options.host != null) { - assert(typeof options.host === 'string'); - this.host = options.host; - } - - if (options.port != null) { - assert(util.isU16(options.port), 'Port must be a number.'); - this.port = options.port; - } - - if (options.sockets != null) { - assert(typeof options.sockets === 'boolean'); - this.sockets = options.sockets; - } - - if (options.prefix != null) { - assert(typeof options.prefix === 'string'); - this.prefix = options.prefix; - this.keyFile = path.join(this.prefix, 'key.pem'); - this.certFile = path.join(this.prefix, 'cert.pem'); - } - - if (options.ssl != null) { - assert(typeof options.ssl === 'boolean'); - this.ssl = options.ssl; - } - - if (options.keyFile != null) { - assert(typeof options.keyFile === 'string'); - this.keyFile = options.keyFile; - } - - if (options.certFile != null) { - assert(typeof options.certFile === 'string'); - this.certFile = options.certFile; - } - - if (options.key != null) { - assert(typeof options.key === 'string' || Buffer.isBuffer(options.key)); - this.key = options.key; - } - - if (options.cert != null) { - assert(typeof options.cert === 'string' || Buffer.isBuffer(options.cert)); - this.cert = options.cert; - } - - if (options.ca != null) { - assert(Array.isArray(options.ca)); - this.ca = options.ca; - } - - if (this.ssl) { - assert(this.key || this.keyFile, 'SSL specified with no provided key.'); - assert(this.cert || this.certFile, 'SSL specified with no provided cert.'); - } - - return this; -}; - -/** - * Load key and cert file. - * @private - */ - -HTTPBaseOptions.prototype.load = function load() { - if (!this.ssl) - return; - - if (this.keyFile) - this.key = fs.readFileSync(this.keyFile); - - if (this.certFile) - this.cert = fs.readFileSync(this.certFile); -}; - -/** - * Instantiate http server options from object. - * @param {Object} options - * @returns {HTTPBaseOptions} - */ - -HTTPBaseOptions.fromOptions = function fromOptions(options) { - return new HTTPBaseOptions().fromOptions(options); -}; - -/** - * Get HTTP server backend. - * @private - * @returns {Object} - */ - -HTTPBaseOptions.prototype.getBackend = function getBackend() { - return this.ssl ? require('https') : require('http'); -}; - -/** - * Get HTTP server options. - * @private - * @returns {Object} - */ - -HTTPBaseOptions.prototype.toHTTP = function toHTTP() { - if (!this.ssl) - return undefined; - - return { - key: this.key, - cert: this.cert, - ca: this.ca - }; -}; - -/** - * HTTP Base Options - * @alias module:http.BodyParserOptions - * @constructor - * @param {Object} options - */ - -function BodyParserOptions(options) { - if (!(this instanceof BodyParserOptions)) - return new BodyParserOptions(options); - - this.keyLimit = 100; - this.bodyLimit = 20 << 20; - this.contentType = null; - this.timeout = 10 * 1000; - - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from object. - * @private - * @param {Object} options - * @returns {BodyParserOptions} - */ - -BodyParserOptions.prototype.fromOptions = function fromOptions(options) { - assert(options); - - if (options.keyLimit != null) { - assert(typeof options.keyLimit === 'number'); - this.keyLimit = options.keyLimit; - } - - if (options.bodyLimit != null) { - assert(typeof options.bodyLimit === 'number'); - this.bodyLimit = options.bodyLimit; - } - - if (options.contentType != null) { - assert(typeof options.contentType === 'string'); - this.contentType = options.contentType; - } - - return this; -}; - -/** - * Route - * @constructor - * @ignore - */ - -function Route(ctx, path, handler) { - if (!(this instanceof Route)) - return new Route(ctx, path, handler); - - this.ctx = null; - this.path = null; - this.handler = null; - - this.regex = /^/; - this.map = []; - this.compiled = false; - - if (ctx) { - assert(typeof ctx === 'object'); - this.ctx = ctx; - } - - if (path) { - if (path instanceof RegExp) { - this.regex = path; - } else { - assert(typeof path === 'string'); - assert(path.length > 0); - this.path = path; - } - } - - assert(handler); - assert(typeof handler === 'function' || typeof handler === 'object'); - - this.handler = handler; -} - -Route.prototype.compile = function compile() { - let path = this.path; - const map = this.map; - - if (this.compiled) - return; - - this.compiled = true; - - if (!path) - return; - - path = path.replace(/(\/[^\/]+)\?/g, '(?:$1)?'); - path = path.replace(/\.(?!\+)/g, '\\.'); - path = path.replace(/\*/g, '.*?'); - path = path.replace(/%/g, '\\'); - - path = path.replace(/:(\w+)/g, (str, name) => { - map.push(name); - return '([^/]+)'; - }); - - this.regex = new RegExp('^' + path + '$'); -}; - -Route.prototype.match = function match(pathname) { - this.compile(); - - assert(this.regex); - - const matches = this.regex.exec(pathname); - - if (!matches) - return null; - - const params = Object.create(null); - - for (let i = 1; i < matches.length; i++) { - const item = matches[i]; - const key = this.map[i - 1]; - - if (key) - params[key] = item; - - params[i - 1] = item; - } - - return params; -}; - -Route.prototype.hasPrefix = function hasPrefix(pathname) { - if (!this.path) - return true; - - return pathname.indexOf(this.path) === 0; -}; - -Route.prototype.call = async function call(req, res) { - await this.handler.call(this.ctx, req, res); - return res.sent; -}; - -/** - * Routes - * @constructor - * @ignore - */ - -function Routes() { - if (!(this instanceof Routes)) - return new Routes(); - - this.get = []; - this.post = []; - this.put = []; - this.del = []; -} - -Routes.prototype.getHandlers = function getHandlers(method) { - if (!method) - return null; - - method = method.toUpperCase(); - - switch (method) { - case 'GET': - return this.get; - case 'POST': - return this.post; - case 'PUT': - return this.put; - case 'DELETE': - return this.del; - default: - return null; - } -}; - -/** - * Request - * @constructor - * @ignore - */ - -function Request(req, res, url) { - if (!(this instanceof Request)) - return new Request(req, res, url); - - EventEmitter.call(this); - - this.req = null; - this.res = null; - this.socket = null; - this.method = 'GET'; - this.headers = Object.create(null); - this.contentType = 'bin'; - this.url = '/'; - this.pathname = '/'; - this.path = []; - this.trailing = false; - this.query = Object.create(null); - this.params = Object.create(null); - this.body = Object.create(null); - this.hasBody = false; - this.username = null; - this.readable = true; - this.writable = false; - - if (req) - this.init(req, res, url); -} - -Object.setPrototypeOf(Request.prototype, EventEmitter.prototype); - -Request.prototype.init = function init(req, res, url) { - assert(req); - assert(res); - - this.req = req; - this.res = res; - this.socket = req.socket; - this.method = req.method; - this.headers = req.headers; - this.contentType = parseType(req.headers['content-type']); - - req.on('error', (err) => { - this.emit('error', err); - }); - - req.on('data', (data) => { - this.emit('data', data); - }); - - req.on('end', () => { - this.emit('end'); - }); - - if (url != null) { - try { - this.parse(url); - } catch (e) { - ; - } - } -}; - -Request.prototype.parse = function parse(url) { - const uri = URL.parse(url); - - let pathname = uri.pathname; - let query = Object.create(null); - let trailing = false; - - if (pathname) { - pathname = pathname.replace(/\/{2,}/g, '/'); - - if (pathname[0] !== '/') - pathname = '/' + pathname; - - if (pathname.length > 1) { - if (pathname[pathname.length - 1] === '/') { - pathname = pathname.slice(0, -1); - trailing = true; - } - } - - pathname = pathname.replace(/%2f/gi, ''); - pathname = unescape(pathname); - } else { - pathname = '/'; - } - - assert(pathname.length > 0); - assert(pathname[0] === '/'); - - if (pathname.length > 1) - assert(pathname[pathname.length - 1] !== '/'); - - let path = pathname; - - if (path[0] === '/') - path = path.substring(1); - - let parts = path.split('/'); - - if (parts.length === 1) { - if (parts[0].length === 0) - parts = []; - } - - url = pathname; - - if (uri.search && uri.search.length > 1) { - assert(uri.search[0] === '?'); - url += uri.search; - } - - if (uri.hash && uri.hash.length > 1) { - assert(uri.hash[0] === '#'); - url += uri.hash; - } - - if (uri.query) - query = parsePairs(uri.query, 100); - - this.url = url; - this.pathname = pathname; - this.path = parts; - this.query = query; - this.trailing = trailing; -}; - -Request.prototype.rewrite = function rewrite(url) { - const req = new Request(); - req.init(this.req, this.res, url); - req.body = this.body; - req.hasBody = this.hasBody; - return req; -}; - -Request.prototype.valid = function valid() { - return new Validator([this.query, this.params, this.body]); -}; - -Request.prototype.pipe = function pipe(dest) { - return this.req.pipe(dest); -}; - -Request.prototype.pause = function pause() { - return this.req.pause(); -}; - -Request.prototype.resume = function resume() { - return this.req.resume(); -}; - -Request.prototype.destroy = function destroy() { - return this.req.destroy(); -}; - -/** - * Response - * @constructor - * @ignore - */ - -function Response(req, res) { - if (!(this instanceof Response)) - return new Response(req, res); - - EventEmitter.call(this); - - this.req = req; - this.res = res; - this.sent = false; - this.readable = false; - this.writable = true; - this.statusCode = 200; - this.res.statusCode = 200; - - if (req) - this.init(req, res); -} - -Object.setPrototypeOf(Response.prototype, EventEmitter.prototype); - -Response.prototype.init = function init(req, res) { - assert(req); - assert(res); - - res.on('error', (err) => { - this.emit('error', err); - }); - - res.on('drain', () => { - this.emit('drain'); - }); - - res.on('close', () => { - this.emit('close'); - }); -}; - -Response.prototype.setStatus = function setStatus(code) { - this.statusCode = code; - this.res.statusCode = code; -}; - -Response.prototype.setType = function setType(type) { - this.setHeader('Content-Type', getType(type)); -}; - -Response.prototype.hasType = function hasType() { - return this.getHeader('Content-Type') != null; -}; - -Response.prototype.destroy = function destroy() { - return this.res.destroy(); -}; - -Response.prototype.setHeader = function setHeader(key, value) { - return this.res.setHeader(key, value); -}; - -Response.prototype.getHeader = function getHeader(key) { - return this.res.getHeader(key); -}; - -Response.prototype.writeHead = function writeHead(code, headers) { - return this.res.writeHead(code, headers); -}; - -Response.prototype.write = function write(data, enc) { - return this.res.write(data, enc); -}; - -Response.prototype.end = function end(data, enc) { - this.sent = true; - return this.res.end(data, enc); -}; - -Response.prototype.error = function error(code, err) { - if (this.sent) - return; - - if (!code) - code = 400; - - this.send(code, { - error: { - type: err.type || 'Error', - message: err.message, - code: err.code - } - }); -}; - -Response.prototype.redirect = function redirect(code, url) { - if (!url) { - url = code; - code = 301; - } - - this.setStatus(code); - this.setHeader('Location', url); - this.end(); -}; - -Response.prototype.send = function send(code, msg, type) { - if (this.sent) - return; - - assert(typeof code === 'number', 'Code must be a number.'); - - if (msg == null) { - msg = { - error: { - type: 'Error', - message: 'No message.' - } - }; - } - - if (msg && typeof msg === 'object' && !Buffer.isBuffer(msg)) { - msg = JSON.stringify(msg, null, 2) + '\n'; - if (!type) - type = 'json'; - assert(type === 'json', 'Bad type passed with json object.'); - } - - if (!type && !this.hasType()) - type = typeof msg === 'string' ? 'txt' : 'bin'; - - this.setStatus(code); - - if (type) - this.setType(type); - - if (typeof msg === 'string') { - const len = Buffer.byteLength(msg, 'utf8'); - this.setHeader('Content-Length', len.toString(10)); - try { - this.write(msg, 'utf8'); - this.end(); - } catch (e) { - ; - } - return; - } - - if (Buffer.isBuffer(msg)) { - this.setHeader('Content-Length', msg.length.toString(10)); - try { - this.write(msg); - this.end(); - } catch (e) { - ; - } - return; - } - - assert(false, 'Bad object passed to send.'); -}; - -/** - * WebSocket - * @constructor - * @ignore - * @param {SocketIO.Socket} - */ - -function WebSocket(socket, ctx) { - if (!(this instanceof WebSocket)) - return new WebSocket(socket, ctx); - - EventEmitter.call(this); - - this.context = ctx; - this.socket = socket; - this.remoteAddress = socket.conn.remoteAddress; - this.hooks = Object.create(null); - this.channels = new Map(); - this.auth = false; - this.filter = null; - this.prev = null; - this.next = null; - - this.init(); -} - -Object.setPrototypeOf(WebSocket.prototype, EventEmitter.prototype); - -WebSocket.prototype.init = function init() { - const socket = this.socket; - const onevent = socket.onevent.bind(socket); - - socket.onevent = (packet) => { - const result = onevent(packet); - this.onevent(packet); - return result; - }; - - socket.on('error', (err) => { - this.dispatch('error', err); - }); - - socket.on('disconnect', () => { - this.dispatch('close'); - }); -}; - -WebSocket.prototype.onevent = async function onevent(packet) { - const args = (packet.data || []).slice(); - const type = args.shift() || ''; - - let ack; - if (typeof args[args.length - 1] === 'function') - ack = args.pop(); - else - ack = this.socket.ack(packet.id); - - let result; - try { - result = await this.fire(type, args); - } catch (e) { - ack({ - type: e.type || 'Error', - message: e.message, - code: e.code - }); - return; - } - - if (result === undefined) - return; - - ack(null, result); -}; - -WebSocket.prototype.hook = function hook(type, handler) { - assert(!this.hooks[type], 'Event already added.'); - this.hooks[type] = handler; -}; - -WebSocket.prototype.fire = async function fire(type, args) { - const handler = this.hooks[type]; - - if (!handler) - return undefined; - - return await handler.call(this.context, args); -}; - -WebSocket.prototype.join = function join(name) { - this.dispatch('join channel', name); -}; - -WebSocket.prototype.leave = function leave(name) { - this.dispatch('leave channel', name); -}; - -WebSocket.prototype.dispatch = function dispatch() { - const emit = EventEmitter.prototype.emit; - return emit.apply(this, arguments); -}; - -WebSocket.prototype.emit = function emit() { - return this.socket.emit.apply(this.socket, arguments); -}; - -WebSocket.prototype.call = function call(...args) { - const socket = this.socket; - return new Promise((resolve, reject) => { - args.push(co.wrap(resolve, reject)); - socket.emit(...args); - }); -}; - -WebSocket.prototype.destroy = function destroy() { - return this.socket.disconnect(); -}; - -/* - * Helpers - */ - -function parsePairs(str, limit) { - const parts = str.split('&'); - const data = Object.create(null); - - if (parts.length > limit) - return data; - - assert(!limit || parts.length <= limit, 'Too many keys in querystring.'); - - for (const pair of parts) { - const index = pair.indexOf('='); - - let key, value; - if (index === -1) { - key = pair; - value = ''; - } else { - key = pair.substring(0, index); - value = pair.substring(index + 1); - } - - key = unescape(key); - - if (key.length === 0) - continue; - - value = unescape(value); - - if (value.length === 0) - continue; - - data[key] = value; - } - - return data; -} - -function unescape(str) { - try { - str = decodeURIComponent(str); - str = str.replace(/\+/g, ' '); - } catch (e) { - ; - } - str = str.replace(/\0/g, ''); - return str; -} - -function getType(type) { - switch (type) { - case 'json': - return 'application/json'; - case 'form': - return 'application/x-www-form-urlencoded; charset=utf-8'; - case 'html': - return 'text/html; charset=utf-8'; - case 'xml': - return 'application/xml; charset=utf-8'; - case 'js': - return 'application/javascript; charset=utf-8'; - case 'css': - return 'text/css; charset=utf-8'; - case 'txt': - return 'text/plain; charset=utf-8'; - case 'bin': - return 'application/octet-stream'; - default: - return type; - } -} - -function parseType(type) { - type = type || ''; - type = type.split(';')[0]; - type = type.toLowerCase(); - type = type.trim(); - - switch (type) { - case 'text/x-json': - case 'application/json': - return 'json'; - case 'application/x-www-form-urlencoded': - return 'form'; - case 'text/html': - case 'application/xhtml+xml': - return 'html'; - case 'text/javascript': - case 'application/javascript': - return 'js'; - case 'text/css': - return 'css'; - case 'text/plain': - return 'txt'; - case 'application/octet-stream': - return 'bin'; - default: - return 'bin'; - } -} - -/* - * Expose - */ - -module.exports = HTTPBase; diff --git a/lib/http/client-browser.js b/lib/http/client-browser.js deleted file mode 100644 index 212272704..000000000 --- a/lib/http/client-browser.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -exports.unsupported = true; diff --git a/lib/http/client.js b/lib/http/client.js deleted file mode 100644 index abb9f5931..000000000 --- a/lib/http/client.js +++ /dev/null @@ -1,998 +0,0 @@ -/*! - * client.js - http client for wallets - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const Network = require('../protocol/network'); -const AsyncObject = require('../utils/asyncobject'); -const RPCClient = require('./rpcclient'); -const request = require('./request'); - -/** - * Bcoin HTTP client. - * @alias module:http.Client - * @constructor - * @param {String} uri - * @param {Object?} options - */ - -function HTTPClient(options) { - if (!(this instanceof HTTPClient)) - return new HTTPClient(options); - - if (!options) - options = {}; - - if (typeof options === 'string') - options = { uri: options }; - - AsyncObject.call(this); - - this.options = options; - this.network = Network.get(options.network); - - this.uri = options.uri || `http://localhost:${this.network.rpcPort}`; - this.socket = null; - this.apiKey = options.apiKey; - this.auth = options.auth; - this.rpc = new RPCClient(options); -} - -Object.setPrototypeOf(HTTPClient.prototype, AsyncObject.prototype); - -/** - * Open the client, wait for socket to connect. - * @alias HTTPClient#open - * @returns {Promise} - */ - -HTTPClient.prototype._open = async function _open() { - let IOClient; - - try { - IOClient = require('socket.io-client'); - } catch (e) { - ; - } - - if (!IOClient) - return; - - this.socket = new IOClient(this.uri, { - transports: ['websocket'], - forceNew: true - }); - - this.socket.on('error', (err) => { - this.emit('error', err); - }); - - this.socket.on('version', (info) => { - if (info.network !== this.network.type) - this.emit('error', new Error('Wrong network.')); - }); - - this.socket.on('wallet tx', (details) => { - this.emit('tx', details); - }); - - this.socket.on('wallet confirmed', (details) => { - this.emit('confirmed', details); - }); - - this.socket.on('wallet unconfirmed', (details) => { - this.emit('unconfirmed', details); - }); - - this.socket.on('wallet conflict', (details) => { - this.emit('conflict', details); - }); - - this.socket.on('wallet updated', (details) => { - this.emit('updated', details); - }); - - this.socket.on('wallet address', (receive) => { - this.emit('address', receive); - }); - - this.socket.on('wallet balance', (balance) => { - this.emit('balance', balance); - }); - - await this.onConnect(); - await this.sendAuth(); -}; - -/** - * Close the client, wait for the socket to close. - * @alias HTTPClient#close - * @returns {Promise} - */ - -HTTPClient.prototype._close = function _close() { - if (!this.socket) - return Promise.resolve(); - - this.socket.disconnect(); - this.socket = null; - - return Promise.resolve(); -}; - -/** - * Wait for websocket connection. - * @private - * @returns {Promise} - */ - -HTTPClient.prototype.onConnect = function onConnect() { - return new Promise((resolve, reject) => { - this.socket.once('connect', resolve); - }); -}; - -/** - * Wait for websocket auth. - * @private - * @returns {Promise} - */ - -HTTPClient.prototype.sendAuth = function sendAuth() { - return new Promise((resolve, reject) => { - this.socket.emit('auth', this.apiKey, (err) => { - if (err) { - reject(new Error(err.message)); - return; - } - resolve(); - }); - }); -}; - -/** - * Wait for websocket auth. - * @private - * @returns {Promise} - */ - -HTTPClient.prototype.sendWalletAuth = function sendWalletAuth() { - return new Promise((resolve, reject) => { - this.socket.emit('wallet auth', this.apiKey, (err) => { - if (err) { - reject(new Error(err.message)); - return; - } - resolve(); - }); - }); -}; - -/** - * Wait for websocket disconnection. - * @private - * @returns {Promise} - */ - -HTTPClient.prototype.onDisconnect = function onDisconnect() { - return new Promise((resolve, reject) => { - this.socket.once('disconnect', resolve); - }); -}; - -/** - * Make an http request to endpoint. - * @private - * @param {String} method - * @param {String} endpoint - Path. - * @param {Object} json - Body or query depending on method. - * @returns {Promise} - Returns Object?. - */ - -HTTPClient.prototype._request = async function _request(method, endpoint, json) { - if (this.token) { - if (!json) - json = {}; - json.token = this.token; - } - - let query; - if (json && method === 'get') { - query = json; - json = null; - } - - const res = await request({ - method: method, - uri: this.uri + endpoint, - pool: true, - query: query, - json: json, - auth: { - username: 'bitcoinrpc', - password: this.apiKey || '' - } - }); - - if (res.statusCode === 404) - return null; - - if (res.statusCode === 401) - throw new Error('Unauthorized (bad API key).'); - - if (res.type !== 'json') - throw new Error('Bad response (wrong content-type).'); - - if (!res.body) - throw new Error('Bad response (no body).'); - - if (res.body.error) - throw new Error(res.body.error.message); - - if (res.statusCode !== 200) - throw new Error(`Status code: ${res.statusCode}.`); - - const network = res.headers['x-bcoin-network']; - - if (network && network !== this.network.type) - throw new Error('Bad response (wrong network).'); - - return res.body; -}; - -/** - * Make a GET http request to endpoint. - * @private - * @param {String} endpoint - Path. - * @param {Object} json - Querystring. - * @returns {Promise} - Returns Object?. - */ - -HTTPClient.prototype._get = function _get(endpoint, json) { - return this._request('get', endpoint, json); -}; - -/** - * Make a POST http request to endpoint. - * @private - * @param {String} endpoint - Path. - * @param {Object} json - Body. - * @returns {Promise} - Returns Object?. - */ - -HTTPClient.prototype._post = function _post(endpoint, json) { - return this._request('post', endpoint, json); -}; - -/** - * Make a PUT http request to endpoint. - * @private - * @param {String} endpoint - Path. - * @param {Object} json - Body. - * @returns {Promise} - Returns Object?. - */ - -HTTPClient.prototype._put = function _put(endpoint, json) { - return this._request('put', endpoint, json); -}; - -/** - * Make a DELETE http request to endpoint. - * @private - * @param {String} endpoint - Path. - * @param {Object} json - Body. - * @returns {Promise} - Returns Object?. - */ - -HTTPClient.prototype._del = function _del(endpoint, json) { - return this._request('delete', endpoint, json); -}; - -/** - * Get a mempool snapshot. - * @returns {Promise} - Returns {@link Hash}[]. - */ - -HTTPClient.prototype.getMempool = function getMempool() { - return this._get('/mempool'); -}; - -/** - * Get some info about the server (network and version). - * @returns {Promise} - Returns Object. - */ - -HTTPClient.prototype.getInfo = function getInfo() { - return this._get('/'); -}; - -/** - * Get coins that pertain to an address from the mempool or chain database. - * Takes into account spent coins in the mempool. - * @param {String} address - * @returns {Promise} - Returns {@link Coin}[]. - */ - -HTTPClient.prototype.getCoinsByAddress = function getCoinsByAddress(address) { - return this._get(`/coin/address/${address}`); -}; - -/** - * Get coins that pertain to addresses from the mempool or chain database. - * Takes into account spent coins in the mempool. - * @param {String[]} addresses - * @returns {Promise} - Returns {@link Coin}[]. - */ - -HTTPClient.prototype.getCoinsByAddresses = function getCoinsByAddresses(addresses) { - return this._post('/coin/address', { addresses }); -}; - -/** - * Retrieve a coin from the mempool or chain database. - * Takes into account spent coins in the mempool. - * @param {Hash} hash - * @param {Number} index - * @returns {Promise} - Returns {@link Coin}. - */ - -HTTPClient.prototype.getCoin = function getCoin(hash, index) { - return this._get(`/coin/${hash}/${index}`); -}; - -/** - * Retrieve transactions pertaining to an - * address from the mempool or chain database. - * @param {String} address - * @returns {Promise} - Returns {@link TX}[]. - */ - -HTTPClient.prototype.getTXByAddress = function getTXByAddress(address) { - return this._get(`/tx/address/${address}`); -}; - -/** - * Retrieve transactions pertaining to - * addresses from the mempool or chain database. - * @param {String[]} addresses - * @returns {Promise} - Returns {@link TX}[]. - */ - -HTTPClient.prototype.getTXByAddresses = function getTXByAddresses(addresses) { - return this._post('/tx/address', { addresses }); -}; - -/** - * Retrieve a transaction from the mempool or chain database. - * @param {Hash} hash - * @returns {Promise} - Returns {@link TX}. - */ - -HTTPClient.prototype.getTX = function getTX(hash) { - return this._get(`/tx/${hash}`); -}; - -/** - * Retrieve a block from the chain database. - * @param {Hash|Number} block - * @returns {Promise} - Returns {@link Block}. - */ - -HTTPClient.prototype.getBlock = function getBlock(block) { - return this._get(`/block/${block}`); -}; - -/** - * Add a transaction to the mempool and broadcast it. - * @param {TX} tx - * @returns {Promise} - */ - -HTTPClient.prototype.broadcast = function broadcast(tx) { - return this._post('/broadcast', { tx: toHex(tx) }); -}; - -/** - * Rescan the chain. - * @param {Number} height - * @returns {Promise} - */ - -HTTPClient.prototype.rescan = function rescan(height) { - return this._post('/wallet/_admin/rescan', { height }); -}; - -/** - * Reset the chain. - * @param {Number} height - * @returns {Promise} - */ - -HTTPClient.prototype.reset = function reset(height) { - return this._post('/reset', { height }); -}; - -/** - * Resend pending transactions. - * @returns {Promise} - */ - -HTTPClient.prototype.resend = function resend() { - return this._post('/wallet/_admin/resend', {}); -}; - -/** - * Backup the walletdb. - * @param {String} path - * @returns {Promise} - */ - -HTTPClient.prototype.backup = function backup(path) { - return this._post('/wallet/_admin/backup', { path }); -}; - -/** - * Listen for events on wallet id. - * @param {String} id - * @param {String?} token - * @returns {Promise} - */ - -HTTPClient.prototype.join = function join(id, token) { - if (!this.socket) - return Promise.resolve(); - - return new Promise((resolve, reject) => { - this.socket.emit('wallet join', id, token, (err) => { - if (err) { - reject(new Error(err.message)); - return; - } - resolve(); - }); - }); -}; - -/** - * Unlisten for events on wallet id. - * @param {String} id - */ - -HTTPClient.prototype.leave = function leave(id) { - if (!this.socket) - return Promise.resolve(); - - return new Promise((resolve, reject) => { - this.socket.emit('wallet leave', id, (err) => { - if (err) { - reject(new Error(err.message)); - return; - } - resolve(); - }); - }); -}; - -/** - * Get list of all wallet IDs. - * @returns {Promise} - */ - -HTTPClient.prototype.getWallets = function getWallets() { - return this._get('/wallet/_admin/wallets'); -}; - -/** - * Create a wallet. - * @param {Object} options - * @returns {Promise} - */ - -HTTPClient.prototype.createWallet = function createWallet(options) { - assert(options.id, 'Must pass an id parameter'); - return this._put(`/wallet/${options.id}`, options); -}; - -/** - * Get the raw wallet JSON. - * @param {String} id - * @returns {Promise} - */ - -HTTPClient.prototype.getWallet = function getWallet(id) { - return this._get(`/wallet/${id}`); -}; - -/** - * Get wallet transaction history. - * @param {String} id - * @returns {Promise} - */ - -HTTPClient.prototype.getHistory = function getHistory(id, account) { - return this._get(`/wallet/${id}/tx/history`, { account }); -}; - -/** - * Get wallet coins. - * @param {String} id - * @returns {Promise} - */ - -HTTPClient.prototype.getCoins = function getCoins(id, account) { - return this._get(`/wallet/${id}/coin`, { account }); -}; - -/** - * Get all unconfirmed transactions. - * @param {String} id - * @returns {Promise} - */ - -HTTPClient.prototype.getPending = function getPending(id, account) { - return this._get(`/wallet/${id}/tx/unconfirmed`, { account }); -}; - -/** - * Calculate wallet balance. - * @param {String} id - * @returns {Promise} - */ - -HTTPClient.prototype.getBalance = function getBalance(id, account) { - return this._get(`/wallet/${id}/balance`, { account }); -}; - -/** - * Get last N wallet transactions. - * @param {String} id - * @param {Number} limit - Max number of transactions. - * @returns {Promise} - */ - -HTTPClient.prototype.getLast = function getLast(id, account, limit) { - return this._get(`/wallet/${id}/tx/last`, { account, limit }); -}; - -/** - * Get wallet transactions by timestamp range. - * @param {String} id - * @param {Object} options - * @param {Number} options.start - Start time. - * @param {Number} options.end - End time. - * @param {Number?} options.limit - Max number of records. - * @param {Boolean?} options.reverse - Reverse order. - * @returns {Promise} - */ - -HTTPClient.prototype.getRange = function getRange(id, account, options) { - const body = { - account: account, - start: options.start, - end: options.end , - limit: options.limit, - reverse: options.reverse - }; - return this._get(`/wallet/${id}/tx/range`, body); -}; - -/** - * Get transaction (only possible if the transaction - * is available in the wallet history). - * @param {String} id - * @param {Hash} hash - * @returns {Promise} - */ - -HTTPClient.prototype.getWalletTX = function getWalletTX(id, hash) { - return this._get(`/wallet/${id}/tx/${hash}`); -}; - -/** - * Get wallet blocks. - * @param {String} id - * @param {Number} height - * @returns {Promise} - */ - -HTTPClient.prototype.getWalletBlocks = function getWalletBlocks(id) { - return this._get(`/wallet/${id}/block`); -}; - -/** - * Get wallet block. - * @param {String} id - * @param {Number} height - * @returns {Promise} - */ - -HTTPClient.prototype.getWalletBlock = function getWalletBlock(id, height) { - return this._get(`/wallet/${id}/block/${height}`); -}; - -/** - * Get unspent coin (only possible if the transaction - * is available in the wallet history). - * @param {String} id - * @param {Hash} hash - * @param {Number} index - * @returns {Promise} - */ - -HTTPClient.prototype.getWalletCoin = function getWalletCoin(id, hash, index) { - return this._get(`/wallet/${id}/coin/${hash}/${index}`); -}; - -/** - * Create a transaction, fill, sign, and broadcast. - * @param {String} id - * @param {Object} options - * @param {String} options.address - * @param {Amount} options.value - * @returns {Promise} - */ - -HTTPClient.prototype.send = function send(id, options) { - const body = Object.assign({}, options); - - if (!body.outputs) - body.outputs = []; - - body.outputs = body.outputs.map((output) => { - return { - value: output.value, - address: output.address, - script: toHex(output.script) - }; - }); - - return this._post(`/wallet/${id}/send`, body); -}; - -/** - * Generate a new token. - * @param {(String|Buffer)?} passphrase - * @returns {Promise} - */ - -HTTPClient.prototype.retoken = async function retoken(id, passphrase) { - const body = await this._post(`/wallet/${id}/retoken`, { passphrase }); - return body.token; -}; - -/** - * Change or set master key's passphrase. - * @param {(String|Buffer)?} old - * @param {String|Buffer} new_ - * @returns {Promise} - */ - -HTTPClient.prototype.setPassphrase = function setPassphrase(id, old, new_) { - const body = { old: old, new: new_ }; - return this._post(`/wallet/${id}/passphrase`, body); -}; - -/** - * Create a transaction, fill. - * @param {String} id - * @param {Object} options - * @returns {Promise} - */ - -HTTPClient.prototype.createTX = function createTX(id, options) { - const body = Object.assign({}, options); - - if (!body.outputs) - body.outputs = []; - - body.outputs = body.outputs.map((output) => { - return { - value: output.value, - address: output.address, - script: toHex(output.script) - }; - }); - - return this._post(`/wallet/${id}/create`, body); -}; - -/** - * Sign a transaction. - * @param {String} id - * @param {TX} tx - * @param {Object} options - * @returns {Promise} - */ - -HTTPClient.prototype.sign = function sign(id, tx, options) { - const body = Object.assign({}, options); - body.tx = toHex(tx); - return this._post(`/wallet/${id}/sign`, body); -}; - -/** - * @param {String} id - * @param {Number} now - Current time. - * @param {Number} age - Age delta (delete transactions older than `now - age`). - * @returns {Promise} - */ - -HTTPClient.prototype.zapWallet = function zapWallet(id, account, age) { - return this._post(`/wallet/${id}/zap`, { account, age }); -}; - -/** - * Get wallet key. - * @param {String} id - * @param {String} address - * @returns {Promise} - */ - -HTTPClient.prototype.getKey = function getKey(id, address) { - return this._get(`/wallet/${id}/key/${address}`); -}; - -/** - * Get wallet key WIF dump. - * @param {String} id - * @param {String} address - * @param {String?} passphrase - * @returns {Promise} - */ - -HTTPClient.prototype.getWIF = function getWIF(id, address, passphrase) { - return this._get(`/wallet/${id}/wif/${address}`, { passphrase }); -}; - -/** - * Add a public account/purpose key to the wallet for multisig. - * @param {String} id - * @param {(String|Number)?} account - * @param {Base58String} key - Account (bip44) or - * Purpose (bip45) key (can be in base58 form). - * @returns {Promise} - */ - -HTTPClient.prototype.addSharedKey = function addSharedKey(id, account, key) { - const body = { account: account, accountKey: key }; - return this._put(`/wallet/${id}/shared-key`, body); -}; - -/** - * Remove a public account/purpose key to the wallet for multisig. - * @param {String} id - * @param {(String|Number)?} account - * @param {Base58String} key - Account (bip44) or Purpose - * (bip45) key (can be in base58 form). - * @returns {Promise} - */ - -HTTPClient.prototype.removeSharedKey = function removeSharedKey(id, account, key) { - const body = { account: account, accountKey: key }; - return this._del(`/wallet/${id}/shared-key`, body); -}; - -/** - * Import private key. - * @param {String} id - * @param {Number|String} account - * @param {String} key - * @returns {Promise} - */ - -HTTPClient.prototype.importPrivate = function importPrivate(id, account, key, passphrase) { - const body = { account: account, privateKey: key, passphrase: passphrase }; - return this._post(`/wallet/${id}/import`, body); -}; - -/** - * Import public key. - * @param {String} id - * @param {Number|String} account - * @param {String} key - * @returns {Promise} - */ - -HTTPClient.prototype.importPublic = function importPublic(id, account, key) { - const body = { account: account, publicKey: key }; - return this._post(`/wallet/${id}/import`, body); -}; - -/** - * Import address. - * @param {String} id - * @param {Number|String} account - * @param {String} address - * @returns {Promise} - */ - -HTTPClient.prototype.importAddress = function importAddress(id, account, address) { - return this._post(`/wallet/${id}/import`, { account, address }); -}; - -/** - * Lock a coin. - * @param {String} id - * @param {String} hash - * @param {Number} index - * @returns {Promise} - */ - -HTTPClient.prototype.lockCoin = function lockCoin(id, hash, index) { - return this._put(`/wallet/${id}/locked/${hash}/${index}`, {}); -}; - -/** - * Unlock a coin. - * @param {String} id - * @param {String} hash - * @param {Number} index - * @returns {Promise} - */ - -HTTPClient.prototype.unlockCoin = function unlockCoin(id, hash, index) { - return this._del(`/wallet/${id}/locked/${hash}/${index}`, {}); -}; - -/** - * Get locked coins. - * @param {String} id - * @returns {Promise} - */ - -HTTPClient.prototype.getLocked = function getLocked(id) { - return this._get(`/wallet/${id}/locked`); -}; - -/** - * Lock wallet. - * @param {String} id - * @returns {Promise} - */ - -HTTPClient.prototype.lock = function lock(id) { - return this._post(`/wallet/${id}/lock`, {}); -}; - -/** - * Unlock wallet. - * @param {String} id - * @param {String} passphrase - * @param {Number} timeout - * @returns {Promise} - */ - -HTTPClient.prototype.unlock = function unlock(id, passphrase, timeout) { - return this._post(`/wallet/${id}/unlock`, { passphrase, timeout }); -}; - -/** - * Resend pending wallet transactions. - * @returns {Promise} - */ - -HTTPClient.prototype.resendWallet = function resendWallet(id) { - return this._post(`/wallet/${id}/resend`, {}); -}; - -/** - * Get wallet accounts. - * @param {String} id - * @returns {Promise} - Returns Array. - */ - -HTTPClient.prototype.getAccounts = function getAccounts(id) { - return this._get(`/wallet/${id}/account`); -}; - -/** - * Get wallet master key. - * @param {String} id - * @returns {Promise} - */ - -HTTPClient.prototype.getMaster = function getMaster(id) { - return this._get(`/wallet/${id}/master`); -}; - -/** - * Get wallet account. - * @param {String} id - * @param {String} account - * @returns {Promise} - */ - -HTTPClient.prototype.getAccount = function getAccount(id, account) { - return this._get(`/wallet/${id}/account/${account}`); -}; - -/** - * Create account. - * @param {String} id - * @param {String} name - * @param {Object} options - * @returns {Promise} - */ - -HTTPClient.prototype.createAccount = function createAccount(id, name, options) { - return this._put(`/wallet/${id}/account/${name}`, options || {}); -}; - -/** - * Create address. - * @param {String} id - * @param {Object} options - * @returns {Promise} - */ - -HTTPClient.prototype.createAddress = function createAddress(id, options) { - if (!options) - options = {}; - - if (typeof options === 'string') - options = { account: options }; - - return this._post(`/wallet/${id}/address`, options); -}; - -/** - * Create change address. - * @param {String} id - * @param {Object} options - * @returns {Promise} - */ - -HTTPClient.prototype.createChange = function createChange(id, options) { - if (!options) - options = {}; - - if (typeof options === 'string') - options = { account: options }; - - return this._post(`/wallet/${id}/change`, options); -}; - -/** - * Create nested address. - * @param {String} id - * @param {Object} options - * @returns {Promise} - */ - -HTTPClient.prototype.createNested = function createNested(id, options) { - if (!options) - options = {}; - - if (typeof options === 'string') - options = { account: options }; - - return this._post(`/wallet/${id}/nested`, options); -}; - -/* - * Helpers - */ - -function toHex(obj) { - if (!obj) - return null; - - if (obj.toRaw) - obj = obj.toRaw(); - - if (Buffer.isBuffer(obj)) - obj = obj.toString('hex'); - - return obj; -} - -/* - * Expose - */ - -module.exports = HTTPClient; diff --git a/lib/http/index.js b/lib/http/index.js deleted file mode 100644 index 99c6cdec5..000000000 --- a/lib/http/index.js +++ /dev/null @@ -1,21 +0,0 @@ -/*! - * http/index.js - http for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module http - */ - -exports.Base = require('./base'); -exports.Client = require('./client'); -exports.request = require('./request'); -exports.RPCBase = require('./rpcbase'); -exports.RPCClient = require('./rpcclient'); -exports.RPC = require('./rpc'); -exports.Server = require('./server'); -exports.Wallet = require('./wallet'); diff --git a/lib/http/request-browser.js b/lib/http/request-browser.js deleted file mode 100644 index 212272704..000000000 --- a/lib/http/request-browser.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -exports.unsupported = true; diff --git a/lib/http/request.js b/lib/http/request.js deleted file mode 100644 index 76b35509c..000000000 --- a/lib/http/request.js +++ /dev/null @@ -1,663 +0,0 @@ -/* - * request.js - http request for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const Stream = require('stream').Stream; -const assert = require('assert'); -let url, qs, http, https, StringDecoder; - -/* - * Constants - */ - -const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1)' - + ' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36'; - -/** - * Request Options - * @constructor - * @ignore - * @param {Object} options - */ - -function RequestOptions(options) { - if (!(this instanceof RequestOptions)) - return new RequestOptions(options); - - this.uri = 'http://localhost:80/'; - this.host = 'localhost'; - this.path = '/'; - this.port = 80; - this.ssl = false; - this.method = 'GET'; - this.strictSSL = true; - this.pool = false; - this.agent = USER_AGENT; - - this.type = null; - this.expect = null; - this.query = null; - this.body = null; - this.auth = null; - this.limit = 10 << 20; - this.maxRedirects = 5; - this.timeout = 5000; - this.buffer = false; - this.headers = null; - - // Hack - ensureRequires(); - - if (options) - this.fromOptions(options); -} - -RequestOptions.prototype.setURI = function setURI(uri) { - assert(typeof uri === 'string'); - - if (!/:\/\//.test(uri)) - uri = (this.ssl ? 'https://' : 'http://') + uri; - - uri = url.parse(uri); - - assert(uri.protocol === 'http:' || uri.protocol === 'https:'); - - this.uri = uri; - this.ssl = uri.protocol === 'https:'; - - if (uri.search) - this.query = qs.parse(uri.search); - - this.host = uri.hostname; - this.path = uri.pathname; - this.port = uri.port || (this.ssl ? 443 : 80); - - if (uri.auth) { - const parts = uri.auth.split(':'); - this.auth = { - username: parts[0] || '', - password: parts[1] || '' - }; - } -}; - -RequestOptions.prototype.fromOptions = function fromOptions(options) { - if (typeof options === 'string') - options = { uri: options }; - - if (options.ssl != null) { - assert(typeof options.ssl === 'boolean'); - this.ssl = options.ssl; - } - - if (options.uri != null) - this.setURI(options.uri); - - if (options.url != null) - this.setURI(options.url); - - if (options.method != null) { - assert(typeof options.method === 'string'); - this.method = options.method.toUpperCase(); - } - - if (options.strictSSL != null) { - assert(typeof options.strictSSL === 'boolean'); - this.strictSSL = options.strictSSL; - } - - if (options.pool != null) { - assert(typeof options.pool === 'boolean'); - this.pool = options.pool; - } - - if (options.agent != null) { - assert(typeof options.agent === 'string'); - this.agent = options.agent; - } - - if (options.auth != null) { - assert(typeof options.auth === 'object'); - assert(typeof options.auth.username === 'string'); - assert(typeof options.auth.password === 'string'); - this.auth = options.auth; - } - - if (options.query != null) { - if (typeof options.query === 'string') { - this.query = qs.stringify(options.query); - } else { - assert(typeof options.query === 'object'); - this.query = options.query; - } - } - - if (options.json != null) { - assert(typeof options.json === 'object'); - this.body = Buffer.from(JSON.stringify(options.json), 'utf8'); - this.type = 'json'; - } - - if (options.form != null) { - assert(typeof options.form === 'object'); - this.body = Buffer.from(qs.stringify(options.form), 'utf8'); - this.type = 'form'; - } - - if (options.type != null) { - assert(typeof options.type === 'string'); - assert(getType(options.type)); - this.type = options.type; - } - - if (options.expect != null) { - assert(typeof options.expect === 'string'); - assert(getType(options.expect)); - this.expect = options.expect; - } - - if (options.body != null) { - if (typeof options.body === 'string') { - this.body = Buffer.from(options.body, 'utf8'); - } else { - assert(Buffer.isBuffer(options.body)); - this.body = options.body; - } - } - - if (options.timeout != null) { - assert(typeof options.timeout === 'number'); - this.timeout = options.timeout; - } - - if (options.limit != null) { - assert(typeof options.limit === 'number'); - this.limit = options.limit; - } - - if (options.maxRedirects != null) { - assert(typeof options.maxRedirects === 'number'); - this.maxRedirects = options.maxRedirects; - } - - if (options.buffer != null) { - assert(typeof options.buffer === 'boolean'); - this.buffer = options.buffer; - } - - if (options.headers != null) { - assert(typeof options.headers === 'object'); - this.headers = options.headers; - } -}; - -RequestOptions.prototype.isExpected = function isExpected(type) { - if (!this.expect) - return true; - - return this.expect === type; -}; - -RequestOptions.prototype.isOverflow = function isOverflow(hdr) { - if (!hdr) - return false; - - if (!this.buffer) - return false; - - const length = parseInt(hdr, 10); - - if (!isFinite(length)) - return true; - - return length > this.limit; -}; - -RequestOptions.prototype.getBackend = function getBackend() { - ensureRequires(this.ssl); - return this.ssl ? https : http; -}; - -RequestOptions.prototype.getHeaders = function getHeaders() { - if (this.headers) - return this.headers; - - const headers = Object.create(null); - - headers['User-Agent'] = this.agent; - - if (this.type) - headers['Content-Type'] = getType(this.type); - - if (this.body) - headers['Content-Length'] = this.body.length.toString(10); - - if (this.auth) { - const auth = `${this.auth.username}:${this.auth.password}`; - const data = Buffer.from(auth, 'utf8'); - headers['Authorization'] = `Basic ${data.toString('base64')}`; - } - - return headers; -}; - -RequestOptions.prototype.toHTTP = function toHTTP() { - let query = ''; - - if (this.query) - query = '?' + qs.stringify(this.query); - - return { - method: this.method, - host: this.host, - port: this.port, - path: this.path + query, - headers: this.getHeaders(), - agent: this.pool ? null : false, - rejectUnauthorized: this.strictSSL - }; -}; - -/** - * Request - * @alias module:http.Request - * @constructor - * @private - * @param {Object} options - */ - -function Request(options) { - if (!(this instanceof Request)) - return new Request(options); - - Stream.call(this); - - this.options = new RequestOptions(options); - this.request = null; - this.response = null; - this.statusCode = 0; - this.headers = null; - this.type = 'bin'; - this.redirects = 0; - this.timeout = null; - this.finished = false; - - this.onResponse = this._onResponse.bind(this); - this.onData = this._onData.bind(this); - this.onEnd = this._onEnd.bind(this); - - this.total = 0; - this.decoder = null; - this.body = null; - this.buffer = null; -} - -Object.setPrototypeOf(Request.prototype, Stream.prototype); - -Request.prototype.startTimeout = function startTimeout() { - if (!this.options.timeout) - return; - - this.timeout = setTimeout(() => { - this.finish(new Error('Request timed out.')); - }, this.options.timeout); -}; - -Request.prototype.stopTimeout = function stopTimeout() { - if (this.timeout != null) { - clearTimeout(this.timeout); - this.timeout = null; - } -}; - -Request.prototype.cleanup = function cleanup() { - this.stopTimeout(); - - if (this.request) { - this.request.removeListener('response', this.onResponse); - this.request.removeListener('error', this.onEnd); - this.request.addListener('error', () => {}); - } - - if (this.response) { - this.response.removeListener('data', this.onData); - this.response.removeListener('error', this.onEnd); - this.response.removeListener('end', this.onEnd); - this.response.addListener('error', () => {}); - } -}; - -Request.prototype.close = function close() { - if (this.request) { - try { - this.request.abort(); - } catch (e) { - ; - } - } - - if (this.response) { - try { - this.response.destroy(); - } catch (e) { - ; - } - } - - this.cleanup(); - - this.request = null; - this.response = null; -}; - -Request.prototype.destroy = function destroy() { - this.close(); -}; - -Request.prototype.start = function start() { - const backend = this.options.getBackend(); - const options = this.options.toHTTP(); - - this.startTimeout(); - - this.request = backend.request(options); - this.response = null; - - if (this.options.body) - this.request.write(this.options.body); - - this.request.on('response', this.onResponse); - this.request.on('error', this.onEnd); -}; - -Request.prototype.write = function write(data) { - return this.request.write(data); -}; - -Request.prototype.end = function end() { - return this.request.end(); -}; - -Request.prototype.finish = function finish(err) { - if (this.finished) - return; - - this.finished = true; - - if (err) { - this.destroy(); - this.emit('error', err); - return; - } - - this.cleanup(); - - if (this.options.buffer) { - assert(this.buffer != null); - switch (this.type) { - case 'bin': { - this.body = Buffer.concat(this.buffer); - this.buffer = null; - break; - } - case 'json': { - const buffer = this.buffer.trim(); - - this.buffer = null; - - if (buffer.length === 0) - break; - - let body; - try { - body = JSON.parse(buffer); - } catch (e) { - this.emit('error', e); - return; - } - - if (!body || typeof body !== 'object') { - this.emit('error', new Error('JSON body is a non-object.')); - return; - } - - this.body = body; - - break; - } - case 'form': { - const buffer = this.buffer; - - this.buffer = null; - - try { - this.body = qs.parse(buffer); - } catch (e) { - this.emit('error', e); - return; - } - - break; - } - default: { - this.body = this.buffer; - this.buffer = null; - break; - } - } - } - - this.emit('end'); - this.emit('close'); -}; - -Request.prototype._onResponse = function _onResponse(response) { - const location = response.headers['location']; - - if (location) { - if (++this.redirects > this.options.maxRedirects) { - this.finish(new Error('Too many redirects.')); - return; - } - this.close(); - this.options.setURI(location); - this.start(); - this.end(); - return; - } - - const contentType = response.headers['content-type']; - const type = parseType(contentType); - - if (!this.options.isExpected(type)) { - this.finish(new Error('Wrong content-type for response.')); - return; - } - - const length = response.headers['content-length']; - - if (this.options.isOverflow(length)) { - this.finish(new Error('Response exceeded limit.')); - return; - } - - this.response = response; - this.statusCode = response.statusCode; - this.headers = response.headers; - this.type = type; - - this.response.on('data', this.onData); - this.response.on('error', this.onEnd); - this.response.on('end', this.onEnd); - - this.emit('headers', response.headers); - this.emit('type', this.type); - this.emit('response', response); - - if (this.options.buffer) { - if (this.type !== 'bin') { - this.decoder = new StringDecoder('utf8'); - this.buffer = ''; - } else { - this.buffer = []; - } - } -}; - -Request.prototype._onData = function _onData(data) { - this.total += data.length; - - this.emit('data', data); - - if (this.options.buffer) { - if (this.options.limit) { - if (this.total > this.options.limit) { - this.finish(new Error('Response exceeded limit.')); - return; - } - } - - if (this.decoder) { - this.buffer += this.decoder.write(data); - return; - } - - this.buffer.push(data); - } -}; - -Request.prototype._onEnd = function _onEnd(err) { - this.finish(err); -}; - -/** - * Make an HTTP request. - * @alias module:http.request - * @param {Object} options - * @param {String} options.uri - * @param {Object?} options.query - * @param {Object?} options.body - * @param {Object?} options.json - * @param {Object?} options.form - * @param {String?} options.type - One of `"json"`, - * `"form"`, `"text"`, or `"bin"`. - * @param {String?} options.agent - User agent string. - * @param {Object?} [options.strictSSL=true] - Whether to accept bad certs. - * @param {Object?} options.method - HTTP method. - * @param {Object?} options.auth - * @param {String?} options.auth.username - * @param {String?} options.auth.password - * @param {String?} options.expect - Type to expect (see options.type). - * Error will be returned if the response is not of this type. - * @param {Number?} options.limit - Byte limit on response. - * @returns {Promise} - */ - -function request(options) { - if (typeof options === 'string') - options = { uri: options }; - - options.buffer = true; - - return new Promise((resolve, reject) => { - const req = new Request(options); - - req.on('error', err => reject(err)); - req.on('end', () => resolve(req)); - - req.start(); - req.end(); - }); -} - -request.stream = function stream(options) { - const req = new Request(options); - req.start(); - return req; -}; - -/* - * Helpers - */ - -function parseType(type) { - type = type || ''; - type = type.split(';')[0]; - type = type.toLowerCase(); - type = type.trim(); - - switch (type) { - case 'text/x-json': - case 'application/json': - return 'json'; - case 'application/x-www-form-urlencoded': - return 'form'; - case 'text/html': - case 'application/xhtml+xml': - return 'html'; - case 'text/xml': - case 'application/xml': - return 'xml'; - case 'text/javascript': - case 'application/javascript': - return 'js'; - case 'text/css': - return 'css'; - case 'text/plain': - return 'txt'; - case 'application/octet-stream': - return 'bin'; - default: - return 'bin'; - } -} - -function getType(type) { - switch (type) { - case 'json': - return 'application/json; charset=utf-8'; - case 'form': - return 'application/x-www-form-urlencoded; charset=utf-8'; - case 'html': - return 'text/html; charset=utf-8'; - case 'xml': - return 'application/xml; charset=utf-8'; - case 'js': - return 'application/javascript; charset=utf-8'; - case 'css': - return 'text/css; charset=utf-8'; - case 'txt': - return 'text/plain; charset=utf-8'; - case 'bin': - return 'application/octet-stream'; - default: - throw new Error(`Unknown type: ${type}.`); - } -} - -function ensureRequires(ssl) { - if (!url) - url = require('url'); - - if (!qs) - qs = require('querystring'); - - if (!http) - http = require('http'); - - if (ssl && !https) - https = require('https'); - - if (!StringDecoder) - StringDecoder = require('string_decoder').StringDecoder; -} - -/* - * Expose - */ - -module.exports = request; diff --git a/lib/http/rpcbase.js b/lib/http/rpcbase.js index 81c7b3ab3..e69de29bb 100644 --- a/lib/http/rpcbase.js +++ b/lib/http/rpcbase.js @@ -1,324 +0,0 @@ -/*! - * rpcbase.js - json rpc for bcoin. - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const EventEmitter = require('events'); -const Lock = require('../utils/lock'); -const Logger = require('../node/logger'); - -/** - * JSON RPC - * @alias module:http.RPCBase - * @constructor - */ - -function RPCBase() { - if (!(this instanceof RPCBase)) - return new RPCBase(); - - EventEmitter.call(this); - - this.logger = Logger.global; - this.calls = Object.create(null); - this.mounts = []; - this.locker = new Lock(); -} - -Object.setPrototypeOf(RPCBase.prototype, EventEmitter.prototype); - -/** - * RPC errors. - * @enum {Number} - * @default - */ - -RPCBase.errors = { - // Standard JSON-RPC 2.0 errors - INVALID_REQUEST: -32600, - METHOD_NOT_FOUND: -32601, - INVALID_PARAMS: -32602, - INTERNAL_ERROR: -32603, - PARSE_ERROR: -32700, - - // General application defined errors - MISC_ERROR: -1, - FORBIDDEN_BY_SAFE_MODE: -2, - TYPE_ERROR: -3, - INVALID_ADDRESS_OR_KEY: -5, - OUT_OF_MEMORY: -7, - INVALID_PARAMETER: -8, - DATABASE_ERROR: -20, - DESERIALIZATION_ERROR: -22, - VERIFY_ERROR: -25, - VERIFY_REJECTED: -26, - VERIFY_ALREADY_IN_CHAIN: -27, - IN_WARMUP: -28, - - // Aliases for backward compatibility - TRANSACTION_ERROR: -25, - TRANSACTION_REJECTED: -26, - TRANSACTION_ALREADY_IN_CHAIN: -27, - - // P2P client errors - CLIENT_NOT_CONNECTED: -9, - CLIENT_IN_INITIAL_DOWNLOAD: -10, - CLIENT_NODE_ALREADY_ADDED: -23, - CLIENT_NODE_NOT_ADDED: -24, - CLIENT_NODE_NOT_CONNECTED: -29, - CLIENT_INVALID_IP_OR_SUBNET: -30, - CLIENT_P2P_DISABLED: -31, - - // Wallet errors - WALLET_ERROR: -4, - WALLET_INSUFFICIENT_FUNDS: -6, - WALLET_INVALID_ACCOUNT_NAME: -11, - WALLET_KEYPOOL_RAN_OUT: -12, - WALLET_UNLOCK_NEEDED: -13, - WALLET_PASSPHRASE_INCORRECT: -14, - WALLET_WRONG_ENC_STATE: -15, - WALLET_ENCRYPTION_FAILED: -16, - WALLET_ALREADY_UNLOCKED: -17 -}; - -/** - * Magic string for signing. - * @const {String} - * @default - */ - -RPCBase.MAGIC_STRING = 'Bitcoin Signed Message:\n'; - -/** - * Execute batched RPC calls. - * @param {Object|Object[]} body - * @param {Object} query - * @returns {Promise} - */ - -RPCBase.prototype.call = async function call(body, query) { - let cmds = body; - let out = []; - let array = true; - - if (!Array.isArray(cmds)) { - cmds = [cmds]; - array = false; - } - - for (const cmd of cmds) { - if (!cmd || typeof cmd !== 'object') { - out.push({ - result: null, - error: { - message: 'Invalid request.', - code: RPCBase.errors.INVALID_REQUEST - }, - id: null - }); - continue; - } - - if (cmd.id && typeof cmd.id === 'object') { - out.push({ - result: null, - error: { - message: 'Invalid ID.', - code: RPCBase.errors.INVALID_REQUEST - }, - id: null - }); - continue; - } - - if (cmd.id == null) - cmd.id = null; - - if (!cmd.params) - cmd.params = []; - - if (typeof cmd.method !== 'string') { - out.push({ - result: null, - error: { - message: 'Method not found.', - code: RPCBase.errors.METHOD_NOT_FOUND - }, - id: cmd.id - }); - continue; - } - - if (!Array.isArray(cmd.params)) { - out.push({ - result: null, - error: { - message: 'Invalid params.', - code: RPCBase.errors.INVALID_PARAMS - }, - id: cmd.id - }); - continue; - } - - if (cmd.method !== 'getwork' - && cmd.method !== 'getblocktemplate' - && cmd.method !== 'getbestblockhash') { - this.logger.debug('Handling RPC call: %s.', cmd.method); - if (cmd.method !== 'submitblock' - && cmd.method !== 'getmemorypool') { - this.logger.debug(cmd.params); - } - } - - if (cmd.method === 'getwork') { - if (query.longpoll) - cmd.method = 'getworklp'; - } - - let result; - try { - result = await this.execute(cmd); - } catch (err) { - let code; - - switch (err.type) { - case 'RPCError': - code = err.code; - break; - case 'ValidationError': - code = RPCBase.errors.TYPE_ERROR; - break; - case 'EncodingError': - code = RPCBase.errors.DESERIALIZATION_ERROR; - break; - case 'FundingError': - code = RPCBase.errors.WALLET_INSUFFICIENT_FUNDS; - break; - default: - code = RPCBase.errors.INTERNAL_ERROR; - this.logger.error('RPC internal error.'); - this.logger.error(err); - this.logger.error(err.stack); - break; - } - - out.push({ - result: null, - error: { - message: err.message, - code: code - }, - id: cmd.id - }); - - continue; - } - - if (result === undefined) - result = null; - - out.push({ - result: result, - error: null, - id: cmd.id - }); - } - - if (!array) - out = out[0]; - - return out; -}; - -/** - * Execute an RPC call. - * @private - * @param {Object} json - * @param {Boolean} help - * @returns {Promise} - */ - -RPCBase.prototype.execute = async function execute(json, help) { - const func = this.calls[json.method]; - - if (!func) { - for (const mount of this.mounts) { - if (mount.calls[json.method]) - return await mount.execute(json, help); - } - throw new RPCError(RPCBase.errors.METHOD_NOT_FOUND, - `Method not found: ${json.method}.`); - } - - return await func.call(this, json.params, help); -}; - -/** - * Add a custom RPC call. - * @param {String} name - * @param {Function} func - */ - -RPCBase.prototype.add = function add(name, func) { - assert(typeof func === 'function', 'Handler must be a function.'); - assert(!this.calls[name], 'Duplicate RPC call.'); - this.calls[name] = func; -}; - -/** - * Mount another RPC object. - * @param {Object} rpc - */ - -RPCBase.prototype.mount = function mount(rpc) { - assert(rpc, 'RPC must be an object.'); - assert(typeof rpc.execute === 'function', 'Execute must be a method.'); - this.mounts.push(rpc); -}; - -/** - * Attach to another RPC object. - * @param {Object} rpc - */ - -RPCBase.prototype.attach = function attach(rpc) { - assert(rpc, 'RPC must be an object.'); - assert(typeof rpc.execute === 'function', 'Execute must be a method.'); - rpc.mount(this); -}; - -/** - * RPC Error - * @constructor - * @ignore - */ - -function RPCError(code, msg) { - Error.call(this); - - assert(typeof code === 'number'); - assert(typeof msg === 'string'); - - this.type = 'RPCError'; - this.message = msg; - this.code = code; - - if (Error.captureStackTrace) - Error.captureStackTrace(this, RPCError); -} - -Object.setPrototypeOf(RPCError.prototype, Error.prototype); - -/* - * Expose - */ - -exports = RPCBase; -exports.RPCError = RPCError; - -module.exports = exports; diff --git a/lib/http/rpcclient-browser.js b/lib/http/rpcclient-browser.js deleted file mode 100644 index 212272704..000000000 --- a/lib/http/rpcclient-browser.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -exports.unsupported = true; diff --git a/lib/http/rpcclient.js b/lib/http/rpcclient.js deleted file mode 100644 index 0e44e6ae0..000000000 --- a/lib/http/rpcclient.js +++ /dev/null @@ -1,101 +0,0 @@ -/*! - * rpcclient.js - json rpc client for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const Network = require('../protocol/network'); -const request = require('./request'); - -/** - * Bcoin RPC client. - * @alias module:http.RPCClient - * @constructor - * @param {String} uri - * @param {Object?} options - */ - -function RPCClient(options) { - if (!(this instanceof RPCClient)) - return new RPCClient(options); - - if (!options) - options = {}; - - if (typeof options === 'string') - options = { uri: options }; - - this.options = options; - this.network = Network.get(options.network); - - this.uri = options.uri || `http://localhost:${this.network.rpcPort}`; - this.apiKey = options.apiKey; - this.id = 0; -} - -/** - * Make a json rpc request. - * @private - * @param {String} method - RPC method name. - * @param {Array} params - RPC parameters. - * @returns {Promise} - Returns Object?. - */ - -RPCClient.prototype.execute = async function execute(method, params) { - const res = await request({ - method: 'POST', - uri: this.uri, - pool: true, - json: { - method: method, - params: params, - id: this.id++ - }, - auth: { - username: 'bitcoinrpc', - password: this.apiKey || '' - } - }); - - if (res.statusCode === 401) - throw new RPCError('Unauthorized (bad API key).', -1); - - if (res.type !== 'json') - throw new Error('Bad response (wrong content-type).'); - - if (!res.body) - throw new Error('No body for JSON-RPC response.'); - - if (res.body.error) - throw new RPCError(res.body.error.message, res.body.error.code); - - if (res.statusCode !== 200) - throw new Error(`Status code: ${res.statusCode}.`); - - return res.body.result; -}; - -/* - * Helpers - */ - -function RPCError(msg, code) { - Error.call(this); - - this.type = 'RPCError'; - this.message = String(msg); - this.code = code >>> 0; - - if (Error.captureStackTrace) - Error.captureStackTrace(this, RPCError); -} - -Object.setPrototypeOf(RPCError.prototype, Error.prototype); - -/* - * Expose - */ - -module.exports = RPCClient; diff --git a/lib/http/server-browser.js b/lib/http/server-browser.js deleted file mode 100644 index 212272704..000000000 --- a/lib/http/server-browser.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -exports.unsupported = true; diff --git a/lib/http/server.js b/lib/http/server.js deleted file mode 100644 index 1079ce3e1..000000000 --- a/lib/http/server.js +++ /dev/null @@ -1,825 +0,0 @@ -/*! - * server.js - http server for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const path = require('path'); -const HTTPBase = require('./base'); -const util = require('../utils/util'); -const base58 = require('../utils/base58'); -const Bloom = require('../utils/bloom'); -const TX = require('../primitives/tx'); -const Outpoint = require('../primitives/outpoint'); -const digest = require('../crypto/digest'); -const random = require('../crypto/random'); -const ccmp = require('../crypto/ccmp'); -const Network = require('../protocol/network'); -const Validator = require('../utils/validator'); -const pkg = require('../pkg'); - -/** - * HTTPServer - * @alias module:http.Server - * @constructor - * @param {Object} options - * @param {Fullnode} options.node - * @see HTTPBase - * @emits HTTPServer#socket - */ - -function HTTPServer(options) { - if (!(this instanceof HTTPServer)) - return new HTTPServer(options); - - options = new HTTPOptions(options); - - HTTPBase.call(this, options); - - this.options = options; - this.network = this.options.network; - this.logger = this.options.logger.context('http'); - this.node = this.options.node; - - this.chain = this.node.chain; - this.mempool = this.node.mempool; - this.pool = this.node.pool; - this.fees = this.node.fees; - this.miner = this.node.miner; - this.rpc = this.node.rpc; - - this.init(); -} - -Object.setPrototypeOf(HTTPServer.prototype, HTTPBase.prototype); - -/** - * Initialize routes. - * @private - */ - -HTTPServer.prototype.init = function init() { - this.on('request', (req, res) => { - if (req.method === 'POST' && req.pathname === '/') - return; - - this.logger.debug('Request for method=%s path=%s (%s).', - req.method, req.pathname, req.socket.remoteAddress); - }); - - this.on('listening', (address) => { - this.logger.info('Node HTTP server listening on %s (port=%d).', - address.address, address.port); - }); - - this.initRouter(); - this.initSockets(); -}; - -/** - * Initialize routes. - * @private - */ - -HTTPServer.prototype.initRouter = function initRouter() { - this.use(this.cors()); - - if (!this.options.noAuth) { - this.use(this.basicAuth({ - password: this.options.apiKey, - realm: 'node' - })); - } - - this.use(this.bodyParser({ - contentType: 'json' - })); - - this.use(this.jsonRPC(this.rpc)); - - this.get('/', async (req, res) => { - const totalTX = this.mempool ? this.mempool.map.size : 0; - const size = this.mempool ? this.mempool.getSize() : 0; - let addr = this.pool.hosts.getLocal(); - - if (!addr) - addr = this.pool.hosts.address; - - res.send(200, { - version: pkg.version, - network: this.network.type, - chain: { - height: this.chain.height, - tip: this.chain.tip.rhash(), - progress: this.chain.getProgress() - }, - pool: { - host: addr.host, - port: addr.port, - agent: this.pool.options.agent, - services: this.pool.options.services.toString(2), - outbound: this.pool.peers.outbound, - inbound: this.pool.peers.inbound - }, - mempool: { - tx: totalTX, - size: size - }, - time: { - uptime: this.node.uptime(), - system: util.now(), - adjusted: this.network.now(), - offset: this.network.time.offset - }, - memory: util.memoryUsage() - }); - }); - - // UTXO by address - this.get('/coin/address/:address', async (req, res) => { - const valid = req.valid(); - const address = valid.str('address'); - - enforce(address, 'Address is required.'); - enforce(!this.chain.options.spv, 'Cannot get coins in SPV mode.'); - - const coins = await this.node.getCoinsByAddress(address); - const result = []; - - for (const coin of coins) - result.push(coin.getJSON(this.network)); - - res.send(200, result); - }); - - // UTXO by id - this.get('/coin/:hash/:index', async (req, res) => { - const valid = req.valid(); - const hash = valid.hash('hash'); - const index = valid.u32('index'); - - enforce(hash, 'Hash is required.'); - enforce(index != null, 'Index is required.'); - enforce(!this.chain.options.spv, 'Cannot get coins in SPV mode.'); - - const coin = await this.node.getCoin(hash, index); - - if (!coin) { - res.send(404); - return; - } - - res.send(200, coin.getJSON(this.network)); - }); - - // Bulk read UTXOs - this.post('/coin/address', async (req, res) => { - const valid = req.valid(); - const address = valid.array('addresses'); - - enforce(address, 'Address is required.'); - enforce(!this.chain.options.spv, 'Cannot get coins in SPV mode.'); - - const coins = await this.node.getCoinsByAddress(address); - const result = []; - - for (const coin of coins) - result.push(coin.getJSON(this.network)); - - res.send(200, result); - }); - - // TX by hash - this.get('/tx/:hash', async (req, res) => { - const valid = req.valid(); - const hash = valid.hash('hash'); - - enforce(hash, 'Hash is required.'); - enforce(!this.chain.options.spv, 'Cannot get TX in SPV mode.'); - - const meta = await this.node.getMeta(hash); - - if (!meta) { - res.send(404); - return; - } - - const view = await this.node.getMetaView(meta); - - res.send(200, meta.getJSON(this.network, view, this.chain.height)); - }); - - // TX by address - this.get('/tx/address/:address', async (req, res) => { - const valid = req.valid(); - const address = valid.str('address'); - - enforce(address, 'Address is required.'); - enforce(!this.chain.options.spv, 'Cannot get TX in SPV mode.'); - - const metas = await this.node.getMetaByAddress(address); - const result = []; - - for (const meta of metas) { - const view = await this.node.getMetaView(meta); - result.push(meta.getJSON(this.network, view, this.chain.height)); - } - - res.send(200, result); - }); - - // Bulk read TXs - this.post('/tx/address', async (req, res) => { - const valid = req.valid(); - const address = valid.array('addresses'); - - enforce(address, 'Address is required.'); - enforce(!this.chain.options.spv, 'Cannot get TX in SPV mode.'); - - const metas = await this.node.getMetaByAddress(address); - const result = []; - - for (const meta of metas) { - const view = await this.node.getMetaView(meta); - result.push(meta.getJSON(this.network, view, this.chain.height)); - } - - res.send(200, result); - }); - - // Block by hash/height - this.get('/block/:block', async (req, res) => { - const valid = req.valid(); - let hash = valid.get('block'); - - enforce(typeof hash === 'string', 'Hash or height required.'); - enforce(!this.chain.options.spv, 'Cannot get block in SPV mode.'); - - if (hash.length === 64) - hash = util.revHex(hash); - else - hash = parseInt(hash, 10); - - const block = await this.chain.getBlock(hash); - - if (!block) { - res.send(404); - return; - } - - const view = await this.chain.getBlockView(block); - - if (!view) { - res.send(404); - return; - } - - const height = await this.chain.getHeight(hash); - const depth = this.chain.height - height + 1; - - res.send(200, block.getJSON(this.network, view, height, depth)); - }); - - // Mempool snapshot - this.get('/mempool', async (req, res) => { - enforce(this.mempool, 'No mempool available.'); - - const hashes = this.mempool.getSnapshot(); - const result = []; - - for (const hash of hashes) - result.push(util.revHex(hash)); - - res.send(200, result); - }); - - // Broadcast TX - this.post('/broadcast', async (req, res) => { - const valid = req.valid(); - const raw = valid.buf('tx'); - - enforce(raw, 'TX is required.'); - - const tx = TX.fromRaw(raw); - - await this.node.sendTX(tx); - - res.send(200, { success: true }); - }); - - // Estimate fee - this.get('/fee', async (req, res) => { - const valid = req.valid(); - const blocks = valid.u32('blocks'); - - if (!this.fees) { - res.send(200, { rate: this.network.feeRate }); - return; - } - - const fee = this.fees.estimateFee(blocks); - - res.send(200, { rate: fee }); - }); - - // Reset chain - this.post('/reset', async (req, res) => { - const valid = req.valid(); - const height = valid.u32('height'); - - enforce(height != null, 'Height is required.'); - - await this.chain.reset(height); - - res.send(200, { success: true }); - }); -}; - -/** - * Initialize websockets. - * @private - */ - -HTTPServer.prototype.initSockets = function initSockets() { - if (!this.io) - return; - - this.on('socket', (socket) => { - this.handleSocket(socket); - }); -}; - -/** - * Handle new websocket. - * @private - * @param {WebSocket} socket - */ - -HTTPServer.prototype.handleSocket = function handleSocket(socket) { - socket.hook('auth', (args) => { - if (socket.auth) - throw new Error('Already authed.'); - - if (!this.options.noAuth) { - const valid = new Validator([args]); - const key = valid.str(0, ''); - - if (key.length > 255) - throw new Error('Invalid API key.'); - - const data = Buffer.from(key, 'ascii'); - const hash = digest.hash256(data); - - if (!ccmp(hash, this.options.apiHash)) - throw new Error('Invalid API key.'); - } - - socket.auth = true; - - this.logger.info('Successful auth from %s.', socket.remoteAddress); - this.handleAuth(socket); - - return null; - }); - - socket.emit('version', { - version: pkg.version, - network: this.network.type - }); -}; - -/** - * Handle new auth'd websocket. - * @private - * @param {WebSocket} socket - */ - -HTTPServer.prototype.handleAuth = function handleAuth(socket) { - socket.hook('watch chain', (args) => { - socket.join('chain'); - return null; - }); - - socket.hook('unwatch chain', (args) => { - socket.leave('chain'); - return null; - }); - - socket.hook('watch mempool', (args) => { - socket.join('mempool'); - return null; - }); - - socket.hook('unwatch mempool', (args) => { - socket.leave('mempool'); - return null; - }); - - socket.hook('set filter', (args) => { - const valid = new Validator([args]); - const data = valid.buf(0); - - if (!data) - throw new Error('Invalid parameter.'); - - socket.filter = Bloom.fromRaw(data); - - return null; - }); - - socket.hook('get tip', (args) => { - return this.chain.tip.toRaw(); - }); - - socket.hook('get entry', async (args) => { - const valid = new Validator([args]); - const block = valid.numhash(0); - - if (block == null) - throw new Error('Invalid parameter.'); - - const entry = await this.chain.getEntry(block); - - if (!entry) - return null; - - if (!await this.chain.isMainChain(entry)) - return null; - - return entry.toRaw(); - }); - - socket.hook('add filter', (args) => { - const valid = new Validator([args]); - const chunks = valid.array(0); - - if (!chunks) - throw new Error('Invalid parameter.'); - - if (!socket.filter) - throw new Error('No filter set.'); - - const items = new Validator([chunks]); - - for (let i = 0; i < chunks.length; i++) { - const data = items.buf(i); - - if (!data) - throw new Error('Bad data chunk.'); - - this.filter.add(data); - - if (this.node.spv) - this.pool.watch(data); - } - - return null; - }); - - socket.hook('reset filter', (args) => { - socket.filter = null; - return null; - }); - - socket.hook('estimate fee', (args) => { - const valid = new Validator([args]); - const blocks = valid.u32(0); - - if (!this.fees) - return this.network.feeRate; - - return this.fees.estimateFee(blocks); - }); - - socket.hook('send', (args) => { - const valid = new Validator([args]); - const data = valid.buf(0); - - if (!data) - throw new Error('Invalid parameter.'); - - const tx = TX.fromRaw(data); - - this.node.send(tx); - - return null; - }); - - socket.hook('rescan', (args) => { - const valid = new Validator([args]); - const start = valid.numhash(0); - - if (start == null) - throw new Error('Invalid parameter.'); - - return this.scan(socket, start); - }); - - this.bindChain(); -}; - -/** - * Bind to chain events. - * @private - */ - -HTTPServer.prototype.bindChain = function bindChain() { - const pool = this.mempool || this.pool; - - this.chain.on('connect', (entry, block, view) => { - const list = this.channel('chain'); - - if (!list) - return; - - const raw = entry.toRaw(); - - this.to('chain', 'chain connect', raw); - - for (let item = list.head; item; item = item.next) { - const socket = item.value; - const txs = this.filterBlock(socket, block); - socket.emit('block connect', raw, txs); - } - }); - - this.chain.on('disconnect', (entry, block, view) => { - const list = this.channel('chain'); - - if (!list) - return; - - const raw = entry.toRaw(); - - this.to('chain', 'chain disconnect', raw); - this.to('chain', 'block disconnect', raw); - }); - - this.chain.on('reset', (tip) => { - const list = this.channel('chain'); - - if (!list) - return; - - const raw = tip.toRaw(); - - this.to('chain', 'chain reset', raw); - }); - - pool.on('tx', (tx) => { - const list = this.channel('mempool'); - - if (!list) - return; - - const raw = tx.toRaw(); - - for (let item = list.head; item; item = item.next) { - const socket = item.value; - - if (!this.filterTX(socket, tx)) - continue; - - socket.emit('tx', raw); - } - }); -}; - -/** - * Filter block by socket. - * @private - * @param {WebSocket} socket - * @param {Block} block - * @returns {TX[]} - */ - -HTTPServer.prototype.filterBlock = function filterBlock(socket, block) { - if (!socket.filter) - return []; - - const txs = []; - - for (const tx of block.txs) { - if (this.filterTX(socket, tx)) - txs.push(tx.toRaw()); - } - - return txs; -}; - -/** - * Filter transaction by socket. - * @private - * @param {WebSocket} socket - * @param {TX} tx - * @returns {Boolean} - */ - -HTTPServer.prototype.filterTX = function filterTX(socket, tx) { - if (!socket.filter) - return false; - - let found = false; - - for (let i = 0; i < tx.outputs.length; i++) { - const output = tx.outputs[i]; - const hash = output.getHash(); - - if (!hash) - continue; - - if (socket.filter.test(hash)) { - const prevout = Outpoint.fromTX(tx, i); - socket.filter.add(prevout.toRaw()); - found = true; - } - } - - if (found) - return true; - - if (!tx.isCoinbase()) { - for (const {prevout} of tx.inputs) { - if (socket.filter.test(prevout.toRaw())) - return true; - } - } - - return false; -}; - -/** - * Scan using a socket's filter. - * @private - * @param {WebSocket} socket - * @param {Hash} start - * @returns {Promise} - */ - -HTTPServer.prototype.scan = async function scan(socket, start) { - const scanner = this.scanner.bind(this, socket); - await this.node.scan(start, socket.filter, scanner); - return null; -}; - -/** - * Handle rescan iteration. - * @private - * @param {WebSocket} socket - * @param {ChainEntry} entry - * @param {TX[]} txs - * @returns {Promise} - */ - -HTTPServer.prototype.scanner = function scanner(socket, entry, txs) { - const block = entry.toRaw(); - const raw = []; - - for (const tx of txs) - raw.push(tx.toRaw()); - - socket.emit('block rescan', block, raw); - - return Promise.resolve(); -}; - -/** - * HTTPOptions - * @alias module:http.HTTPOptions - * @constructor - * @param {Object} options - */ - -function HTTPOptions(options) { - if (!(this instanceof HTTPOptions)) - return new HTTPOptions(options); - - this.network = Network.primary; - this.logger = null; - this.node = null; - this.apiKey = base58.encode(random.randomBytes(20)); - this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'ascii')); - this.noAuth = false; - - this.prefix = null; - this.host = '127.0.0.1'; - this.port = 8080; - this.ssl = false; - this.keyFile = null; - this.certFile = null; - - this.fromOptions(options); -} - -/** - * Inject properties from object. - * @private - * @param {Object} options - * @returns {HTTPOptions} - */ - -HTTPOptions.prototype.fromOptions = function fromOptions(options) { - assert(options); - assert(options.node && typeof options.node === 'object', - 'HTTP Server requires a Node.'); - - this.node = options.node; - this.network = options.node.network; - this.logger = options.node.logger; - - this.port = this.network.rpcPort; - - if (options.logger != null) { - assert(typeof options.logger === 'object'); - this.logger = options.logger; - } - - if (options.apiKey != null) { - assert(typeof options.apiKey === 'string', - 'API key must be a string.'); - assert(options.apiKey.length <= 255, - 'API key must be under 256 bytes.'); - assert(util.isAscii(options.apiKey), - 'API key must be ascii.'); - this.apiKey = options.apiKey; - this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'ascii')); - } - - if (options.noAuth != null) { - assert(typeof options.noAuth === 'boolean'); - this.noAuth = options.noAuth; - } - - if (options.prefix != null) { - assert(typeof options.prefix === 'string'); - this.prefix = options.prefix; - this.keyFile = path.join(this.prefix, 'key.pem'); - this.certFile = path.join(this.prefix, 'cert.pem'); - } - - if (options.host != null) { - assert(typeof options.host === 'string'); - this.host = options.host; - } - - if (options.port != null) { - assert(util.isU16(options.port), 'Port must be a number.'); - this.port = options.port; - } - - if (options.ssl != null) { - assert(typeof options.ssl === 'boolean'); - this.ssl = options.ssl; - } - - if (options.keyFile != null) { - assert(typeof options.keyFile === 'string'); - this.keyFile = options.keyFile; - } - - if (options.certFile != null) { - assert(typeof options.certFile === 'string'); - this.certFile = options.certFile; - } - - // Allow no-auth implicitly - // if we're listening locally. - if (!options.apiKey) { - if (this.host === '127.0.0.1' || this.host === '::1') - this.noAuth = true; - } - - return this; -}; - -/** - * Instantiate http options from object. - * @param {Object} options - * @returns {HTTPOptions} - */ - -HTTPOptions.fromOptions = function fromOptions(options) { - return new HTTPOptions().fromOptions(options); -}; - -/* - * Helpers - */ - -function enforce(value, msg) { - if (!value) { - const err = new Error(msg); - err.statusCode = 400; - throw err; - } -} - -/* - * Expose - */ - -module.exports = HTTPServer; diff --git a/lib/http/wallet-browser.js b/lib/http/wallet-browser.js deleted file mode 100644 index 212272704..000000000 --- a/lib/http/wallet-browser.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -exports.unsupported = true; diff --git a/lib/http/wallet.js b/lib/http/wallet.js deleted file mode 100644 index 2f5d15943..000000000 --- a/lib/http/wallet.js +++ /dev/null @@ -1,498 +0,0 @@ -/*! - * wallet.js - http wallet for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const EventEmitter = require('events'); -const Network = require('../protocol/network'); -const Client = require('./client'); - -/** - * HTTPWallet - * @alias module:http.Wallet - * @constructor - * @param {String} uri - */ - -function HTTPWallet(options) { - if (!(this instanceof HTTPWallet)) - return new HTTPWallet(options); - - EventEmitter.call(this); - - if (!options) - options = {}; - - if (typeof options === 'string') - options = { uri: options }; - - this.options = options; - this.network = Network.get(options.network); - - this.client = new Client(options); - this.uri = options.uri; - this.id = null; - this.token = null; - - if (options.id) - this.id = options.id; - - if (options.token) { - this.token = options.token; - if (Buffer.isBuffer(this.token)) - this.token = this.token.toString('hex'); - this.client.token = this.token; - } - - this._init(); -} - -Object.setPrototypeOf(HTTPWallet.prototype, EventEmitter.prototype); - -/** - * Initialize the wallet. - * @private - */ - -HTTPWallet.prototype._init = function _init() { - this.client.on('tx', (details) => { - this.emit('tx', details); - }); - - this.client.on('confirmed', (details) => { - this.emit('confirmed', details); - }); - - this.client.on('unconfirmed', (tx, details) => { - this.emit('unconfirmed', details); - }); - - this.client.on('conflict', (tx, details) => { - this.emit('conflict', details); - }); - - this.client.on('balance', (balance) => { - this.emit('balance', balance); - }); - - this.client.on('address', (receive) => { - this.emit('address', receive); - }); - - this.client.on('error', (err) => { - this.emit('error', err); - }); -}; - -/** - * Open the client and get a wallet. - * @alias HTTPWallet#open - * @returns {Promise} - */ - -HTTPWallet.prototype.open = async function open(options) { - if (options) { - if (options.id) - this.id = options.id; - - if (options.token) { - this.token = options.token; - if (Buffer.isBuffer(this.token)) - this.token = this.token.toString('hex'); - this.client.token = this.token; - } - } - - assert(this.id, 'No ID provided.'); - - await this.client.open(); - await this.client.sendWalletAuth(); - await this.client.join(this.id, this.token); -}; - -/** - * Open the client and create a wallet. - * @alias HTTPWallet#open - * @returns {Promise} - */ - -HTTPWallet.prototype.create = async function create(options) { - await this.client.open(); - await this.client.sendWalletAuth(); - - const wallet = await this.client.createWallet(options); - - this.id = wallet.id; - this.token = wallet.token; - this.client.token = this.token; - - await this.client.join(this.id, this.token); - - return wallet; -}; - -/** - * Close the client, wait for the socket to close. - * @alias HTTPWallet#close - * @returns {Promise} - */ - -HTTPWallet.prototype.close = function close() { - return this.client.close(); -}; - -/** - * Wait for websocket disconnection. - * @private - * @returns {Promise} - */ - -HTTPWallet.prototype.onDisconnect = function onDisconnect() { - return this.client.onDisconnect(); -}; - -/** - * @see Wallet#getHistory - */ - -HTTPWallet.prototype.getHistory = function getHistory(account) { - return this.client.getHistory(this.id, account); -}; - -/** - * @see Wallet#getCoins - */ - -HTTPWallet.prototype.getCoins = function getCoins(account) { - return this.client.getCoins(this.id, account); -}; - -/** - * @see Wallet#getPending - */ - -HTTPWallet.prototype.getPending = function getPending(account) { - return this.client.getPending(this.id, account); -}; - -/** - * @see Wallet#getBalance - */ - -HTTPWallet.prototype.getBalance = function getBalance(account) { - return this.client.getBalance(this.id, account); -}; - -/** - * @see Wallet#getLast - */ - -HTTPWallet.prototype.getLast = function getLast(account, limit) { - return this.client.getLast(this.id, account, limit); -}; - -/** - * @see Wallet#getRange - */ - -HTTPWallet.prototype.getRange = function getRange(account, options) { - return this.client.getRange(this.id, account, options); -}; - -/** - * @see Wallet#getTX - */ - -HTTPWallet.prototype.getTX = function getTX(hash) { - return this.client.getWalletTX(this.id, hash); -}; - -/** - * @see Wallet#getBlocks - */ - -HTTPWallet.prototype.getBlocks = function getBlocks() { - return this.client.getWalletBlocks(this.id); -}; - -/** - * @see Wallet#getBlock - */ - -HTTPWallet.prototype.getBlock = function getBlock(height) { - return this.client.getWalletBlock(this.id, height); -}; - -/** - * @see Wallet#getCoin - */ - -HTTPWallet.prototype.getCoin = function getCoin(hash, index) { - return this.client.getWalletCoin(this.id, hash, index); -}; - -/** - * @see Wallet#zap - */ - -HTTPWallet.prototype.zap = function zap(account, age) { - return this.client.zapWallet(this.id, account, age); -}; - -/** - * @see Wallet#createTX - */ - -HTTPWallet.prototype.createTX = function createTX(options) { - return this.client.createTX(this.id, options); -}; - -/** - * @see HTTPClient#walletSend - */ - -HTTPWallet.prototype.send = function send(options) { - return this.client.send(this.id, options); -}; - -/** - * @see Wallet#sign - */ - -HTTPWallet.prototype.sign = function sign(tx, options) { - return this.client.sign(this.id, tx, options); -}; - -/** - * @see HTTPClient#getWallet - */ - -HTTPWallet.prototype.getInfo = function getInfo() { - return this.client.getWallet(this.id); -}; - -/** - * @see Wallet#getAccounts - */ - -HTTPWallet.prototype.getAccounts = function getAccounts() { - return this.client.getAccounts(this.id); -}; - -/** - * @see Wallet#master - */ - -HTTPWallet.prototype.getMaster = function getMaster() { - return this.client.getMaster(this.id); -}; - -/** - * @see Wallet#getAccount - */ - -HTTPWallet.prototype.getAccount = function getAccount(account) { - return this.client.getAccount(this.id, account); -}; - -/** - * @see Wallet#createAccount - */ - -HTTPWallet.prototype.createAccount = function createAccount(name, options) { - return this.client.createAccount(this.id, name, options); -}; - -/** - * @see Wallet#createAddress - */ - -HTTPWallet.prototype.createAddress = function createAddress(account) { - return this.client.createAddress(this.id, account); -}; - -/** - * @see Wallet#createAddress - */ - -HTTPWallet.prototype.createChange = function createChange(account) { - return this.client.createChange(this.id, account); -}; - -/** - * @see Wallet#createAddress - */ - -HTTPWallet.prototype.createNested = function createNested(account) { - return this.client.createNested(this.id, account); -}; - -/** - * @see Wallet#setPassphrase - */ - -HTTPWallet.prototype.setPassphrase = function setPassphrase(old, new_) { - return this.client.setPassphrase(this.id, old, new_); -}; - -/** - * @see Wallet#retoken - */ - -HTTPWallet.prototype.retoken = async function retoken(passphrase) { - const token = await this.client.retoken(this.id, passphrase); - - this.token = token; - this.client.token = token; - - return token; -}; - -/** - * Import private key. - * @param {Number|String} account - * @param {String} key - * @returns {Promise} - */ - -HTTPWallet.prototype.importPrivate = function importPrivate(account, key, passphrase) { - return this.client.importPrivate(this.id, account, key, passphrase); -}; - -/** - * Import public key. - * @param {Number|String} account - * @param {String} key - * @returns {Promise} - */ - -HTTPWallet.prototype.importPublic = function importPublic(account, key) { - return this.client.importPublic(this.id, account, key); -}; - -/** - * Import address. - * @param {Number|String} account - * @param {String} address - * @returns {Promise} - */ - -HTTPWallet.prototype.importAddress = function importAddress(account, address) { - return this.client.importAddress(this.id, account, address); -}; - -/** - * Lock a coin. - * @param {String} hash - * @param {Number} index - * @returns {Promise} - */ - -HTTPWallet.prototype.lockCoin = function lockCoin(hash, index) { - return this.client.lockCoin(this.id, hash, index); -}; - -/** - * Unlock a coin. - * @param {String} hash - * @param {Number} index - * @returns {Promise} - */ - -HTTPWallet.prototype.unlockCoin = function unlockCoin(hash, index) { - return this.client.unlockCoin(this.id, hash, index); -}; - -/** - * Get locked coins. - * @returns {Promise} - */ - -HTTPWallet.prototype.getLocked = function getLocked() { - return this.client.getLocked(this.id); -}; - -/** - * Lock wallet. - * @returns {Promise} - */ - -HTTPWallet.prototype.lock = function lock() { - return this.client.lock(this.id); -}; - -/** - * Unlock wallet. - * @param {String} passphrase - * @param {Number} timeout - * @returns {Promise} - */ - -HTTPWallet.prototype.unlock = function unlock(passphrase, timeout) { - return this.client.unlock(this.id, passphrase, timeout); -}; - -/** - * Get wallet key. - * @param {String} address - * @returns {Promise} - */ - -HTTPWallet.prototype.getKey = function getKey(address) { - return this.client.getKey(this.id, address); -}; - -/** - * Get wallet key WIF dump. - * @param {String} address - * @param {String?} passphrase - * @returns {Promise} - */ - -HTTPWallet.prototype.getWIF = function getWIF(address, passphrase) { - return this.client.getWIF(this.id, address, passphrase); -}; - -/** - * Add a public account/purpose key to the wallet for multisig. - * @param {(String|Number)?} account - * @param {Base58String} key - Account (bip44) or - * Purpose (bip45) key (can be in base58 form). - * @returns {Promise} - */ - -HTTPWallet.prototype.addSharedKey = function addSharedKey(account, key) { - return this.client.addSharedKey(this.id, account, key); -}; - -/** - * Remove a public account/purpose key to the wallet for multisig. - * @param {(String|Number)?} account - * @param {Base58String} key - Account (bip44) or Purpose - * (bip45) key (can be in base58 form). - * @returns {Promise} - */ - -HTTPWallet.prototype.removeSharedKey = function removeSharedKey(account, key) { - return this.client.removeSharedKey(this.id, account, key); -}; - -/** - * Resend wallet transactions. - * @returns {Promise} - */ - -HTTPWallet.prototype.resend = function resend() { - return this.client.resendWallet(this.id); -}; - -/* - * Expose - */ - -module.exports = HTTPWallet; diff --git a/lib/indexer/addrindexer.js b/lib/indexer/addrindexer.js new file mode 100644 index 000000000..10b850061 --- /dev/null +++ b/lib/indexer/addrindexer.js @@ -0,0 +1,276 @@ +/*! + * addrindexer.js - address indexer for bcoin + * Copyright (c) 2018, the bcoin developers (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const assert = require('assert'); +const bdb = require('bdb'); +const bio = require('bufio'); +const layout = require('./layout'); +const Address = require('../primitives/address'); +const Indexer = require('./indexer'); + +/* + * AddrIndexer Database Layout: + * A[addr-prefix][addr-hash][height][index] -> dummy (tx by address) + * C[height][index] -> hash (tx hash by height and index) + * c[hash]-> height + index (tx height and index by hash) + * + * The database layout is organized so that transactions are + * sorted in the same order as the blocks using the block height + * and transaction index. This provides the ability to query for + * sets of transactions within that order. For a wallet that would + * like to synchronize or rescan, this could be a query for all of + * the latest transactions, but not for earlier transactions that + * are already known. + * + * To be able to query for all transactions in multiple sets without + * reference to height and index, there is a mapping from tx hash to + * the height and index as an entry point. + * + * A mapping of height and index is kept for each transaction + * hash so that the tx hash is not repeated for every address within + * a transaction. + */ + +Object.assign(layout, { + A: bdb.key('A', ['uint8', 'hash', 'uint32', 'uint32']), + C: bdb.key('C', ['uint32', 'uint32']), + c: bdb.key('c', ['hash256']) +}); + +/** + * Count + */ + +class Count { + /** + * Create count record. + * @constructor + * @param {Number} height + * @param {Number} index + */ + + constructor(height, index) { + this.height = height || 0; + this.index = index || 0; + + assert((this.height >>> 0) === this.height); + assert((this.index >>> 0) === this.index); + } + + /** + * Serialize. + * @returns {Buffer} + */ + + toRaw() { + const bw = bio.write(8); + + bw.writeU32(this.height); + bw.writeU32(this.index); + + return bw.render(); + } + + /** + * Deserialize. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + const br = bio.read(data); + + this.height = br.readU32(); + this.index = br.readU32(); + + return this; + } + + /** + * Instantiate a count from a buffer. + * @param {Buffer} data + * @returns {Count} + */ + + static fromRaw(data) { + return new this().fromRaw(data); + } +} + +/** + * AddrIndexer + * @alias module:indexer.AddrIndexer + * @extends Indexer + */ + +class AddrIndexer extends Indexer { + /** + * Create a indexer + * @constructor + * @param {Object} options + */ + + constructor(options) { + super('addr', options); + + this.db = bdb.create(this.options); + this.maxTxs = options.maxTxs || 100; + } + + /** + * Index transactions by address. + * @private + * @param {BlockMeta} meta + * @param {Block} block + * @param {CoinView} view + */ + + async indexBlock(meta, block, view) { + const height = meta.height; + + for (let i = 0; i < block.txs.length; i++) { + const tx = block.txs[i]; + const hash = tx.hash(); + const count = new Count(height, i); + + let hasAddress = false; + + for (const addr of tx.getAddresses(view)) { + const prefix = addr.getPrefix(this.network); + + if (prefix < 0) + continue; + + const addrHash = addr.getHash(); + + this.put(layout.A.encode(prefix, addrHash, height, i), null); + + hasAddress = true; + } + + if (hasAddress) { + this.put(layout.C.encode(height, i), hash); + this.put(layout.c.encode(hash), count.toRaw()); + } + } + } + + /** + * Remove addresses from index. + * @private + * @param {BlockMeta} meta + * @param {Block} block + * @param {CoinView} view + */ + + async unindexBlock(meta, block, view) { + const height = meta.height; + + for (let i = 0; i < block.txs.length; i++) { + const tx = block.txs[i]; + const hash = tx.hash(); + + let hasAddress = false; + + for (const addr of tx.getAddresses(view)) { + const prefix = addr.getPrefix(this.network); + + if (prefix < 0) + continue; + + const addrHash = addr.getHash(); + + this.del(layout.A.encode(prefix, addrHash, height, i)); + + hasAddress = true; + } + + if (hasAddress) { + this.del(layout.C.encode(height, i)); + this.del(layout.c.encode(hash)); + } + } + } + + /** + * Get transaction hashes to an address in ascending or descending + * order. If the `after` argument is supplied, results will be given + * _after_ that transaction hash. The default order is ascending from + * oldest to latest. + * @param {Address} addr + * @param {Object} options + * @param {Buffer} options.after - A transaction hash + * @param {Number} options.limit + * @param {Boolean} options.reverse + * @returns {Promise} - Returns {@link Hash}[]. + */ + + async getHashesByAddress(addr, options = {}) { + const {after, reverse} = options; + let {limit} = options; + + if (!limit) + limit = this.maxTxs; + + if (limit > this.maxTxs) + throw new Error(`Limit above max of ${this.maxTxs}.`); + + const hash = Address.getHash(addr); + const prefix = addr.getPrefix(this.network); + + const opts = { + limit, + reverse, + parse: (key) => { + const [,, height, index] = layout.A.decode(key); + return [height, index]; + } + }; + + // Determine if the hash -> height + index mapping exists. + const hasAfter = (after && await this.db.has(layout.c.encode(after))); + + // Check to see if results should be skipped because + // the after hash is expected to be within a following + // mempool query. + const skip = (after && !hasAfter && !reverse); + if (skip) + return []; + + if (after && hasAfter) { + // Give results starting from after + // the tx hash for the address. + const raw = await this.db.get(layout.c.encode(after)); + const count = Count.fromRaw(raw); + const {height, index} = count; + + if (!reverse) { + opts.gt = layout.A.min(prefix, hash, height, index); + opts.lte = layout.A.max(prefix, hash); + } else { + opts.gte = layout.A.min(prefix, hash); + opts.lt = layout.A.max(prefix, hash, height, index); + } + } else { + // Give earliest or latest results + // for the address. + opts.gte = layout.A.min(prefix, hash); + opts.lte = layout.A.max(prefix, hash); + } + + const txs = await this.db.keys(opts); + const hashes = []; + + for (const [height, index] of txs) + hashes.push(await this.db.get(layout.C.encode(height, index))); + + return hashes; + } +} + +module.exports = AddrIndexer; diff --git a/lib/indexer/index.js b/lib/indexer/index.js new file mode 100644 index 000000000..129b3d93b --- /dev/null +++ b/lib/indexer/index.js @@ -0,0 +1,15 @@ +/*! + * index.js - indexer for bcoin + * Copyright (c) 2018, the bcoin developers (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +/** + * @module indexer + */ + +exports.Indexer = require('./indexer'); +exports.TXIndexer = require('./txindexer'); +exports.AddrIndexer = require('./addrindexer'); diff --git a/lib/indexer/indexer.js b/lib/indexer/indexer.js new file mode 100644 index 000000000..d1d138327 --- /dev/null +++ b/lib/indexer/indexer.js @@ -0,0 +1,695 @@ +/*! + * indexer.js - abstract interface for bcoin indexers + * Copyright (c) 2018, the bcoin developers (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const assert = require('assert'); +const path = require('path'); +const fs = require('bfile'); +const bio = require('bufio'); +const EventEmitter = require('events'); +const Logger = require('blgr'); +const Network = require('../protocol/network'); +const util = require('../utils/util'); +const layout = require('./layout'); +const CoinView = require('../coins/coinview'); +const Block = require('../primitives/block'); +const {ZERO_HASH} = require('../protocol/consensus'); + +/** + * Indexer + * The class which indexers inherit from and implement the + * `indexBlock` and `unindexBlock` methods and database + * and storage initialization for indexing blocks. + * @alias module:indexer.Indexer + * @extends EventEmitter + * @abstract + */ + +class Indexer extends EventEmitter { + /** + * Create an indexer. + * @constructor + * @param {String} module + * @param {Object} options + */ + + constructor(module, options) { + super(); + + assert(typeof module === 'string'); + assert(module.length > 0); + + this.options = new IndexOptions(module, options); + + this.network = this.options.network; + this.logger = this.options.logger.context(`${module}indexer`); + this.blocks = this.options.blocks; + this.chain = this.options.chain; + + this.db = null; + this.batch = null; + this.bound = []; + this.syncing = false; + this.height = 0; + } + + /** + * Start a new batch write. + * @returns {Batch} + */ + + start() { + assert(this.batch === null, 'Already started.'); + this.batch = this.db.batch(); + return this.batch; + } + + /** + * Put key and value to the current batch. + * @param {String} key + * @param {Buffer} value + */ + + put(key, value) { + this.batch.put(key, value); + } + + /** + * Delete key from the current batch. + * @param {String} key + */ + + del(key) { + this.batch.del(key); + } + + /** + * Commit the current batch. + * @returns {Promise} + */ + + async commit() { + await this.batch.write(); + this.batch = null; + } + + /** + * Open the indexer, open the database, + * initialize height, and bind to events. + * @returns {Promise} + */ + + async open() { + this.logger.info('Indexer is loading.'); + + await this.ensure(); + await this.db.open(); + await this.db.verify(layout.V.encode(), 'index', 0); + await this.verifyNetwork(); + + // Initialize the indexed height. + const data = await this.db.get(layout.R.encode()); + if (data) + this.height = bio.readU32(data, 0); + else + await this.saveGenesis(); + + // Bind to chain events. + this.bind(); + } + + /** + * Close the indexer, wait for the database to close, + * unbind all events. + * @returns {Promise} + */ + + async close() { + await this.db.close(); + for (const [event, listener] of this.bound) + this.chain.removeListener(event, listener); + + this.bound.length = 0; + } + + /** + * Ensure prefix directory (prefix/index). + * @returns {Promise} + */ + + async ensure() { + if (fs.unsupported) + return; + + if (this.options.memory) + return; + + await fs.mkdirp(this.options.prefix); + } + + /** + * Verify network of index. + * @returns {Promise} + */ + + async verifyNetwork() { + let raw = await this.db.get(layout.O.encode()); + + if (!raw) { + raw = bio.write(4).writeU32(this.network.magic).render(); + await this.db.put(layout.O.encode(), raw); + return; + } + + const magic = bio.readU32(raw, 0); + + if (magic !== this.network.magic) + throw new Error('Indexer: Network mismatch.'); + } + + /** + * A special case for indexing the genesis block. The genesis + * block coins are not spendable, however indexers can still index + * the block for historical and informational purposes. + * @private + * @returns {Promise} + */ + + async saveGenesis() { + this.start(); + + const block = Block.fromRaw(Buffer.from(this.network.genesisBlock, 'hex')); + const meta = new BlockMeta(block.hash(), 0); + + await this.indexBlock(meta, block, new CoinView()); + await this._setTip(meta); + await this.commit(); + + this.height = 0; + } + + /** + * Bind to chain events and save listeners for removal on close + * @private + */ + + bind() { + const listener = async (entry, block, view) => { + const meta = new BlockMeta(entry.hash, entry.height); + + try { + await this.sync(meta, block, view); + } catch (e) { + this.emit('error', e); + } + }; + + for (const event of ['connect', 'disconnect', 'reset']) { + this.bound.push([event, listener]); + this.chain.on(event, listener); + } + } + + /** + * Get a chain entry for the main chain only. + * @private + * @returns {Promise} + */ + + async getEntry(hash) { + const entry = await this.chain.getEntry(hash); + + if (!entry) + return null; + + if (!await this.chain.isMainChain(entry)) + return null; + + return entry; + } + + /** + * Get a index block meta. + * @param {Hash} hash + * @returns {Promise} + */ + + async getBlockMeta(height) { + const data = await this.db.get(layout.h.encode(height)); + + if (!data) + return null; + + return new BlockMeta(data, height); + } + + /** + * Sync with the chain. + * @param {BlockMeta} meta + * @param {Block} block + * @param {CoinView} view + * @returns {Promise} + */ + + async sync(meta, block, view) { + if (this.syncing) + return; + + this.syncing = true; + + const connected = await this._syncBlock(meta, block, view); + + if (connected) { + this.syncing = false; + } else { + (async () => { + try { + await this._syncChain(); + } catch (e) { + this.emit('error', e); + } finally { + this.syncing = false; + } + })(); + } + } + + /** + * Sync with the chain with a block. + * @private + * @param {BlockMeta} meta + * @param {Block} block + * @param {CoinView} view + * @returns {Promise} + */ + + async _syncBlock(meta, block, view) { + // In the case that the next block is being + // connected or the current block disconnected + // use the block and view being passed directly, + // instead of reading that information again. + if (meta && block && view) { + if (meta.height === this.height + 1) { + // Make sure that the block is connected to + // the indexer chain. + const prev = await this.getBlockMeta(this.height); + if (prev.hash.compare(block.prevBlock) !== 0) + return false; + + await this._addBlock(meta, block, view); + return true; + } else if (meta.height === this.height) { + // Make sure that this is the current block. + const current = await this.getBlockMeta(this.height); + if (current.hash.compare(block.hash()) !== 0) + return false; + + await this._removeBlock(meta, block, view); + return true; + } + } + return false; + } + + /** + * Sync with the chain. + * @private + * @returns {Promise} + */ + + async _syncChain() { + let height = this.height; + + // In the case that the indexer has never + // started, sync to the best height. + if (!height) { + await this._rollforward(); + return; + } + + // Check for a re-org that might + // leave chain in a different state. + // Scan chain backwards until we + // find a common height. + while (height > 0) { + const meta = await this.getBlockMeta(height); + assert(meta); + + if (await this.getEntry(meta.hash)) + break; + + height -= 1; + } + + if (height < this.height) { + await this._rollback(height); + await this._rollforward(); + } else { + await this._rollforward(); + } + } + + /** + * Scan blockchain to the best chain height. + * @private + * @returns {Promise} + */ + + async _rollforward() { + this.logger.info('Indexing to best height from height (%d).', this.height); + + for (let height = this.height + 1; ; height++) { + const entry = await this.getEntry(height); + if (!entry) + break; + + const meta = new BlockMeta(entry.hash, height); + + const block = await this.chain.getBlock(entry.hash); + assert(block); + + const view = await this.chain.getBlockView(block); + assert(view); + + await this._addBlock(meta, block, view); + } + } + + /** + * Rollback to a given chain height. + * @param {Number} height + * @returns {Promise} + */ + + async _rollback(height) { + if (height > this.height) { + this.logger.warning( + 'Ignoring rollback to future height (%d).', + height); + return; + } + + this.logger.info('Rolling back to height %d.', height); + + while (this.height > height && this.height > 1) { + const meta = await this.getBlockMeta(this.height); + assert(meta); + + const block = await this.chain.getBlock(meta.hash); + assert(block); + + const view = await this.chain.getBlockView(block); + assert(view); + + await this._removeBlock(meta, block, view); + } + } + + /** + * Add a block's transactions without a lock. + * @private + * @param {BlockMeta} meta + * @param {Block} block + * @param {CoinView} view + * @returns {Promise} + */ + + async _addBlock(meta, block, view) { + const start = util.bench(); + + if (meta.height !== this.height + 1) + throw new Error('Indexer: Can not add block.'); + + // Start the batch write. + this.start(); + + // Call the implemented indexer to add to + // the batch write. + await this.indexBlock(meta, block, view); + + // Sync the height to the new tip. + const height = await this._setTip(meta); + + // Commit the write batch to disk. + await this.commit(); + + // Update height _after_ successful commit. + this.height = height; + + // Log the current indexer status. + this.logStatus(start, block, meta); + } + + /** + * Process block indexing + * Indexers will implement this method to process the block for indexing + * @param {BlockMeta} meta + * @param {Block} block + * @param {CoinView} view + * @returns {Promise} + */ + + async indexBlock(meta, block, view) { + ; + } + + /** + * Undo block indexing + * Indexers will implement this method to undo indexing for the block + * @param {BlockMeta} meta + * @param {Block} block + * @param {CoinView} view + * @returns {Promise} + */ + + async unindexBlock(meta, block, view) { + ; + } + + /** + * Unconfirm a block's transactions. + * @private + * @param {BlockMeta} meta + * @param {Block} block + * @param {CoinView} view + * @returns {Promise} + */ + + async _removeBlock(meta, block, view) { + const start = util.bench(); + + if (meta.height !== this.height) + throw new Error('Indexer: Can not remove block.'); + + // Start the batch write. + this.start(); + + // Call the implemented indexer to add to + // the batch write. + await this.unindexBlock(meta, block, view); + + const prev = await this.getBlockMeta(meta.height - 1); + assert(prev); + + // Sync the height to the previous tip. + const height = await this._setTip(prev); + + // Commit the write batch to disk. + await this.commit(); + + // Update height _after_ successful commit. + this.height = height; + + // Log the current indexer status. + this.logStatus(start, block, meta, true); + } + + /** + * Update the current height to tip. + * @param {BlockMeta} tip + * @returns {Promise} + */ + + async _setTip(meta) { + if (meta.height < this.height) { + assert(meta.height === this.height - 1); + this.del(layout.h.encode(this.height)); + } else if (meta.height > this.height) { + assert(meta.height === this.height + 1); + } + + // Add to batch write to save tip and height. + this.put(layout.h.encode(meta.height), meta.hash); + + const raw = bio.write(4).writeU32(meta.height).render(); + this.put(layout.R.encode(), raw); + + return meta.height; + } + + /** + * Test whether the indexer has reached its slow height. + * @private + * @returns {Boolean} + */ + + isSlow() { + if (this.height === 1 || this.height % 20 === 0) + return true; + + if (this.height >= this.network.block.slowHeight) + return true; + + return false; + } + + /** + * Log the current indexer status. + * @private + * @param {Array} start + * @param {Block} block + * @param {BlockMeta} meta + * @param {Boolean} reverse + */ + + logStatus(start, block, meta, reverse) { + if (!this.isSlow()) + return; + + const elapsed = util.bench(start); + + const msg = reverse ? 'removed from' : 'added to'; + + this.logger.info( + 'Block (%d) %s indexer (txs=%d time=%d).', + meta.height, + msg, + block.txs.length, + elapsed); + } +} + +/** + * Block Meta + */ + +class BlockMeta { + constructor(hash, height) { + this.hash = hash || ZERO_HASH; + this.height = height || 0; + + assert(Buffer.isBuffer(this.hash) && this.hash.length === 32); + assert(Number.isInteger(this.height)); + } +} + +/** + * Index Options + */ + +class IndexOptions { + /** + * Create index options. + * @constructor + * @param {String} module + * @param {Object} options + */ + + constructor(module, options) { + this.module = module; + this.network = Network.primary; + this.logger = Logger.global; + this.blocks = null; + this.chain = null; + + this.prefix = null; + this.location = null; + this.memory = true; + this.maxFiles = 64; + this.cacheSize = 16 << 20; + this.compression = true; + + if (options) + this.fromOptions(options); + } + + /** + * Inject properties from object. + * @private + * @param {Object} options + * @returns {IndexOptions} + */ + + fromOptions(options) { + assert(options.blocks && typeof options.blocks === 'object', + 'Indexer requires a blockstore.'); + assert(options.chain && typeof options.chain === 'object', + 'Indexer requires chain.'); + assert(!options.prune, 'Can not index while pruned.'); + + this.blocks = options.blocks; + this.chain = options.chain; + + if (options.network != null) + this.network = Network.get(options.network); + + if (options.logger != null) { + assert(typeof options.logger === 'object'); + this.logger = options.logger; + } + + if (options.prefix != null) { + assert(typeof options.prefix === 'string'); + this.prefix = options.prefix; + this.prefix = path.join(this.prefix, 'index'); + this.location = path.join(this.prefix, this.module); + } + + if (options.location != null) { + assert(typeof options.location === 'string'); + this.location = options.location; + } + + if (options.memory != null) { + assert(typeof options.memory === 'boolean'); + this.memory = options.memory; + } + + if (options.maxFiles != null) { + assert((options.maxFiles >>> 0) === options.maxFiles); + this.maxFiles = options.maxFiles; + } + + if (options.cacheSize != null) { + assert(Number.isSafeInteger(options.cacheSize) && options.cacheSize >= 0); + this.cacheSize = options.cacheSize; + } + + if (options.compression != null) { + assert(typeof options.compression === 'boolean'); + this.compression = options.compression; + } + + return this; + } + + /** + * Instantiate indexer options from object. + * @param {Object} options + * @returns {IndexOptions} + */ + + static fromOptions(options) { + return new this().fromOptions(options); + } +} + +/* + * Expose + */ + +module.exports = Indexer; diff --git a/lib/indexer/layout.js b/lib/indexer/layout.js new file mode 100644 index 000000000..3edbd9ede --- /dev/null +++ b/lib/indexer/layout.js @@ -0,0 +1,32 @@ +/*! + * layout.js - indexer layout for bcoin + * Copyright (c) 2018, the bcoin developers (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const bdb = require('bdb'); + +/* + * Index database layout: + * To be extended by indexer implementations. + * + * V -> db version + * O -> flags + * h[height] -> block hash + * R -> index sync height + */ + +const layout = { + V: bdb.key('V'), + O: bdb.key('O'), + h: bdb.key('h', ['uint32']), + R: bdb.key('R') +}; + +/* + * Expose + */ + +module.exports = layout; diff --git a/lib/indexer/txindexer.js b/lib/indexer/txindexer.js new file mode 100644 index 000000000..14798acea --- /dev/null +++ b/lib/indexer/txindexer.js @@ -0,0 +1,319 @@ +/*! + * txindexer.js - transaction indexer for bcoin + * Copyright (c) 2018, the bcoin developers (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const assert = require('bsert'); +const bdb = require('bdb'); +const bio = require('bufio'); +const layout = require('./layout'); +const consensus = require('../protocol/consensus'); +const TX = require('../primitives/tx'); +const TXMeta = require('../primitives/txmeta'); +const Indexer = require('./indexer'); + +/* + * TXIndexer Database Layout: + * t[hash] -> tx record + * b[height] -> block record + * + * The transaction index maps a transaction to a block + * and an index, offset, and length within that block. The + * block hash is stored in a separate record by height so that + * the 32 byte hash is not repeated for every transaction + * within a block. + */ + +Object.assign(layout, { + t: bdb.key('t', ['hash256']), + b: bdb.key('b', ['uint32']) +}); + +/** + * Block Record + */ + +class BlockRecord { + /** + * Create a block record. + * @constructor + */ + + constructor(options = {}) { + this.block = options.block || consensus.ZERO_HASH; + this.time = options.time || 0; + + assert(this.block.length === 32); + assert((this.time >>> 0) === this.time); + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + const br = bio.read(data); + + this.block = br.readHash(); + this.time = br.readU32(); + + return this; + } + + /** + * Instantiate block record from serialized data. + * @param {Hash} hash + * @param {Buffer} data + * @returns {BlockRecord} + */ + + static fromRaw(data) { + return new this().fromRaw(data); + } + + /** + * Serialize the block record. + * @returns {Buffer} + */ + + toRaw() { + const bw = bio.write(36); + + bw.writeHash(this.block); + bw.writeU32(this.time); + + return bw.render(); + } +} + +/** + * Transaction Record + */ + +class TxRecord { + /** + * Create a transaction record. + * @constructor + */ + + constructor(options = {}) { + this.height = options.height || 0; + this.index = options.index || 0; + this.offset = options.offset || 0; + this.length = options.length || 0; + + assert((this.height >>> 0) === this.height); + assert((this.index >>> 0) === this.index); + assert((this.offset >>> 0) === this.offset); + assert((this.length >>> 0) === this.length); + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + const br = bio.read(data); + + this.height = br.readU32(); + this.index = br.readU32(); + this.offset = br.readU32(); + this.length = br.readU32(); + + return this; + } + + /** + * Instantiate transaction record from serialized data. + * @param {Hash} hash + * @param {Buffer} data + * @returns {BlockRecord} + */ + + static fromRaw(data) { + return new this().fromRaw(data); + } + + /** + * Serialize the transaction record. + * @returns {Buffer} + */ + + toRaw() { + const bw = bio.write(16); + + bw.writeU32(this.height); + bw.writeU32(this.index); + bw.writeU32(this.offset); + bw.writeU32(this.length); + + return bw.render(); + } +} + +/** + * TXIndexer + * @alias module:indexer.TXIndexer + * @extends Indexer + */ + +class TXIndexer extends Indexer { + /** + * Create a indexer + * @constructor + * @param {Object} options + */ + + constructor(options) { + super('tx', options); + + this.db = bdb.create(this.options); + } + + /** + * Index transactions by txid. + * @private + * @param {BlockMeta} meta + * @param {Block} block + * @param {CoinView} view + */ + + async indexBlock(meta, block, view) { + assert(block.hasRaw(), 'Expected raw data for block.'); + const brecord = new BlockRecord({ + block: meta.hash, + time: block.time + }); + + this.put(layout.b.encode(meta.height), brecord.toRaw()); + + for (let i = 0; i < block.txs.length; i++) { + const tx = block.txs[i]; + + const hash = tx.hash(); + const {offset, size} = tx.getPosition(); + + const txrecord = new TxRecord({ + height: meta.height, + index: i, + offset: offset, + length: size + }); + + this.put(layout.t.encode(hash), txrecord.toRaw()); + } + } + + /** + * Remove transactions from index. + * @private + * @param {BlockMeta} meta + * @param {Block} block + * @param {CoinView} view + */ + + async unindexBlock(meta, block, view) { + this.del(layout.b.encode(meta.height)); + + for (let i = 0; i < block.txs.length; i++) { + const tx = block.txs[i]; + const hash = tx.hash(); + this.del(layout.t.encode(hash)); + } + } + + /** + * Get a transaction with metadata. + * @param {Hash} hash + * @returns {Promise} - Returns {@link TXMeta}. + */ + + async getMeta(hash) { + const raw = await this.db.get(layout.t.encode(hash)); + if (!raw) + return null; + + const record = TxRecord.fromRaw(raw); + const {height, index, offset, length} = record; + + const braw = await this.db.get(layout.b.encode(height)); + if (!braw) + return null; + + const brecord = BlockRecord.fromRaw(braw); + const {block, time} = brecord; + + const data = await this.blocks.read(block, offset, length); + + const tx = TX.fromRaw(data); + + const meta = TXMeta.fromTX(tx); + meta.height = height; + meta.block = block; + meta.time = time; + meta.index = index; + + return meta; + } + + /** + * Retrieve a transaction. + * @param {Hash} hash + * @returns {Promise} - Returns {@link TX}. + */ + + async getTX(hash) { + const meta = await this.getMeta(hash); + + if (!meta) + return null; + + return meta.tx; + } + + /** + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ + + async hasTX(hash) { + return this.db.has(layout.t.encode(hash)); + } + + /** + * Get coin viewpoint (historical). + * @param {TX} tx + * @returns {Promise} - Returns {@link CoinView}. + */ + + async getSpentView(tx) { + const view = await this.chain.getCoinView(tx); + + for (const {prevout} of tx.inputs) { + if (view.hasEntry(prevout)) + continue; + + const {hash, index} = prevout; + const meta = await this.getMeta(hash); + + if (!meta) + continue; + + const {tx, height} = meta; + + if (index < tx.outputs.length) + view.addIndex(tx, index, height); + } + + return view; + } +} + +module.exports = TXIndexer; diff --git a/lib/mempool/addrindexer.js b/lib/mempool/addrindexer.js new file mode 100644 index 000000000..311dbd3e9 --- /dev/null +++ b/lib/mempool/addrindexer.js @@ -0,0 +1,243 @@ +/*! + * mempool.js - mempool for bcoin + * Copyright (c) 2018-2019, the bcoin developers (MIT License). + * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const assert = require('bsert'); +const {BufferMap} = require('buffer-map'); +const TXMeta = require('../primitives/txmeta'); + +/** + * Address Indexer + * @ignore + */ + +class AddrIndexer { + /** + * Create TX address index. + * @constructor + * @param {Network} network + */ + + constructor(network) { + this.network = network; + + // Map of addr->entries. + this.index = new BufferMap(); + + // Map of txid->addrs. + this.map = new BufferMap(); + } + + reset() { + this.index.clear(); + this.map.clear(); + } + + getKey(addr) { + const prefix = addr.getPrefix(this.network); + + if (prefix < 0) + return null; + + const hash = addr.getHash(); + const size = hash.length + 1; + const raw = Buffer.allocUnsafe(size); + + let written = raw.writeUInt8(prefix); + written += hash.copy(raw, 1); + assert(written === size); + + return raw; + } + + /** + * Get transactions by address. + * @param {Address} addr + * @param {Object} options + * @param {Number} options.limit + * @param {Number} options.reverse + * @param {Buffer} options.after + */ + + get(addr, options = {}) { + const values = this.getEntries(addr, options); + + const out = []; + + for (const entry of values) + out.push(entry.tx); + + return out; + } + + /** + * Get transaction meta by address. + * @param {Address} addr + * @param {Object} options + * @param {Number} options.limit + * @param {Number} options.reverse + * @param {Buffer} options.after + */ + + getMeta(addr, options = {}) { + const values = this.getEntries(addr, options); + + const out = []; + + for (const entry of values) { + const meta = TXMeta.fromTX(entry.tx); + meta.mtime = entry.time; + out.push(meta); + } + + return out; + } + + /** + * Get entries by address. + * @param {Address} addr + * @param {Object} options + * @param {Number} options.limit + * @param {Number} options.reverse + * @param {Buffer} options.after + */ + + getEntries(addr, options = {}) { + const {limit, reverse, after} = options; + const key = this.getKey(addr); + + if (!key) + return []; + + const items = this.index.get(key); + + if (!items) + return []; + + let values = []; + + // Check to see if results should be skipped because + // the after hash is expected to be within a following + // confirmed query. + const skip = (after && !items.has(after) && reverse); + + if (skip) + return values; + + if (after && items.has(after)) { + // Give results starting from after + // the tx hash for the address. + let index = 0; + + for (const k of items.keys()) { + if (k.compare(after) === 0) + break; + index += 1; + } + + values = Array.from(items.values()); + + let start = index + 1; + let end = values.length; + + if (end - start > limit) + end = start + limit; + + if (reverse) { + start = 0; + end = index; + + if (end > limit) + start = end - limit; + } + + values = values.slice(start, end); + } else { + // Give earliest or latest results + // for the address. + values = Array.from(items.values()); + + if (values.length > limit) { + let start = 0; + let end = limit; + + if (reverse) { + start = values.length - limit; + end = values.length; + } + + values = values.slice(start, end); + } + } + + if (reverse) + values.reverse(); + + return values; + } + + insert(entry, view) { + const tx = entry.tx; + const hash = tx.hash(); + const addrs = tx.getAddresses(view); + + if (addrs.length === 0) + return; + + for (const addr of addrs) { + const key = this.getKey(addr); + + if (!key) + continue; + + let items = this.index.get(key); + + if (!items) { + items = new BufferMap(); + this.index.set(key, items); + } + + assert(!items.has(hash)); + items.set(hash, entry); + } + + this.map.set(hash, addrs); + } + + remove(hash) { + const addrs = this.map.get(hash); + + if (!addrs) + return; + + for (const addr of addrs) { + const key = this.getKey(addr); + + if (!key) + continue; + + const items = this.index.get(key); + + assert(items); + assert(items.has(hash)); + + items.delete(hash); + + if (items.size === 0) + this.index.delete(key); + } + + this.map.delete(hash); + } +} + +/* + * Expose + */ + +module.exports = AddrIndexer; diff --git a/lib/mempool/fees.js b/lib/mempool/fees.js index 2519f4cef..03385b18c 100644 --- a/lib/mempool/fees.js +++ b/lib/mempool/fees.js @@ -8,14 +8,14 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); +const assert = require('bsert'); +const bio = require('bufio'); +const Logger = require('blgr'); +const {BufferMap} = require('buffer-map'); +const binary = require('../utils/binary'); const consensus = require('../protocol/consensus'); const policy = require('../protocol/policy'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); -const encoding = require('../utils/encoding'); -const Logger = require('../node/logger'); +const {encoding} = bio; /* * Constants @@ -30,7 +30,7 @@ const SUFFICIENT_PRITXS = 0.2; const MIN_FEERATE = 10; const MAX_FEERATE = 1e6; /* 1e7 */ const INF_FEERATE = consensus.MAX_MONEY; -const MIN_PRIORITY = 10; +const MIN_PRIORITY = 1; const MAX_PRIORITY = 1e16; const INF_PRIORITY = 1e9 * consensus.MAX_MONEY; const FEE_SPACING = 1.1; @@ -39,666 +39,679 @@ const PRI_SPACING = 2; /** * Confirmation stats. * @alias module:mempool.ConfirmStats - * @constructor - * @param {String} type - * @param {Logger?} logger */ -function ConfirmStats(type, logger) { - if (!(this instanceof ConfirmStats)) - return new ConfirmStats(type, logger); +class ConfirmStats { + /** + * Create confirmation stats. + * @constructor + * @param {String} type + * @param {Logger?} logger + */ - this.logger = Logger.global; + constructor(type, logger) { + this.logger = Logger.global; - this.type = type; - this.decay = 0; - this.maxConfirms = 0; + this.type = type; + this.decay = 0; + this.maxConfirms = 0; - this.buckets = new Float64Array(0); - this.bucketMap = new DoubleMap(); + this.buckets = new Float64Array(0); + this.bucketMap = new DoubleMap(); - this.confAvg = []; - this.curBlockConf = []; - this.unconfTX = []; + this.confAvg = []; + this.curBlockConf = []; + this.unconfTX = []; - this.oldUnconfTX = new Int32Array(0); - this.curBlockTX = new Int32Array(0); - this.txAvg = new Float64Array(0); - this.curBlockVal = new Float64Array(0); - this.avg = new Float64Array(0); + this.oldUnconfTX = new Int32Array(0); + this.curBlockTX = new Int32Array(0); + this.txAvg = new Float64Array(0); + this.curBlockVal = new Float64Array(0); + this.avg = new Float64Array(0); - if (logger) { - assert(typeof logger === 'object'); - this.logger = logger.context('fees'); + if (logger) { + assert(typeof logger === 'object'); + this.logger = logger.context('fees'); + } } -} - -/** - * Initialize stats. - * @param {Array} buckets - * @param {Number} maxConfirms - * @param {Number} decay - * @private - */ -ConfirmStats.prototype.init = function init(buckets, maxConfirms, decay) { - this.maxConfirms = maxConfirms; - this.decay = decay; + /** + * Initialize stats. + * @param {Array} buckets + * @param {Number} maxConfirms + * @param {Number} decay + * @private + */ - this.buckets = new Float64Array(buckets.length); - this.bucketMap = new DoubleMap(); + init(buckets, maxConfirms, decay) { + this.maxConfirms = maxConfirms; + this.decay = decay; - for (let i = 0; i < buckets.length; i++) { - this.buckets[i] = buckets[i]; - this.bucketMap.insert(buckets[i], i); - } - - this.confAvg = new Array(maxConfirms); - this.curBlockConf = new Array(maxConfirms); - this.unconfTX = new Array(maxConfirms); + this.buckets = new Float64Array(buckets.length); + this.bucketMap = new DoubleMap(); - for (let i = 0; i < maxConfirms; i++) { - this.confAvg[i] = new Float64Array(buckets.length); - this.curBlockConf[i] = new Int32Array(buckets.length); - this.unconfTX[i] = new Int32Array(buckets.length); - } + for (let i = 0; i < buckets.length; i++) { + this.buckets[i] = buckets[i]; + this.bucketMap.insert(buckets[i], i); + } - this.oldUnconfTX = new Int32Array(buckets.length); - this.curBlockTX = new Int32Array(buckets.length); - this.txAvg = new Float64Array(buckets.length); - this.curBlockVal = new Float64Array(buckets.length); - this.avg = new Float64Array(buckets.length); -}; + this.confAvg = new Array(maxConfirms); + this.curBlockConf = new Array(maxConfirms); + this.unconfTX = new Array(maxConfirms); -/** - * Clear data for the current block. - * @param {Number} height - */ + for (let i = 0; i < maxConfirms; i++) { + this.confAvg[i] = new Float64Array(buckets.length); + this.curBlockConf[i] = new Int32Array(buckets.length); + this.unconfTX[i] = new Int32Array(buckets.length); + } -ConfirmStats.prototype.clearCurrent = function clearCurrent(height) { - for (let i = 0; i < this.buckets.length; i++) { - this.oldUnconfTX[i] = this.unconfTX[height % this.unconfTX.length][i]; - this.unconfTX[height % this.unconfTX.length][i] = 0; - for (let j = 0; j < this.curBlockConf.length; j++) - this.curBlockConf[j][i] = 0; - this.curBlockTX[i] = 0; - this.curBlockVal[i] = 0; + this.oldUnconfTX = new Int32Array(buckets.length); + this.curBlockTX = new Int32Array(buckets.length); + this.txAvg = new Float64Array(buckets.length); + this.curBlockVal = new Float64Array(buckets.length); + this.avg = new Float64Array(buckets.length); } -}; - -/** - * Record a rate or priority based on number of blocks to confirm. - * @param {Number} blocks - Blocks to confirm. - * @param {Rate|Number} val - Rate or priority. - */ -ConfirmStats.prototype.record = function record(blocks, val) { - if (blocks < 1) - return; + /** + * Clear data for the current block. + * @param {Number} height + */ + + clearCurrent(height) { + for (let i = 0; i < this.buckets.length; i++) { + this.oldUnconfTX[i] = this.unconfTX[height % this.unconfTX.length][i]; + this.unconfTX[height % this.unconfTX.length][i] = 0; + for (let j = 0; j < this.curBlockConf.length; j++) + this.curBlockConf[j][i] = 0; + this.curBlockTX[i] = 0; + this.curBlockVal[i] = 0; + } + } - const bucketIndex = this.bucketMap.search(val); + /** + * Record a rate or priority based on number of blocks to confirm. + * @param {Number} blocks - Blocks to confirm. + * @param {Rate|Number} val - Rate or priority. + */ - for (let i = blocks; i <= this.curBlockConf.length; i++) - this.curBlockConf[i - 1][bucketIndex]++; + record(blocks, val) { + if (blocks < 1) + return; - this.curBlockTX[bucketIndex]++; - this.curBlockVal[bucketIndex] += val; -}; + const bucketIndex = this.bucketMap.search(val); -/** - * Update moving averages. - */ + for (let i = blocks; i <= this.curBlockConf.length; i++) + this.curBlockConf[i - 1][bucketIndex]++; -ConfirmStats.prototype.updateAverages = function updateAverages() { - for (let i = 0; i < this.buckets.length; i++) { - for (let j = 0; j < this.confAvg.length; j++) { - this.confAvg[j][i] = - this.confAvg[j][i] * this.decay + this.curBlockConf[j][i]; - } - this.avg[i] = this.avg[i] * this.decay + this.curBlockVal[i]; - this.txAvg[i] = this.txAvg[i] * this.decay + this.curBlockTX[i]; + this.curBlockTX[bucketIndex]++; + this.curBlockVal[bucketIndex] += val; } -}; -/** - * Estimate the median value for rate or priority. - * @param {Number} target - Confirmation target. - * @param {Number} needed - Sufficient tx value. - * @param {Number} breakpoint - Success break point. - * @param {Boolean} greater - Whether to look for lowest value. - * @param {Number} height - Block height. - * @returns {Rate|Number} Returns -1 on error. - */ + /** + * Update moving averages. + */ -ConfirmStats.prototype.estimateMedian = function estimateMedian(target, needed, breakpoint, greater, height) { - const max = this.buckets.length - 1; - const start = greater ? max : 0; - const step = greater ? -1 : 1; - const bins = this.unconfTX.length; - let conf = 0; - let total = 0; - let extra = 0; - let near = start; - let far = start; - let bestNear = start; - let bestFar = start; - let found = false; - let median = -1; - let sum = 0; - - for (let i = start; i >= 0 && i <= max; i += step) { - far = i; - conf += this.confAvg[target - 1][i]; - total += this.txAvg[i]; - - for (let j = target; j < this.maxConfirms; j++) - extra += this.unconfTX[Math.max(height - j, 0) % bins][i]; - - extra += this.oldUnconfTX[i]; - - if (total >= needed / (1 - this.decay)) { - const perc = conf / (total + extra); - - if (greater && perc < breakpoint) - break; - - if (!greater && perc > breakpoint) - break; - - found = true; - conf = 0; - total = 0; - extra = 0; - bestNear = near; - bestFar = far; - near = i + step; + updateAverages() { + for (let i = 0; i < this.buckets.length; i++) { + for (let j = 0; j < this.confAvg.length; j++) { + this.confAvg[j][i] = + this.confAvg[j][i] * this.decay + this.curBlockConf[j][i]; + } + this.avg[i] = this.avg[i] * this.decay + this.curBlockVal[i]; + this.txAvg[i] = this.txAvg[i] * this.decay + this.curBlockTX[i]; } } - const minBucket = bestNear < bestFar ? bestNear : bestFar; - const maxBucket = bestNear > bestFar ? bestNear : bestFar; - - for (let i = minBucket; i <= maxBucket; i++) - sum += this.txAvg[i]; - - if (found && sum !== 0) { - sum = sum / 2; - for (let j = minBucket; j <= maxBucket; j++) { - if (this.txAvg[j] < sum) { - sum -= this.txAvg[j]; - } else { - median = this.avg[j] / this.txAvg[j]; - break; + /** + * Estimate the median value for rate or priority. + * @param {Number} target - Confirmation target. + * @param {Number} needed - Sufficient tx value. + * @param {Number} breakpoint - Success break point. + * @param {Boolean} greater - Whether to look for lowest value. + * @param {Number} height - Block height. + * @returns {Rate|Number} Returns -1 on error. + */ + + estimateMedian(target, needed, breakpoint, greater, height) { + const max = this.buckets.length - 1; + const start = greater ? max : 0; + const step = greater ? -1 : 1; + const bins = this.unconfTX.length; + let conf = 0; + let total = 0; + let extra = 0; + let near = start; + let far = start; + let bestNear = start; + let bestFar = start; + let found = false; + let median = -1; + let sum = 0; + + for (let i = start; i >= 0 && i <= max; i += step) { + far = i; + conf += this.confAvg[target - 1][i]; + total += this.txAvg[i]; + + for (let j = target; j < this.maxConfirms; j++) + extra += this.unconfTX[Math.max(height - j, 0) % bins][i]; + + extra += this.oldUnconfTX[i]; + + if (total >= needed / (1 - this.decay)) { + const perc = conf / (total + extra); + + if (greater && perc < breakpoint) + break; + + if (!greater && perc > breakpoint) + break; + + found = true; + conf = 0; + total = 0; + extra = 0; + bestNear = near; + bestFar = far; + near = i + step; } } - } - - return median; -}; -/** - * Add a transaction's rate/priority to be tracked. - * @param {Number} height - Block height. - * @param {Number} val - * @returns {Number} Bucket index. - */ + const minBucket = bestNear < bestFar ? bestNear : bestFar; + const maxBucket = bestNear > bestFar ? bestNear : bestFar; + + for (let i = minBucket; i <= maxBucket; i++) + sum += this.txAvg[i]; + + if (found && sum !== 0) { + sum = sum / 2; + for (let j = minBucket; j <= maxBucket; j++) { + if (this.txAvg[j] < sum) { + sum -= this.txAvg[j]; + } else { + median = this.avg[j] / this.txAvg[j]; + break; + } + } + } -ConfirmStats.prototype.addTX = function addTX(height, val) { - const bucketIndex = this.bucketMap.search(val); - const blockIndex = height % this.unconfTX.length; - this.unconfTX[blockIndex][bucketIndex]++; - this.logger.spam('Adding tx to %s.', this.type); - return bucketIndex; -}; + return median; + } -/** - * Remove a transaction from tracking. - * @param {Number} entryHeight - * @param {Number} bestHeight - * @param {Number} bucketIndex - */ + /** + * Add a transaction's rate/priority to be tracked. + * @param {Number} height - Block height. + * @param {Number} val + * @returns {Number} Bucket index. + */ + + addTX(height, val) { + const bucketIndex = this.bucketMap.search(val); + const blockIndex = height % this.unconfTX.length; + this.unconfTX[blockIndex][bucketIndex]++; + this.logger.spam('Adding tx to %s.', this.type); + return bucketIndex; + } -ConfirmStats.prototype.removeTX = function removeTX(entryHeight, bestHeight, bucketIndex) { - let blocksAgo = bestHeight - entryHeight; + /** + * Remove a transaction from tracking. + * @param {Number} entryHeight + * @param {Number} bestHeight + * @param {Number} bucketIndex + */ - if (bestHeight === 0) - blocksAgo = 0; + removeTX(entryHeight, bestHeight, bucketIndex) { + let blocksAgo = bestHeight - entryHeight; - if (blocksAgo < 0) { - this.logger.debug('Blocks ago is negative for mempool tx.'); - return; - } + if (bestHeight === 0) + blocksAgo = 0; - if (blocksAgo >= this.unconfTX.length) { - if (this.oldUnconfTX[bucketIndex] > 0) { - this.oldUnconfTX[bucketIndex]--; - } else { - this.logger.debug('Mempool tx removed >25 blocks (bucket=%d).', - bucketIndex); + if (blocksAgo < 0) { + this.logger.debug('Blocks ago is negative for mempool tx.'); + return; } - } else { - const blockIndex = entryHeight % this.unconfTX.length; - if (this.unconfTX[blockIndex][bucketIndex] > 0) { - this.unconfTX[blockIndex][bucketIndex]--; + + if (blocksAgo >= this.unconfTX.length) { + if (this.oldUnconfTX[bucketIndex] > 0) { + this.oldUnconfTX[bucketIndex]--; + } else { + this.logger.debug('Mempool tx removed >25 blocks (bucket=%d).', + bucketIndex); + } } else { - this.logger.debug('Mempool tx removed (block=%d, bucket=%d).', - blockIndex, bucketIndex); + const blockIndex = entryHeight % this.unconfTX.length; + if (this.unconfTX[blockIndex][bucketIndex] > 0) { + this.unconfTX[blockIndex][bucketIndex]--; + } else { + this.logger.debug('Mempool tx removed (block=%d, bucket=%d).', + blockIndex, bucketIndex); + } } } -}; -/** - * Get serialization size. - * @returns {Number} - */ + /** + * Get serialization size. + * @returns {Number} + */ -ConfirmStats.prototype.getSize = function getSize() { - let size = 0; + getSize() { + let size = 0; - size += 8; + size += 8; - size += sizeArray(this.buckets); - size += sizeArray(this.avg); - size += sizeArray(this.txAvg); + size += sizeArray(this.buckets); + size += sizeArray(this.avg); + size += sizeArray(this.txAvg); - size += encoding.sizeVarint(this.maxConfirms); + size += encoding.sizeVarint(this.maxConfirms); - for (let i = 0; i < this.maxConfirms; i++) - size += sizeArray(this.confAvg[i]); + for (let i = 0; i < this.maxConfirms; i++) + size += sizeArray(this.confAvg[i]); - return size; -}; + return size; + } -/** - * Serialize confirm stats. - * @returns {Buffer} - */ + /** + * Serialize confirm stats. + * @returns {Buffer} + */ -ConfirmStats.prototype.toRaw = function toRaw() { - const size = this.getSize(); - const bw = new StaticWriter(size); + toRaw() { + const size = this.getSize(); + const bw = bio.write(size); - bw.writeDouble(this.decay); - writeArray(bw, this.buckets); - writeArray(bw, this.avg); - writeArray(bw, this.txAvg); - bw.writeVarint(this.maxConfirms); + bw.writeDouble(this.decay); + writeArray(bw, this.buckets); + writeArray(bw, this.avg); + writeArray(bw, this.txAvg); + bw.writeVarint(this.maxConfirms); - for (let i = 0; i < this.maxConfirms; i++) - writeArray(bw, this.confAvg[i]); + for (let i = 0; i < this.maxConfirms; i++) + writeArray(bw, this.confAvg[i]); - return bw.render(); -}; + return bw.render(); + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @returns {ConfirmStats} - */ + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + * @returns {ConfirmStats} + */ -ConfirmStats.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); - const decay = br.readDouble(); - const buckets = readArray(br); - const avg = readArray(br); - const txAvg = readArray(br); - const maxConfirms = br.readVarint(); - const confAvg = new Array(maxConfirms); + fromRaw(data) { + const br = bio.read(data); + const decay = br.readDouble(); + const buckets = readArray(br); + const avg = readArray(br); + const txAvg = readArray(br); + const maxConfirms = br.readVarint(); + const confAvg = new Array(maxConfirms); - for (let i = 0; i < maxConfirms; i++) - confAvg[i] = readArray(br); + for (let i = 0; i < maxConfirms; i++) + confAvg[i] = readArray(br); - if (decay <= 0 || decay >= 1) - throw new Error('Decay must be between 0 and 1 (non-inclusive).'); + if (decay <= 0 || decay >= 1) + throw new Error('Decay must be between 0 and 1 (non-inclusive).'); - if (buckets.length <= 1 || buckets.length > 1000) - throw new Error('Must have between 2 and 1000 fee/pri buckets.'); + if (buckets.length <= 1 || buckets.length > 1000) + throw new Error('Must have between 2 and 1000 fee/pri buckets.'); - if (avg.length !== buckets.length) - throw new Error('Mismatch in fee/pri average bucket count.'); + if (avg.length !== buckets.length) + throw new Error('Mismatch in fee/pri average bucket count.'); - if (txAvg.length !== buckets.length) - throw new Error('Mismatch in tx count bucket count.'); + if (txAvg.length !== buckets.length) + throw new Error('Mismatch in tx count bucket count.'); - if (maxConfirms <= 0 || maxConfirms > 6 * 24 * 7) - throw new Error('Must maintain estimates for between 1 and 1008 confirms.'); + if (maxConfirms <= 0 || maxConfirms > 6 * 24 * 7) + throw new Error('Must maintain estimates for between 1-1008 confirms.'); - for (let i = 0; i < maxConfirms; i++) { - if (confAvg[i].length !== buckets.length) - throw new Error('Mismatch in fee/pri conf average bucket count.'); - } + for (let i = 0; i < maxConfirms; i++) { + if (confAvg[i].length !== buckets.length) + throw new Error('Mismatch in fee/pri conf average bucket count.'); + } - this.init(buckets, maxConfirms, decay); + this.init(buckets, maxConfirms, decay); - this.avg = avg; - this.txAvg = txAvg; - this.confAvg = confAvg; + this.avg = avg; + this.txAvg = txAvg; + this.confAvg = confAvg; - return this; -}; + return this; + } -/** - * Instantiate confirm stats from serialized data. - * @param {Buffer} data - * @param {String} type - * @param {Logger?} logger - * @returns {ConfirmStats} - */ + /** + * Instantiate confirm stats from serialized data. + * @param {Buffer} data + * @param {String} type + * @param {Logger?} logger + * @returns {ConfirmStats} + */ -ConfirmStats.fromRaw = function fromRaw(data, type, logger) { - return new ConfirmStats(type, logger).fromRaw(data); -}; + static fromRaw(data, type, logger) { + return new this(type, logger).fromRaw(data); + } +} /** + * Policy Estimator * Estimator for fees and priority. * @alias module:mempool.PolicyEstimator - * @constructor - * @param {Logger?} logger */ -function PolicyEstimator(logger) { - if (!(this instanceof PolicyEstimator)) - return new PolicyEstimator(logger); +class PolicyEstimator { + /** + * Create an estimator. + * @constructor + * @param {Logger?} logger + */ - this.logger = Logger.global; + constructor(logger) { + this.logger = Logger.global; - this.minTrackedFee = MIN_FEERATE; - this.minTrackedPri = MIN_PRIORITY; + this.minTrackedFee = MIN_FEERATE; + this.minTrackedPri = MIN_PRIORITY; - this.feeStats = new ConfirmStats('FeeRate'); - this.priStats = new ConfirmStats('Priority'); + this.feeStats = new ConfirmStats('FeeRate'); + this.priStats = new ConfirmStats('Priority'); - this.feeUnlikely = 0; - this.feeLikely = INF_FEERATE; - this.priUnlikely = 0; - this.priLikely = INF_PRIORITY; + this.feeUnlikely = 0; + this.feeLikely = INF_FEERATE; + this.priUnlikely = 0; + this.priLikely = INF_PRIORITY; - this.map = new Map(); - this.bestHeight = 0; + this.map = new BufferMap(); + this.bestHeight = 0; - if (policy.MIN_RELAY >= MIN_FEERATE) - this.minTrackedFee = policy.MIN_RELAY; + if (policy.MIN_RELAY >= MIN_FEERATE) + this.minTrackedFee = policy.MIN_RELAY; - if (policy.FREE_THRESHOLD >= MIN_PRIORITY) - this.minTrackedPri = policy.FREE_THRESHOLD; + if (policy.FREE_THRESHOLD >= MIN_PRIORITY) + this.minTrackedPri = policy.FREE_THRESHOLD; - if (logger) { - assert(typeof logger === 'object'); - this.logger = logger.context('fees'); - this.feeStats.logger = this.logger; - this.priStats.logger = this.logger; + if (logger) { + assert(typeof logger === 'object'); + this.logger = logger.context('fees'); + this.feeStats.logger = this.logger; + this.priStats.logger = this.logger; + } } -} -/** - * Serialization version. - * @const {Number} - * @default - */ + /** + * Initialize the estimator. + * @private + */ -PolicyEstimator.VERSION = 0; + init() { + const minFee = this.minTrackedFee; + const minPri = this.minTrackedPri; -/** - * Initialize the estimator. - * @private - */ + const fee = []; -PolicyEstimator.prototype.init = function init() { - const minFee = this.minTrackedFee; - const minPri = this.minTrackedPri; + for (let b = minFee; b <= MAX_FEERATE; b *= FEE_SPACING) + fee.push(b); - const fee = []; + fee.push(INF_FEERATE); - for (let b = minFee; b <= MAX_FEERATE; b *= FEE_SPACING) - fee.push(b); + const priority = []; - fee.push(INF_FEERATE); + for (let b = minPri; b <= MAX_PRIORITY; b *= PRI_SPACING) + priority.push(b); - const priority = []; + priority.push(INF_PRIORITY); - for (let b = minPri; b <= MAX_PRIORITY; b *= PRI_SPACING) - priority.push(b); + this.feeStats.init(fee, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY); + this.priStats.init(priority, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY); + } - priority.push(INF_PRIORITY); + /** + * Reset the estimator. + */ - this.feeStats.init(fee, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY); - this.priStats.init(priority, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY); -}; + reset() { + this.feeUnlikely = 0; + this.feeLikely = INF_FEERATE; + this.priUnlikely = 0; + this.priLikely = INF_PRIORITY; -/** - * Reset the estimator. - */ + this.map.clear(); + this.bestHeight = 0; -PolicyEstimator.prototype.reset = function reset() { - this.feeUnlikely = 0; - this.feeLikely = INF_FEERATE; - this.priUnlikely = 0; - this.priLikely = INF_PRIORITY; + this.init(); + } - this.map.clear(); - this.bestHeight = 0; + /** + * Stop tracking a tx. Remove from map. + * @param {Hash} hash + */ - this.init(); -}; + removeTX(hash) { + const item = this.map.get(hash); -/** - * Stop tracking a tx. Remove from map. - * @param {Hash} hash - */ + if (!item) { + this.logger.spam('Mempool tx %h not found.', hash); + return; + } -PolicyEstimator.prototype.removeTX = function removeTX(hash) { - const item = this.map.get(hash); + this.feeStats.removeTX(item.blockHeight, this.bestHeight, item.bucketIndex); - if (!item) { - this.logger.spam('Mempool tx %s not found.', util.revHex(hash)); - return; + this.map.delete(hash); } - this.feeStats.removeTX(item.blockHeight, this.bestHeight, item.bucketIndex); - - this.map.delete(hash); -}; - -/** - * Test whether a fee should be used for calculation. - * @param {Amount} fee - * @param {Number} priority - * @returns {Boolean} - */ - -PolicyEstimator.prototype.isFeePoint = function isFeePoint(fee, priority) { - if ((priority < this.minTrackedPri && fee >= this.minTrackedFee) - || (priority < this.priUnlikely && fee > this.feeLikely)) { - return true; + /** + * Test whether a fee should be used for calculation. + * @param {Amount} fee + * @param {Number} priority + * @returns {Boolean} + */ + + isFeePoint(fee, priority) { + if ((priority < this.minTrackedPri && fee >= this.minTrackedFee) + || (priority < this.priUnlikely && fee > this.feeLikely)) { + return true; + } + return false; } - return false; -}; -/** - * Test whether a priority should be used for calculation. - * @param {Amount} fee - * @param {Number} priority - * @returns {Boolean} - */ - -PolicyEstimator.prototype.isPriPoint = function isPriPoint(fee, priority) { - if ((fee < this.minTrackedFee && priority >= this.minTrackedPri) - || (fee < this.feeUnlikely && priority > this.priLikely)) { - return true; + /** + * Test whether a priority should be used for calculation. + * @param {Amount} fee + * @param {Number} priority + * @returns {Boolean} + */ + + isPriPoint(fee, priority) { + if ((fee < this.minTrackedFee && priority >= this.minTrackedPri) + || (fee < this.feeUnlikely && priority > this.priLikely)) { + return true; + } + return false; } - return false; -}; - -/** - * Process a mempool entry. - * @param {MempoolEntry} entry - * @param {Boolean} current - Whether the chain is synced. - */ -PolicyEstimator.prototype.processTX = function processTX(entry, current) { - const height = entry.height; - const hash = entry.hash('hex'); + /** + * Process a mempool entry. + * @param {MempoolEntry} entry + * @param {Boolean} current - Whether the chain is synced. + */ - if (this.map.has(hash)) { - this.logger.debug('Mempool tx %s already tracked.', entry.txid()); - return; - } + processTX(entry, current) { + const height = entry.height; + const hash = entry.hash(); - // Ignore reorgs. - if (height < this.bestHeight) - return; - - // Wait for chain to sync. - if (!current) - return; + if (this.map.has(hash)) { + this.logger.debug('Mempool tx %h already tracked.', entry.hash()); + return; + } - // Requires other mempool txs in order to be confirmed. Ignore. - if (entry.dependencies) - return; + // Ignore reorgs. + if (height < this.bestHeight) + return; + + // Wait for chain to sync. + if (!current) + return; + + // Requires other mempool txs in order to be confirmed. Ignore. + if (entry.dependencies) + return; + + const fee = entry.getFee(); + const rate = entry.getRate(); + const priority = entry.getPriority(height); + + this.logger.spam('Processing mempool tx %h.', entry.hash()); + + if (fee === 0 || this.isPriPoint(rate, priority)) { + const item = new StatEntry(); + item.blockHeight = height; + item.bucketIndex = this.priStats.addTX(height, priority); + this.map.set(hash, item); + } else if (rate >= this.minTrackedFee || this.isFeePoint(rate, priority)) { + const item = new StatEntry(); + item.blockHeight = height; + item.bucketIndex = this.feeStats.addTX(height, rate); + this.map.set(hash, item); + } else { + this.logger.spam('Not adding tx %h.', entry.hash()); + } + } - const fee = entry.getFee(); - const rate = entry.getRate(); - const priority = entry.getPriority(height); + /** + * Process an entry being removed from the mempool. + * @param {Number} height - Block height. + * @param {MempoolEntry} entry + */ + + processBlockTX(height, entry) { + // Requires other mempool txs in order to be confirmed. Ignore. + if (entry.dependencies) + return; + + const blocks = height - entry.height; + + if (blocks <= 0) { + this.logger.debug( + 'Block tx %h had negative blocks to confirm (%d, %d).', + entry.hash(), + height, + entry.height); + return; + } - this.logger.spam('Processing mempool tx %s.', entry.txid()); + const fee = entry.getFee(); + const rate = entry.getRate(); + const priority = entry.getPriority(height); - if (fee === 0 || this.isPriPoint(rate, priority)) { - const item = new StatEntry(); - item.blockHeight = height; - item.bucketIndex = this.priStats.addTX(height, priority); - this.map.set(hash, item); - } else if (this.isFeePoint(rate, priority)) { - const item = new StatEntry(); - item.blockHeight = height; - item.bucketIndex = this.feeStats.addTX(height, rate); - this.map.set(hash, item); - } else { - this.logger.spam('Not adding tx %s.', entry.txid()); + if (fee === 0 || this.isPriPoint(rate, priority)) + this.priStats.record(blocks, priority); + else if (this.isFeePoint(rate, priority)) + this.feeStats.record(blocks, rate); } -}; - -/** - * Process an entry being removed from the mempool. - * @param {Number} height - Block height. - * @param {MempoolEntry} entry - */ -PolicyEstimator.prototype.processBlockTX = function processBlockTX(height, entry) { - // Requires other mempool txs in order to be confirmed. Ignore. - if (entry.dependencies) - return; + /** + * Process a block of transaction entries being removed from the mempool. + * @param {Number} height - Block height. + * @param {MempoolEntry[]} entries + * @param {Boolean} current - Whether the chain is synced. + */ - const blocks = height - entry.height; + processBlock(height, entries, current) { + // Ignore reorgs. + if (height <= this.bestHeight) + return; - if (blocks <= 0) { - this.logger.debug( - 'Block tx %s had negative blocks to confirm (%d, %d).', - entry.txid(), - height, - entry.height); - return; - } + this.bestHeight = height; - const fee = entry.getFee(); - const rate = entry.getRate(); - const priority = entry.getPriority(height); + if (entries.length === 0) + return; - if (fee === 0 || this.isPriPoint(rate, priority)) - this.priStats.record(blocks, priority); - else if (this.isFeePoint(rate, priority)) - this.feeStats.record(blocks, rate); -}; - -/** - * Process a block of transaction entries being removed from the mempool. - * @param {Number} height - Block height. - * @param {MempoolEntry[]} entries - * @param {Boolean} current - Whether the chain is synced. - */ + // Wait for chain to sync. + if (!current) + return; -PolicyEstimator.prototype.processBlock = function processBlock(height, entries, current) { - // Ignore reorgs. - if (height <= this.bestHeight) - return; + this.logger.debug('Recalculating dynamic cutoffs.'); - this.bestHeight = height; + this.feeLikely = this.feeStats.estimateMedian( + 2, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, + true, height); - if (entries.length === 0) - return; + if (this.feeLikely === -1) + this.feeLikely = INF_FEERATE; - // Wait for chain to sync. - if (!current) - return; + this.feeUnlikely = this.feeStats.estimateMedian( + 10, SUFFICIENT_FEETXS, UNLIKELY_PCT, + false, height); - this.logger.debug('Recalculating dynamic cutoffs.'); + if (this.feeUnlikely === -1) + this.feeUnlikely = 0; - this.feeLikely = this.feeStats.estimateMedian( - 2, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, - true, height); + this.priLikely = this.priStats.estimateMedian( + 2, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT, + true, height); - if (this.feeLikely === -1) - this.feeLikely = INF_FEERATE; + if (this.priLikely === -1) + this.priLikely = INF_PRIORITY; - this.feeUnlikely = this.feeStats.estimateMedian( - 10, SUFFICIENT_FEETXS, UNLIKELY_PCT, - false, height); + this.priUnlikely = this.priStats.estimateMedian( + 10, SUFFICIENT_PRITXS, UNLIKELY_PCT, + false, height); - if (this.feeUnlikely === -1) - this.feeUnlikely = 0; + if (this.priUnlikely === -1) + this.priUnlikely = 0; - this.priLikely = this.priStats.estimateMedian( - 2, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT, - true, height); + this.feeStats.clearCurrent(height); + this.priStats.clearCurrent(height); - if (this.priLikely === -1) - this.priLikely = INF_PRIORITY; + for (const entry of entries) + this.processBlockTX(height, entry); - this.priUnlikely = this.priStats.estimateMedian( - 10, SUFFICIENT_PRITXS, UNLIKELY_PCT, - false, height); + this.feeStats.updateAverages(); + this.priStats.updateAverages(); - if (this.priUnlikely === -1) - this.priUnlikely = 0; + this.logger.debug('Done updating estimates' + + ' for %d confirmed entries. New mempool map size %d.', + entries.length, this.map.size); - this.feeStats.clearCurrent(height); - this.priStats.clearCurrent(height); + this.logger.debug('New fee rate: %d.', this.estimateFee()); + } - for (const entry of entries) - this.processBlockTX(height, entry); + /** + * Estimate a fee rate. + * @param {Number} [target=1] - Confirmation target. + * @param {Boolean} [smart=true] - Smart estimation. + * @returns {Rate} + */ - this.feeStats.updateAverages(); - this.priStats.updateAverages(); + estimateFee(target, smart) { + if (!target) + target = 1; - this.logger.debug('Done updating estimates' - + ' for %d confirmed entries. New mempool map size %d.', - entries.length, this.map.size); + if (smart == null) + smart = true; - this.logger.debug('New fee rate: %d.', this.estimateFee()); -}; + assert((target >>> 0) === target, 'Target must be a number.'); + assert(target <= this.feeStats.maxConfirms, + 'Too many confirmations for estimate.'); -/** - * Estimate a fee rate. - * @param {Number} [target=1] - Confirmation target. - * @param {Boolean} [smart=true] - Smart estimation. - * @returns {Rate} - */ + if (!smart) { + const rate = this.feeStats.estimateMedian( + target, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, + true, this.bestHeight); -PolicyEstimator.prototype.estimateFee = function estimateFee(target, smart) { - if (!target) - target = 1; + if (rate < 0) + return 0; - if (smart == null) - smart = true; + return Math.floor(rate); + } - assert(util.isU32(target), 'Target must be a number.'); - assert(target <= this.feeStats.maxConfirms, - 'Too many confirmations for estimate.'); + let rate = -1; + while (rate < 0 && target <= this.feeStats.maxConfirms) { + rate = this.feeStats.estimateMedian( + target++, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, + true, this.bestHeight); + } - if (!smart) { - const rate = this.feeStats.estimateMedian( - target, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, - true, this.bestHeight); + target -= 1; if (rate < 0) return 0; @@ -706,165 +719,170 @@ PolicyEstimator.prototype.estimateFee = function estimateFee(target, smart) { return Math.floor(rate); } - let rate = -1; - while (rate < 0 && target <= this.feeStats.maxConfirms) { - rate = this.feeStats.estimateMedian( - target++, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, - true, this.bestHeight); - } - - target -= 1; - - if (rate < 0) - return 0; - - return Math.floor(rate); -}; - -/** - * Estimate a priority. - * @param {Number} [target=1] - Confirmation target. - * @param {Boolean} [smart=true] - Smart estimation. - * @returns {Number} - */ + /** + * Estimate a priority. + * @param {Number} [target=1] - Confirmation target. + * @param {Boolean} [smart=true] - Smart estimation. + * @returns {Number} + */ + + estimatePriority(target, smart) { + if (!target) + target = 1; + + if (smart == null) + smart = true; + + assert((target >>> 0) === target, 'Target must be a number.'); + assert(target <= this.priStats.maxConfirms, + 'Too many confirmations for estimate.'); + + if (!smart) { + const priority = this.priStats.estimateMedian( + target, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT, + true, this.bestHeight); + return Math.floor(priority); + } -PolicyEstimator.prototype.estimatePriority = function estimatePriority(target, smart) { - if (!target) - target = 1; + let priority = -1; + while (priority < 0 && target <= this.priStats.maxConfirms) { + priority = this.priStats.estimateMedian( + target++, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT, + true, this.bestHeight); + } - if (smart == null) - smart = true; + target -= 1; - assert(util.isU32(target), 'Target must be a number.'); - assert(target <= this.priStats.maxConfirms, - 'Too many confirmations for estimate.'); + if (priority < 0) + return 0; - if (!smart) { - const priority = this.priStats.estimateMedian( - target, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT, - true, this.bestHeight); return Math.floor(priority); } - let priority = -1; - while (priority < 0 && target <= this.priStats.maxConfirms) { - priority = this.priStats.estimateMedian( - target++, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT, - true, this.bestHeight); - } - - target -= 1; + /** + * Get serialization size. + * @returns {Number} + */ - if (priority < 0) - return 0; + getSize() { + let size = 0; + size += 5; + size += encoding.sizeVarlen(this.feeStats.getSize()); + return size; + } - return Math.floor(priority); -}; + /** + * Serialize the estimator. + * @returns {Buffer} + */ -/** - * Get serialization size. - * @returns {Number} - */ + toRaw() { + const size = this.getSize(); + const bw = bio.write(size); -PolicyEstimator.prototype.getSize = function getSize() { - let size = 0; - size += 5; - size += encoding.sizeVarlen(this.feeStats.getSize()); - return size; -}; + bw.writeU8(PolicyEstimator.VERSION); + bw.writeU32(this.bestHeight); + bw.writeVarBytes(this.feeStats.toRaw()); -/** - * Serialize the estimator. - * @returns {Buffer} - */ + return bw.render(); + } -PolicyEstimator.prototype.toRaw = function toRaw() { - const size = this.getSize(); - const bw = new StaticWriter(size); + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + * @returns {PolicyEstimator} + */ - bw.writeU8(PolicyEstimator.VERSION); - bw.writeU32(this.bestHeight); - bw.writeVarBytes(this.feeStats.toRaw()); + fromRaw(data) { + const br = bio.read(data); - return bw.render(); -}; + if (br.readU8() !== PolicyEstimator.VERSION) + throw new Error('Bad serialization version for estimator.'); -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @returns {PolicyEstimator} - */ + this.bestHeight = br.readU32(); + this.feeStats.fromRaw(br.readVarBytes()); -PolicyEstimator.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); - - if (br.readU8() !== PolicyEstimator.VERSION) - throw new Error('Bad serialization version for estimator.'); + return this; + } - this.bestHeight = br.readU32(); - this.feeStats.fromRaw(br.readVarBytes()); + /** + * Instantiate a policy estimator from serialized data. + * @param {Buffer} data + * @param {Logger?} logger + * @returns {PolicyEstimator} + */ - return this; -}; + static fromRaw(data, logger) { + return new this(logger).fromRaw(data); + } -/** - * Instantiate a policy estimator from serialized data. - * @param {Buffer} data - * @param {Logger?} logger - * @returns {PolicyEstimator} - */ + /** + * Inject properties from estimator. + * @param {PolicyEstimator} estimator + * @returns {PolicyEstimator} + */ -PolicyEstimator.fromRaw = function fromRaw(data, logger) { - return new PolicyEstimator(logger).fromRaw(data); -}; + inject(estimator) { + this.bestHeight = estimator.bestHeight; + this.feeStats = estimator.feeStats; + return this; + } +} /** - * Inject properties from estimator. - * @param {PolicyEstimator} estimator - * @returns {PolicyEstimator} + * Serialization version. + * @const {Number} + * @default */ -PolicyEstimator.prototype.inject = function inject(estimator) { - this.bestHeight = estimator.bestHeight; - this.feeStats = estimator.feeStats; - return this; -}; +PolicyEstimator.VERSION = 0; /** - * StatEntry + * Stat Entry * @alias module:mempool.StatEntry * @ignore */ -function StatEntry() { - this.blockHeight = -1; - this.bucketIndex = -1; +class StatEntry { + /** + * StatEntry + * @constructor + */ + + constructor() { + this.blockHeight = -1; + this.bucketIndex = -1; + } } /** - * DoubleMap + * Double Map * @alias module:mempool.DoubleMap * @ignore */ -function DoubleMap() { - if (!(this instanceof DoubleMap)) - return new DoubleMap(); +class DoubleMap { + /** + * DoubleMap + * @constructor + */ - this.buckets = []; -} + constructor() { + this.buckets = []; + } -DoubleMap.prototype.insert = function insert(key, value) { - const i = util.binarySearch(this.buckets, key, compare, true); - this.buckets.splice(i, 0, [key, value]); -}; + insert(key, value) { + const i = binary.search(this.buckets, key, compare, true); + this.buckets.splice(i, 0, [key, value]); + } -DoubleMap.prototype.search = function search(key) { - const i = util.binarySearch(this.buckets, key, compare, true); - assert(this.buckets.length !== 0, 'Cannot search.'); - return this.buckets[i][1]; -}; + search(key) { + assert(this.buckets.length !== 0, 'Cannot search.'); + const i = binary.search(this.buckets, key, compare, true); + return this.buckets[i][1]; + } +} /* * Helpers @@ -899,8 +917,4 @@ function readArray(br) { * Expose */ -exports = PolicyEstimator; -exports.PolicyEstimator = PolicyEstimator; -exports.ConfirmStats = ConfirmStats; - -module.exports = exports; +module.exports = PolicyEstimator; diff --git a/lib/mempool/layout-browser.js b/lib/mempool/layout-browser.js deleted file mode 100644 index b7da4bb4c..000000000 --- a/lib/mempool/layout-browser.js +++ /dev/null @@ -1,41 +0,0 @@ -/*! - * layout-browser.js - mempooldb layout for browser. - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); - -const layout = { - binary: false, - R: 'R', - V: 'V', - F: 'F', - e: function e(hash) { - return 'e' + hex(hash); - }, - ee: function ee(key) { - assert(typeof key === 'string'); - assert(key.length === 65); - return key.slice(1, 65); - } -}; - -/* - * Helpers - */ - -function hex(hash) { - if (Buffer.isBuffer(hash)) - hash = hash.toString('hex'); - assert(typeof hash === 'string'); - return hash; -} - -/* - * Expose - */ - -module.exports = layout; diff --git a/lib/mempool/layout.js b/lib/mempool/layout.js index 3d009a567..81fddc34f 100644 --- a/lib/mempool/layout.js +++ b/lib/mempool/layout.js @@ -6,44 +6,24 @@ 'use strict'; -const assert = require('assert'); +const bdb = require('bdb'); /* * Database Layout: - * R -> tip hash * V -> db version - * e[id][hash] -> entry + * v -> serialization version + * R -> tip hash + * e[hash] -> entry */ const layout = { - binary: true, - R: Buffer.from([0x52]), - V: Buffer.from([0x76]), - F: Buffer.from([0x46]), - e: function e(hash) { - const key = Buffer.allocUnsafe(33); - key[0] = 0x65; - write(key, hash, 1); - return key; - }, - ee: function ee(key) { - assert(Buffer.isBuffer(key)); - assert(key.length === 33); - return key.toString('hex', 1, 33); - } + V: bdb.key('V'), + v: bdb.key('v'), + R: bdb.key('R'), + F: bdb.key('F'), + e: bdb.key('e', ['hash256']) }; -/* - * Helpers - */ - -function write(data, str, off) { - if (Buffer.isBuffer(str)) - return str.copy(data, off); - assert(typeof str === 'string'); - return data.write(str, off, 'hex'); -} - /* * Expose */ diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index 04cb55cdc..fbf3ab8ac 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -6,17 +6,19 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const path = require('path'); -const AsyncObject = require('../utils/asyncobject'); +const EventEmitter = require('events'); +const bdb = require('bdb'); +const {RollingFilter} = require('bfilter'); +const Heap = require('bheep'); +const {BufferMap, BufferSet} = require('buffer-map'); const common = require('../blockchain/common'); const consensus = require('../protocol/consensus'); const policy = require('../protocol/policy'); const util = require('../utils/util'); -const random = require('../crypto/random'); +const random = require('bcrypto/lib/random'); const {VerifyError} = require('../protocol/errors'); -const RollingFilter = require('../utils/rollingfilter'); -const Address = require('../primitives/address'); const Script = require('../script/script'); const Outpoint = require('../primitives/outpoint'); const TX = require('../primitives/tx'); @@ -24,2532 +26,2322 @@ const Coin = require('../primitives/coin'); const TXMeta = require('../primitives/txmeta'); const MempoolEntry = require('./mempoolentry'); const Network = require('../protocol/network'); -const encoding = require('../utils/encoding'); const layout = require('./layout'); -const LDB = require('../db/ldb'); +const AddrIndexer = require('./addrindexer'); const Fees = require('./fees'); const CoinView = require('../coins/coinview'); -const Heap = require('../utils/heap'); /** + * Mempool * Represents a mempool. + * @extends EventEmitter * @alias module:mempool.Mempool - * @constructor - * @param {Object} options - * @param {String?} options.name - Database name. - * @param {String?} options.location - Database file location. - * @param {String?} options.db - Database backend (`"memory"` by default). - * @param {Boolean?} options.limitFree - * @param {Number?} options.limitFreeRelay - * @param {Number?} options.maxSize - Max pool size (default ~300mb). - * @param {Boolean?} options.relayPriority - * @param {Boolean?} options.requireStandard - * @param {Boolean?} options.rejectAbsurdFees - * @param {Boolean?} options.relay - * @property {Boolean} loaded - * @property {Object} db - * @property {Number} size - * @property {Lock} locker - * @property {Number} freeCount - * @property {Number} lastTime - * @property {Number} maxSize - * @property {Rate} minRelayFee - * @emits Mempool#open - * @emits Mempool#error - * @emits Mempool#tx - * @emits Mempool#add tx - * @emits Mempool#remove tx */ -function Mempool(options) { - if (!(this instanceof Mempool)) - return new Mempool(options); +class Mempool extends EventEmitter { + /** + * Create a mempool. + * @constructor + * @param {Object} options + */ - AsyncObject.call(this); + constructor(options) { + super(); - this.options = new MempoolOptions(options); + this.opened = false; + this.options = new MempoolOptions(options); - this.network = this.options.network; - this.logger = this.options.logger.context('mempool'); - this.workers = this.options.workers; - this.chain = this.options.chain; - this.fees = this.options.fees; + this.network = this.options.network; + this.logger = this.options.logger.context('mempool'); + this.workers = this.options.workers; + this.chain = this.options.chain; + this.fees = this.options.fees; - this.locker = this.chain.locker; + this.locker = this.chain.locker; - this.cache = new MempoolCache(this.options); + this.cache = new MempoolCache(this.options); - this.size = 0; - this.freeCount = 0; - this.lastTime = 0; - this.lastFlush = 0; - this.tip = this.network.genesis.hash; + this.size = 0; + this.freeCount = 0; + this.lastTime = 0; + this.lastFlush = 0; + this.tip = this.network.genesis.hash; - this.waiting = new Map(); - this.orphans = new Map(); - this.map = new Map(); - this.spents = new Map(); - this.rejects = new RollingFilter(120000, 0.000001); + this.waiting = new BufferMap(); + this.orphans = new BufferMap(); + this.map = new BufferMap(); + this.spents = new BufferMap(); + this.rejects = new RollingFilter(120000, 0.000001); - this.coinIndex = new CoinIndex(); - this.txIndex = new TXIndex(); -} + this.addrindex = new AddrIndexer(this.network); + } -Object.setPrototypeOf(Mempool.prototype, AsyncObject.prototype); + /** + * Open the chain, wait for the database to load. + * @returns {Promise} + */ -/** - * Open the chain, wait for the database to load. - * @method - * @alias Mempool#open - * @returns {Promise} - */ + async open() { + assert(!this.opened, 'Mempool is already open.'); + this.opened = true; -Mempool.prototype._open = async function _open() { - await this.chain.open(); - await this.cache.open(); + await this.cache.open(); - if (this.options.persistent) { - const entries = await this.cache.getEntries(); + if (this.options.persistent) { + const entries = await this.cache.getEntries(); - for (const entry of entries) - this.trackEntry(entry); + for (const entry of entries) + this.trackEntry(entry); - for (const entry of entries) { - this.updateAncestors(entry, addFee); + for (const entry of entries) { + this.updateAncestors(entry, addFee); - if (this.options.indexAddress) { - const view = await this.getCoinView(entry.tx); - this.indexEntry(entry, view); + if (this.options.indexAddress) { + const view = await this.getCoinView(entry.tx); + this.indexEntry(entry, view); + } } - } - this.logger.info( - 'Loaded mempool from disk (%d entries).', - entries.length); + this.logger.info( + 'Loaded mempool from disk (%d entries).', + entries.length); - if (this.fees) { - const fees = await this.cache.getFees(); + if (this.fees) { + const fees = await this.cache.getFees(); - if (fees) { - this.fees.inject(fees); - this.logger.info( - 'Loaded mempool fee data (rate=%d).', - this.fees.estimateFee()); + if (fees) { + this.fees.inject(fees); + this.logger.info( + 'Loaded mempool fee data (rate=%d).', + this.fees.estimateFee()); + } } } - } - - this.tip = this.chain.tip.hash; - const size = (this.options.maxSize / 1024).toFixed(2); + this.tip = this.chain.tip.hash; - this.logger.info('Mempool loaded (maxsize=%dkb).', size); -}; + const size = (this.options.maxSize / 1024).toFixed(2); -/** - * Close the chain, wait for the database to close. - * @alias Mempool#close - * @returns {Promise} - */ - -Mempool.prototype._close = async function _close() { - await this.cache.close(); -}; + this.logger.info('Mempool loaded (maxsize=%dkb).', size); + } -/** - * Notify the mempool that a new block has come - * in (removes all transactions contained in the - * block from the mempool). - * @method - * @param {ChainEntry} block - * @param {TX[]} txs - * @returns {Promise} - */ + /** + * Close the chain, wait for the database to close. + * @returns {Promise} + */ -Mempool.prototype.addBlock = async function addBlock(block, txs) { - const unlock = await this.locker.lock(); - try { - return await this._addBlock(block, txs); - } finally { - unlock(); + async close() { + assert(this.opened, 'Mempool is not open.'); + this.opened = false; + return this.cache.close(); } -}; -/** - * Notify the mempool that a new block - * has come without a lock. - * @private - * @param {ChainEntry} block - * @param {TX[]} txs - * @returns {Promise} - */ + /** + * Notify the mempool that a new block has come + * in (removes all transactions contained in the + * block from the mempool). + * @method + * @param {ChainEntry} block + * @param {TX[]} txs + * @returns {Promise} + */ -Mempool.prototype._addBlock = async function _addBlock(block, txs) { - if (this.map.size === 0) { - this.tip = block.hash; - return; + async addBlock(block, txs) { + const unlock = await this.locker.lock(); + try { + return await this._addBlock(block, txs); + } finally { + unlock(); + } } - const entries = []; - - for (let i = txs.length - 1; i >= 1; i--) { - const tx = txs[i]; - const hash = tx.hash('hex'); - const entry = this.getEntry(hash); + /** + * Notify the mempool that a new block + * has come without a lock. + * @private + * @param {ChainEntry} block + * @param {TX[]} txs + * @returns {Promise} + */ - if (!entry) { - this.removeOrphan(hash); - this.removeDoubleSpends(tx); - if (this.waiting.has(hash)) - await this.handleOrphans(tx); - continue; + async _addBlock(block, txs) { + if (this.map.size === 0) { + this.tip = block.hash; + return; } - this.removeEntry(entry); - - this.emit('confirmed', tx, block); + const entries = []; - entries.push(entry); - } + for (let i = txs.length - 1; i >= 1; i--) { + const tx = txs[i]; + const hash = tx.hash(); + const entry = this.getEntry(hash); - // We need to reset the rejects filter periodically. - // There may be a locktime in a TX that is now valid. - this.rejects.reset(); + if (!entry) { + this.removeOrphan(hash); + this.removeDoubleSpends(tx); + if (this.waiting.has(hash)) + await this.handleOrphans(tx); + continue; + } - if (this.fees) { - this.fees.processBlock(block.height, entries, this.chain.synced); - this.cache.writeFees(this.fees); - } + this.removeEntry(entry); - this.cache.sync(block.hash); + this.emit('confirmed', tx, block); - await this.cache.flush(); + entries.push(entry); + } - this.tip = block.hash; + // We need to reset the rejects filter periodically. + // There may be a locktime in a TX that is now valid. + this.rejects.reset(); - if (entries.length === 0) - return; + if (this.fees) { + this.fees.processBlock(block.height, entries, this.chain.synced); + this.cache.writeFees(this.fees); + } - this.logger.debug( - 'Removed %d txs from mempool for block %d.', - entries.length, block.height); -}; + this.cache.sync(block.hash); -/** - * Notify the mempool that a block has been disconnected - * from the main chain (reinserts transactions into the mempool). - * @method - * @param {ChainEntry} block - * @param {TX[]} txs - * @returns {Promise} - */ + await this.cache.flush(); -Mempool.prototype.removeBlock = async function removeBlock(block, txs) { - const unlock = await this.locker.lock(); - try { - return await this._removeBlock(block, txs); - } finally { - unlock(); - } -}; + this.tip = block.hash; -/** - * Notify the mempool that a block - * has been disconnected without a lock. - * @method - * @private - * @param {ChainEntry} block - * @param {TX[]} txs - * @returns {Promise} - */ + if (entries.length === 0) + return; -Mempool.prototype._removeBlock = async function _removeBlock(block, txs) { - if (this.map.size === 0) { - this.tip = block.prevBlock; - return; + this.logger.debug( + 'Removed %d txs from mempool for block %d.', + entries.length, block.height); } - let total = 0; - - for (let i = 1; i < txs.length; i++) { - const tx = txs[i]; - const hash = tx.hash('hex'); - - if (this.hasEntry(hash)) - continue; + /** + * Notify the mempool that a block has been disconnected + * from the main chain (reinserts transactions into the mempool). + * @method + * @param {ChainEntry} block + * @param {TX[]} txs + * @returns {Promise} + */ + async removeBlock(block, txs) { + const unlock = await this.locker.lock(); try { - await this.insertTX(tx, -1); - total++; - } catch (e) { - this.emit('error', e); - continue; + return await this._removeBlock(block, txs); + } finally { + unlock(); } - - this.emit('unconfirmed', tx, block); } - this.rejects.reset(); - - this.cache.sync(block.prevBlock); - - await this.cache.flush(); - - this.tip = block.prevBlock; - - if (total === 0) - return; - - this.logger.debug( - 'Added %d txs back into the mempool for block %d.', - total, block.height); -}; - -/** - * Sanitize the mempool after a reorg. - * @private - * @returns {Promise} - */ - -Mempool.prototype._handleReorg = async function _handleReorg() { - const height = this.chain.height + 1; - const mtp = await this.chain.getMedianTime(this.chain.tip); - const remove = []; - - for (const [hash, entry] of this.map) { - const {tx} = entry; - - if (!tx.isFinal(height, mtp)) { - remove.push(hash); - continue; + /** + * Notify the mempool that a block + * has been disconnected without a lock. + * @method + * @private + * @param {ChainEntry} block + * @param {TX[]} txs + * @returns {Promise} + */ + + async _removeBlock(block, txs) { + if (this.map.size === 0) { + this.tip = block.prevBlock; + return; } - if (tx.version > 1) { - let hasLocks = false; + let total = 0; - for (const {sequence} of tx.inputs) { - if (!(sequence & consensus.SEQUENCE_DISABLE_FLAG)) { - hasLocks = true; - break; - } - } + for (let i = 1; i < txs.length; i++) { + const tx = txs[i]; + const hash = tx.hash(); - if (hasLocks) { - remove.push(hash); + if (this.hasEntry(hash)) + continue; + + try { + await this.insertTX(tx, -1); + total++; + } catch (e) { + this.emit('error', e); continue; } + + this.emit('unconfirmed', tx, block); } - if (entry.coinbase) - remove.push(hash); - } + this.rejects.reset(); - for (const hash of remove) { - const entry = this.getEntry(hash); + this.cache.sync(block.prevBlock); - if (!entry) - continue; + await this.cache.flush(); - this.evictEntry(entry); - } -}; + this.tip = block.prevBlock; -/** - * Reset the mempool. - * @method - * @returns {Promise} - */ + if (total === 0) + return; -Mempool.prototype.reset = async function reset() { - const unlock = await this.locker.lock(); - try { - return await this._reset(); - } finally { - unlock(); + this.logger.debug( + 'Added %d txs back into the mempool for block %d.', + total, block.height); } -}; - -/** - * Reset the mempool without a lock. - * @private - */ -Mempool.prototype._reset = async function _reset() { - this.logger.info('Mempool reset (%d txs removed).', this.map.size); + /** + * Sanitize the mempool after a reorg. + * @private + * @returns {Promise} + */ - this.size = 0; + async _handleReorg() { + const height = this.chain.height + 1; + const mtp = await this.chain.getMedianTime(this.chain.tip); + const remove = []; - this.waiting.clear(); - this.orphans.clear(); - this.map.clear(); - this.spents.clear(); - this.coinIndex.reset(); - this.txIndex.reset(); + for (const [hash, entry] of this.map) { + const {tx} = entry; - this.freeCount = 0; - this.lastTime = 0; - - if (this.fees) - this.fees.reset(); - - this.rejects.reset(); - - if (this.options.persistent) { - await this.cache.wipe(); - this.cache.clear(); - } + if (!tx.isFinal(height, mtp)) { + remove.push(hash); + continue; + } - this.tip = this.chain.tip.hash; -}; + if (tx.version > 1) { + let hasLocks = false; -/** - * Ensure the size of the mempool stays below `maxSize`. - * Evicts entries by timestamp and cumulative fee rate. - * @param {MempoolEntry} added - * @returns {Promise} - */ - -Mempool.prototype.limitSize = function limitSize(added) { - const maxSize = this.options.maxSize; + for (const {sequence} of tx.inputs) { + if (!(sequence & consensus.SEQUENCE_DISABLE_FLAG)) { + hasLocks = true; + break; + } + } - if (this.size <= maxSize) - return false; + if (hasLocks) { + remove.push(hash); + continue; + } + } - const threshold = maxSize - (maxSize / 10); - const expiryTime = this.options.expiryTime; + if (entry.coinbase) + remove.push(hash); + } - const now = util.now(); - let start = util.hrtime(); - const queue = new Heap(cmpRate); + for (const hash of remove) { + const entry = this.getEntry(hash); - for (const entry of this.map.values()) { - if (this.hasDepends(entry.tx)) - continue; + if (!entry) + continue; - if (now < entry.time + expiryTime) { - queue.insert(entry); - continue; + this.evictEntry(entry); } + } - this.logger.debug( - 'Removing package %s from mempool (too old).', - entry.txid()); + /** + * Reset the mempool. + * @method + * @returns {Promise} + */ - this.evictEntry(entry); + async reset() { + const unlock = await this.locker.lock(); + try { + return await this._reset(); + } finally { + unlock(); + } } - if (this.size <= threshold) - return !this.hasEntry(added); + /** + * Reset the mempool without a lock. + * @private + */ - this.logger.debug( - '(bench) Heap mempool traversal: %d.', - util.hrtime(start)); + async _reset() { + this.logger.info('Mempool reset (%d txs removed).', this.map.size); - start = util.hrtime(); + this.size = 0; - this.logger.debug( - '(bench) Heap mempool queue size: %d.', - queue.size()); + this.waiting.clear(); + this.orphans.clear(); + this.map.clear(); + this.spents.clear(); + this.addrindex.reset(); - while (queue.size() > 0) { - const entry = queue.shift(); - const hash = entry.hash('hex'); + this.freeCount = 0; + this.lastTime = 0; - assert(this.hasEntry(hash)); + if (this.fees) + this.fees.reset(); - this.logger.debug( - 'Removing package %s from mempool (low fee).', - entry.txid()); + this.rejects.reset(); - this.evictEntry(entry); + if (this.options.persistent) { + await this.cache.wipe(); + this.cache.clear(); + } - if (this.size <= threshold) - break; + this.tip = this.chain.tip.hash; } - this.logger.debug( - '(bench) Heap mempool map removal: %d.', - util.hrtime(start)); + /** + * Ensure the size of the mempool stays below `maxSize`. + * Evicts entries by timestamp and cumulative fee rate. + * @param {MempoolEntry} added + * @returns {Promise} + */ - return !this.hasEntry(added); -}; + limitSize(added) { + const maxSize = this.options.maxSize; -/** - * Retrieve a transaction from the mempool. - * @param {Hash} hash - * @returns {TX} - */ + if (this.size <= maxSize) + return false; -Mempool.prototype.getTX = function getTX(hash) { - const entry = this.map.get(hash); + const threshold = maxSize - (maxSize / 10); + const expiryTime = this.options.expiryTime; - if (!entry) - return null; + const now = util.now(); + let start = util.bench(); + const queue = new Heap(cmpRate); - return entry.tx; -}; + for (const entry of this.map.values()) { + if (this.hasDepends(entry.tx)) + continue; -/** - * Retrieve a transaction from the mempool. - * @param {Hash} hash - * @returns {MempoolEntry} - */ + if (now < entry.time + expiryTime) { + queue.insert(entry); + continue; + } -Mempool.prototype.getEntry = function getEntry(hash) { - return this.map.get(hash); -}; + this.logger.debug( + 'Removing package %h from mempool (too old).', + entry.hash()); -/** - * Retrieve a coin from the mempool (unspents only). - * @param {Hash} hash - * @param {Number} index - * @returns {Coin} - */ + this.evictEntry(entry); + } -Mempool.prototype.getCoin = function getCoin(hash, index) { - const entry = this.map.get(hash); + if (this.size <= threshold) + return !this.hasEntry(added); - if (!entry) - return null; + this.logger.debug( + '(bench) Heap mempool traversal: %d.', + util.bench(start)); - if (this.isSpent(hash, index)) - return null; + start = util.bench(); - if (index >= entry.tx.outputs.length) - return null; - - return Coin.fromTX(entry.tx, index, -1); -}; + this.logger.debug( + '(bench) Heap mempool queue size: %d.', + queue.size()); -/** - * Check to see if a coin has been spent. This differs from - * {@link ChainDB#isSpent} in that it actually maintains a - * map of spent coins, whereas ChainDB may return `true` - * for transaction outputs that never existed. - * @param {Hash} hash - * @param {Number} index - * @returns {Boolean} - */ + while (queue.size() > 0) { + const entry = queue.shift(); + const hash = entry.hash(); -Mempool.prototype.isSpent = function isSpent(hash, index) { - const key = Outpoint.toKey(hash, index); - return this.spents.has(key); -}; + assert(this.hasEntry(hash)); -/** - * Get an output's spender entry. - * @param {Hash} hash - * @param {Number} index - * @returns {MempoolEntry} - */ + this.logger.debug( + 'Removing package %h from mempool (low fee).', + entry.hash()); -Mempool.prototype.getSpent = function getSpent(hash, index) { - const key = Outpoint.toKey(hash, index); - return this.spents.get(key); -}; + this.evictEntry(entry); -/** - * Get an output's spender transaction. - * @param {Hash} hash - * @param {Number} index - * @returns {MempoolEntry} - */ + if (this.size <= threshold) + break; + } -Mempool.prototype.getSpentTX = function getSpentTX(hash, index) { - const key = Outpoint.toKey(hash, index); - const entry = this.spents.get(key); + this.logger.debug( + '(bench) Heap mempool map removal: %d.', + util.bench(start)); - if (!entry) - return null; + return !this.hasEntry(added); + } - return entry.tx; -}; + /** + * Retrieve a transaction from the mempool. + * @param {Hash} hash + * @returns {TX} + */ -/** - * Find all coins pertaining to a certain address. - * @param {Address[]} addrs - * @returns {Coin[]} - */ + getTX(hash) { + const entry = this.map.get(hash); -Mempool.prototype.getCoinsByAddress = function getCoinsByAddress(addrs) { - if (!Array.isArray(addrs)) - addrs = [addrs]; + if (!entry) + return null; - const out = []; + return entry.tx; + } - for (const addr of addrs) { - const hash = Address.getHash(addr, 'hex'); - const coins = this.coinIndex.get(hash); + /** + * Retrieve a transaction from the mempool. + * @param {Hash} hash + * @returns {MempoolEntry} + */ - for (const coin of coins) - out.push(coin); + getEntry(hash) { + return this.map.get(hash); } - return out; -}; + /** + * Retrieve a coin from the mempool (unspents only). + * @param {Hash} hash + * @param {Number} index + * @returns {Coin} + */ -/** - * Find all transactions pertaining to a certain address. - * @param {Address[]} addrs - * @returns {TX[]} - */ + getCoin(hash, index) { + const entry = this.map.get(hash); -Mempool.prototype.getTXByAddress = function getTXByAddress(addrs) { - if (!Array.isArray(addrs)) - addrs = [addrs]; + if (!entry) + return null; - const out = []; + if (this.isSpent(hash, index)) + return null; - for (const addr of addrs) { - const hash = Address.getHash(addr, 'hex'); - const txs = this.txIndex.get(hash); + if (index >= entry.tx.outputs.length) + return null; - for (const tx of txs) - out.push(tx); + return Coin.fromTX(entry.tx, index, -1); } - return out; -}; + /** + * Check whether coin is still unspent. + * @param {Hash} hash + * @param {Number} index + * @returns {boolean} + */ -/** - * Find all transactions pertaining to a certain address. - * @param {Address[]} addrs - * @returns {TXMeta[]} - */ + hasCoin(hash, index) { + const entry = this.map.get(hash); -Mempool.prototype.getMetaByAddress = function getMetaByAddress(addrs) { - if (!Array.isArray(addrs)) - addrs = [addrs]; + if (!entry) + return false; - const out = []; + if (this.isSpent(hash, index)) + return false; - for (const addr of addrs) { - const hash = Address.getHash(addr, 'hex'); - const txs = this.txIndex.getMeta(hash); + if (index >= entry.tx.outputs.length) + return false; - for (const tx of txs) - out.push(tx); + return true; } - return out; -}; + /** + * Check to see if a coin has been spent. This differs from + * {@link ChainDB#isSpent} in that it actually maintains a + * map of spent coins, whereas ChainDB may return `true` + * for transaction outputs that never existed. + * @param {Hash} hash + * @param {Number} index + * @returns {Boolean} + */ -/** - * Retrieve a transaction from the mempool. - * @param {Hash} hash - * @returns {TXMeta} - */ - -Mempool.prototype.getMeta = function getMeta(hash) { - const entry = this.getEntry(hash); + isSpent(hash, index) { + const key = Outpoint.toKey(hash, index); + return this.spents.has(key); + } - if (!entry) - return null; + /** + * Get an output's spender entry. + * @param {Hash} hash + * @param {Number} index + * @returns {MempoolEntry} + */ - const meta = TXMeta.fromTX(entry.tx); - meta.mtime = entry.time; + getSpent(hash, index) { + const key = Outpoint.toKey(hash, index); + return this.spents.get(key); + } - return meta; -}; + /** + * Get an output's spender transaction. + * @param {Hash} hash + * @param {Number} index + * @returns {MempoolEntry} + */ -/** - * Test the mempool to see if it contains a transaction. - * @param {Hash} hash - * @returns {Boolean} - */ + getSpentTX(hash, index) { + const key = Outpoint.toKey(hash, index); + const entry = this.spents.get(key); -Mempool.prototype.hasEntry = function hasEntry(hash) { - return this.map.has(hash); -}; + if (!entry) + return null; -/** - * Test the mempool to see if it - * contains a transaction or an orphan. - * @param {Hash} hash - * @returns {Boolean} - */ + return entry.tx; + } -Mempool.prototype.has = function has(hash) { - if (this.locker.has(hash)) - return true; + /** + * Find all transactions pertaining to a certain address. + * @param {Address} addr + * @param {Object} options + * @param {Number} options.limit + * @param {Number} options.reverse + * @param {Buffer} options.after + * @returns {TX[]} + */ - if (this.hasOrphan(hash)) - return true; + getTXByAddress(addr, options) { + return this.addrindex.get(addr, options); + } - return this.hasEntry(hash); -}; + /** + * Find all transactions pertaining to a certain address. + * @param {Address} addr + * @param {Object} options + * @param {Number} options.limit + * @param {Number} options.reverse + * @param {Buffer} options.after + * @returns {TXMeta[]} + */ -/** - * Test the mempool to see if it - * contains a transaction or an orphan. - * @private - * @param {Hash} hash - * @returns {Boolean} - */ + getMetaByAddress(addr, options) { + return this.addrindex.getMeta(addr, options); + } -Mempool.prototype.exists = function exists(hash) { - if (this.locker.hasPending(hash)) - return true; + /** + * Retrieve a transaction from the mempool. + * @param {Hash} hash + * @returns {TXMeta} + */ - if (this.hasOrphan(hash)) - return true; + getMeta(hash) { + const entry = this.getEntry(hash); - return this.hasEntry(hash); -}; + if (!entry) + return null; -/** - * Test the mempool to see if it - * contains a recent reject. - * @param {Hash} hash - * @returns {Boolean} - */ + const meta = TXMeta.fromTX(entry.tx); + meta.mtime = entry.time; -Mempool.prototype.hasReject = function hasReject(hash) { - return this.rejects.test(hash, 'hex'); -}; + return meta; + } -/** - * Add a transaction to the mempool. Note that this - * will lock the mempool until the transaction is - * fully processed. - * @method - * @param {TX} tx - * @param {Number?} id - * @returns {Promise} - */ + /** + * Test the mempool to see if it contains a transaction. + * @param {Hash} hash + * @returns {Boolean} + */ -Mempool.prototype.addTX = async function addTX(tx, id) { - const hash = tx.hash('hex'); - const unlock = await this.locker.lock(hash); - try { - return await this._addTX(tx, id); - } finally { - unlock(); + hasEntry(hash) { + return this.map.has(hash); } -}; -/** - * Add a transaction to the mempool without a lock. - * @method - * @private - * @param {TX} tx - * @param {Number?} id - * @returns {Promise} - */ + /** + * Test the mempool to see if it + * contains a transaction or an orphan. + * @param {Hash} hash + * @returns {Boolean} + */ -Mempool.prototype._addTX = async function _addTX(tx, id) { - if (id == null) - id = -1; + has(hash) { + if (this.locker.has(hash)) + return true; - let missing; - try { - missing = await this.insertTX(tx, id); - } catch (err) { - if (err.type === 'VerifyError') { - if (!tx.hasWitness() && !err.malleated) - this.rejects.add(tx.hash()); - } - throw err; - } + if (this.hasOrphan(hash)) + return true; - if (util.now() - this.lastFlush > 10) { - await this.cache.flush(); - this.lastFlush = util.now(); + return this.hasEntry(hash); } - return missing; -}; + /** + * Test the mempool to see if it + * contains a transaction or an orphan. + * @private + * @param {Hash} hash + * @returns {Boolean} + */ -/** - * Add a transaction to the mempool without a lock. - * @method - * @private - * @param {TX} tx - * @param {Number?} id - * @returns {Promise} - */ - -Mempool.prototype.insertTX = async function insertTX(tx, id) { - assert(!tx.mutable, 'Cannot add mutable TX to mempool.'); + exists(hash) { + if (this.locker.pending(hash)) + return true; - const lockFlags = common.lockFlags.STANDARD_LOCKTIME_FLAGS; - const height = this.chain.height; - const hash = tx.hash('hex'); + if (this.hasOrphan(hash)) + return true; - // Basic sanity checks. - // This is important because it ensures - // other functions will be overflow safe. - const [valid, reason, score] = tx.checkSanity(); + return this.hasEntry(hash); + } - if (!valid) - throw new VerifyError(tx, 'invalid', reason, score); + /** + * Test the mempool to see if it + * contains a recent reject. + * @param {Hash} hash + * @returns {Boolean} + */ - // Coinbases are an insta-ban. - // Why? Who knows. - if (tx.isCoinbase()) { - throw new VerifyError(tx, - 'invalid', - 'coinbase', - 100); + hasReject(hash) { + return this.rejects.test(hash); } - // Do not allow CSV until it's activated. - if (this.options.requireStandard) { - if (!this.chain.state.hasCSV() && tx.version >= 2) { - throw new VerifyError(tx, - 'nonstandard', - 'premature-version2-tx', - 0); - } - } + /** + * Add a transaction to the mempool. Note that this + * will lock the mempool until the transaction is + * fully processed. + * @method + * @param {TX} tx + * @param {Number?} id + * @returns {Promise} + */ - // Do not allow segwit until it's activated. - if (!this.chain.state.hasWitness() && !this.options.prematureWitness) { - if (tx.hasWitness()) { - throw new VerifyError(tx, - 'nonstandard', - 'no-witness-yet', - 0, - true); + async addTX(tx, id) { + const hash = tx.hash(); + const unlock = await this.locker.lock(hash); + try { + return await this._addTX(tx, id); + } finally { + unlock(); } } - // Non-contextual standardness checks. - if (this.options.requireStandard) { - const [valid, reason, score] = tx.checkStandard(); + /** + * Add a transaction to the mempool without a lock. + * @method + * @private + * @param {TX} tx + * @param {Number?} id + * @returns {Promise} + */ - if (!valid) - throw new VerifyError(tx, 'nonstandard', reason, score); + async _addTX(tx, id) { + if (id == null) + id = -1; - if (!this.options.replaceByFee) { - if (tx.isRBF()) { - throw new VerifyError(tx, - 'nonstandard', - 'replace-by-fee', - 0); + let missing; + try { + missing = await this.insertTX(tx, id); + } catch (err) { + if (err.type === 'VerifyError') { + if (!tx.hasWitness() && !err.malleated) + this.rejects.add(tx.hash()); } + throw err; } - } - // Verify transaction finality (see isFinal()). - if (!await this.verifyFinal(tx, lockFlags)) { - throw new VerifyError(tx, - 'nonstandard', - 'non-final', - 0); - } - - // We can maybe ignore this. - if (this.exists(hash)) { - throw new VerifyError(tx, - 'alreadyknown', - 'txn-already-in-mempool', - 0); - } - - // We can test whether this is an - // non-fully-spent transaction on - // the chain. - if (await this.chain.hasCoins(tx)) { - throw new VerifyError(tx, - 'alreadyknown', - 'txn-already-known', - 0); - } - - // Quick and dirty test to verify we're - // not double-spending an output in the - // mempool. - if (this.isDoubleSpend(tx)) { - this.emit('conflict', tx); - throw new VerifyError(tx, - 'duplicate', - 'bad-txns-inputs-spent', - 0); - } - - // Get coin viewpoint as it - // pertains to the mempool. - const view = await this.getCoinView(tx); - - // Maybe store as an orphan. - const missing = this.maybeOrphan(tx, view, id); + if (util.now() - this.lastFlush > 10) { + await this.cache.flush(); + this.lastFlush = util.now(); + } - // Return missing outpoint hashes. - if (missing) return missing; - - // Create a new mempool entry - // at current chain height. - const entry = MempoolEntry.fromTX(tx, view, height); - - // Contextual verification. - await this.verify(entry, view); - - // Add and index the entry. - await this.addEntry(entry, view); - - // Trim size if we're too big. - if (this.limitSize(hash)) { - throw new VerifyError(tx, - 'insufficientfee', - 'mempool full', - 0); } - return null; -}; + /** + * Add a transaction to the mempool without a lock. + * @method + * @private + * @param {TX} tx + * @param {Number?} id + * @returns {Promise} + */ -/** - * Verify a transaction with mempool standards. - * @method - * @param {TX} tx - * @param {CoinView} view - * @returns {Promise} - */ + async insertTX(tx, id) { + assert(!tx.mutable, 'Cannot add mutable TX to mempool.'); -Mempool.prototype.verify = async function verify(entry, view) { - const height = this.chain.height + 1; - const lockFlags = common.lockFlags.STANDARD_LOCKTIME_FLAGS; - const tx = entry.tx; + const lockFlags = common.lockFlags.STANDARD_LOCKTIME_FLAGS; + const height = this.chain.height; + const hash = tx.hash(); - // Verify sequence locks. - if (!await this.verifyLocks(tx, view, lockFlags)) { - throw new VerifyError(tx, - 'nonstandard', - 'non-BIP68-final', - 0); - } + // Basic sanity checks. + // This is important because it ensures + // other functions will be overflow safe. + const [valid, reason, score] = tx.checkSanity(); + + if (!valid) + throw new VerifyError(tx, 'invalid', reason, score); - // Check input an witness standardness. - if (this.options.requireStandard) { - if (!tx.hasStandardInputs(view)) { + // Coinbases are an insta-ban. + // Why? Who knows. + if (tx.isCoinbase()) { throw new VerifyError(tx, - 'nonstandard', - 'bad-txns-nonstandard-inputs', - 0); + 'invalid', + 'coinbase', + 100); } - if (this.chain.state.hasWitness()) { - if (!tx.hasStandardWitness(view)) { + + // Do not allow CSV until it's activated. + if (this.options.requireStandard) { + if (!this.chain.state.hasCSV() && tx.version >= 2) { throw new VerifyError(tx, 'nonstandard', - 'bad-witness-nonstandard', + 'premature-version2-tx', + 0); + } + } + + // Do not allow segwit until it's activated. + if (!this.chain.state.hasWitness() && !this.options.prematureWitness) { + if (tx.hasWitness()) { + throw new VerifyError(tx, + 'nonstandard', + 'no-witness-yet', 0, true); } } - } - // Annoying process known as sigops counting. - if (entry.sigops > policy.MAX_TX_SIGOPS_COST) { - throw new VerifyError(tx, - 'nonstandard', - 'bad-txns-too-many-sigops', - 0); - } + // Non-contextual standardness checks. + if (this.options.requireStandard) { + const [valid, reason, score] = tx.checkStandard(); - // Make sure this guy gave a decent fee. - const minFee = policy.getMinFee(entry.size, this.options.minRelay); + if (!valid) + throw new VerifyError(tx, 'nonstandard', reason, score); + + if (!this.options.replaceByFee) { + if (tx.isRBF()) { + throw new VerifyError(tx, + 'nonstandard', + 'replace-by-fee', + 0); + } + } + } - if (this.options.relayPriority && entry.fee < minFee) { - if (!entry.isFree(height)) { + // Verify transaction finality (see isFinal()). + if (!await this.verifyFinal(tx, lockFlags)) { throw new VerifyError(tx, - 'insufficientfee', - 'insufficient priority', + 'nonstandard', + 'non-final', 0); } - } - - // Continuously rate-limit free (really, very-low-fee) - // transactions. This mitigates 'penny-flooding'. - if (this.options.limitFree && entry.fee < minFee) { - const now = util.now(); - - // Use an exponentially decaying ~10-minute window. - this.freeCount *= Math.pow(1 - 1 / 600, now - this.lastTime); - this.lastTime = now; - // The limitFreeRelay unit is thousand-bytes-per-minute - // At default rate it would take over a month to fill 1GB. - if (this.freeCount > this.options.limitFreeRelay * 10 * 1000) { + // We can maybe ignore this. + if (this.exists(hash)) { throw new VerifyError(tx, - 'insufficientfee', - 'rate limited free transaction', + 'alreadyknown', + 'txn-already-in-mempool', 0); } - this.freeCount += entry.size; - } - - // Important safety feature. - if (this.options.rejectAbsurdFees && entry.fee > minFee * 10000) - throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0); - - // Why do we have this here? Nested transactions are cool. - if (this.countAncestors(entry) + 1 > this.options.maxAncestors) { - throw new VerifyError(tx, - 'nonstandard', - 'too-long-mempool-chain', - 0); - } + // We can test whether this is an + // non-fully-spent transaction on + // the chain. + if (await this.chain.hasCoins(tx)) { + throw new VerifyError(tx, + 'alreadyknown', + 'txn-already-known', + 0); + } - // Contextual sanity checks. - const [fee, reason, score] = tx.checkInputs(view, height); + // Quick and dirty test to verify we're + // not double-spending an output in the + // mempool. + if (this.isDoubleSpend(tx)) { + this.emit('conflict', tx); + throw new VerifyError(tx, + 'duplicate', + 'bad-txns-inputs-spent', + 0); + } - if (fee === -1) - throw new VerifyError(tx, 'invalid', reason, score); + // Get coin viewpoint as it + // pertains to the mempool. + const view = await this.getCoinView(tx); - // Script verification. - let flags = Script.flags.STANDARD_VERIFY_FLAGS; - try { - await this.verifyInputs(tx, view, flags); - } catch (err) { - if (tx.hasWitness()) - throw err; + // Maybe store as an orphan. + const missing = this.maybeOrphan(tx, view, id); - // Try without segwit and cleanstack. - flags &= ~Script.flags.VERIFY_WITNESS; - flags &= ~Script.flags.VERIFY_CLEANSTACK; + // Return missing outpoint hashes. + if (missing) + return missing; - // If it failed, the first verification - // was the only result we needed. - if (!await this.verifyResult(tx, view, flags)) - throw err; + // Create a new mempool entry + // at current chain height. + const entry = MempoolEntry.fromTX(tx, view, height); - // If it succeeded, segwit may be causing the - // failure. Try with segwit but without cleanstack. - flags |= Script.flags.VERIFY_CLEANSTACK; + // Contextual verification. + await this.verify(entry, view); - // Cleanstack was causing the failure. - if (await this.verifyResult(tx, view, flags)) - throw err; + // Add and index the entry. + await this.addEntry(entry, view); - // Do not insert into reject cache. - err.malleated = true; - throw err; - } - - // Paranoid checks. - if (this.options.paranoidChecks) { - const flags = Script.flags.MANDATORY_VERIFY_FLAGS; - assert(await this.verifyResult(tx, view, flags), - 'BUG: Verify failed for mandatory but not standard.'); - } -}; - -/** - * Verify inputs, return a boolean - * instead of an error based on success. - * @method - * @param {TX} tx - * @param {CoinView} view - * @param {VerifyFlags} flags - * @returns {Promise} - */ + // Trim size if we're too big. + if (this.limitSize(hash)) { + throw new VerifyError(tx, + 'insufficientfee', + 'mempool full', + 0); + } -Mempool.prototype.verifyResult = async function verifyResult(tx, view, flags) { - try { - await this.verifyInputs(tx, view, flags); - } catch (err) { - if (err.type === 'VerifyError') - return false; - throw err; + return null; } - return true; -}; - -/** - * Verify inputs for standard - * _and_ mandatory flags on failure. - * @method - * @param {TX} tx - * @param {CoinView} view - * @param {VerifyFlags} flags - * @returns {Promise} - */ -Mempool.prototype.verifyInputs = async function verifyInputs(tx, view, flags) { - if (await tx.verifyAsync(view, flags, this.workers)) - return; + /** + * Verify a transaction with mempool standards. + * @method + * @param {TX} tx + * @param {CoinView} view + * @returns {Promise} + */ - if (flags & Script.flags.ONLY_STANDARD_VERIFY_FLAGS) { - flags &= ~Script.flags.ONLY_STANDARD_VERIFY_FLAGS; + async verify(entry, view) { + const height = this.chain.height + 1; + const lockFlags = common.lockFlags.STANDARD_LOCKTIME_FLAGS; + const tx = entry.tx; - if (await tx.verifyAsync(view, flags, this.workers)) { + // Verify sequence locks. + if (!await this.verifyLocks(tx, view, lockFlags)) { throw new VerifyError(tx, 'nonstandard', - 'non-mandatory-script-verify-flag', + 'non-BIP68-final', 0); } - } - - throw new VerifyError(tx, - 'nonstandard', - 'mandatory-script-verify-flag', - 100); -}; - -/** - * Add a transaction to the mempool without performing any - * validation. Note that this method does not lock the mempool - * and may lend itself to race conditions if used unwisely. - * This function will also resolve orphans if possible (the - * resolved orphans _will_ be validated). - * @method - * @param {MempoolEntry} entry - * @param {CoinView} view - * @returns {Promise} - */ - -Mempool.prototype.addEntry = async function addEntry(entry, view) { - const tx = entry.tx; - - this.trackEntry(entry, view); - this.updateAncestors(entry, addFee); + // Check input an witness standardness. + if (this.options.requireStandard) { + if (!tx.hasStandardInputs(view)) { + throw new VerifyError(tx, + 'nonstandard', + 'bad-txns-nonstandard-inputs', + 0); + } + if (this.chain.state.hasWitness()) { + if (!tx.hasStandardWitness(view)) { + throw new VerifyError(tx, + 'nonstandard', + 'bad-witness-nonstandard', + 0, + true); + } + } + } - this.emit('tx', tx, view); - this.emit('add entry', entry); + // Annoying process known as sigops counting. + if (entry.sigops > policy.MAX_TX_SIGOPS_COST) { + throw new VerifyError(tx, + 'nonstandard', + 'bad-txns-too-many-sigops', + 0); + } - if (this.fees) - this.fees.processTX(entry, this.chain.synced); + // Make sure this guy gave a decent fee. + const minFee = policy.getMinFee(entry.size, this.options.minRelay); - this.logger.debug( - 'Added %s to mempool (txs=%d).', - tx.txid(), this.map.size); + if (this.options.relayPriority && entry.fee < minFee) { + if (!entry.isFree(height)) { + throw new VerifyError(tx, + 'insufficientfee', + 'insufficient priority', + 0); + } + } - this.cache.save(entry); + // Continuously rate-limit free (really, very-low-fee) + // transactions. This mitigates 'penny-flooding'. + if (this.options.limitFree && entry.fee < minFee) { + const now = util.now(); - await this.handleOrphans(tx); -}; + // Use an exponentially decaying ~10-minute window. + this.freeCount *= Math.pow(1 - 1 / 600, now - this.lastTime); + this.lastTime = now; -/** - * Remove a transaction from the mempool. - * Generally only called when a new block - * is added to the main chain. - * @param {MempoolEntry} entry - */ + // The limitFreeRelay unit is thousand-bytes-per-minute + // At default rate it would take over a month to fill 1GB. + if (this.freeCount > this.options.limitFreeRelay * 10 * 1000) { + throw new VerifyError(tx, + 'insufficientfee', + 'rate limited free transaction', + 0); + } -Mempool.prototype.removeEntry = function removeEntry(entry) { - const tx = entry.tx; - const hash = tx.hash('hex'); + this.freeCount += entry.size; + } - this.untrackEntry(entry); + // Important safety feature. + if (this.options.rejectAbsurdFees && entry.fee > minFee * 10000) + throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0); - if (this.fees) - this.fees.removeTX(hash); + // Why do we have this here? Nested transactions are cool. + if (this.countAncestors(entry) + 1 > this.options.maxAncestors) { + throw new VerifyError(tx, + 'nonstandard', + 'too-long-mempool-chain', + 0); + } - this.cache.remove(tx.hash()); + // Contextual sanity checks. + const [fee, reason, score] = tx.checkInputs(view, height); - this.emit('remove entry', entry); -}; + if (fee === -1) + throw new VerifyError(tx, 'invalid', reason, score); -/** - * Remove a transaction from the mempool. - * Recursively remove its spenders. - * @param {MempoolEntry} entry - */ + // Script verification. + let flags = Script.flags.STANDARD_VERIFY_FLAGS; + try { + await this.verifyInputs(tx, view, flags); + } catch (err) { + if (tx.hasWitness()) + throw err; -Mempool.prototype.evictEntry = function evictEntry(entry) { - this.removeSpenders(entry); - this.updateAncestors(entry, removeFee); - this.removeEntry(entry); -}; + // Try without segwit and cleanstack. + flags &= ~Script.flags.VERIFY_WITNESS; + flags &= ~Script.flags.VERIFY_CLEANSTACK; -/** - * Recursively remove spenders of a transaction. - * @private - * @param {MempoolEntry} entry - */ + // If it failed, the first verification + // was the only result we needed. + if (!await this.verifyResult(tx, view, flags)) + throw err; -Mempool.prototype.removeSpenders = function removeSpenders(entry) { - const tx = entry.tx; - const hash = tx.hash('hex'); + // If it succeeded, segwit may be causing the + // failure. Try with segwit but without cleanstack. + flags |= Script.flags.VERIFY_WITNESS; - for (let i = 0; i < tx.outputs.length; i++) { - const spender = this.getSpent(hash, i); + // Cleanstack was causing the failure. + if (await this.verifyResult(tx, view, flags)) + throw err; - if (!spender) - continue; + // Do not insert into reject cache. + err.malleated = true; + throw err; + } - this.removeSpenders(spender); - this.removeEntry(spender); + // Paranoid checks. + if (this.options.paranoidChecks) { + const flags = Script.flags.MANDATORY_VERIFY_FLAGS; + assert(await this.verifyResult(tx, view, flags), + 'BUG: Verify failed for mandatory but not standard.'); + } } -}; - -/** - * Count the highest number of - * ancestors a transaction may have. - * @param {MempoolEntry} entry - * @returns {Number} - */ - -Mempool.prototype.countAncestors = function countAncestors(entry) { - return this._countAncestors(entry, new Set(), entry, nop); -}; - -/** - * Count the highest number of - * ancestors a transaction may have. - * Update descendant fees and size. - * @param {MempoolEntry} entry - * @param {Function} map - * @returns {Number} - */ - -Mempool.prototype.updateAncestors = function updateAncestors(entry, map) { - return this._countAncestors(entry, new Set(), entry, map); -}; - -/** - * Traverse ancestors and count. - * @private - * @param {MempoolEntry} entry - * @param {Object} set - * @param {MempoolEntry} child - * @param {Function} map - * @returns {Number} - */ - -Mempool.prototype._countAncestors = function _countAncestors(entry, set, child, map) { - const tx = entry.tx; - - for (const {prevout} of tx.inputs) { - const hash = prevout.hash; - const parent = this.getEntry(hash); - - if (!parent) - continue; - - if (set.has(hash)) - continue; - - set.add(hash); - - map(parent, child); - if (set.size > this.options.maxAncestors) - break; + /** + * Verify inputs, return a boolean + * instead of an error based on success. + * @method + * @param {TX} tx + * @param {CoinView} view + * @param {VerifyFlags} flags + * @returns {Promise} + */ - this._countAncestors(parent, set, child, map); - - if (set.size > this.options.maxAncestors) - break; + async verifyResult(tx, view, flags) { + try { + await this.verifyInputs(tx, view, flags); + } catch (err) { + if (err.type === 'VerifyError') + return false; + throw err; + } + return true; } - return set.size; -}; - -/** - * Count the highest number of - * descendants a transaction may have. - * @param {MempoolEntry} entry - * @returns {Number} - */ - -Mempool.prototype.countDescendants = function countDescendants(entry) { - return this._countDescendants(entry, new Set()); -}; - -/** - * Count the highest number of - * descendants a transaction may have. - * @private - * @param {MempoolEntry} entry - * @param {Object} set - * @returns {Number} - */ - -Mempool.prototype._countDescendants = function _countDescendants(entry, set) { - const tx = entry.tx; - const hash = tx.hash('hex'); - - for (let i = 0; i < tx.outputs.length; i++) { - const child = this.getSpent(hash, i); + /** + * Verify inputs for standard + * _and_ mandatory flags on failure. + * @method + * @param {TX} tx + * @param {CoinView} view + * @param {VerifyFlags} flags + * @returns {Promise} + */ - if (!child) - continue; + async verifyInputs(tx, view, flags) { + if (await tx.verifyAsync(view, flags, this.workers)) + return; - const next = child.hash('hex'); + if (flags & Script.flags.ONLY_STANDARD_VERIFY_FLAGS) { + flags &= ~Script.flags.ONLY_STANDARD_VERIFY_FLAGS; - if (set.has(next)) - continue; - - set.add(next); + if (await tx.verifyAsync(view, flags, this.workers)) { + throw new VerifyError(tx, + 'nonstandard', + 'non-mandatory-script-verify-flag', + 0); + } + } - this._countDescendants(child, set); + throw new VerifyError(tx, + 'nonstandard', + 'mandatory-script-verify-flag', + 100); } - return set.size; -}; - -/** - * Get all transaction ancestors. - * @param {MempoolEntry} entry - * @returns {MempoolEntry[]} - */ + /** + * Add a transaction to the mempool without performing any + * validation. Note that this method does not lock the mempool + * and may lend itself to race conditions if used unwisely. + * This function will also resolve orphans if possible (the + * resolved orphans _will_ be validated). + * @method + * @param {MempoolEntry} entry + * @param {CoinView} view + * @returns {Promise} + */ -Mempool.prototype.getAncestors = function getAncestors(entry) { - return this._getAncestors(entry, [], new Set()); -}; + async addEntry(entry, view) { + const tx = entry.tx; -/** - * Get all transaction ancestors. - * @private - * @param {MempoolEntry} entry - * @param {MempoolEntry[]} entries - * @param {Object} set - * @returns {MempoolEntry[]} - */ + this.trackEntry(entry, view); -Mempool.prototype._getAncestors = function _getAncestors(entry, entries, set) { - const tx = entry.tx; + this.updateAncestors(entry, addFee); - for (const {prevout} of tx.inputs) { - const hash = prevout.hash; - const parent = this.getEntry(hash); + this.emit('tx', tx, view); + this.emit('add entry', entry); - if (!parent) - continue; + if (this.fees) + this.fees.processTX(entry, this.chain.synced); - if (set.has(hash)) - continue; + this.logger.debug( + 'Added %h to mempool (txs=%d).', + tx.hash(), this.map.size); - set.add(hash); - entries.push(parent); + this.cache.save(entry); - this._getAncestors(parent, entries, set); + await this.handleOrphans(tx); } - return entries; -}; - -/** - * Get all a transaction descendants. - * @param {MempoolEntry} entry - * @returns {MempoolEntry[]} - */ - -Mempool.prototype.getDescendants = function getDescendants(entry) { - return this._getDescendants(entry, [], new Set()); -}; - -/** - * Get all a transaction descendants. - * @param {MempoolEntry} entry - * @param {MempoolEntry[]} entries - * @param {Object} set - * @returns {MempoolEntry[]} - */ + /** + * Remove a transaction from the mempool. + * Generally only called when a new block + * is added to the main chain. + * @param {MempoolEntry} entry + */ -Mempool.prototype._getDescendants = function _getDescendants(entry, entries, set) { - const tx = entry.tx; - const hash = tx.hash('hex'); - - for (let i = 0; i < tx.outputs.length; i++) { - const child = this.getSpent(hash, i); - - if (!child) - continue; + removeEntry(entry) { + const tx = entry.tx; + const hash = tx.hash(); - const next = child.hash('hex'); + this.untrackEntry(entry); - if (set.has(next)) - continue; + if (this.fees) + this.fees.removeTX(hash); - set.add(next); - entries.push(child); + this.cache.remove(hash); - this._getDescendants(child, entries, set); + this.emit('remove entry', entry); } - return entries; -}; - -/** - * Find a unconfirmed transactions that - * this transaction depends on. - * @param {TX} tx - * @returns {Hash[]} - */ - -Mempool.prototype.getDepends = function getDepends(tx) { - const prevout = tx.getPrevout(); - const depends = []; + /** + * Remove a transaction from the mempool. + * Recursively remove its spenders. + * @param {MempoolEntry} entry + */ - for (const hash of prevout) { - if (this.hasEntry(hash)) - depends.push(hash); + evictEntry(entry) { + this.removeSpenders(entry); + this.updateAncestors(entry, removeFee); + this.removeEntry(entry); } - return depends; -}; - -/** - * Test whether a transaction has dependencies. - * @param {TX} tx - * @returns {Boolean} - */ + /** + * Recursively remove spenders of a transaction. + * @private + * @param {MempoolEntry} entry + */ -Mempool.prototype.hasDepends = function hasDepends(tx) { - for (const {prevout} of tx.inputs) { - if (this.hasEntry(prevout.hash)) - return true; - } - return false; -}; + removeSpenders(entry) { + const tx = entry.tx; + const hash = tx.hash(); -/** - * Return the full balance of all unspents in the mempool - * (not very useful in practice, only used for testing). - * @returns {Amount} - */ + for (let i = 0; i < tx.outputs.length; i++) { + const spender = this.getSpent(hash, i); -Mempool.prototype.getBalance = function getBalance() { - let total = 0; + if (!spender) + continue; - for (const [hash, entry] of this.map) { - const tx = entry.tx; - for (let i = 0; i < tx.outputs.length; i++) { - const coin = this.getCoin(hash, i); - if (coin) - total += coin.value; + this.removeSpenders(spender); + this.removeEntry(spender); } } - return total; -}; + /** + * Count the highest number of + * ancestors a transaction may have. + * @param {MempoolEntry} entry + * @returns {Number} + */ -/** - * Retrieve _all_ transactions from the mempool. - * @returns {TX[]} - */ + countAncestors(entry) { + return this._countAncestors(entry, new BufferSet(), entry, nop); + } -Mempool.prototype.getHistory = function getHistory() { - const txs = []; + /** + * Count the highest number of + * ancestors a transaction may have. + * Update descendant fees and size. + * @param {MempoolEntry} entry + * @param {Function} map + * @returns {Number} + */ - for (const entry of this.map.values()) - txs.push(entry.tx); + updateAncestors(entry, map) { + return this._countAncestors(entry, new BufferSet(), entry, map); + } - return txs; -}; + /** + * Traverse ancestors and count. + * @private + * @param {MempoolEntry} entry + * @param {Object} set + * @param {MempoolEntry} child + * @param {Function} map + * @returns {Number} + */ -/** - * Retrieve an orphan transaction. - * @param {Hash} hash - * @returns {TX} - */ + _countAncestors(entry, set, child, map) { + const tx = entry.tx; -Mempool.prototype.getOrphan = function getOrphan(hash) { - return this.orphans.get(hash); -}; + for (const {prevout} of tx.inputs) { + const hash = prevout.hash; + const parent = this.getEntry(hash); -/** - * @param {Hash} hash - * @returns {Boolean} - */ + if (!parent) + continue; -Mempool.prototype.hasOrphan = function hasOrphan(hash) { - return this.orphans.has(hash); -}; + if (set.has(hash)) + continue; -/** - * Maybe store an orphaned transaction. - * @param {TX} tx - * @param {CoinView} view - * @param {Number} id - */ + set.add(hash); -Mempool.prototype.maybeOrphan = function maybeOrphan(tx, view, id) { - const hashes = new Set(); - const missing = []; + map(parent, child); - for (const {prevout} of tx.inputs) { - if (view.hasEntry(prevout)) - continue; + if (set.size > this.options.maxAncestors) + break; - if (this.hasReject(prevout.hash)) { - this.logger.debug('Not storing orphan %s (rejected parents).', tx.txid()); - this.rejects.add(tx.hash()); - return missing; - } + this._countAncestors(parent, set, child, map); - if (this.hasEntry(prevout.hash)) { - this.logger.debug( - 'Not storing orphan %s (non-existent output).', - tx.txid()); - this.rejects.add(tx.hash()); - return missing; + if (set.size > this.options.maxAncestors) + break; } - hashes.add(prevout.hash); - } - - // Not an orphan. - if (hashes.size === 0) - return null; - - // Weight limit for orphans. - if (tx.getWeight() > policy.MAX_TX_WEIGHT) { - this.logger.debug('Ignoring large orphan: %s', tx.txid()); - if (!tx.hasWitness()) - this.rejects.add(tx.hash()); - return missing; + return set.size; } - if (this.options.maxOrphans === 0) - return missing; - - this.limitOrphans(); - - const hash = tx.hash('hex'); - - for (const prev of hashes.keys()) { - if (!this.waiting.has(prev)) - this.waiting.set(prev, new Set()); + /** + * Count the highest number of + * descendants a transaction may have. + * @param {MempoolEntry} entry + * @returns {Number} + */ - this.waiting.get(prev).add(hash); - - missing.push(prev); + countDescendants(entry) { + return this._countDescendants(entry, new BufferSet()); } - this.orphans.set(hash, new Orphan(tx, missing.length, id)); - - this.logger.debug('Added orphan %s to mempool.', tx.txid()); - - this.emit('add orphan', tx); + /** + * Count the highest number of + * descendants a transaction may have. + * @private + * @param {MempoolEntry} entry + * @param {Object} set + * @returns {Number} + */ - return missing; -}; - -/** - * Resolve orphans and attempt to add to mempool. - * @method - * @param {TX} parent - * @returns {Promise} - Returns {@link TX}[]. - */ - -Mempool.prototype.handleOrphans = async function handleOrphans(parent) { - const resolved = this.resolveOrphans(parent); - - for (const orphan of resolved) { - let tx, missing; - - try { - tx = orphan.toTX(); - } catch (e) { - this.logger.warning('%s %s', - 'Warning: possible memory corruption.', - 'Orphan failed deserialization.'); - continue; - } + _countDescendants(entry, set) { + const tx = entry.tx; + const hash = tx.hash(); - try { - missing = await this.insertTX(tx, orphan.id); - } catch (err) { - if (err.type === 'VerifyError') { - this.logger.debug( - 'Could not resolve orphan %s: %s.', - tx.txid(), err.message); + for (let i = 0; i < tx.outputs.length; i++) { + const child = this.getSpent(hash, i); - if (!tx.hasWitness() && !err.malleated) - this.rejects.add(tx.hash()); + if (!child) + continue; - this.emit('bad orphan', err, orphan.id); + const next = child.hash(); + if (set.has(next)) continue; - } - throw err; - } - // Can happen if an existing parent is - // evicted in the interim between fetching - // the non-present parents. - if (missing && missing.length > 0) { - this.logger.debug( - 'Transaction %s was double-orphaned in mempool.', - tx.txid()); - this.removeOrphan(tx.hash('hex')); - continue; + set.add(next); + + this._countDescendants(child, set); } - this.logger.debug('Resolved orphan %s in mempool.', tx.txid()); + return set.size; } -}; -/** - * Potentially resolve any transactions - * that redeem the passed-in transaction. - * Deletes all orphan entries and - * returns orphan objects. - * @param {TX} parent - * @returns {Orphan[]} - */ + /** + * Get all transaction ancestors. + * @param {MempoolEntry} entry + * @returns {MempoolEntry[]} + */ -Mempool.prototype.resolveOrphans = function resolveOrphans(parent) { - const hash = parent.hash('hex'); - const set = this.waiting.get(hash); + getAncestors(entry) { + return this._getAncestors(entry, [], new BufferSet()); + } - if (!set) - return []; + /** + * Get all transaction ancestors. + * @private + * @param {MempoolEntry} entry + * @param {MempoolEntry[]} entries + * @param {Object} set + * @returns {MempoolEntry[]} + */ - assert(set.size > 0); + _getAncestors(entry, entries, set) { + const tx = entry.tx; - const resolved = []; + for (const {prevout} of tx.inputs) { + const hash = prevout.hash; + const parent = this.getEntry(hash); - for (const hash of set.keys()) { - const orphan = this.getOrphan(hash); + if (!parent) + continue; - assert(orphan); + if (set.has(hash)) + continue; - if (--orphan.missing === 0) { - this.orphans.delete(hash); - resolved.push(orphan); + set.add(hash); + entries.push(parent); + + this._getAncestors(parent, entries, set); } + + return entries; } - this.waiting.delete(hash); + /** + * Get all a transaction descendants. + * @param {MempoolEntry} entry + * @returns {MempoolEntry[]} + */ - return resolved; -}; + getDescendants(entry) { + return this._getDescendants(entry, [], new BufferSet()); + } -/** - * Remove a transaction from the mempool. - * @param {Hash} tx - * @returns {Boolean} - */ + /** + * Get all a transaction descendants. + * @param {MempoolEntry} entry + * @param {MempoolEntry[]} entries + * @param {Object} set + * @returns {MempoolEntry[]} + */ -Mempool.prototype.removeOrphan = function removeOrphan(hash) { - const orphan = this.getOrphan(hash); + _getDescendants(entry, entries, set) { + const tx = entry.tx; + const hash = tx.hash(); - if (!orphan) - return false; + for (let i = 0; i < tx.outputs.length; i++) { + const child = this.getSpent(hash, i); - let tx; - try { - tx = orphan.toTX(); - } catch (e) { - this.orphans.delete(hash); - this.logger.warning('%s %s', - 'Warning: possible memory corruption.', - 'Orphan failed deserialization.'); - return false; - } + if (!child) + continue; - for (const prev of tx.getPrevout()) { - const set = this.waiting.get(prev); + const next = child.hash(); - if (!set) - continue; + if (set.has(next)) + continue; - assert(set.has(hash)); + set.add(next); + entries.push(child); - set.delete(hash); + this._getDescendants(child, entries, set); + } - if (set.size === 0) - this.waiting.delete(prev); + return entries; } - this.orphans.delete(hash); + /** + * Find a unconfirmed transactions that + * this transaction depends on. + * @param {TX} tx + * @returns {Hash[]} + */ - this.emit('remove orphan', tx); + getDepends(tx) { + const prevout = tx.getPrevout(); + const depends = []; - return true; -}; + for (const hash of prevout) { + if (this.hasEntry(hash)) + depends.push(hash); + } -/** - * Remove a random orphan transaction from the mempool. - * @returns {Boolean} - */ + return depends; + } + + /** + * Test whether a transaction has dependencies. + * @param {TX} tx + * @returns {Boolean} + */ -Mempool.prototype.limitOrphans = function limitOrphans() { - if (this.orphans.size < this.options.maxOrphans) + hasDepends(tx) { + for (const {prevout} of tx.inputs) { + if (this.hasEntry(prevout.hash)) + return true; + } return false; + } - let index = random.randomRange(0, this.orphans.size); + /** + * Return the full balance of all unspents in the mempool + * (not very useful in practice, only used for testing). + * @returns {Amount} + */ - let hash; - for (hash of this.orphans.keys()) { - if (index === 0) - break; - index--; - } + getBalance() { + let total = 0; - assert(hash); + for (const [hash, entry] of this.map) { + const tx = entry.tx; + for (let i = 0; i < tx.outputs.length; i++) { + const coin = this.getCoin(hash, i); + if (coin) + total += coin.value; + } + } - this.logger.debug('Removing orphan %s from mempool.', util.revHex(hash)); + return total; + } - this.removeOrphan(hash); + /** + * Retrieve _all_ transactions from the mempool. + * @returns {TX[]} + */ - return true; -}; + getHistory() { + const txs = []; -/** - * Test all of a transactions outpoints to see if they are doublespends. - * Note that this will only test against the mempool spents, not the - * blockchain's. The blockchain spents are not checked against because - * the blockchain does not maintain a spent list. The transaction will - * be seen as an orphan rather than a double spend. - * @param {TX} tx - * @returns {Promise} - Returns Boolean. - */ + for (const entry of this.map.values()) + txs.push(entry.tx); -Mempool.prototype.isDoubleSpend = function isDoubleSpend(tx) { - for (const {prevout} of tx.inputs) { - const {hash, index} = prevout; - if (this.isSpent(hash, index)) - return true; + return txs; } - return false; -}; + /** + * Retrieve an orphan transaction. + * @param {Hash} hash + * @returns {TX} + */ -/** - * Get coin viewpoint (lock). - * @method - * @param {TX} tx - * @returns {Promise} - Returns {@link CoinView}. - */ - -Mempool.prototype.getSpentView = async function getSpentView(tx) { - const unlock = await this.locker.lock(); - try { - return await this.getCoinView(tx); - } finally { - unlock(); + getOrphan(hash) { + return this.orphans.get(hash); } -}; -/** - * Get coin viewpoint (no lock). - * @method - * @param {TX} tx - * @returns {Promise} - Returns {@link CoinView}. - */ + /** + * @param {Hash} hash + * @returns {Boolean} + */ -Mempool.prototype.getCoinView = async function getCoinView(tx) { - const view = new CoinView(); + hasOrphan(hash) { + return this.orphans.has(hash); + } - for (const {prevout} of tx.inputs) { - const {hash, index} = prevout; - const tx = this.getTX(hash); + /** + * Maybe store an orphaned transaction. + * @param {TX} tx + * @param {CoinView} view + * @param {Number} id + */ - if (tx) { - if (index < tx.outputs.length) - view.addIndex(tx, index, -1); - continue; - } + maybeOrphan(tx, view, id) { + const hashes = new BufferSet(); + const missing = []; - const coin = await this.chain.readCoin(prevout); + for (const {prevout} of tx.inputs) { + if (view.hasEntry(prevout)) + continue; - if (coin) - view.addEntry(prevout, coin); - } + if (this.hasReject(prevout.hash)) { + this.logger.debug( + 'Not storing orphan %h (rejected parents).', + tx.hash()); + this.rejects.add(tx.hash()); + return missing; + } - return view; -}; + if (this.hasEntry(prevout.hash)) { + this.logger.debug( + 'Not storing orphan %h (non-existent output).', + tx.hash()); + this.rejects.add(tx.hash()); + return missing; + } -/** - * Get a snapshot of all transaction hashes in the mempool. Used - * for generating INV packets in response to MEMPOOL packets. - * @returns {Hash[]} - */ + hashes.add(prevout.hash); + } -Mempool.prototype.getSnapshot = function getSnapshot() { - const keys = []; + // Not an orphan. + if (hashes.size === 0) + return null; - for (const hash of this.map.keys()) - keys.push(hash); + // Weight limit for orphans. + if (tx.getWeight() > policy.MAX_TX_WEIGHT) { + this.logger.debug('Ignoring large orphan: %h', tx.hash()); + if (!tx.hasWitness()) + this.rejects.add(tx.hash()); + return missing; + } - return keys; -}; + if (this.options.maxOrphans === 0) + return missing; -/** - * Check sequence locks on a transaction against the current tip. - * @param {TX} tx - * @param {CoinView} view - * @param {LockFlags} flags - * @returns {Promise} - Returns Boolean. - */ + this.limitOrphans(); -Mempool.prototype.verifyLocks = function verifyLocks(tx, view, flags) { - return this.chain.verifyLocks(this.chain.tip, tx, view, flags); -}; + const hash = tx.hash(); -/** - * Check locktime on a transaction against the current tip. - * @param {TX} tx - * @param {LockFlags} flags - * @returns {Promise} - Returns Boolean. - */ + for (const prev of hashes.keys()) { + if (!this.waiting.has(prev)) + this.waiting.set(prev, new BufferSet()); -Mempool.prototype.verifyFinal = function verifyFinal(tx, flags) { - return this.chain.verifyFinal(this.chain.tip, tx, flags); -}; + this.waiting.get(prev).add(hash); -/** - * Map a transaction to the mempool. - * @private - * @param {MempoolEntry} entry - * @param {CoinView} view - */ + missing.push(prev); + } -Mempool.prototype.trackEntry = function trackEntry(entry, view) { - const tx = entry.tx; - const hash = tx.hash('hex'); + this.orphans.set(hash, new Orphan(tx, missing.length, id)); - assert(!this.map.has(hash)); - this.map.set(hash, entry); + this.logger.debug('Added orphan %h to mempool.', tx.hash()); - assert(!tx.isCoinbase()); + this.emit('add orphan', tx); - for (const {prevout} of tx.inputs) { - const key = prevout.toKey(); - this.spents.set(key, entry); + return missing; } - if (this.options.indexAddress && view) - this.indexEntry(entry, view); + /** + * Resolve orphans and attempt to add to mempool. + * @method + * @param {TX} parent + * @returns {Promise} - Returns {@link TX}[]. + */ - this.size += entry.memUsage(); -}; + async handleOrphans(parent) { + const resolved = this.resolveOrphans(parent); -/** - * Unmap a transaction from the mempool. - * @private - * @param {MempoolEntry} entry - */ + for (const orphan of resolved) { + let tx, missing; -Mempool.prototype.untrackEntry = function untrackEntry(entry) { - const tx = entry.tx; - const hash = tx.hash('hex'); + try { + tx = orphan.toTX(); + } catch (e) { + this.logger.warning('%s %s', + 'Warning: possible memory corruption.', + 'Orphan failed deserialization.'); + continue; + } - assert(this.map.has(hash)); - this.map.delete(hash); + try { + missing = await this.insertTX(tx, orphan.id); + } catch (err) { + if (err.type === 'VerifyError') { + this.logger.debug( + 'Could not resolve orphan %h: %s.', + tx.hash(), err.message); - assert(!tx.isCoinbase()); + if (!tx.hasWitness() && !err.malleated) + this.rejects.add(tx.hash()); - for (const {prevout} of tx.inputs) { - const key = prevout.toKey(); - this.spents.delete(key); - } + this.emit('bad orphan', err, orphan.id); - if (this.options.indexAddress) - this.unindexEntry(entry); + continue; + } + throw err; + } - this.size -= entry.memUsage(); -}; + // Can happen if an existing parent is + // evicted in the interim between fetching + // the non-present parents. + if (missing && missing.length > 0) { + this.logger.debug( + 'Transaction %h was double-orphaned in mempool.', + tx.hash()); + this.removeOrphan(tx.hash()); + continue; + } -/** - * Index an entry by address. - * @private - * @param {MempoolEntry} entry - * @param {CoinView} view - */ + this.logger.debug('Resolved orphan %h in mempool.', tx.hash()); + } + } -Mempool.prototype.indexEntry = function indexEntry(entry, view) { - const tx = entry.tx; + /** + * Potentially resolve any transactions + * that redeem the passed-in transaction. + * Deletes all orphan entries and + * returns orphan objects. + * @param {TX} parent + * @returns {Orphan[]} + */ - this.txIndex.insert(entry, view); + resolveOrphans(parent) { + const hash = parent.hash(); + const set = this.waiting.get(hash); - for (const {prevout} of tx.inputs) { - const {hash, index} = prevout; - this.coinIndex.remove(hash, index); - } + if (!set) + return []; - for (let i = 0; i < tx.outputs.length; i++) - this.coinIndex.insert(tx, i); -}; + assert(set.size > 0); -/** - * Unindex an entry by address. - * @private - * @param {MempoolEntry} entry - */ + const resolved = []; -Mempool.prototype.unindexEntry = function unindexEntry(entry) { - const tx = entry.tx; - const hash = tx.hash('hex'); + for (const hash of set.keys()) { + const orphan = this.getOrphan(hash); - this.txIndex.remove(hash); + assert(orphan); - for (const {prevout} of tx.inputs) { - const {hash, index} = prevout; - const prev = this.getTX(hash); + if (--orphan.missing === 0) { + this.orphans.delete(hash); + resolved.push(orphan); + } + } - if (!prev) - continue; + this.waiting.delete(hash); - this.coinIndex.insert(prev, index); + return resolved; } - for (let i = 0; i < tx.outputs.length; i++) - this.coinIndex.remove(hash, i); -}; + /** + * Remove a transaction from the mempool. + * @param {Hash} tx + * @returns {Boolean} + */ -/** - * Recursively remove double spenders - * of a mined transaction's outpoints. - * @private - * @param {TX} tx - */ + removeOrphan(hash) { + const orphan = this.getOrphan(hash); -Mempool.prototype.removeDoubleSpends = function removeDoubleSpends(tx) { - for (const {prevout} of tx.inputs) { - const {hash, index} = prevout; - const spent = this.getSpent(hash, index); + if (!orphan) + return false; - if (!spent) - continue; + let tx; + try { + tx = orphan.toTX(); + } catch (e) { + this.orphans.delete(hash); + this.logger.warning('%s %s', + 'Warning: possible memory corruption.', + 'Orphan failed deserialization.'); + return false; + } - this.logger.debug( - 'Removing double spender from mempool: %s.', - spent.txid()); + for (const prev of tx.getPrevout()) { + const set = this.waiting.get(prev); - this.evictEntry(spent); + if (!set) + continue; - this.emit('double spend', spent); - } -}; + assert(set.has(hash)); -/** - * Calculate the memory usage of the entire mempool. - * @see DynamicMemoryUsage() - * @returns {Number} Usage in bytes. - */ + set.delete(hash); -Mempool.prototype.getSize = function getSize() { - return this.size; -}; + if (set.size === 0) + this.waiting.delete(prev); + } -/** - * Prioritise transaction. - * @param {MempoolEntry} entry - * @param {Number} pri - * @param {Amount} fee - */ + this.orphans.delete(hash); -Mempool.prototype.prioritise = function prioritise(entry, pri, fee) { - if (-pri > entry.priority) - pri = -entry.priority; + this.emit('remove orphan', tx); - entry.priority += pri; + return true; + } - if (-fee > entry.deltaFee) - fee = -entry.deltaFee; + /** + * Remove a random orphan transaction from the mempool. + * @returns {Boolean} + */ - if (fee === 0) - return; + limitOrphans() { + if (this.orphans.size < this.options.maxOrphans) + return false; - this.updateAncestors(entry, prePrioritise); + let index = random.randomRange(0, this.orphans.size); - entry.deltaFee += fee; - entry.descFee += fee; + let hash; + for (hash of this.orphans.keys()) { + if (index === 0) + break; + index--; + } - this.updateAncestors(entry, postPrioritise); -}; + assert(hash); -/** - * MempoolOptions - * @alias module:mempool.MempoolOptions - * @constructor - * @param {Object} - */ + this.logger.debug('Removing orphan %h from mempool.', hash); -function MempoolOptions(options) { - if (!(this instanceof MempoolOptions)) - return new MempoolOptions(options); - - this.network = Network.primary; - this.chain = null; - this.logger = null; - this.workers = null; - this.fees = null; - - this.limitFree = true; - this.limitFreeRelay = 15; - this.relayPriority = true; - this.requireStandard = this.network.requireStandard; - this.rejectAbsurdFees = true; - this.prematureWitness = false; - this.paranoidChecks = false; - this.replaceByFee = false; - - this.maxSize = policy.MEMPOOL_MAX_SIZE; - this.maxOrphans = policy.MEMPOOL_MAX_ORPHANS; - this.maxAncestors = policy.MEMPOOL_MAX_ANCESTORS; - this.expiryTime = policy.MEMPOOL_EXPIRY_TIME; - this.minRelay = this.network.minRelay; - - this.prefix = null; - this.location = null; - this.db = 'memory'; - this.maxFiles = 64; - this.cacheSize = 32 << 20; - this.compression = true; - this.bufferKeys = layout.binary; - - this.persistent = false; - - this.fromOptions(options); -} + this.removeOrphan(hash); -/** - * Inject properties from object. - * @private - * @param {Object} options - * @returns {MempoolOptions} - */ + return true; + } -MempoolOptions.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Mempool requires options.'); - assert(options.chain && typeof options.chain === 'object', - 'Mempool requires a blockchain.'); + /** + * Test all of a transactions outpoints to see if they are doublespends. + * Note that this will only test against the mempool spents, not the + * blockchain's. The blockchain spents are not checked against because + * the blockchain does not maintain a spent list. The transaction will + * be seen as an orphan rather than a double spend. + * @param {TX} tx + * @returns {Promise} - Returns Boolean. + */ + + isDoubleSpend(tx) { + for (const {prevout} of tx.inputs) { + const {hash, index} = prevout; + if (this.isSpent(hash, index)) + return true; + } - this.chain = options.chain; - this.network = options.chain.network; - this.logger = options.chain.logger; - this.workers = options.chain.workers; + return false; + } - this.requireStandard = this.network.requireStandard; - this.minRelay = this.network.minRelay; + /** + * Get coin viewpoint (lock). + * Note: this does not return + * historical view of coins from the indexers. + * @method + * @param {TX} tx + * @returns {Promise} - Returns {@link CoinView}. + */ - if (options.logger != null) { - assert(typeof options.logger === 'object'); - this.logger = options.logger; + async getSpentView(tx) { + const unlock = await this.locker.lock(); + try { + return await this._getSpentView(tx); + } finally { + unlock(); + } } - if (options.workers != null) { - assert(typeof options.workers === 'object'); - this.workers = options.workers; - } + /** + * Get coin viewpoint + * @param {TX} tx + * @returns {Promise} - Returns {@link CoinView} + */ - if (options.fees != null) { - assert(typeof options.fees === 'object'); - this.fees = options.fees; - } + async _getSpentView(tx) { + const view = new CoinView(); - if (options.limitFree != null) { - assert(typeof options.limitFree === 'boolean'); - this.limitFree = options.limitFree; - } + for (const {prevout} of tx.inputs) { + const {hash, index} = prevout; + const tx = this.getTX(hash); - if (options.limitFreeRelay != null) { - assert(util.isU32(options.limitFreeRelay)); - this.limitFreeRelay = options.limitFreeRelay; - } + if (tx) { + if (index < tx.outputs.length) + view.addIndex(tx, index, -1); + continue; + } - if (options.relayPriority != null) { - assert(typeof options.relayPriority === 'boolean'); - this.relayPriority = options.relayPriority; - } + const coin = await this.chain.readCoin(prevout); - if (options.requireStandard != null) { - assert(typeof options.requireStandard === 'boolean'); - this.requireStandard = options.requireStandard; - } + if (coin) + view.addEntry(prevout, coin); + } - if (options.rejectAbsurdFees != null) { - assert(typeof options.rejectAbsurdFees === 'boolean'); - this.rejectAbsurdFees = options.rejectAbsurdFees; + return view; } - if (options.prematureWitness != null) { - assert(typeof options.prematureWitness === 'boolean'); - this.prematureWitness = options.prematureWitness; - } + /** + * Get coin viewpoint (no lock). + * @method + * @param {TX} tx + * @returns {Promise} - Returns {@link CoinView}. + */ - if (options.paranoidChecks != null) { - assert(typeof options.paranoidChecks === 'boolean'); - this.paranoidChecks = options.paranoidChecks; - } + async getCoinView(tx) { + const view = new CoinView(); - if (options.replaceByFee != null) { - assert(typeof options.replaceByFee === 'boolean'); - this.replaceByFee = options.replaceByFee; - } + for (const {prevout} of tx.inputs) { + const {hash, index} = prevout; + const tx = this.getTX(hash); - if (options.maxSize != null) { - assert(util.isU64(options.maxSize)); - this.maxSize = options.maxSize; - } + if (tx) { + if (this.hasCoin(hash, index)) + view.addIndex(tx, index, -1); + continue; + } - if (options.maxOrphans != null) { - assert(util.isU32(options.maxOrphans)); - this.maxOrphans = options.maxOrphans; - } + const coin = await this.chain.readCoin(prevout); - if (options.maxAncestors != null) { - assert(util.isU32(options.maxAncestors)); - this.maxAncestors = options.maxAncestors; - } + if (coin) + view.addEntry(prevout, coin); + } - if (options.expiryTime != null) { - assert(util.isU32(options.expiryTime)); - this.expiryTime = options.expiryTime; + return view; } - if (options.minRelay != null) { - assert(util.isU64(options.minRelay)); - this.minRelay = options.minRelay; - } + /** + * Get a snapshot of all transaction hashes in the mempool. Used + * for generating INV packets in response to MEMPOOL packets. + * @returns {Hash[]} + */ - if (options.prefix != null) { - assert(typeof options.prefix === 'string'); - this.prefix = options.prefix; - this.location = path.join(this.prefix, 'mempool'); - } + getSnapshot() { + const keys = []; - if (options.location != null) { - assert(typeof options.location === 'string'); - this.location = options.location; - } + for (const hash of this.map.keys()) + keys.push(hash); - if (options.db != null) { - assert(typeof options.db === 'string'); - this.db = options.db; + return keys; } - if (options.maxFiles != null) { - assert(util.isU32(options.maxFiles)); - this.maxFiles = options.maxFiles; - } + /** + * Check sequence locks on a transaction against the current tip. + * @param {TX} tx + * @param {CoinView} view + * @param {LockFlags} flags + * @returns {Promise} - Returns Boolean. + */ - if (options.cacheSize != null) { - assert(util.isU64(options.cacheSize)); - this.cacheSize = options.cacheSize; + verifyLocks(tx, view, flags) { + return this.chain.verifyLocks(this.chain.tip, tx, view, flags); } - if (options.compression != null) { - assert(typeof options.compression === 'boolean'); - this.compression = options.compression; - } + /** + * Check locktime on a transaction against the current tip. + * @param {TX} tx + * @param {LockFlags} flags + * @returns {Promise} - Returns Boolean. + */ - if (options.persistent != null) { - assert(typeof options.persistent === 'boolean'); - this.persistent = options.persistent; + verifyFinal(tx, flags) { + return this.chain.verifyFinal(this.chain.tip, tx, flags); } - if (options.indexAddress != null) { - assert(typeof options.indexAddress === 'boolean'); - this.indexAddress = options.indexAddress; - } + /** + * Map a transaction to the mempool. + * @private + * @param {MempoolEntry} entry + * @param {CoinView} view + */ - return this; -}; + trackEntry(entry, view) { + const tx = entry.tx; + const hash = tx.hash(); -/** - * Instantiate mempool options from object. - * @param {Object} options - * @returns {MempoolOptions} - */ + assert(!this.map.has(hash)); + this.map.set(hash, entry); -MempoolOptions.fromOptions = function fromOptions(options) { - return new MempoolOptions().fromOptions(options); -}; + assert(!tx.isCoinbase()); -/** - * TX Address Index - * @constructor - * @ignore - */ + for (const {prevout} of tx.inputs) { + const key = prevout.toKey(); + this.spents.set(key, entry); + } -function TXIndex() { - // Map of addr->entries. - this.index = new Map(); + if (this.options.indexAddress && view) + this.indexEntry(entry, view); - // Map of txid->addrs. - this.map = new Map(); -} + this.size += entry.memUsage(); + } + + /** + * Unmap a transaction from the mempool. + * @private + * @param {MempoolEntry} entry + */ -TXIndex.prototype.reset = function reset() { - this.index.clear(); - this.map.clear(); -}; + untrackEntry(entry) { + const tx = entry.tx; + const hash = tx.hash(); -TXIndex.prototype.get = function get(addr) { - const items = this.index.get(addr); + assert(this.map.has(hash)); + this.map.delete(hash); - if (!items) - return []; + assert(!tx.isCoinbase()); - const out = []; + for (const {prevout} of tx.inputs) { + const key = prevout.toKey(); + this.spents.delete(key); + } - for (const entry of items.values()) - out.push(entry.tx); + if (this.options.indexAddress) + this.unindexEntry(entry); - return out; -}; + this.size -= entry.memUsage(); + } -TXIndex.prototype.getMeta = function getMeta(addr) { - const items = this.index.get(addr); + /** + * Index an entry by address. + * @private + * @param {MempoolEntry} entry + * @param {CoinView} view + */ - if (!items) - return []; + indexEntry(entry, view) { + this.addrindex.insert(entry, view); + } - const out = []; + /** + * Unindex an entry by address. + * @private + * @param {MempoolEntry} entry + */ - for (const entry of items.values()) { - const meta = TXMeta.fromTX(entry.tx); - meta.mtime = entry.time; - out.push(meta); + unindexEntry(entry) { + const hash = entry.tx.hash(); + this.addrindex.remove(hash); } - return out; -}; + /** + * Recursively remove double spenders + * of a mined transaction's outpoints. + * @private + * @param {TX} tx + */ -TXIndex.prototype.insert = function insert(entry, view) { - const tx = entry.tx; - const hash = tx.hash('hex'); - const addrs = tx.getHashes(view, 'hex'); + removeDoubleSpends(tx) { + for (const {prevout} of tx.inputs) { + const {hash, index} = prevout; + const spent = this.getSpent(hash, index); - if (addrs.length === 0) - return; + if (!spent) + continue; + + this.logger.debug( + 'Removing double spender from mempool: %h.', + spent.hash()); - for (const addr of addrs) { - let items = this.index.get(addr); + this.evictEntry(spent); - if (!items) { - items = new Map(); - this.index.set(addr, items); + this.emit('double spend', spent); } + } - assert(!items.has(hash)); - items.set(hash, entry); + /** + * Calculate the memory usage of the entire mempool. + * @see DynamicMemoryUsage() + * @returns {Number} Usage in bytes. + */ + + getSize() { + return this.size; } - this.map.set(hash, addrs); -}; + /** + * Prioritise transaction. + * @param {MempoolEntry} entry + * @param {Number} pri + * @param {Amount} fee + */ -TXIndex.prototype.remove = function remove(hash) { - const addrs = this.map.get(hash); + prioritise(entry, pri, fee) { + if (-pri > entry.priority) + pri = -entry.priority; - if (!addrs) - return; + entry.priority += pri; - for (const addr of addrs) { - const items = this.index.get(addr); + if (-fee > entry.deltaFee) + fee = -entry.deltaFee; - assert(items); - assert(items.has(hash)); + if (fee === 0) + return; - items.delete(hash); + this.updateAncestors(entry, prePrioritise); - if (items.size === 0) - this.index.delete(addr); - } + entry.deltaFee += fee; + entry.descFee += fee; - this.map.delete(hash); -}; + this.updateAncestors(entry, postPrioritise); + } +} /** - * Coin Address Index - * @constructor - * @ignore + * Mempool Options + * @alias module:mempool.MempoolOptions */ -function CoinIndex() { - // Map of addr->coins. - this.index = new Map(); +class MempoolOptions { + /** + * Create mempool options. + * @constructor + * @param {Object} + */ + + constructor(options) { + this.network = Network.primary; + this.chain = null; + this.logger = null; + this.workers = null; + this.fees = null; + + this.limitFree = true; + this.limitFreeRelay = 15; + this.relayPriority = true; + this.requireStandard = this.network.requireStandard; + this.rejectAbsurdFees = true; + this.prematureWitness = false; + this.paranoidChecks = false; + this.replaceByFee = false; + + this.maxSize = policy.MEMPOOL_MAX_SIZE; + this.maxOrphans = policy.MEMPOOL_MAX_ORPHANS; + this.maxAncestors = policy.MEMPOOL_MAX_ANCESTORS; + this.expiryTime = policy.MEMPOOL_EXPIRY_TIME; + this.minRelay = this.network.minRelay; + + this.prefix = null; + this.location = null; + this.memory = true; + this.maxFiles = 64; + this.cacheSize = 32 << 20; + this.compression = true; + + this.persistent = false; + + this.fromOptions(options); + } + + /** + * Inject properties from object. + * @private + * @param {Object} options + * @returns {MempoolOptions} + */ + + fromOptions(options) { + assert(options, 'Mempool requires options.'); + assert(options.chain && typeof options.chain === 'object', + 'Mempool requires a blockchain.'); + + this.chain = options.chain; + this.network = options.chain.network; + this.logger = options.chain.logger; + this.workers = options.chain.workers; + + this.requireStandard = this.network.requireStandard; + this.minRelay = this.network.minRelay; + + if (options.logger != null) { + assert(typeof options.logger === 'object'); + this.logger = options.logger; + } - // Map of outpoint->addr. - this.map = new Map(); -} + if (options.workers != null) { + assert(typeof options.workers === 'object'); + this.workers = options.workers; + } -CoinIndex.prototype.reset = function reset() { - this.index.clear(); - this.map.clear(); -}; + if (options.fees != null) { + assert(typeof options.fees === 'object'); + this.fees = options.fees; + } -CoinIndex.prototype.get = function get(addr) { - const items = this.index.get(addr); + if (options.limitFree != null) { + assert(typeof options.limitFree === 'boolean'); + this.limitFree = options.limitFree; + } - if (!items) - return []; + if (options.limitFreeRelay != null) { + assert((options.limitFreeRelay >>> 0) === options.limitFreeRelay); + this.limitFreeRelay = options.limitFreeRelay; + } - const out = []; + if (options.relayPriority != null) { + assert(typeof options.relayPriority === 'boolean'); + this.relayPriority = options.relayPriority; + } - for (const coin of items.values()) - out.push(coin.toCoin()); + if (options.requireStandard != null) { + assert(typeof options.requireStandard === 'boolean'); + this.requireStandard = options.requireStandard; + } - return out; -}; + if (options.rejectAbsurdFees != null) { + assert(typeof options.rejectAbsurdFees === 'boolean'); + this.rejectAbsurdFees = options.rejectAbsurdFees; + } -CoinIndex.prototype.insert = function insert(tx, index) { - const output = tx.outputs[index]; - const hash = tx.hash('hex'); - const addr = output.getHash('hex'); + if (options.prematureWitness != null) { + assert(typeof options.prematureWitness === 'boolean'); + this.prematureWitness = options.prematureWitness; + } - if (!addr) - return; + if (options.paranoidChecks != null) { + assert(typeof options.paranoidChecks === 'boolean'); + this.paranoidChecks = options.paranoidChecks; + } - let items = this.index.get(addr); + if (options.replaceByFee != null) { + assert(typeof options.replaceByFee === 'boolean'); + this.replaceByFee = options.replaceByFee; + } - if (!items) { - items = new Map(); - this.index.set(addr, items); - } + if (options.maxSize != null) { + assert((options.maxSize >>> 0) === options.maxSize); + this.maxSize = options.maxSize; + } - const key = Outpoint.toKey(hash, index); + if (options.maxOrphans != null) { + assert((options.maxOrphans >>> 0) === options.maxOrphans); + this.maxOrphans = options.maxOrphans; + } + + if (options.maxAncestors != null) { + assert((options.maxAncestors >>> 0) === options.maxAncestors); + this.maxAncestors = options.maxAncestors; + } - assert(!items.has(key)); - items.set(key, new IndexedCoin(tx, index)); + if (options.expiryTime != null) { + assert((options.expiryTime >>> 0) === options.expiryTime); + this.expiryTime = options.expiryTime; + } - this.map.set(key, addr); -}; + if (options.minRelay != null) { + assert((options.minRelay >>> 0) === options.minRelay); + this.minRelay = options.minRelay; + } -CoinIndex.prototype.remove = function remove(hash, index) { - const key = Outpoint.toKey(hash, index); - const addr = this.map.get(key); + if (options.prefix != null) { + assert(typeof options.prefix === 'string'); + this.prefix = options.prefix; + this.location = path.join(this.prefix, 'mempool'); + } - if (!addr) - return; + if (options.location != null) { + assert(typeof options.location === 'string'); + this.location = options.location; + } - const items = this.index.get(addr); + if (options.memory != null) { + assert(typeof options.memory === 'boolean'); + this.memory = options.memory; + } - assert(items); - assert(items.has(key)); - items.delete(key); + if (options.maxFiles != null) { + assert((options.maxFiles >>> 0) === options.maxFiles); + this.maxFiles = options.maxFiles; + } - if (items.size === 0) - this.index.delete(addr); + if (options.cacheSize != null) { + assert(Number.isSafeInteger(options.cacheSize)); + assert(options.cacheSize >= 0); + this.cacheSize = options.cacheSize; + } - this.map.delete(key); -}; + if (options.compression != null) { + assert(typeof options.compression === 'boolean'); + this.compression = options.compression; + } -/** - * IndexedCoin - * @constructor - * @ignore - * @param {TX} tx - * @param {Number} index - */ + if (options.persistent != null) { + assert(typeof options.persistent === 'boolean'); + this.persistent = options.persistent; + } -function IndexedCoin(tx, index) { - this.tx = tx; - this.index = index; -} + if (options.indexAddress != null) { + assert(typeof options.indexAddress === 'boolean'); + this.indexAddress = options.indexAddress; + } + + return this; + } + + /** + * Instantiate mempool options from object. + * @param {Object} options + * @returns {MempoolOptions} + */ -IndexedCoin.prototype.toCoin = function toCoin() { - return Coin.fromTX(this.tx, this.index, -1); -}; + static fromOptions(options) { + return new MempoolOptions().fromOptions(options); + } +} /** * Orphan - * @constructor * @ignore - * @param {TX} tx - * @param {Hash[]} missing - * @param {Number} id */ -function Orphan(tx, missing, id) { - this.raw = tx.toRaw(); - this.missing = missing; - this.id = id; -} +class Orphan { + /** + * Create an orphan. + * @constructor + * @param {TX} tx + * @param {Hash[]} missing + * @param {Number} id + */ + + constructor(tx, missing, id) { + this.raw = tx.toRaw(); + this.missing = missing; + this.id = id; + } -Orphan.prototype.toTX = function toTX() { - return TX.fromRaw(this.raw); -}; + toTX() { + return TX.fromRaw(this.raw); + } +} /** * Mempool Cache * @ignore - * @constructor - * @param {Object} options */ -function MempoolCache(options) { - if (!(this instanceof MempoolCache)) - return new MempoolCache(options); +class MempoolCache { + /** + * Create a mempool cache. + * @constructor + * @param {Object} options + */ - this.logger = options.logger; - this.chain = options.chain; - this.network = options.network; - this.db = null; - this.batch = null; + constructor(options) { + this.logger = options.logger; + this.chain = options.chain; + this.network = options.network; + this.db = null; + this.batch = null; - if (options.persistent) - this.db = LDB(options); -} + if (options.persistent) + this.db = bdb.create(options); + } -MempoolCache.VERSION = 2; + async getVersion() { + const data = await this.db.get(layout.v.encode()); -MempoolCache.prototype.getVersion = async function getVersion() { - const data = await this.db.get(layout.V); + if (!data) + return -1; - if (!data) - return -1; + return data.readUInt32LE(0, true); + } - return data.readUInt32LE(0, true); -}; + async getTip() { + return this.db.get(layout.R.encode()); + } -MempoolCache.prototype.getTip = async function getTip() { - const hash = await this.db.get(layout.R); + async getFees() { + const data = await this.db.get(layout.F.encode()); - if (!hash) - return null; + if (!data) + return null; - return hash.toString('hex'); -}; + let fees = null; -MempoolCache.prototype.getFees = async function getFees() { - const data = await this.db.get(layout.F); + try { + fees = Fees.fromRaw(data); + } catch (e) { + this.logger.warning( + 'Fee data failed deserialization: %s.', + e.message); + } - if (!data) - return null; + return fees; + } - let fees; - try { - fees = Fees.fromRaw(data); - } catch (e) { - this.logger.warning( - 'Fee data failed deserialization: %s.', - e.message); + getEntries() { + return this.db.values({ + gte: layout.e.min(), + lte: layout.e.max(), + parse: data => MempoolEntry.fromRaw(data) + }); } - return fees; -}; + getKeys() { + return this.db.keys({ + gte: layout.e.min(), + lte: layout.e.max() + }); + } -MempoolCache.prototype.getEntries = function getEntries() { - return this.db.values({ - gte: layout.e(encoding.ZERO_HASH), - lte: layout.e(encoding.MAX_HASH), - parse: MempoolEntry.fromRaw - }); -}; + async open() { + if (!this.db) + return; -MempoolCache.prototype.getKeys = function getKeys() { - return this.db.keys({ - gte: layout.e(encoding.ZERO_HASH), - lte: layout.e(encoding.MAX_HASH) - }); -}; + await this.db.open(); + await this.db.verify(layout.V.encode(), 'mempool', 0); -MempoolCache.prototype.open = async function open() { - if (!this.db) - return; + await this.verify(); - await this.db.open(); - await this.verify(); + this.batch = this.db.batch(); + } - this.batch = this.db.batch(); -}; + async close() { + if (!this.db) + return; -MempoolCache.prototype.close = async function close() { - if (!this.db) - return; + await this.db.close(); - await this.db.close(); + this.batch = null; + } - this.batch = null; -}; + save(entry) { + if (!this.db) + return; -MempoolCache.prototype.save = function save(entry) { - if (!this.db) - return; + this.batch.put(layout.e.encode(entry.hash()), entry.toRaw()); + } - this.batch.put(layout.e(entry.tx.hash()), entry.toRaw()); -}; + remove(hash) { + if (!this.db) + return; -MempoolCache.prototype.remove = function remove(hash) { - if (!this.db) - return; + this.batch.del(layout.e.encode(hash)); + } - this.batch.del(layout.e(hash)); -}; + sync(tip) { + if (!this.db) + return; -MempoolCache.prototype.sync = function sync(hash) { - if (!this.db) - return; + this.batch.put(layout.R.encode(), tip); + } - this.batch.put(layout.R, Buffer.from(hash, 'hex')); -}; + writeFees(fees) { + if (!this.db) + return; -MempoolCache.prototype.writeFees = function writeFees(fees) { - if (!this.db) - return; + this.batch.put(layout.F.encode(), fees.toRaw()); + } - this.batch.put(layout.F, fees.toRaw()); -}; + clear() { + this.batch.clear(); + this.batch = this.db.batch(); + } -MempoolCache.prototype.clear = function clear() { - this.batch.clear(); - this.batch = this.db.batch(); -}; + async flush() { + if (!this.db) + return; -MempoolCache.prototype.flush = async function flush() { - if (!this.db) - return; + await this.batch.write(); - await this.batch.write(); + this.batch = this.db.batch(); + } - this.batch = this.db.batch(); -}; + async init(hash) { + const batch = this.db.batch(); + batch.put(layout.v.encode(), fromU32(MempoolCache.VERSION)); + batch.put(layout.R.encode(), hash); + await batch.write(); + } -MempoolCache.prototype.init = async function init(hash) { - const batch = this.db.batch(); - batch.put(layout.V, encoding.U32(MempoolCache.VERSION)); - batch.put(layout.R, Buffer.from(hash, 'hex')); - await batch.write(); -}; + async verify() { + let version = await this.getVersion(); + let tip; -MempoolCache.prototype.verify = async function verify() { - let version = await this.getVersion(); - let tip; + if (version === -1) { + version = MempoolCache.VERSION; + tip = this.chain.tip.hash; - if (version === -1) { - version = MempoolCache.VERSION; - tip = this.chain.tip.hash; + this.logger.info( + 'Mempool cache is empty. Writing tip %h.', + tip); - this.logger.info( - 'Mempool cache is empty. Writing tip %s.', - util.revHex(tip)); + await this.init(tip); + } - await this.init(tip); - } + if (version !== MempoolCache.VERSION) { + this.logger.warning( + 'Mempool cache version mismatch (%d != %d)!', + version, + MempoolCache.VERSION); + this.logger.warning('Invalidating mempool cache.'); + await this.wipe(); + return false; + } - if (version !== MempoolCache.VERSION) { - this.logger.warning( - 'Mempool cache version mismatch (%d != %d)!', - version, - MempoolCache.VERSION); - this.logger.warning('Invalidating mempool cache.'); - await this.wipe(); - return false; - } + tip = await this.getTip(); - tip = await this.getTip(); + if (!tip || !tip.equals(this.chain.tip.hash)) { + this.logger.warning( + 'Mempool tip not consistent with chain tip (%h != %h)!', + tip, + this.chain.tip.hash); + this.logger.warning('Invalidating mempool cache.'); + await this.wipe(); + return false; + } - if (tip !== this.chain.tip.hash) { - this.logger.warning( - 'Mempool tip not consistent with chain tip (%s != %s)!', - util.revHex(tip), - this.chain.tip.rhash()); - this.logger.warning('Invalidating mempool cache.'); - await this.wipe(); - return false; + return true; } - return true; -}; + async wipe() { + const batch = this.db.batch(); + const keys = await this.getKeys(); -MempoolCache.prototype.wipe = async function wipe() { - const batch = this.db.batch(); - const keys = await this.getKeys(); + for (const key of keys) + batch.del(key); - for (const key of keys) - batch.del(key); + batch.put(layout.v.encode(), fromU32(MempoolCache.VERSION)); + batch.put(layout.R.encode(), this.chain.tip.hash); + batch.del(layout.F.encode()); - batch.put(layout.V, encoding.U32(MempoolCache.VERSION)); - batch.put(layout.R, Buffer.from(this.chain.tip.hash, 'hex')); - batch.del(layout.F); + await batch.write(); - await batch.write(); + this.logger.info('Removed %d mempool entries from disk.', keys.length); + } +} - this.logger.info('Removed %d mempool entries from disk.', keys.length); -}; +MempoolCache.VERSION = 2; /* * Helpers @@ -2611,6 +2403,12 @@ function useDesc(a) { return y > x; } +function fromU32(num) { + const data = Buffer.allocUnsafe(4); + data.writeUInt32LE(num, 0, true); + return data; +} + /* * Expose */ diff --git a/lib/mempool/mempoolentry.js b/lib/mempool/mempoolentry.js index 2091e07f5..4fecb1ec1 100644 --- a/lib/mempool/mempoolentry.js +++ b/lib/mempool/mempoolentry.js @@ -6,23 +6,16 @@ 'use strict'; +const bio = require('bufio'); const policy = require('../protocol/policy'); const util = require('../utils/util'); const Script = require('../script/script'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); const TX = require('../primitives/tx'); /** + * Mempool Entry * Represents a mempool entry. * @alias module:mempool.MempoolEntry - * @constructor - * @param {Object} options - * @param {TX} options.tx - Transaction in mempool. - * @param {Number} options.height - Entry height. - * @param {Number} options.priority - Entry priority. - * @param {Number} options.time - Entry time. - * @param {Amount} options.value - Value of on-chain coins. * @property {TX} tx * @property {Number} height * @property {Number} priority @@ -30,337 +23,350 @@ const TX = require('../primitives/tx'); * @property {Amount} value */ -function MempoolEntry(options) { - if (!(this instanceof MempoolEntry)) - return new MempoolEntry(options); - - this.tx = null; - this.height = -1; - this.size = 0; - this.sigops = 0; - this.priority = 0; - this.fee = 0; - this.deltaFee = 0; - this.time = 0; - this.value = 0; - this.coinbase = false; - this.dependencies = false; - this.descFee = 0; - this.descSize = 0; - - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -MempoolEntry.prototype.fromOptions = function fromOptions(options) { - this.tx = options.tx; - this.height = options.height; - this.size = options.size; - this.sigops = options.sigops; - this.priority = options.priority; - this.fee = options.fee; - this.deltaFee = options.deltaFee; - this.time = options.time; - this.value = options.value; - this.coinbase = options.coinbase; - this.dependencies = options.dependencies; - this.descFee = options.descFee; - this.descSize = options.descSize; - return this; -}; - -/** - * Instantiate mempool entry from options. - * @param {Object} options - * @returns {MempoolEntry} - */ - -MempoolEntry.fromOptions = function fromOptions(options) { - return new MempoolEntry().fromOptions(options); -}; - -/** - * Inject properties from transaction. - * @private - * @param {TX} tx - * @param {Number} height - */ - -MempoolEntry.prototype.fromTX = function fromTX(tx, view, height) { - const flags = Script.flags.STANDARD_VERIFY_FLAGS; - const value = tx.getChainValue(view); - const sigops = tx.getSigopsCost(view, flags); - const size = tx.getSigopsSize(sigops); - const priority = tx.getPriority(view, height, size); - const fee = tx.getFee(view); +class MempoolEntry { + /** + * Create a mempool entry. + * @constructor + * @param {Object} options + * @param {TX} options.tx - Transaction in mempool. + * @param {Number} options.height - Entry height. + * @param {Number} options.priority - Entry priority. + * @param {Number} options.time - Entry time. + * @param {Amount} options.value - Value of on-chain coins. + */ + + constructor(options) { + this.tx = null; + this.height = -1; + this.size = 0; + this.sigops = 0; + this.priority = 0; + this.fee = 0; + this.deltaFee = 0; + this.time = 0; + this.value = 0; + this.coinbase = false; + this.dependencies = false; + this.descFee = 0; + this.descSize = 0; + + if (options) + this.fromOptions(options); + } - let dependencies = false; - let coinbase = false; + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + this.tx = options.tx; + this.height = options.height; + this.size = options.size; + this.sigops = options.sigops; + this.priority = options.priority; + this.fee = options.fee; + this.deltaFee = options.deltaFee; + this.time = options.time; + this.value = options.value; + this.coinbase = options.coinbase; + this.dependencies = options.dependencies; + this.descFee = options.descFee; + this.descSize = options.descSize; + return this; + } - for (const {prevout} of tx.inputs) { - if (view.isCoinbase(prevout)) - coinbase = true; + /** + * Instantiate mempool entry from options. + * @param {Object} options + * @returns {MempoolEntry} + */ - if (view.getHeight(prevout) === -1) - dependencies = true; + static fromOptions(options) { + return new this().fromOptions(options); } - this.tx = tx; - this.height = height; - this.size = size; - this.sigops = sigops; - this.priority = priority; - this.fee = fee; - this.deltaFee = fee; - this.time = util.now(); - this.value = value; - this.coinbase = coinbase; - this.dependencies = dependencies; - this.descFee = fee; - this.descSize = size; - - return this; -}; + /** + * Inject properties from transaction. + * @private + * @param {TX} tx + * @param {Number} height + */ + + fromTX(tx, view, height) { + const flags = Script.flags.STANDARD_VERIFY_FLAGS; + const value = tx.getChainValue(view); + const sigops = tx.getSigopsCost(view, flags); + const size = tx.getSigopsSize(sigops); + const priority = tx.getPriority(view, height, size); + const fee = tx.getFee(view); + + let dependencies = false; + let coinbase = false; + + for (const {prevout} of tx.inputs) { + if (view.isCoinbase(prevout)) + coinbase = true; + + if (view.getHeight(prevout) === -1) + dependencies = true; + } -/** - * Create a mempool entry from a TX. - * @param {TX} tx - * @param {Number} height - Entry height. - * @returns {MempoolEntry} - */ + this.tx = tx; + this.height = height; + this.size = size; + this.sigops = sigops; + this.priority = priority; + this.fee = fee; + this.deltaFee = fee; + this.time = util.now(); + this.value = value; + this.coinbase = coinbase; + this.dependencies = dependencies; + this.descFee = fee; + this.descSize = size; + + return this; + } -MempoolEntry.fromTX = function fromTX(tx, view, height) { - return new MempoolEntry().fromTX(tx, view, height); -}; + /** + * Create a mempool entry from a TX. + * @param {TX} tx + * @param {Number} height - Entry height. + * @returns {MempoolEntry} + */ -/** - * Calculate transaction hash. - * @param {String?} enc - * @returns {Hash} - */ + static fromTX(tx, view, height) { + return new this().fromTX(tx, view, height); + } -MempoolEntry.prototype.hash = function hash(enc) { - return this.tx.hash(enc); -}; + /** + * Calculate transaction hash. + * @param {String?} enc + * @returns {Hash} + */ -/** - * Calculate reverse transaction hash. - * @returns {Hash} - */ + hash(enc) { + return this.tx.hash(enc); + } -MempoolEntry.prototype.txid = function txid() { - return this.tx.txid(); -}; + /** + * Calculate reverse transaction hash. + * @returns {Hash} + */ -/** - * Calculate priority, taking into account - * the entry height delta, modified size, - * and chain value. - * @param {Number} height - * @returns {Number} Priority. - */ + txid() { + return this.tx.txid(); + } -MempoolEntry.prototype.getPriority = function getPriority(height) { - const delta = height - this.height; - const priority = (delta * this.value) / this.size; - let result = this.priority + Math.floor(priority); - if (result < 0) - result = 0; - return result; -}; + /** + * Calculate priority, taking into account + * the entry height delta, modified size, + * and chain value. + * @param {Number} height + * @returns {Number} Priority. + */ -/** - * Get fee. - * @returns {Amount} - */ + getPriority(height) { + const delta = height - this.height; + const priority = (delta * this.value) / this.size; -MempoolEntry.prototype.getFee = function getFee() { - return this.fee; -}; + let result = this.priority + Math.floor(priority); -/** - * Get delta fee. - * @returns {Amount} - */ + if (result < 0) + result = 0; -MempoolEntry.prototype.getDeltaFee = function getDeltaFee() { - return this.deltaFee; -}; + return result; + } -/** - * Calculate fee rate. - * @returns {Rate} - */ + /** + * Get fee. + * @returns {Amount} + */ -MempoolEntry.prototype.getRate = function getRate() { - return policy.getRate(this.size, this.fee); -}; + getFee() { + return this.fee; + } -/** - * Calculate delta fee rate. - * @returns {Rate} - */ + /** + * Get delta fee. + * @returns {Amount} + */ -MempoolEntry.prototype.getDeltaRate = function getDeltaRate() { - return policy.getRate(this.size, this.deltaFee); -}; + getDeltaFee() { + return this.deltaFee; + } -/** - * Calculate fee cumulative descendant rate. - * @returns {Rate} - */ + /** + * Calculate fee rate. + * @returns {Rate} + */ -MempoolEntry.prototype.getDescRate = function getDescRate() { - return policy.getRate(this.descSize, this.descFee); -}; + getRate() { + return policy.getRate(this.size, this.fee); + } -/** - * Calculate the memory usage of a transaction. - * Note that this only calculates the JS heap - * size. Sizes of buffers are ignored (the v8 - * heap is what we care most about). All numbers - * are based on the output of v8 heap snapshots - * of TX objects. - * @returns {Number} Usage in bytes. - */ + /** + * Calculate delta fee rate. + * @returns {Rate} + */ -MempoolEntry.prototype.memUsage = function memUsage() { - const tx = this.tx; - let total = 0; + getDeltaRate() { + return policy.getRate(this.size, this.deltaFee); + } - total += 176; // mempool entry - total += 48; // coinbase - total += 48; // dependencies + /** + * Calculate fee cumulative descendant rate. + * @returns {Rate} + */ - total += 208; // tx - total += 80; // _hash - total += 88; // _hhash - total += 80; // _raw - total += 80; // _whash - total += 48; // mutable + getDescRate() { + return policy.getRate(this.descSize, this.descFee); + } - total += 32; // input array + /** + * Calculate the memory usage of a transaction. + * Note that this only calculates the JS heap + * size. Sizes of buffers are ignored (the v8 + * heap is what we care most about). All numbers + * are based on the output of v8 heap snapshots + * of TX objects. + * @returns {Number} Usage in bytes. + */ + + memUsage() { + const tx = this.tx; + let total = 0; + + total += 176; // mempool entry + total += 48; // coinbase + total += 48; // dependencies + + total += 208; // tx + total += 80; // _hash + total += 88; // _hhash + total += 80; // _raw + total += 80; // _whash + total += 48; // mutable + + total += 32; // input array + + for (const input of tx.inputs) { + total += 120; // input + total += 104; // prevout + total += 88; // prevout hash + + total += 40; // script + total += 80; // script raw buffer + total += 32; // script code array + total += input.script.code.length * 40; // opcodes + + for (const op of input.script.code) { + if (op.data) + total += 80; // op buffers + } + + total += 96; // witness + total += 32; // witness items + total += input.witness.items.length * 80; // witness buffers + } - for (const input of tx.inputs) { - total += 120; // input - total += 104; // prevout - total += 88; // prevout hash + total += 32; // output array - total += 40; // script - total += 80; // script raw buffer - total += 32; // script code array - total += input.script.code.length * 40; // opcodes + for (const output of tx.outputs) { + total += 104; // output + total += 40; // script + total += 80; // script raw buffer + total += 32; // script code array + total += output.script.code.length * 40; // opcodes - for (const op of input.script.code) { - if (op.data) - total += 80; // op buffers + for (const op of output.script.code) { + if (op.data) + total += 80; // op buffers + } } - total += 96; // witness - total += 32; // witness items - total += input.witness.items.length * 80; // witness buffers + return total; } - total += 32; // output array - - for (const output of tx.outputs) { - total += 104; // output - total += 40; // script - total += 80; // script raw buffer - total += 32; // script code array - total += output.script.code.length * 40; // opcodes - - for (const op of output.script.code) { - if (op.data) - total += 80; // op buffers - } + /** + * Test whether the entry is free with + * the current priority (calculated by + * current height). + * @param {Number} height + * @returns {Boolean} + */ + + isFree(height) { + const priority = this.getPriority(height); + return priority > policy.FREE_THRESHOLD; } - return total; -}; - -/** - * Test whether the entry is free with - * the current priority (calculated by - * current height). - * @param {Number} height - * @returns {Boolean} - */ + /** + * Get entry serialization size. + * @returns {Number} + */ -MempoolEntry.prototype.isFree = function isFree(height) { - const priority = this.getPriority(height); - return priority > policy.FREE_THRESHOLD; -}; - -/** - * Get entry serialization size. - * @returns {Number} - */ - -MempoolEntry.prototype.getSize = function getSize() { - return this.tx.getSize() + 42; -}; - -/** - * Serialize entry to a buffer. - * @returns {Buffer} - */ - -MempoolEntry.prototype.toRaw = function toRaw() { - const bw = new StaticWriter(this.getSize()); - bw.writeBytes(this.tx.toRaw()); - bw.writeU32(this.height); - bw.writeU32(this.size); - bw.writeU32(this.sigops); - bw.writeDouble(this.priority); - bw.writeU64(this.fee); - bw.writeU32(this.time); - bw.writeU64(this.value); - bw.writeU8(this.coinbase ? 1 : 0); - bw.writeU8(this.dependencies ? 1 : 0); - return bw.render(); -}; + getSize() { + return this.tx.getSize() + 42; + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @returns {MempoolEntry} - */ + /** + * Serialize entry to a buffer. + * @returns {Buffer} + */ + + toRaw() { + const bw = bio.write(this.getSize()); + bw.writeBytes(this.tx.toRaw()); + bw.writeU32(this.height); + bw.writeU32(this.size); + bw.writeU32(this.sigops); + bw.writeDouble(this.priority); + bw.writeU64(this.fee); + bw.writeU32(this.time); + bw.writeU64(this.value); + bw.writeU8(this.coinbase ? 1 : 0); + bw.writeU8(this.dependencies ? 1 : 0); + return bw.render(); + } -MempoolEntry.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); - this.tx = TX.fromReader(br); - this.height = br.readU32(); - this.size = br.readU32(); - this.sigops = br.readU32(); - this.priority = br.readDouble(); - this.fee = br.readU64(); - this.deltaFee = this.fee; - this.time = br.readU32(); - this.value = br.readU64(); - this.coinbase = br.readU8() === 1; - this.dependencies = br.readU8() === 1; - this.descFee = this.fee; - this.descSize = this.size; - return this; -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + * @returns {MempoolEntry} + */ + + fromRaw(data) { + const br = bio.read(data); + this.tx = TX.fromReader(br); + this.height = br.readU32(); + this.size = br.readU32(); + this.sigops = br.readU32(); + this.priority = br.readDouble(); + this.fee = br.readU64(); + this.deltaFee = this.fee; + this.time = br.readU32(); + this.value = br.readU64(); + this.coinbase = br.readU8() === 1; + this.dependencies = br.readU8() === 1; + this.descFee = this.fee; + this.descSize = this.size; + return this; + } -/** - * Instantiate entry from serialized data. - * @param {Buffer} data - * @returns {MempoolEntry} - */ + /** + * Instantiate entry from serialized data. + * @param {Buffer} data + * @returns {MempoolEntry} + */ -MempoolEntry.fromRaw = function fromRaw(data) { - return new MempoolEntry().fromRaw(data); -}; + static fromRaw(data) { + return new this().fromRaw(data); + } +} /* * Expose diff --git a/lib/mining/common.js b/lib/mining/common.js index 3b43c7f6b..1754e1acc 100644 --- a/lib/mining/common.js +++ b/lib/mining/common.js @@ -6,9 +6,9 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const consensus = require('../protocol/consensus'); -const BN = require('../crypto/bn'); +const BN = require('bcrypto/lib/bn.js'); /** * @exports mining/common @@ -41,17 +41,6 @@ common.swap32 = function swap32(data) { return data; }; -/** - * Swap 32 bit endianness of uint256 (hex). - * @param {String} str - * @returns {String} - */ - -common.swap32hex = function swap32hex(str) { - const data = Buffer.from(str, 'hex'); - return common.swap32(data).toString('hex'); -}; - /** * Compare two uint256le's. * @param {Buffer} a diff --git a/lib/mining/cpuminer.js b/lib/mining/cpuminer.js index 3e39d2b53..aa861ce06 100644 --- a/lib/mining/cpuminer.js +++ b/lib/mining/cpuminer.js @@ -7,569 +7,573 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); +const EventEmitter = require('events'); +const {Lock} = require('bmutex'); const util = require('../utils/util'); -const co = require('../utils/co'); -const AsyncObject = require('../utils/asyncobject'); const mine = require('./mine'); -const Lock = require('../utils/lock'); /** * CPU miner. * @alias module:mining.CPUMiner - * @constructor - * @param {Miner} miner - * @emits CPUMiner#block - * @emits CPUMiner#status */ -function CPUMiner(miner) { - if (!(this instanceof CPUMiner)) - return new CPUMiner(miner); +class CPUMiner extends EventEmitter { + /** + * Create a CPU miner. + * @constructor + * @param {Miner} miner + */ - AsyncObject.call(this); + constructor(miner) { + super(); - this.miner = miner; - this.network = this.miner.network; - this.logger = this.miner.logger.context('cpuminer'); - this.workers = this.miner.workers; - this.chain = this.miner.chain; - this.locker = new Lock(); + this.opened = false; + this.miner = miner; + this.network = this.miner.network; + this.logger = this.miner.logger.context('cpuminer'); + this.workers = this.miner.workers; + this.chain = this.miner.chain; + this.locker = new Lock(); - this.running = false; - this.stopping = false; - this.job = null; - this.stopJob = null; - - this._init(); -} - -Object.setPrototypeOf(CPUMiner.prototype, AsyncObject.prototype); + this.running = false; + this.stopping = false; + this.job = null; + this.stopJob = null; -/** - * Nonce range interval. - * @const {Number} - * @default - */ + this.init(); + } -CPUMiner.INTERVAL = 0xffffffff / 1500 | 0; + /** + * Initialize the miner. + * @private + */ -/** - * Initialize the miner. - * @private - */ + init() { + this.chain.on('tip', (tip) => { + if (!this.job) + return; -CPUMiner.prototype._init = function _init() { - this.chain.on('tip', (tip) => { - if (!this.job) - return; + if (this.job.attempt.prevBlock.equals(tip.prevBlock)) + this.job.destroy(); + }); + } - if (this.job.attempt.prevBlock === tip.prevBlock) - this.job.destroy(); - }); -}; + /** + * Open the miner. + * @returns {Promise} + */ -/** - * Open the miner. - * @method - * @alias module:mining.CPUMiner#open - * @returns {Promise} - */ + async open() { + assert(!this.opened, 'CPUMiner is already open.'); + this.opened = true; + } -CPUMiner.prototype._open = async function _open() { -}; + /** + * Close the miner. + * @returns {Promise} + */ -/** - * Close the miner. - * @method - * @alias module:mining.CPUMiner#close - * @returns {Promise} - */ + async close() { + assert(this.opened, 'CPUMiner is not open.'); + this.opened = false; + return this.stop(); + } -CPUMiner.prototype._close = async function _close() { - await this.stop(); -}; + /** + * Start mining. + * @method + */ -/** - * Start mining. - * @method - */ + start() { + assert(!this.running, 'Miner is already running.'); + this._start().catch(() => {}); + } -CPUMiner.prototype.start = function start() { - assert(!this.running, 'Miner is already running.'); - this._start().catch(() => {}); -}; + /** + * Start mining. + * @method + * @private + * @returns {Promise} + */ -/** - * Start mining. - * @method - * @private - * @returns {Promise} - */ + async _start() { + assert(!this.running, 'Miner is already running.'); -CPUMiner.prototype._start = async function _start() { - assert(!this.running, 'Miner is already running.'); + this.running = true; + this.stopping = false; - this.running = true; - this.stopping = false; + for (;;) { + this.job = null; - for (;;) { - this.job = null; + try { + this.job = await this.createJob(); + } catch (e) { + if (this.stopping) + break; + this.emit('error', e); + break; + } - try { - this.job = await this.createJob(); - } catch (e) { if (this.stopping) break; - this.emit('error', e); - break; - } - if (this.stopping) - break; + let block; + try { + block = await this.mineAsync(this.job); + } catch (e) { + if (this.stopping) + break; + this.emit('error', e); + break; + } - let block; - try { - block = await this.mineAsync(this.job); - } catch (e) { if (this.stopping) break; - this.emit('error', e); - break; - } - if (this.stopping) - break; + if (!block) + continue; - if (!block) - continue; + let entry; + try { + entry = await this.chain.add(block); + } catch (e) { + if (this.stopping) + break; - let entry; - try { - entry = await this.chain.add(block); - } catch (e) { - if (this.stopping) + if (e.type === 'VerifyError') { + this.logger.warning('Mined an invalid block!'); + this.logger.error(e); + continue; + } + + this.emit('error', e); break; + } - if (e.type === 'VerifyError') { - this.logger.warning('Mined an invalid block!'); - this.logger.error(e); + if (!entry) { + this.logger.warning('Mined a bad-prevblk (race condition?)'); continue; } - this.emit('error', e); - break; - } + if (this.stopping) + break; - if (!entry) { - this.logger.warning('Mined a bad-prevblk (race condition?)'); - continue; - } + // Log the block hex as a failsafe (in case we can't send it). + this.logger.info('Found block: %d (%h).', entry.height, entry.hash); - if (this.stopping) - break; + this.emit('block', block, entry); + } - // Log the block hex as a failsafe (in case we can't send it). - this.logger.info('Found block: %d (%s).', entry.height, entry.rhash()); - this.logger.debug('Raw: %s', block.toRaw().toString('hex')); + const job = this.stopJob; - this.emit('block', block, entry); + if (job) { + this.stopJob = null; + job.resolve(); + } } - const job = this.stopJob; + /** + * Stop mining. + * @method + * @returns {Promise} + */ - if (job) { - this.stopJob = null; - job.resolve(); + async stop() { + const unlock = await this.locker.lock(); + try { + return await this._stop(); + } finally { + unlock(); + } } -}; -/** - * Stop mining. - * @method - * @returns {Promise} - */ + /** + * Stop mining (without a lock). + * @method + * @returns {Promise} + */ -CPUMiner.prototype.stop = async function stop() { - const unlock = await this.locker.lock(); - try { - return await this._stop(); - } finally { - unlock(); - } -}; + async _stop() { + if (!this.running) + return; -/** - * Stop mining (without a lock). - * @method - * @returns {Promise} - */ + assert(this.running, 'Miner is not running.'); + assert(!this.stopping, 'Miner is already stopping.'); -CPUMiner.prototype._stop = async function _stop() { - if (!this.running) - return; + this.stopping = true; - assert(this.running, 'Miner is not running.'); - assert(!this.stopping, 'Miner is already stopping.'); + if (this.job) { + this.job.destroy(); + this.job = null; + } - this.stopping = true; + await this.wait(); - if (this.job) { - this.job.destroy(); + this.running = false; + this.stopping = false; this.job = null; } - await this.wait(); - - this.running = false; - this.stopping = false; - this.job = null; -}; - -/** - * Wait for `done` event. - * @private - * @returns {Promise} - */ - -CPUMiner.prototype.wait = function wait() { - return new Promise((resolve, reject) => { - assert(!this.stopJob); - this.stopJob = co.job(resolve, reject); - }); -}; - -/** - * Create a mining job. - * @method - * @param {ChainEntry?} tip - * @param {Address?} address - * @returns {Promise} - Returns {@link Job}. - */ - -CPUMiner.prototype.createJob = async function createJob(tip, address) { - const attempt = await this.miner.createBlock(tip, address); - return new CPUJob(this, attempt); -}; + /** + * Wait for `done` event. + * @private + * @returns {Promise} + */ + + wait() { + return new Promise((resolve, reject) => { + assert(!this.stopJob); + this.stopJob = { resolve, reject }; + }); + } -/** - * Mine a single block. - * @method - * @param {ChainEntry?} tip - * @param {Address?} address - * @returns {Promise} - Returns [{@link Block}]. - */ + /** + * Create a mining job. + * @method + * @param {ChainEntry?} tip + * @param {Address?} address + * @returns {Promise} - Returns {@link Job}. + */ + + async createJob(tip, address) { + const attempt = await this.miner.createBlock(tip, address); + return new CPUJob(this, attempt); + } -CPUMiner.prototype.mineBlock = async function mineBlock(tip, address) { - const job = await this.createJob(tip, address); - return await this.mineAsync(job); -}; + /** + * Mine a single block. + * @method + * @param {ChainEntry?} tip + * @param {Address?} address + * @returns {Promise} - Returns [{@link Block}]. + */ + + async mineBlock(tip, address) { + const job = await this.createJob(tip, address); + return await this.mineAsync(job); + } -/** - * Notify the miner that a new - * tx has entered the mempool. - */ + /** + * Notify the miner that a new + * tx has entered the mempool. + */ -CPUMiner.prototype.notifyEntry = function notifyEntry() { - if (!this.running) - return; + notifyEntry() { + if (!this.running) + return; - if (!this.job) - return; + if (!this.job) + return; - if (util.now() - this.job.start > 10) { - this.job.destroy(); - this.job = null; + if (util.now() - this.job.start > 10) { + this.job.destroy(); + this.job = null; + } } -}; -/** - * Hash until the nonce overflows. - * @param {CPUJob} job - * @returns {Number} nonce - */ + /** + * Hash until the nonce overflows. + * @param {CPUJob} job + * @returns {Number} nonce + */ -CPUMiner.prototype.findNonce = function findNonce(job) { - const data = job.getHeader(); - const target = job.attempt.target; - const interval = CPUMiner.INTERVAL; + findNonce(job) { + const data = job.getHeader(); + const target = job.attempt.target; + const interval = CPUMiner.INTERVAL; - let min = 0; - let max = interval; - let nonce; + let min = 0; + let max = interval; + let nonce; - while (max <= 0xffffffff) { - nonce = mine(data, target, min, max); + while (max <= 0xffffffff) { + nonce = mine(data, target, min, max); - if (nonce !== -1) - break; + if (nonce !== -1) + break; - this.sendStatus(job, max); + this.sendStatus(job, max); - min += interval; - max += interval; + min += interval; + max += interval; + } + + return nonce; } - return nonce; -}; + /** + * Hash until the nonce overflows. + * @method + * @param {CPUJob} job + * @returns {Promise} Returns Number. + */ -/** - * Hash until the nonce overflows. - * @method - * @param {CPUJob} job - * @returns {Promise} Returns Number. - */ + async findNonceAsync(job) { + if (!this.workers) + return this.findNonce(job); -CPUMiner.prototype.findNonceAsync = async function findNonceAsync(job) { - if (!this.workers) - return this.findNonce(job); + const data = job.getHeader(); + const target = job.attempt.target; + const interval = CPUMiner.INTERVAL; - const data = job.getHeader(); - const target = job.attempt.target; - const interval = CPUMiner.INTERVAL; + let min = 0; + let max = interval; + let nonce; - let min = 0; - let max = interval; - let nonce; + while (max <= 0xffffffff) { + nonce = await this.workers.mine(data, target, min, max); - while (max <= 0xffffffff) { - nonce = await this.workers.mine(data, target, min, max); + if (nonce !== -1) + break; - if (nonce !== -1) - break; + if (job.destroyed) + return nonce; - if (job.destroyed) - return nonce; + this.sendStatus(job, max); - this.sendStatus(job, max); + min += interval; + max += interval; + } - min += interval; - max += interval; + return nonce; } - return nonce; -}; + /** + * Mine synchronously until the block is found. + * @param {CPUJob} job + * @returns {Block} + */ -/** - * Mine synchronously until the block is found. - * @param {CPUJob} job - * @returns {Block} - */ + mine(job) { + job.start = util.now(); -CPUMiner.prototype.mine = function mine(job) { - job.start = util.now(); + let nonce; + for (;;) { + nonce = this.findNonce(job); - let nonce; - for (;;) { - nonce = this.findNonce(job); + if (nonce !== -1) + break; - if (nonce !== -1) - break; + job.updateNonce(); - job.updateNonce(); + this.sendStatus(job, 0); + } - this.sendStatus(job, 0); + return job.commit(nonce); } - return job.commit(nonce); -}; + /** + * Mine asynchronously until the block is found. + * @method + * @param {CPUJob} job + * @returns {Promise} - Returns {@link Block}. + */ -/** - * Mine asynchronously until the block is found. - * @method - * @param {CPUJob} job - * @returns {Promise} - Returns {@link Block}. - */ + async mineAsync(job) { + let nonce; -CPUMiner.prototype.mineAsync = async function mineAsync(job) { - let nonce; + job.start = util.now(); - job.start = util.now(); + for (;;) { + nonce = await this.findNonceAsync(job); - for (;;) { - nonce = await this.findNonceAsync(job); + if (nonce !== -1) + break; - if (nonce !== -1) - break; + if (job.destroyed) + return null; - if (job.destroyed) - return null; + job.updateNonce(); - job.updateNonce(); + this.sendStatus(job, 0); + } - this.sendStatus(job, 0); + return job.commit(nonce); } - return job.commit(nonce); -}; + /** + * Send a progress report (emits `status`). + * @param {CPUJob} job + * @param {Number} nonce + */ + + sendStatus(job, nonce) { + const attempt = job.attempt; + const tip = attempt.prevBlock; + const hashes = job.getHashes(nonce); + const hashrate = job.getRate(nonce); + + this.logger.info( + 'Status: hashrate=%dkhs hashes=%d target=%d height=%d tip=%h', + Math.floor(hashrate / 1000), + hashes, + attempt.bits, + attempt.height, + tip); + + this.emit('status', job, hashes, hashrate); + } +} /** - * Send a progress report (emits `status`). - * @param {CPUJob} job - * @param {Number} nonce + * Nonce range interval. + * @const {Number} + * @default */ -CPUMiner.prototype.sendStatus = function sendStatus(job, nonce) { - const attempt = job.attempt; - const tip = util.revHex(attempt.prevBlock); - const hashes = job.getHashes(nonce); - const hashrate = job.getRate(nonce); - - this.logger.info( - 'Status: hashrate=%dkhs hashes=%d target=%d height=%d tip=%s', - Math.floor(hashrate / 1000), - hashes, - attempt.bits, - attempt.height, - tip); - - this.emit('status', job, hashes, hashrate); -}; +CPUMiner.INTERVAL = 0xffffffff / 1500 | 0; /** * Mining Job - * @constructor * @ignore - * @param {CPUMiner} miner - * @param {BlockTemplate} attempt */ -function CPUJob(miner, attempt) { - this.miner = miner; - this.attempt = attempt; - this.destroyed = false; - this.committed = false; - this.start = util.now(); - this.nonce1 = 0; - this.nonce2 = 0; - this.refresh(); -} - -/** - * Get the raw block header. - * @param {Number} nonce - * @returns {Buffer} - */ +class CPUJob { + /** + * Create a mining job. + * @constructor + * @param {CPUMiner} miner + * @param {BlockTemplate} attempt + */ + + constructor(miner, attempt) { + this.miner = miner; + this.attempt = attempt; + this.destroyed = false; + this.committed = false; + this.start = util.now(); + this.nonce1 = 0; + this.nonce2 = 0; + this.refresh(); + } -CPUJob.prototype.getHeader = function getHeader() { - const attempt = this.attempt; - const n1 = this.nonce1; - const n2 = this.nonce2; - const time = attempt.time; - const root = attempt.getRoot(n1, n2); - const data = attempt.getHeader(root, time, 0); - return data; -}; + /** + * Get the raw block header. + * @param {Number} nonce + * @returns {Buffer} + */ + + getHeader() { + const attempt = this.attempt; + const n1 = this.nonce1; + const n2 = this.nonce2; + const time = attempt.time; + const root = attempt.getRoot(n1, n2); + const data = attempt.getHeader(root, time, 0); + return data; + } -/** - * Commit job and return a block. - * @param {Number} nonce - * @returns {Block} - */ + /** + * Commit job and return a block. + * @param {Number} nonce + * @returns {Block} + */ -CPUJob.prototype.commit = function commit(nonce) { - const attempt = this.attempt; - const n1 = this.nonce1; - const n2 = this.nonce2; - const time = attempt.time; + commit(nonce) { + const attempt = this.attempt; + const n1 = this.nonce1; + const n2 = this.nonce2; + const time = attempt.time; - assert(!this.committed, 'Job already committed.'); - this.committed = true; + assert(!this.committed, 'Job already committed.'); + this.committed = true; - const proof = attempt.getProof(n1, n2, time, nonce); + const proof = attempt.getProof(n1, n2, time, nonce); - return attempt.commit(proof); -}; + return attempt.commit(proof); + } -/** - * Mine block synchronously. - * @returns {Block} - */ + /** + * Mine block synchronously. + * @returns {Block} + */ -CPUJob.prototype.mine = function mine() { - return this.miner.mine(this); -}; + mine() { + return this.miner.mine(this); + } -/** - * Mine block asynchronously. - * @returns {Promise} - */ + /** + * Mine block asynchronously. + * @returns {Promise} + */ -CPUJob.prototype.mineAsync = function mineAsync() { - return this.miner.mineAsync(this); -}; + mineAsync() { + return this.miner.mineAsync(this); + } -/** - * Refresh the block template. - */ + /** + * Refresh the block template. + */ -CPUJob.prototype.refresh = function refresh() { - return this.attempt.refresh(); -}; + refresh() { + return this.attempt.refresh(); + } -/** - * Increment the extraNonce. - */ + /** + * Increment the extraNonce. + */ -CPUJob.prototype.updateNonce = function updateNonce() { - if (++this.nonce2 === 0x100000000) { - this.nonce2 = 0; - this.nonce1++; + updateNonce() { + if (++this.nonce2 === 0x100000000) { + this.nonce2 = 0; + this.nonce1++; + } } -}; -/** - * Destroy the job. - */ + /** + * Destroy the job. + */ -CPUJob.prototype.destroy = function destroy() { - assert(!this.destroyed, 'Job already destroyed.'); - this.destroyed = true; -}; + destroy() { + assert(!this.destroyed, 'Job already destroyed.'); + this.destroyed = true; + } -/** - * Calculate number of hashes computed. - * @param {Number} nonce - * @returns {Number} - */ + /** + * Calculate number of hashes computed. + * @param {Number} nonce + * @returns {Number} + */ -CPUJob.prototype.getHashes = function getHashes(nonce) { - const extra = this.nonce1 * 0x100000000 + this.nonce2; - return extra * 0xffffffff + nonce; -}; + getHashes(nonce) { + const extra = this.nonce1 * 0x100000000 + this.nonce2; + return extra * 0xffffffff + nonce; + } -/** - * Calculate hashrate. - * @param {Number} nonce - * @returns {Number} - */ + /** + * Calculate hashrate. + * @param {Number} nonce + * @returns {Number} + */ -CPUJob.prototype.getRate = function getRate(nonce) { - const hashes = this.getHashes(nonce); - const seconds = util.now() - this.start; - return Math.floor(hashes / Math.max(1, seconds)); -}; + getRate(nonce) { + const hashes = this.getHashes(nonce); + const seconds = util.now() - this.start; + return Math.floor(hashes / Math.max(1, seconds)); + } -/** - * Add a transaction to the block. - * @param {TX} tx - * @param {CoinView} view - */ + /** + * Add a transaction to the block. + * @param {TX} tx + * @param {CoinView} view + */ -CPUJob.prototype.addTX = function addTX(tx, view) { - return this.attempt.addTX(tx, view); -}; + addTX(tx, view) { + return this.attempt.addTX(tx, view); + } -/** - * Add a transaction to the block - * (less verification than addTX). - * @param {TX} tx - * @param {CoinView?} view - */ + /** + * Add a transaction to the block + * (less verification than addTX). + * @param {TX} tx + * @param {CoinView?} view + */ -CPUJob.prototype.pushTX = function pushTX(tx, view) { - return this.attempt.pushTX(tx, view); -}; + pushTX(tx, view) { + return this.attempt.pushTX(tx, view); + } +} /* * Expose diff --git a/lib/mining/mine.js b/lib/mining/mine.js index fe6b73be4..0b92b3098 100644 --- a/lib/mining/mine.js +++ b/lib/mining/mine.js @@ -6,8 +6,8 @@ 'use strict'; -const assert = require('assert'); -const scrypt = require('../crypto/scrypt').derive; +const assert = require('bsert'); +const scrypt = require('bcrypto/lib/scrypt').derive; /** * Hash until the nonce overflows. @@ -31,7 +31,7 @@ function mine(data, target, min, max) { return nonce; // Increment the nonce to get a different hash. - nonce++; + nonce += 1; // Update the raw buffer. data.writeUInt32LE(nonce, 76, true); diff --git a/lib/mining/miner.js b/lib/mining/miner.js index 4274bcbd8..df0d8fdc2 100644 --- a/lib/mining/miner.js +++ b/lib/mining/miner.js @@ -7,10 +7,10 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); -const Heap = require('../utils/heap'); -const AsyncObject = require('../utils/asyncobject'); +const assert = require('bsert'); +const EventEmitter = require('events'); +const Heap = require('bheep'); +const {BufferMap} = require('buffer-map'); const Amount = require('../btc/amount'); const Address = require('../primitives/address'); const BlockTemplate = require('./template'); @@ -18,501 +18,501 @@ const Network = require('../protocol/network'); const consensus = require('../protocol/consensus'); const policy = require('../protocol/policy'); const CPUMiner = require('./cpuminer'); -const BlockEntry = BlockTemplate.BlockEntry; +const {BlockEntry} = BlockTemplate; /** + * Miner * A bitcoin miner and block generator. * @alias module:mining.Miner - * @constructor - * @param {Object} options + * @extends EventEmitter */ -function Miner(options) { - if (!(this instanceof Miner)) - return new Miner(options); - - AsyncObject.call(this); - - this.options = new MinerOptions(options); - this.network = this.options.network; - this.logger = this.options.logger.context('miner'); - this.workers = this.options.workers; - this.chain = this.options.chain; - this.mempool = this.options.mempool; - this.addresses = this.options.addresses; - this.locker = this.chain.locker; - this.cpu = new CPUMiner(this); - - this.init(); -} - -Object.setPrototypeOf(Miner.prototype, AsyncObject.prototype); - -/** - * Open the miner, wait for the chain and mempool to load. - * @method - * @alias module:mining.Miner#open - * @returns {Promise} - */ - -Miner.prototype.init = function init() { - this.cpu.on('error', (err) => { - this.emit('error', err); - }); -}; - -/** - * Open the miner, wait for the chain and mempool to load. - * @method - * @alias module:mining.Miner#open - * @returns {Promise} - */ - -Miner.prototype._open = async function _open() { - await this.chain.open(); - - if (this.mempool) - await this.mempool.open(); - - await this.cpu.open(); - - this.logger.info('Miner loaded (flags=%s).', - this.options.coinbaseFlags.toString('utf8')); - - if (this.addresses.length === 0) - this.logger.warning('No reward address is set for miner!'); -}; - -/** - * Close the miner. - * @method - * @alias module:mining.Miner#close - * @returns {Promise} - */ - -Miner.prototype._close = async function _close() { - await this.cpu.close(); -}; - -/** - * Create a block template. - * @method - * @param {ChainEntry?} tip - * @param {Address?} address - * @returns {Promise} - Returns {@link BlockTemplate}. - */ - -Miner.prototype.createBlock = async function createBlock(tip, address) { - const unlock = await this.locker.lock(); - try { - return await this._createBlock(tip, address); - } finally { - unlock(); +class Miner extends EventEmitter { + /** + * Create a bitcoin miner. + * @constructor + * @param {Object} options + */ + + constructor(options) { + super(); + + this.opened = false; + this.options = new MinerOptions(options); + this.network = this.options.network; + this.logger = this.options.logger.context('miner'); + this.workers = this.options.workers; + this.chain = this.options.chain; + this.mempool = this.options.mempool; + this.addresses = this.options.addresses; + this.locker = this.chain.locker; + this.cpu = new CPUMiner(this); + + this.init(); } -}; -/** - * Create a block template (without a lock). - * @method - * @private - * @param {ChainEntry?} tip - * @param {Address?} address - * @returns {Promise} - Returns {@link BlockTemplate}. - */ + /** + * Initialize the miner. + */ -Miner.prototype._createBlock = async function _createBlock(tip, address) { - let version = this.options.version; - - if (!tip) - tip = this.chain.tip; - - if (!address) - address = this.getAddress(); + init() { + this.cpu.on('error', (err) => { + this.emit('error', err); + }); + } - if (version === -1) - version = await this.chain.computeBlockVersion(tip); + /** + * Open the miner, wait for the chain and mempool to load. + * @returns {Promise} + */ - const mtp = await this.chain.getMedianTime(tip); - const time = Math.max(this.network.now(), mtp + 1); + async open() { + assert(!this.opened, 'Miner is already open.'); + this.opened = true; - const state = await this.chain.getDeployments(time, tip); - const target = await this.chain.getTarget(time, tip); + await this.cpu.open(); - const locktime = state.hasMTP() ? mtp : time; + this.logger.info('Miner loaded (flags=%s).', + this.options.coinbaseFlags.toString('utf8')); - const attempt = new BlockTemplate({ - prevBlock: tip.hash, - height: tip.height + 1, - version: version, - time: time, - bits: target, - locktime: locktime, - mtp: mtp, - flags: state.flags, - address: address, - coinbaseFlags: this.options.coinbaseFlags, - witness: state.hasWitness(), - interval: this.network.halvingInterval, - weight: this.options.reservedWeight, - sigops: this.options.reservedSigops - }); + if (this.addresses.length === 0) + this.logger.warning('No reward address is set for miner!'); + } - this.assemble(attempt); + /** + * Close the miner. + * @returns {Promise} + */ - this.logger.debug( - 'Created block template (height=%d, weight=%d, fees=%d, txs=%s, diff=%d).', - attempt.height, - attempt.weight, - Amount.btc(attempt.fees), - attempt.items.length + 1, - attempt.getDifficulty()); + async close() { + assert(this.opened, 'Miner is not open.'); + this.opened = false; + return this.cpu.close(); + } - if (this.options.preverify) { - const block = attempt.toBlock(); + /** + * Create a block template. + * @method + * @param {ChainEntry?} tip + * @param {Address?} address + * @returns {Promise} - Returns {@link BlockTemplate}. + */ + async createBlock(tip, address) { + const unlock = await this.locker.lock(); try { - await this.chain._verifyBlock(block); - } catch (e) { - if (e.type === 'VerifyError') { - this.logger.warning('Miner created invalid block!'); - this.logger.error(e); - throw new Error('BUG: Miner created invalid block.'); - } - throw e; + return await this._createBlock(tip, address); + } finally { + unlock(); } - - this.logger.debug( - 'Preverified block %d successfully!', - attempt.height); } - return attempt; -}; + /** + * Create a block template (without a lock). + * @method + * @private + * @param {ChainEntry?} tip + * @param {Address?} address + * @returns {Promise} - Returns {@link BlockTemplate}. + */ + + async _createBlock(tip, address) { + let version = this.options.version; + + if (!tip) + tip = this.chain.tip; + + if (!address) + address = this.getAddress(); + + if (version === -1) + version = await this.chain.computeBlockVersion(tip); + + const mtp = await this.chain.getMedianTime(tip); + const time = Math.max(this.network.now(), mtp + 1); + + const state = await this.chain.getDeployments(time, tip); + const target = await this.chain.getTarget(time, tip); + + const locktime = state.hasMTP() ? mtp : time; + + const attempt = new BlockTemplate({ + prevBlock: tip.hash, + height: tip.height + 1, + version: version, + time: time, + bits: target, + locktime: locktime, + mtp: mtp, + flags: state.flags, + address: address, + coinbaseFlags: this.options.coinbaseFlags, + witness: state.hasWitness(), + interval: this.network.halvingInterval, + weight: this.options.reservedWeight, + sigops: this.options.reservedSigops + }); + + this.assemble(attempt); -/** - * Update block timestamp. - * @param {BlockTemplate} attempt - */ + this.logger.debug( + 'Created block tmpl (height=%d, weight=%d, fees=%d, txs=%s, diff=%d).', + attempt.height, + attempt.weight, + Amount.btc(attempt.fees), + attempt.items.length + 1, + attempt.getDifficulty()); + + if (this.options.preverify) { + const block = attempt.toBlock(); + + try { + await this.chain._verifyBlock(block); + } catch (e) { + if (e.type === 'VerifyError') { + this.logger.warning('Miner created invalid block!'); + this.logger.error(e); + throw new Error('BUG: Miner created invalid block.'); + } + throw e; + } -Miner.prototype.updateTime = function updateTime(attempt) { - attempt.time = Math.max(this.network.now(), attempt.mtp + 1); -}; + this.logger.debug( + 'Preverified block %d successfully!', + attempt.height); + } -/** - * Create a cpu miner job. - * @method - * @param {ChainEntry?} tip - * @param {Address?} address - * @returns {Promise} Returns {@link CPUJob}. - */ + return attempt; + } -Miner.prototype.createJob = function createJob(tip, address) { - return this.cpu.createJob(tip, address); -}; + /** + * Update block timestamp. + * @param {BlockTemplate} attempt + */ -/** - * Mine a single block. - * @method - * @param {ChainEntry?} tip - * @param {Address?} address - * @returns {Promise} Returns {@link Block}. - */ + updateTime(attempt) { + attempt.time = Math.max(this.network.now(), attempt.mtp + 1); + } -Miner.prototype.mineBlock = function mineBlock(tip, address) { - return this.cpu.mineBlock(tip, address); -}; + /** + * Create a cpu miner job. + * @method + * @param {ChainEntry?} tip + * @param {Address?} address + * @returns {Promise} Returns {@link CPUJob}. + */ -/** - * Add an address to the address list. - * @param {Address} address - */ + createJob(tip, address) { + return this.cpu.createJob(tip, address); + } -Miner.prototype.addAddress = function addAddress(address) { - this.addresses.push(Address(address)); -}; + /** + * Mine a single block. + * @method + * @param {ChainEntry?} tip + * @param {Address?} address + * @returns {Promise} Returns {@link Block}. + */ -/** - * Get a random address from the address list. - * @returns {Address} - */ + mineBlock(tip, address) { + return this.cpu.mineBlock(tip, address); + } -Miner.prototype.getAddress = function getAddress() { - if (this.addresses.length === 0) - return new Address(); - return this.addresses[Math.random() * this.addresses.length | 0]; -}; + /** + * Add an address to the address list. + * @param {Address} address + */ -/** - * Get mempool entries, sort by dependency order. - * Prioritize by priority and fee rates. - * @param {BlockTemplate} attempt - * @returns {MempoolEntry[]} - */ + addAddress(address) { + this.addresses.push(new Address(address)); + } -Miner.prototype.assemble = function assemble(attempt) { - if (!this.mempool) { - attempt.refresh(); - return; + /** + * Get a random address from the address list. + * @returns {Address} + */ + + getAddress() { + if (this.addresses.length === 0) + return new Address(); + return this.addresses[Math.random() * this.addresses.length | 0]; } - assert(this.mempool.tip === this.chain.tip.hash, - 'Mempool/chain tip mismatch! Unsafe to create block.'); + /** + * Get mempool entries, sort by dependency order. + * Prioritize by priority and fee rates. + * @param {BlockTemplate} attempt + * @returns {MempoolEntry[]} + */ + + assemble(attempt) { + if (!this.mempool) { + attempt.refresh(); + return; + } - const depMap = new Map(); - const queue = new Heap(cmpRate); + assert(this.mempool.tip.equals(this.chain.tip.hash), + 'Mempool/chain tip mismatch! Unsafe to create block.'); - let priority = this.options.priorityWeight > 0; + const depMap = new BufferMap(); + const queue = new Heap(cmpRate); - if (priority) - queue.set(cmpPriority); + let priority = this.options.priorityWeight > 0; - for (const entry of this.mempool.map.values()) { - const item = BlockEntry.fromEntry(entry, attempt); - const tx = item.tx; + if (priority) + queue.set(cmpPriority); - if (tx.isCoinbase()) - throw new Error('Cannot add coinbase to block.'); + for (const entry of this.mempool.map.values()) { + const item = BlockEntry.fromEntry(entry, attempt); + const tx = item.tx; - for (const {prevout} of tx.inputs) { - const hash = prevout.hash; + if (tx.isCoinbase()) + throw new Error('Cannot add coinbase to block.'); - if (!this.mempool.hasEntry(hash)) - continue; + for (const {prevout} of tx.inputs) { + const hash = prevout.hash; - item.depCount += 1; + if (!this.mempool.hasEntry(hash)) + continue; - if (!depMap.has(hash)) - depMap.set(hash, []); + item.depCount += 1; - depMap.get(hash).push(item); - } + if (!depMap.has(hash)) + depMap.set(hash, []); - if (item.depCount > 0) - continue; + depMap.get(hash).push(item); + } - queue.insert(item); - } + if (item.depCount > 0) + continue; - while (queue.size() > 0) { - const item = queue.shift(); - const tx = item.tx; - const hash = item.hash; + queue.insert(item); + } - let weight = attempt.weight; - let sigops = attempt.sigops; + while (queue.size() > 0) { + const item = queue.shift(); + const tx = item.tx; + const hash = item.hash; - if (!tx.isFinal(attempt.height, attempt.locktime)) - continue; + let weight = attempt.weight; + let sigops = attempt.sigops; - if (!attempt.witness && tx.hasWitness()) - continue; + if (!tx.isFinal(attempt.height, attempt.locktime)) + continue; - weight += tx.getWeight(); + if (!attempt.witness && tx.hasWitness()) + continue; - if (weight > this.options.maxWeight) - continue; + weight += tx.getWeight(); - sigops += item.sigops; + if (weight > this.options.maxWeight) + continue; - if (sigops > this.options.maxSigops) - continue; + sigops += item.sigops; - if (priority) { - if (weight > this.options.priorityWeight - || item.priority < this.options.priorityThreshold) { - priority = false; - queue.set(cmpRate); - queue.init(); - queue.insert(item); + if (sigops > this.options.maxSigops) continue; + + if (priority) { + if (weight > this.options.priorityWeight + || item.priority < this.options.priorityThreshold) { + priority = false; + queue.set(cmpRate); + queue.init(); + queue.insert(item); + continue; + } + } else { + if (item.free && weight >= this.options.minWeight) + continue; } - } else { - if (item.free && weight >= this.options.minWeight) - continue; - } - attempt.weight = weight; - attempt.sigops = sigops; - attempt.fees += item.fee; - attempt.items.push(item); + attempt.weight = weight; + attempt.sigops = sigops; + attempt.fees += item.fee; + attempt.items.push(item); - const deps = depMap.get(hash); + const deps = depMap.get(hash); - if (!deps) - continue; + if (!deps) + continue; - for (const item of deps) { - if (--item.depCount === 0) - queue.insert(item); + for (const item of deps) { + if (--item.depCount === 0) + queue.insert(item); + } } - } - - attempt.refresh(); - assert(attempt.weight <= consensus.MAX_BLOCK_WEIGHT, - 'Block exceeds reserved weight!'); - - if (this.options.preverify) { - const block = attempt.toBlock(); + attempt.refresh(); - assert(block.getWeight() <= attempt.weight, + assert(attempt.weight <= consensus.MAX_BLOCK_WEIGHT, 'Block exceeds reserved weight!'); - assert(block.getBaseSize() <= consensus.MAX_BLOCK_SIZE, - 'Block exceeds max block size.'); - } -}; + if (this.options.preverify) { + const block = attempt.toBlock(); -/** - * MinerOptions - * @alias module:mining.MinerOptions - * @constructor - * @param {Object} - */ + assert(block.getWeight() <= attempt.weight, + 'Block exceeds reserved weight!'); -function MinerOptions(options) { - if (!(this instanceof MinerOptions)) - return new MinerOptions(options); - - this.network = Network.primary; - this.logger = null; - this.workers = null; - this.chain = null; - this.mempool = null; - - this.version = -1; - this.addresses = []; - this.coinbaseFlags = Buffer.from('mined by fcoin', 'ascii'); - this.preverify = false; - - this.minWeight = policy.MIN_BLOCK_WEIGHT; - this.maxWeight = policy.MAX_BLOCK_WEIGHT; - this.priorityWeight = policy.BLOCK_PRIORITY_WEIGHT; - this.priorityThreshold = policy.BLOCK_PRIORITY_THRESHOLD; - this.maxSigops = consensus.MAX_BLOCK_SIGOPS_COST; - this.reservedWeight = 4000; - this.reservedSigops = 400; - - this.fromOptions(options); + assert(block.getBaseSize() <= consensus.MAX_BLOCK_SIZE, + 'Block exceeds max block size.'); + } + } } /** - * Inject properties from object. - * @private - * @param {Object} options - * @returns {MinerOptions} + * Miner Options + * @alias module:mining.MinerOptions */ -MinerOptions.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Miner requires options.'); - assert(options.chain && typeof options.chain === 'object', - 'Miner requires a blockchain.'); +class MinerOptions { + /** + * Create miner options. + * @constructor + * @param {Object} + */ + + constructor(options) { + this.network = Network.primary; + this.logger = null; + this.workers = null; + this.chain = null; + this.mempool = null; + + this.version = -1; + this.addresses = []; + this.coinbaseFlags = Buffer.from('mined by fcoin', 'ascii'); + this.preverify = false; + + this.minWeight = policy.MIN_BLOCK_WEIGHT; + this.maxWeight = policy.MAX_BLOCK_WEIGHT; + this.priorityWeight = policy.BLOCK_PRIORITY_WEIGHT; + this.priorityThreshold = policy.BLOCK_PRIORITY_THRESHOLD; + this.maxSigops = consensus.MAX_BLOCK_SIGOPS_COST; + this.reservedWeight = 4000; + this.reservedSigops = 400; + + this.fromOptions(options); + } - this.chain = options.chain; - this.network = options.chain.network; - this.logger = options.chain.logger; - this.workers = options.chain.workers; + /** + * Inject properties from object. + * @private + * @param {Object} options + * @returns {MinerOptions} + */ + + fromOptions(options) { + assert(options, 'Miner requires options.'); + assert(options.chain && typeof options.chain === 'object', + 'Miner requires a blockchain.'); + + this.chain = options.chain; + this.network = options.chain.network; + this.logger = options.chain.logger; + this.workers = options.chain.workers; + + if (options.logger != null) { + assert(typeof options.logger === 'object'); + this.logger = options.logger; + } - if (options.logger != null) { - assert(typeof options.logger === 'object'); - this.logger = options.logger; - } + if (options.workers != null) { + assert(typeof options.workers === 'object'); + this.workers = options.workers; + } - if (options.workers != null) { - assert(typeof options.workers === 'object'); - this.workers = options.workers; - } + if (options.mempool != null) { + assert(typeof options.mempool === 'object'); + this.mempool = options.mempool; + } - if (options.mempool != null) { - assert(typeof options.mempool === 'object'); - this.mempool = options.mempool; - } + if (options.version != null) { + assert((options.version >>> 0) === options.version); + this.version = options.version; + } - if (options.version != null) { - assert(util.isInt(options.version)); - this.version = options.version; - } + if (options.address) { + if (Array.isArray(options.address)) { + for (const item of options.address) + this.addresses.push(new Address(item)); + } else { + this.addresses.push(new Address(options.address)); + } + } - if (options.address) { - if (Array.isArray(options.address)) { - for (const item of options.address) + if (options.addresses) { + assert(Array.isArray(options.addresses)); + for (const item of options.addresses) this.addresses.push(new Address(item)); - } else { - this.addresses.push(new Address(options.address)); } - } - if (options.addresses) { - assert(Array.isArray(options.addresses)); - for (const item of options.addresses) - this.addresses.push(new Address(item)); - } + if (options.coinbaseFlags) { + let flags = options.coinbaseFlags; + if (typeof flags === 'string') + flags = Buffer.from(flags, 'utf8'); + assert(Buffer.isBuffer(flags)); + assert(flags.length <= 20, 'Coinbase flags > 20 bytes.'); + this.coinbaseFlags = flags; + } - if (options.coinbaseFlags) { - let flags = options.coinbaseFlags; - if (typeof flags === 'string') - flags = Buffer.from(flags, 'utf8'); - assert(Buffer.isBuffer(flags)); - assert(flags.length <= 20, 'Coinbase flags > 20 bytes.'); - this.coinbaseFlags = flags; - } + if (options.preverify != null) { + assert(typeof options.preverify === 'boolean'); + this.preverify = options.preverify; + } - if (options.preverify != null) { - assert(typeof options.preverify === 'boolean'); - this.preverify = options.preverify; - } + if (options.minWeight != null) { + assert((options.minWeight >>> 0) === options.minWeight); + this.minWeight = options.minWeight; + } - if (options.minWeight != null) { - assert(util.isU32(options.minWeight)); - this.minWeight = options.minWeight; - } + if (options.maxWeight != null) { + assert((options.maxWeight >>> 0) === options.maxWeight); + assert(options.maxWeight <= consensus.MAX_BLOCK_WEIGHT, + 'Max weight must be below MAX_BLOCK_WEIGHT'); + this.maxWeight = options.maxWeight; + } - if (options.maxWeight != null) { - assert(util.isU32(options.maxWeight)); - assert(options.maxWeight <= consensus.MAX_BLOCK_WEIGHT, - 'Max weight must be below MAX_BLOCK_WEIGHT'); - this.maxWeight = options.maxWeight; - } + if (options.maxSigops != null) { + assert((options.maxSigops >>> 0) === options.maxSigops); + assert(options.maxSigops <= consensus.MAX_BLOCK_SIGOPS_COST, + 'Max sigops must be below MAX_BLOCK_SIGOPS_COST'); + this.maxSigops = options.maxSigops; + } - if (options.maxSigops != null) { - assert(util.isU32(options.maxSigops)); - assert(options.maxSigops <= consensus.MAX_BLOCK_SIGOPS_COST, - 'Max sigops must be below MAX_BLOCK_SIGOPS_COST'); - this.maxSigops = options.maxSigops; - } + if (options.priorityWeight != null) { + assert((options.priorityWeight >>> 0) === options.priorityWeight); + this.priorityWeight = options.priorityWeight; + } - if (options.priorityWeight != null) { - assert(util.isU32(options.priorityWeight)); - this.priorityWeight = options.priorityWeight; - } + if (options.priorityThreshold != null) { + assert((options.priorityThreshold >>> 0) === options.priorityThreshold); + this.priorityThreshold = options.priorityThreshold; + } - if (options.priorityThreshold != null) { - assert(util.isU32(options.priorityThreshold)); - this.priorityThreshold = options.priorityThreshold; - } + if (options.reservedWeight != null) { + assert((options.reservedWeight >>> 0) === options.reservedWeight); + this.reservedWeight = options.reservedWeight; + } - if (options.reservedWeight != null) { - assert(util.isU32(options.reservedWeight)); - this.reservedWeight = options.reservedWeight; - } + if (options.reservedSigops != null) { + assert((options.reservedSigops >>> 0) === options.reservedSigops); + this.reservedSigops = options.reservedSigops; + } - if (options.reservedSigops != null) { - assert(util.isU32(options.reservedSigops)); - this.reservedSigops = options.reservedSigops; + return this; } - return this; -}; + /** + * Instantiate miner options from object. + * @param {Object} options + * @returns {MinerOptions} + */ -/** - * Instantiate miner options from object. - * @param {Object} options - * @returns {MinerOptions} - */ - -MinerOptions.fromOptions = function fromOptions(options) { - return new MinerOptions().fromOptions(options); -}; + static fromOptions(options) { + return new this().fromOptions(options); + } +} /* * Helpers diff --git a/lib/mining/template.js b/lib/mining/template.js index 58ed10a5c..d7698f149 100644 --- a/lib/mining/template.js +++ b/lib/mining/template.js @@ -7,11 +7,10 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); +const bio = require('bufio'); +const merkle = require('bcrypto/lib/merkle'); const util = require('../utils/util'); -const digest = require('../crypto/digest'); -const merkle = require('../crypto/merkle'); -const StaticWriter = require('../utils/staticwriter'); const Address = require('../primitives/address'); const TX = require('../primitives/tx'); const Block = require('../primitives/block'); @@ -19,580 +18,587 @@ const Input = require('../primitives/input'); const Output = require('../primitives/output'); const consensus = require('../protocol/consensus'); const policy = require('../protocol/policy'); -const encoding = require('../utils/encoding'); const CoinView = require('../coins/coinview'); const Script = require('../script/script'); const common = require('./common'); -const scrypt = require('../crypto/scrypt').derive; -const DUMMY = Buffer.alloc(0); +const scrypt = require('bcrypto/lib/scrypt').derive; -/** - * Block Template - * @alias module:mining.BlockTemplate - * @constructor - * @param {Object} options +/* + * Constants */ -function BlockTemplate(options) { - if (!(this instanceof BlockTemplate)) - return new BlockTemplate(options); - - this.prevBlock = encoding.NULL_HASH; - this.version = 1; - this.height = 0; - this.time = 0; - this.bits = 0; - this.target = encoding.ZERO_HASH; - this.locktime = 0; - this.mtp = 0; - this.flags = 0; - this.coinbaseFlags = DUMMY; - this.witness = false; - this.address = new Address(); - this.sigops = 400; - this.weight = 4000; - this.interval = 210000; - this.fees = 0; - this.tree = new MerkleTree(); - this.commitment = encoding.ZERO_HASH; - this.left = DUMMY; - this.right = DUMMY; - this.items = []; - - if (options) - this.fromOptions(options); -} +const DUMMY = Buffer.alloc(0); /** - * Inject properties from options. - * @private - * @param {Object} options - * @returns {BlockTemplate} + * Block Template + * @alias module:mining.BlockTemplate */ -BlockTemplate.prototype.fromOptions = function fromOptions(options) { - assert(options); - - if (options.prevBlock != null) { - assert(typeof options.prevBlock === 'string'); - this.prevBlock = options.prevBlock; +class BlockTemplate { + /** + * Create a block template. + * @constructor + * @param {Object} options + */ + + constructor(options) { + this.prevBlock = consensus.ZERO_HASH; + this.version = 1; + this.height = 0; + this.time = 0; + this.bits = 0; + this.target = consensus.ZERO_HASH; + this.locktime = 0; + this.mtp = 0; + this.flags = 0; + this.coinbaseFlags = DUMMY; + this.witness = false; + this.address = new Address(); + this.sigops = 400; + this.weight = 4000; + this.interval = 210000; + this.fees = 0; + this.tree = new MerkleTree(); + this.commitment = consensus.ZERO_HASH; + this.left = DUMMY; + this.right = DUMMY; + this.items = []; + + if (options) + this.fromOptions(options); } - if (options.version != null) { - assert(typeof options.version === 'number'); - this.version = options.version; - } - - if (options.height != null) { - assert(typeof options.height === 'number'); - this.height = options.height; - } - - if (options.time != null) { - assert(typeof options.time === 'number'); - this.time = options.time; - } + /** + * Inject properties from options. + * @private + * @param {Object} options + * @returns {BlockTemplate} + */ - if (options.bits != null) - this.setBits(options.bits); + fromOptions(options) { + assert(options); - if (options.target != null) - this.setTarget(options.target); - - if (options.locktime != null) { - assert(typeof options.locktime === 'number'); - this.locktime = options.locktime; - } - - if (options.mtp != null) { - assert(typeof options.mtp === 'number'); - this.mtp = options.mtp; - } - - if (options.flags != null) { - assert(typeof options.flags === 'number'); - this.flags = options.flags; - } - - if (options.coinbaseFlags != null) { - assert(Buffer.isBuffer(options.coinbaseFlags)); - this.coinbaseFlags = options.coinbaseFlags; - } - - if (options.witness != null) { - assert(typeof options.witness === 'boolean'); - this.witness = options.witness; - } - - if (options.address != null) - this.address.fromOptions(options.address); - - if (options.sigops != null) { - assert(typeof options.sigops === 'number'); - this.sigops = options.sigops; - } + if (options.prevBlock != null) { + assert(Buffer.isBuffer(options.prevBlock)); + this.prevBlock = options.prevBlock; + } - if (options.weight != null) { - assert(typeof options.weight === 'number'); - this.weight = options.weight; - } + if (options.version != null) { + assert(typeof options.version === 'number'); + this.version = options.version; + } - if (options.interval != null) { - assert(typeof options.interval === 'number'); - this.interval = options.interval; - } + if (options.height != null) { + assert(typeof options.height === 'number'); + this.height = options.height; + } - if (options.fees != null) { - assert(typeof options.fees === 'number'); - this.fees = options.fees; - } + if (options.time != null) { + assert(typeof options.time === 'number'); + this.time = options.time; + } - if (options.items != null) { - assert(Array.isArray(options.items)); - this.items = options.items; - } + if (options.bits != null) + this.setBits(options.bits); - return this; -}; + if (options.target != null) + this.setTarget(options.target); -/** - * Instantiate block template from options. - * @param {Object} options - * @returns {BlockTemplate} - */ - -BlockTemplate.fromOptions = function fromOptions(options) { - return new BlockTemplate().fromOptions(options); -}; + if (options.locktime != null) { + assert(typeof options.locktime === 'number'); + this.locktime = options.locktime; + } -/** - * Create witness commitment hash. - * @returns {Buffer} - */ + if (options.mtp != null) { + assert(typeof options.mtp === 'number'); + this.mtp = options.mtp; + } -BlockTemplate.prototype.getWitnessHash = function getWitnessHash() { - const nonce = encoding.ZERO_HASH; - const leaves = []; + if (options.flags != null) { + assert(typeof options.flags === 'number'); + this.flags = options.flags; + } - leaves.push(encoding.ZERO_HASH); + if (options.coinbaseFlags != null) { + assert(Buffer.isBuffer(options.coinbaseFlags)); + this.coinbaseFlags = options.coinbaseFlags; + } - for (const item of this.items) - leaves.push(item.tx.witnessHash()); + if (options.witness != null) { + assert(typeof options.witness === 'boolean'); + this.witness = options.witness; + } - const [root, malleated] = merkle.createRoot(leaves); + if (options.address != null) + this.address.fromOptions(options.address); - assert(!malleated); + if (options.sigops != null) { + assert(typeof options.sigops === 'number'); + this.sigops = options.sigops; + } - return digest.root256(root, nonce); -}; + if (options.weight != null) { + assert(typeof options.weight === 'number'); + this.weight = options.weight; + } -/** - * Create witness commitment script. - * @returns {Script} - */ + if (options.interval != null) { + assert(typeof options.interval === 'number'); + this.interval = options.interval; + } -BlockTemplate.prototype.getWitnessScript = function getWitnessScript() { - return Script.fromCommitment(this.commitment); -}; + if (options.fees != null) { + assert(typeof options.fees === 'number'); + this.fees = options.fees; + } -/** - * Set the target (bits). - * @param {Number} bits - */ + if (options.items != null) { + assert(Array.isArray(options.items)); + this.items = options.items; + } -BlockTemplate.prototype.setBits = function setBits(bits) { - assert(typeof bits === 'number'); - this.bits = bits; - this.target = common.getTarget(bits); -}; + return this; + } -/** - * Set the target (uint256le). - * @param {Buffer} target - */ + /** + * Instantiate block template from options. + * @param {Object} options + * @returns {BlockTemplate} + */ -BlockTemplate.prototype.setTarget = function setTarget(target) { - assert(Buffer.isBuffer(target)); - this.bits = common.getBits(target); - this.target = target; -}; + static fromOptions(options) { + return new this().fromOptions(options); + } -/** - * Calculate the block reward. - * @returns {Amount} - */ + /** + * Create witness commitment hash. + * @returns {Buffer} + */ -BlockTemplate.prototype.getReward = function getReward() { - const reward = consensus.getReward(this.height, this.interval); - return reward + this.fees; -}; + getWitnessHash() { + const nonce = consensus.ZERO_HASH; + const leaves = []; -/** - * Initialize the default coinbase. - * @param {Buffer} hash - Witness commitment hash. - * @returns {TX} - */ + leaves.push(consensus.ZERO_HASH); -BlockTemplate.prototype.createCoinbase = function createCoinbase(hash) { - const scale = consensus.WITNESS_SCALE_FACTOR; - const cb = new TX(); + for (const {tx} of this.items) + leaves.push(tx.witnessHash()); - // Coinbase input. - const input = new Input(); + const [root, malleated] = merkle.createRoot(hash256, leaves); - // Height (required in v2+ blocks) - input.script.pushInt(this.height); + assert(!malleated); - // Coinbase flags. - input.script.pushData(encoding.ZERO_HASH160); + return hash256.root(root, nonce); + } - // Smaller nonce for good measure. - input.script.pushData(util.nonce(4)); + /** + * Create witness commitment script. + * @returns {Script} + */ - // Extra nonce: incremented when - // the nonce overflows. - input.script.pushData(encoding.ZERO_U64); + getWitnessScript() { + return Script.fromCommitment(this.commitment); + } - input.script.compile(); + /** + * Set the target (bits). + * @param {Number} bits + */ - // Set up the witness nonce. - if (this.witness) { - input.witness.push(encoding.ZERO_HASH); - input.witness.compile(); + setBits(bits) { + assert(typeof bits === 'number'); + this.bits = bits; + this.target = common.getTarget(bits); } - cb.inputs.push(input); + /** + * Set the target (uint256le). + * @param {Buffer} target + */ - // Reward output. - const output = new Output(); - output.script.fromPubkeyhash(encoding.ZERO_HASH160); - output.value = this.getReward(); + setTarget(target) { + assert(Buffer.isBuffer(target)); + this.bits = common.getBits(target); + this.target = target; + } - cb.outputs.push(output); + /** + * Calculate the block reward. + * @returns {Amount} + */ - // If we're using segwit, we - // need to set up the commitment. - if (this.witness) { - // Commitment output. - const commit = new Output(); - commit.script.fromCommitment(hash); - cb.outputs.push(commit); + getReward() { + const reward = consensus.getReward(this.height, this.interval); + return reward + this.fees; } - // Padding for the CB height (constant size). - const op = input.script.get(0); - assert(op); - const padding = 5 - op.getSize(); - assert(padding >= 0); + /** + * Initialize the default coinbase. + * @param {Buffer} hash - Witness commitment hash. + * @returns {TX} + */ - // Reserved size. - // Without segwit: - // CB weight = 504 - // CB stripped size = 126 - // CB size = 126 - // Sigops cost = 4 - // With segwit: - // CB weight = 728 - // CB stripped size = 173 - // CB size = 209 - // Sigops cost = 4 - if (!this.witness) { - assert.strictEqual(cb.getWeight() + padding * scale, 504); - assert.strictEqual(cb.getBaseSize() + padding, 126); - assert.strictEqual(cb.getSize() + padding, 126); - } else { - assert.strictEqual(cb.getWeight() + padding * scale, 728); - assert.strictEqual(cb.getBaseSize() + padding, 173); - assert.strictEqual(cb.getSize() + padding, 209); - } + createCoinbase(hash) { + const scale = consensus.WITNESS_SCALE_FACTOR; + const cb = new TX(); - // Setup coinbase flags (variable size). - input.script.setData(1, this.coinbaseFlags); - input.script.compile(); + // Coinbase input. + const input = new Input(); - // Setup output script (variable size). - output.script.fromAddress(this.address); + // Height (required in v2+ blocks) + input.script.pushInt(this.height); - cb.refresh(); + // Coinbase flags. + input.script.pushData(Buffer.alloc(20, 0x00)); - assert(input.script.getSize() <= 100, - 'Coinbase input script is too large!'); + // Smaller nonce for good measure. + const nonce = Buffer.allocUnsafe(4); + nonce.writeUInt32LE(Math.random() * 0x100000000, 0, true); + input.script.pushData(nonce); - return cb; -}; + // Extra nonce: incremented when + // the nonce overflows. + input.script.pushData(Buffer.alloc(8, 0x00)); -/** - * Refresh the coinbase and merkle tree. - */ + input.script.compile(); -BlockTemplate.prototype.refresh = function refresh() { - const hash = this.getWitnessHash(); - const cb = this.createCoinbase(hash); - const raw = cb.toRaw(); - let size = 0; + // Set up the witness nonce. + if (this.witness) { + input.witness.push(consensus.ZERO_HASH); + input.witness.compile(); + } - size += 4; // version - size += 1; // varint inputs length - size += cb.inputs[0].getSize(); // input size - size -= 4 + 4 + 4; // -(nonce1 + nonce2 + sequence) + cb.inputs.push(input); - // Add to the size for floData :) - let bufferLength = Buffer.from(cb.strFloData).length; + // Reward output. + const output = new Output(); + output.script.fromPubkeyhash(Buffer.alloc(20, 0x00)); + output.value = this.getReward(); - if (cb.strFloData.length > 0){ - size += encoding.sizeVarint(bufferLength); - size += bufferLength - } else { - size += encoding.sizeVarint(0); - } + cb.outputs.push(output); - // Cut off right after the nonce - // push and before the sequence. - const left = raw.slice(0, size); + // If we're using segwit, we + // need to set up the commitment. + if (this.witness) { + // Commitment output. + const commit = new Output(); + commit.script.fromCommitment(hash); + cb.outputs.push(commit); + } - // Include the sequence. - size += 4 + 4; // nonce1 + nonce2 - const right = raw.slice(size); + // Padding for the CB height (constant size). + const op = input.script.get(0); + assert(op); + const padding = 5 - op.getSize(); + assert(padding >= 0); + + // Reserved size. + // Without segwit: + // CB weight = 504 + // CB stripped size = 126 + // CB size = 126 + // Sigops cost = 4 + // With segwit: + // CB weight = 728 + // CB stripped size = 173 + // CB size = 209 + // Sigops cost = 4 + if (!this.witness) { + assert.strictEqual(cb.getWeight() + padding * scale, 504); + assert.strictEqual(cb.getBaseSize() + padding, 126); + assert.strictEqual(cb.getSize() + padding, 126); + } else { + assert.strictEqual(cb.getWeight() + padding * scale, 728); + assert.strictEqual(cb.getBaseSize() + padding, 173); + assert.strictEqual(cb.getSize() + padding, 209); + } - this.commitment = hash; - this.left = left; - this.right = right; - this.tree = MerkleTree.fromItems(this.items); -}; + // Setup coinbase flags (variable size). + input.script.setData(1, this.coinbaseFlags); + input.script.compile(); -/** - * Get raw coinbase with desired nonces. - * @param {Number} nonce1 - * @param {Number} nonce2 - * @returns {Buffer} - */ + // Setup output script (variable size). + output.script.fromAddress(this.address); -BlockTemplate.prototype.getRawCoinbase = function getRawCoinbase(nonce1, nonce2) { - let size = 0; + cb.refresh(); - size += this.left.length; - size += 4 + 4; - size += this.right.length; + assert(input.script.getSize() <= 100, + 'Coinbase input script is too large!'); - const bw = new StaticWriter(size); - bw.writeBytes(this.left); - bw.writeU32BE(nonce1); - bw.writeU32BE(nonce2); - bw.writeBytes(this.right); + return cb; + } - return bw.render(); -}; + /** + * Refresh the coinbase and merkle tree. + */ + + refresh() { + const hash = this.getWitnessHash(); + const cb = this.createCoinbase(hash); + const raw = cb.toRaw(); + let size = 0; + + size += 4; // version + size += 1; // varint inputs length + size += cb.inputs[0].getSize(); // input size + size -= 4 + 4 + 4; // -(nonce1 + nonce2 + sequence) + + // Add to the size for floData :) + let bufferLength = Buffer.from(cb.strFloData).length; + + if (cb.strFloData.length > 0){ + size += encoding.sizeVarint(bufferLength); + size += bufferLength + } else { + size += encoding.sizeVarint(0); + } -/** - * Calculate the merkle root with given nonces. - * @param {Number} nonce1 - * @param {Number} nonce2 - * @returns {Buffer} - */ + // Cut off right after the nonce + // push and before the sequence. + const left = raw.slice(0, size); -BlockTemplate.prototype.getRoot = function getRoot(nonce1, nonce2) { - const raw = this.getRawCoinbase(nonce1, nonce2); - const hash = digest.hash256(raw); - return this.tree.withFirst(hash); -}; + // Include the sequence. + size += 4 + 4; // nonce1 + nonce2 + const right = raw.slice(size); -/** - * Create raw block header with given parameters. - * @param {Buffer} root - * @param {Number} time - * @param {Number} nonce - * @returns {Buffer} - */ + this.commitment = hash; + this.left = left; + this.right = right; + this.tree = MerkleTree.fromItems(this.items); + } -BlockTemplate.prototype.getHeader = function getHeader(root, time, nonce) { - const bw = new StaticWriter(80); + /** + * Get raw coinbase with desired nonces. + * @param {Number} nonce1 + * @param {Number} nonce2 + * @returns {Buffer} + */ - bw.writeU32(this.version); - bw.writeHash(this.prevBlock); - bw.writeHash(root); - bw.writeU32(time); - bw.writeU32(this.bits); - bw.writeU32(nonce); + getRawCoinbase(nonce1, nonce2) { + let size = 0; - return bw.render(); -}; + size += this.left.length; + size += 4 + 4; + size += this.right.length; -/** - * Calculate proof with given parameters. - * @param {Number} nonce1 - * @param {Number} nonce2 - * @param {Number} time - * @param {Number} nonce - * @returns {BlockProof} - */ + const bw = bio.write(size); + bw.writeBytes(this.left); + bw.writeU32BE(nonce1); + bw.writeU32BE(nonce2); + bw.writeBytes(this.right); -BlockTemplate.prototype.getProof = function getProof(nonce1, nonce2, time, nonce) { - const root = this.getRoot(nonce1, nonce2); - const data = this.getHeader(root, time, nonce); - const hash = scrypt(data, data, 1024, 1, 1, 32); - return new BlockProof(hash, root, nonce1, nonce2, time, nonce); -}; + return bw.render(); + } -/** - * Create coinbase from given parameters. - * @param {Number} nonce1 - * @param {Number} nonce2 - * @returns {TX} - */ + /** + * Calculate the merkle root with given nonces. + * @param {Number} nonce1 + * @param {Number} nonce2 + * @returns {Buffer} + */ + + getRoot(nonce1, nonce2) { + const raw = this.getRawCoinbase(nonce1, nonce2); + const hash = hash256.digest(raw); + return this.tree.withFirst(hash); + } -BlockTemplate.prototype.getCoinbase = function getCoinbase(nonce1, nonce2) { - const raw = this.getRawCoinbase(nonce1, nonce2); - const tx = TX.fromRaw(raw); + /** + * Create raw block header with given parameters. + * @param {Buffer} root + * @param {Number} time + * @param {Number} nonce + * @returns {Buffer} + */ + + getHeader(root, time, nonce) { + const bw = bio.write(80); + + bw.writeU32(this.version); + bw.writeHash(this.prevBlock); + bw.writeHash(root); + bw.writeU32(time); + bw.writeU32(this.bits); + bw.writeU32(nonce); + + return bw.render(); + } - if (this.witness) { - const input = tx.inputs[0]; - input.witness.push(encoding.ZERO_HASH); - input.witness.compile(); - tx.refresh(); + /** + * Calculate proof with given parameters. + * @param {Number} nonce1 + * @param {Number} nonce2 + * @param {Number} time + * @param {Number} nonce + * @returns {BlockProof} + */ + + getProof(nonce1, nonce2, time, nonce) { + const root = this.getRoot(nonce1, nonce2); + const data = this.getHeader(root, time, nonce); + const hash = scrypt(data, data, 1024, 1, 1, 32); + return new BlockProof(hash, root, nonce1, nonce2, time, nonce); } - return tx; -}; + /** + * Create coinbase from given parameters. + * @param {Number} nonce1 + * @param {Number} nonce2 + * @returns {TX} + */ + + getCoinbase(nonce1, nonce2) { + const raw = this.getRawCoinbase(nonce1, nonce2); + const tx = TX.fromRaw(raw); + + if (this.witness) { + const input = tx.inputs[0]; + input.witness.push(consensus.ZERO_HASH); + input.witness.compile(); + tx.refresh(); + } -/** - * Create block from calculated proof. - * @param {BlockProof} proof - * @returns {Block} - */ + return tx; + } -BlockTemplate.prototype.commit = function commit(proof) { - const root = proof.root; - const n1 = proof.nonce1; - const n2 = proof.nonce2; - const time = proof.time; - const nonce = proof.nonce; - const block = new Block(); + /** + * Create block from calculated proof. + * @param {BlockProof} proof + * @returns {Block} + */ - block.version = this.version; - block.prevBlock = this.prevBlock; - block.merkleRoot = root.toString('hex'); - block.time = time; - block.bits = this.bits; - block.nonce = nonce; + commit(proof) { + const root = proof.root; + const n1 = proof.nonce1; + const n2 = proof.nonce2; + const time = proof.time; + const nonce = proof.nonce; + const block = new Block(); - const tx = this.getCoinbase(n1, n2); + block.version = this.version; + block.prevBlock = this.prevBlock; + block.merkleRoot = root; + block.time = time; + block.bits = this.bits; + block.nonce = nonce; - block.txs.push(tx); + const tx = this.getCoinbase(n1, n2); - for (const item of this.items) - block.txs.push(item.tx); + block.txs.push(tx); - return block; -}; + for (const item of this.items) + block.txs.push(item.tx); -/** - * Quick and dirty way to - * get a coinbase tx object. - * @returns {TX} - */ + return block; + } -BlockTemplate.prototype.toCoinbase = function toCoinbase() { - return this.getCoinbase(0, 0); -}; + /** + * Quick and dirty way to + * get a coinbase tx object. + * @returns {TX} + */ -/** - * Quick and dirty way to get a block - * object (most likely to be an invalid one). - * @returns {Block} - */ + toCoinbase() { + return this.getCoinbase(0, 0); + } -BlockTemplate.prototype.toBlock = function toBlock() { - const proof = this.getProof(0, 0, this.time, 0); - return this.commit(proof); -}; + /** + * Quick and dirty way to get a block + * object (most likely to be an invalid one). + * @returns {Block} + */ -/** - * Calculate the target difficulty. - * @returns {Number} - */ + toBlock() { + const proof = this.getProof(0, 0, this.time, 0); + return this.commit(proof); + } -BlockTemplate.prototype.getDifficulty = function getDifficulty() { - return common.getDifficulty(this.target); -}; + /** + * Calculate the target difficulty. + * @returns {Number} + */ -/** - * Set the reward output - * address and refresh. - * @param {Address} address - */ + getDifficulty() { + return common.getDifficulty(this.target); + } -BlockTemplate.prototype.setAddress = function setAddress(address) { - this.address = Address(address); - this.refresh(); -}; + /** + * Set the reward output + * address and refresh. + * @param {Address} address + */ -/** - * Add a transaction to the template. - * @param {TX} tx - * @param {CoinView} view - */ + setAddress(address) { + this.address = new Address(address); + this.refresh(); + } -BlockTemplate.prototype.addTX = function addTX(tx, view) { - assert(!tx.mutable, 'Cannot add mutable TX to block.'); + /** + * Add a transaction to the template. + * @param {TX} tx + * @param {CoinView} view + */ - const item = BlockEntry.fromTX(tx, view, this); - const weight = item.tx.getWeight(); - const sigops = item.sigops; + addTX(tx, view) { + assert(!tx.mutable, 'Cannot add mutable TX to block.'); - if (!tx.isFinal(this.height, this.locktime)) - return false; + const item = BlockEntry.fromTX(tx, view, this); + const weight = item.tx.getWeight(); + const sigops = item.sigops; - if (this.weight + weight > consensus.MAX_BLOCK_WEIGHT) - return false; + if (!tx.isFinal(this.height, this.locktime)) + return false; - if (this.sigops + sigops > consensus.MAX_BLOCK_SIGOPS_COST) - return false; + if (this.weight + weight > consensus.MAX_BLOCK_WEIGHT) + return false; - if (!this.witness && tx.hasWitness()) - return false; + if (this.sigops + sigops > consensus.MAX_BLOCK_SIGOPS_COST) + return false; - this.weight += weight; - this.sigops += sigops; - this.fees += item.fee; + if (!this.witness && tx.hasWitness()) + return false; - // Add the tx to our block - this.items.push(item); + this.weight += weight; + this.sigops += sigops; + this.fees += item.fee; - return true; -}; + // Add the tx to our block + this.items.push(item); -/** - * Add a transaction to the template - * (less verification than addTX). - * @param {TX} tx - * @param {CoinView?} view - */ + return true; + } -BlockTemplate.prototype.pushTX = function pushTX(tx, view) { - assert(!tx.mutable, 'Cannot add mutable TX to block.'); + /** + * Add a transaction to the template + * (less verification than addTX). + * @param {TX} tx + * @param {CoinView?} view + */ - if (!view) - view = new CoinView(); + pushTX(tx, view) { + assert(!tx.mutable, 'Cannot add mutable TX to block.'); - const item = BlockEntry.fromTX(tx, view, this); - const weight = item.tx.getWeight(); - const sigops = item.sigops; + if (!view) + view = new CoinView(); - this.weight += weight; - this.sigops += sigops; - this.fees += item.fee; + const item = BlockEntry.fromTX(tx, view, this); + const weight = item.tx.getWeight(); + const sigops = item.sigops; - // Add the tx to our block - this.items.push(item); + this.weight += weight; + this.sigops += sigops; + this.fees += item.fee; - return true; -}; + // Add the tx to our block + this.items.push(item); + + return true; + } +} /** - * BlockEntry + * Block Entry * @alias module:mining.BlockEntry - * @constructor - * @param {TX} tx * @property {TX} tx * @property {Hash} hash * @property {Amount} fee @@ -603,170 +609,190 @@ BlockTemplate.prototype.pushTX = function pushTX(tx, view) { * @property {Number} depCount */ -function BlockEntry(tx) { - this.tx = tx; - this.hash = tx.hash('hex'); - this.fee = 0; - this.rate = 0; - this.priority = 0; - this.free = false; - this.sigops = 0; - this.descRate = 0; - this.depCount = 0; -} +class BlockEntry { + /** + * Create a block entry. + * @constructor + * @param {TX} tx + */ + + constructor(tx) { + this.tx = tx; + this.hash = tx.hash(); + this.fee = 0; + this.rate = 0; + this.priority = 0; + this.free = false; + this.sigops = 0; + this.descRate = 0; + this.depCount = 0; + } -/** - * Instantiate block entry from transaction. - * @param {TX} tx - * @param {CoinView} view - * @param {BlockTemplate} attempt - * @returns {BlockEntry} - */ + /** + * Instantiate block entry from transaction. + * @param {TX} tx + * @param {CoinView} view + * @param {BlockTemplate} attempt + * @returns {BlockEntry} + */ + + static fromTX(tx, view, attempt) { + const item = new this(tx); + item.fee = tx.getFee(view); + item.rate = tx.getRate(view); + item.priority = tx.getPriority(view, attempt.height); + item.free = false; + item.sigops = tx.getSigopsCost(view, attempt.flags); + item.descRate = item.rate; + return item; + } -BlockEntry.fromTX = function fromTX(tx, view, attempt) { - const item = new BlockEntry(tx); - item.fee = tx.getFee(view); - item.rate = tx.getRate(view); - item.priority = tx.getPriority(view, attempt.height); - item.free = false; - item.sigops = tx.getSigopsCost(view, attempt.flags); - item.descRate = item.rate; - return item; -}; + /** + * Instantiate block entry from mempool entry. + * @param {MempoolEntry} entry + * @param {BlockTemplate} attempt + * @returns {BlockEntry} + */ + + static fromEntry(entry, attempt) { + const item = new this(entry.tx); + item.fee = entry.getFee(); + item.rate = entry.getDeltaRate(); + item.priority = entry.getPriority(attempt.height); + item.free = entry.getDeltaFee() < policy.getMinFee(entry.size); + item.sigops = entry.sigops; + item.descRate = entry.getDescRate(); + return item; + } +} /** - * Instantiate block entry from mempool entry. - * @param {MempoolEntry} entry - * @param {BlockTemplate} attempt - * @returns {BlockEntry} + * Block Proof */ -BlockEntry.fromEntry = function fromEntry(entry, attempt) { - const item = new BlockEntry(entry.tx); - item.fee = entry.getFee(); - item.rate = entry.getDeltaRate(); - item.priority = entry.getPriority(attempt.height); - item.free = entry.getDeltaFee() < policy.getMinFee(entry.size); - item.sigops = entry.sigops; - item.descRate = entry.getDescRate(); - return item; -}; - -/* - * BlockProof - * @constructor - * @param {Hash} hash - * @param {Hash} root - * @param {Number} nonce1 - * @param {Number} nonce2 - * @param {Number} time - * @param {Number} nonce - */ - -function BlockProof(hash, root, nonce1, nonce2, time, nonce) { - this.hash = hash; - this.root = root; - this.nonce1 = nonce1; - this.nonce2 = nonce2; - this.time = time; - this.nonce = nonce; -} +class BlockProof { + /** + * Create a block proof. + * @constructor + * @param {Hash} hash + * @param {Hash} root + * @param {Number} nonce1 + * @param {Number} nonce2 + * @param {Number} time + * @param {Number} nonce + */ + + constructor(hash, root, nonce1, nonce2, time, nonce) { + this.hash = hash; + this.root = root; + this.nonce1 = nonce1; + this.nonce2 = nonce2; + this.time = time; + this.nonce = nonce; + } -BlockProof.prototype.rhash = function rhash() { - return util.revHex(this.hash.toString('hex')); -}; + rhash() { + return util.revHex(this.hash); + } -BlockProof.prototype.verify = function verify(target) { - return common.rcmp(this.hash, target) <= 0; -}; + verify(target) { + return common.rcmp(this.hash, target) <= 0; + } -BlockProof.prototype.getDifficulty = function getDifficulty() { - return common.getDifficulty(this.hash); -}; + getDifficulty() { + return common.getDifficulty(this.hash); + } +} -/* - * MerkleTree - * @constructor +/** + * Merkle Tree * @property {Hash[]} steps */ -function MerkleTree() { - this.steps = []; -} +class MerkleTree { + /** + * Create a merkle tree. + * @constructor + */ + + constructor() { + this.steps = []; + } + + withFirst(hash) { + for (const step of this.steps) + hash = hash256.root(hash, step); + return hash; + } -MerkleTree.prototype.withFirst = function withFirst(hash) { - for (const step of this.steps) - hash = digest.root256(hash, step); - return hash; -}; + toJSON() { + const steps = []; -MerkleTree.prototype.toJSON = function toJSON() { - const steps = []; + for (const step of this.steps) + steps.push(step.toString('hex')); - for (const step of this.steps) - steps.push(step.toString('hex')); + return steps; + } - return steps; -}; + fromItems(items) { + const leaves = []; -MerkleTree.prototype.fromItems = function fromItems(items) { - const leaves = []; + leaves.push(consensus.ZERO_HASH); - leaves.push(encoding.ZERO_HASH); + for (const item of items) + leaves.push(item.tx.hash()); - for (const item of items) - leaves.push(item.tx.hash()); + return this.fromLeaves(leaves); + } - return this.fromLeaves(leaves); -}; + static fromItems(items) { + return new this().fromItems(items); + } -MerkleTree.fromItems = function fromItems(items) { - return new MerkleTree().fromItems(items); -}; + fromBlock(txs) { + const leaves = []; -MerkleTree.prototype.fromBlock = function fromBlock(txs) { - const leaves = []; + leaves.push(consensus.ZERO_HASH); - leaves.push(encoding.ZERO_HASH); + for (let i = 1; i < txs.length; i++) { + const tx = txs[i]; + leaves.push(tx.hash()); + } - for (let i = 1; i < txs.length; i++) { - const tx = txs[i]; - leaves.push(tx.hash()); + return this.fromLeaves(leaves); } - return this.fromLeaves(leaves); -}; + static fromBlock(txs) { + return new this().fromBlock(txs); + } -MerkleTree.fromBlock = function fromBlock(txs) { - return new MerkleTree().fromBlock(txs); -}; + fromLeaves(leaves) { + let len = leaves.length; -MerkleTree.prototype.fromLeaves = function fromLeaves(leaves) { - let len = leaves.length; + while (len > 1) { + const hashes = [consensus.ZERO_HASH]; - while (len > 1) { - const hashes = [encoding.ZERO_HASH]; + this.steps.push(leaves[1]); - this.steps.push(leaves[1]); + if (len % 2) + leaves.push(leaves[len - 1]); - if (len % 2) - leaves.push(leaves[len - 1]); + for (let i = 2; i < len; i += 2) { + const hash = hash256.root(leaves[i], leaves[i + 1]); + hashes.push(hash); + } - for (let i = 2; i < len; i += 2) { - const hash = digest.root256(leaves[i], leaves[i + 1]); - hashes.push(hash); + leaves = hashes; + len = leaves.length; } - leaves = hashes; - len = leaves.length; + return this; } - return this; -}; - -MerkleTree.fromLeaves = function fromLeaves(leaves) { - return new MerkleTree().fromLeaves(leaves); -}; + static fromLeaves(leaves) { + return new this().fromLeaves(leaves); + } +} /* * Expose diff --git a/lib/native-browser.js b/lib/native-browser.js deleted file mode 100644 index 8f8d0686f..000000000 --- a/lib/native-browser.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -exports.binding = null; diff --git a/lib/native.js b/lib/native.js deleted file mode 100644 index cdd376393..000000000 --- a/lib/native.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * native.js - native bindings for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -exports.binding = null; - -if (Number(process.env.BCOIN_NO_NATIVE) !== 1) { - try { - exports.binding = require('bcoin-native'); - } catch (e) { - ; - } -} diff --git a/lib/net/bip150.js b/lib/net/bip150.js deleted file mode 100644 index 810857bc8..000000000 --- a/lib/net/bip150.js +++ /dev/null @@ -1,813 +0,0 @@ -/*! - * bip150.js - peer auth. - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - * Resources: - * https://github.com/bitcoin/bips/blob/master/bip-0150.mediawiki - */ - -'use strict'; - -const assert = require('assert'); -const path = require('path'); -const EventEmitter = require('events'); -const co = require('../utils/co'); -const digest = require('../crypto/digest'); -const random = require('../crypto/random'); -const ccmp = require('../crypto/ccmp'); -const packets = require('./packets'); -const secp256k1 = require('../crypto/secp256k1'); -const StaticWriter = require('../utils/staticwriter'); -const base58 = require('../utils/base58'); -const encoding = require('../utils/encoding'); -const IP = require('../utils/ip'); -const dns = require('./dns'); -const fs = require('../utils/fs'); -const Logger = require('../node/logger'); - -/** - * Represents a BIP150 input/output stream. - * @alias module:net.BIP150 - * @constructor - * @param {BIP151} bip151 - * @param {String} host - * @param {Boolean} outbound - * @param {AuthDB} db - * @param {Buffer} key - Identity key. - * @property {BIP151} bip151 - * @property {BIP151Stream} input - * @property {BIP151Stream} output - * @property {String} hostname - * @property {Boolean} outbound - * @property {AuthDB} db - * @property {Buffer} privateKey - * @property {Buffer} publicKey - * @property {Buffer} peerIdentity - * @property {Boolean} challengeReceived - * @property {Boolean} replyReceived - * @property {Boolean} proposeReceived - * @property {Boolean} challengeSent - * @property {Boolean} auth - * @property {Boolean} completed - */ - -function BIP150(bip151, host, outbound, db, key) { - if (!(this instanceof BIP150)) - return new BIP150(bip151, host, outbound, db, key); - - EventEmitter.call(this); - - assert(bip151, 'BIP150 requires BIP151.'); - assert(typeof host === 'string', 'Hostname required.'); - assert(typeof outbound === 'boolean', 'Outbound flag required.'); - assert(db instanceof AuthDB, 'Auth DB required.'); - assert(Buffer.isBuffer(key), 'Identity key required.'); - - this.bip151 = bip151; - this.input = bip151.input; - this.output = bip151.output; - this.hostname = host; - this.outbound = outbound; - this.db = db; - this.privateKey = key; - this.publicKey = secp256k1.publicKeyCreate(key, true); - - this.peerIdentity = null; - this.challengeReceived = false; - this.replyReceived = false; - this.proposeReceived = false; - this.challengeSent = false; - this.auth = false; - this.completed = false; - this.job = null; - this.timeout = null; - this.onAuth = null; - - this._init(); -} - -Object.setPrototypeOf(BIP150.prototype, EventEmitter.prototype); - -/** - * Initialize BIP150. - * @private - */ - -BIP150.prototype._init = function _init() { - if (this.outbound) - this.peerIdentity = this.db.getKnown(this.hostname); -}; - -/** - * Test whether the state should be - * considered authed. This differs - * for inbound vs. outbound. - * @returns {Boolean} - */ - -BIP150.prototype.isAuthed = function isAuthed() { - if (this.outbound) - return this.challengeSent && this.challengeReceived; - return this.challengeReceived && this.replyReceived; -}; - -/** - * Handle a received challenge hash. - * Returns an authreply signature. - * @param {Buffer} hash - * @returns {Buffer} - * @throws on auth failure - */ - -BIP150.prototype.challenge = function challenge(hash) { - const type = this.outbound ? 'r' : 'i'; - - assert(this.bip151.handshake, 'No BIP151 handshake before challenge.'); - assert(!this.challengeReceived, 'Peer challenged twice.'); - this.challengeReceived = true; - - if (hash.equals(encoding.ZERO_HASH)) - throw new Error('Auth failure.'); - - const msg = this.hash(this.input.sid, type, this.publicKey); - - if (!ccmp(hash, msg)) - return encoding.ZERO_SIG64; - - if (this.isAuthed()) { - this.auth = true; - this.emit('auth'); - } - - const sig = secp256k1.sign(msg, this.privateKey); - - // authreply - return secp256k1.fromDER(sig); -}; - -/** - * Handle a received reply signature. - * Returns an authpropose hash. - * @param {Buffer} data - * @returns {Buffer} - * @throws on auth failure - */ - -BIP150.prototype.reply = function reply(data) { - const type = this.outbound ? 'i' : 'r'; - - assert(this.challengeSent, 'Unsolicited reply.'); - assert(!this.replyReceived, 'Peer replied twice.'); - this.replyReceived = true; - - if (data.equals(encoding.ZERO_SIG64)) - throw new Error('Auth failure.'); - - if (!this.peerIdentity) - return random.randomBytes(32); - - const sig = secp256k1.toDER(data); - const msg = this.hash(this.output.sid, type, this.peerIdentity); - - const result = secp256k1.verify(msg, sig, this.peerIdentity); - - if (!result) - return random.randomBytes(32); - - if (this.isAuthed()) { - this.auth = true; - this.emit('auth'); - return null; - } - - assert(this.outbound, 'No challenge received before reply on inbound.'); - - // authpropose - return this.hash(this.input.sid, 'p', this.publicKey); -}; - -/** - * Handle a received propose hash. - * Returns an authchallenge hash. - * @param {Buffer} hash - * @returns {Buffer} - */ - -BIP150.prototype.propose = function propose(hash) { - assert(!this.outbound, 'Outbound peer tried to propose.'); - assert(!this.challengeSent, 'Unsolicited propose.'); - assert(!this.proposeReceived, 'Peer proposed twice.'); - this.proposeReceived = true; - - const match = this.findAuthorized(hash); - - if (!match) - return encoding.ZERO_HASH; - - this.peerIdentity = match; - - // Add them in case we ever connect to them. - this.db.addKnown(this.hostname, this.peerIdentity); - - this.challengeSent = true; - - // authchallenge - return this.hash(this.output.sid, 'r', this.peerIdentity); -}; - -/** - * Create initial authchallenge hash - * for the peer. The peer's identity - * key must be known. - * @returns {AuthChallengePacket} - */ - -BIP150.prototype.toChallenge = function toChallenge() { - assert(this.bip151.handshake, 'No BIP151 handshake before challenge.'); - assert(this.outbound, 'Cannot challenge an inbound connection.'); - assert(this.peerIdentity, 'Cannot challenge without a peer identity.'); - - const msg = this.hash(this.output.sid, 'i', this.peerIdentity); - - assert(!this.challengeSent, 'Cannot initiate challenge twice.'); - this.challengeSent = true; - - return new packets.AuthChallengePacket(msg); -}; - -/** - * Derive new cipher keys based on - * BIP150 data. This differs from - * the regular key derivation of BIP151. - * @param {Buffer} sid - Sesson ID - * @param {Buffer} key - `k1` or `k2` - * @param {Buffer} req - Requesting Identity Key - * @param {Buffer} res - Response Identity Key - * @returns {Buffer} - */ - -BIP150.prototype.rekey = function rekey(sid, key, req, res) { - const seed = Buffer.allocUnsafe(130); - sid.copy(seed, 0); - key.copy(seed, 32); - req.copy(seed, 64); - res.copy(seed, 97); - return digest.hash256(seed); -}; - -/** - * Rekey the BIP151 input stream - * using BIP150-style derivation. - */ - -BIP150.prototype.rekeyInput = function rekeyInput() { - const stream = this.input; - const req = this.peerIdentity; - const res = this.publicKey; - const k1 = this.rekey(stream.sid, stream.k1, req, res); - const k2 = this.rekey(stream.sid, stream.k2, req, res); - stream.rekey(k1, k2); -}; - -/** - * Rekey the BIP151 output stream - * using BIP150-style derivation. - */ - -BIP150.prototype.rekeyOutput = function rekeyOutput() { - const stream = this.output; - const req = this.publicKey; - const res = this.peerIdentity; - const k1 = this.rekey(stream.sid, stream.k1, req, res); - const k2 = this.rekey(stream.sid, stream.k2, req, res); - stream.rekey(k1, k2); -}; - -/** - * Create a hash using the session ID. - * @param {Buffer} sid - * @param {String} ch - * @param {Buffer} key - * @returns {Buffer} - */ - -BIP150.prototype.hash = function hash(sid, ch, key) { - const data = Buffer.allocUnsafe(66); - sid.copy(data, 0); - data[32] = ch.charCodeAt(0); - key.copy(data, 33); - return digest.hash256(data); -}; - -/** - * Find an authorized peer in the Auth - * DB based on a proposal hash. Note - * that the hash to find is specific - * to the state of BIP151. This results - * in an O(n) search. - * @param {Buffer} hash - * @returns {Buffer|null} - */ - -BIP150.prototype.findAuthorized = function findAuthorized(hash) { - // Scary O(n) stuff. - for (const key of this.db.authorized) { - const msg = this.hash(this.output.sid, 'p', key); - - // XXX Do we really need a constant - // time compare here? Do it just to - // be safe I guess. - if (ccmp(msg, hash)) - return key; - } - - return null; -}; - -/** - * Destroy the BIP150 stream and - * any current running wait job. - */ - -BIP150.prototype.destroy = function destroy() { - if (!this.job) - return; - - this.reject(new Error('BIP150 stream was destroyed.')); -}; - -/** - * Cleanup wait job. - * @private - * @returns {Job} - */ - -BIP150.prototype.cleanup = function cleanup() { - const job = this.job; - - assert(!this.completed, 'Already completed.'); - assert(job, 'No completion job.'); - - this.completed = true; - this.job = null; - - if (this.timeout != null) { - clearTimeout(this.timeout); - this.timeout = null; - } - - if (this.onAuth) { - this.removeListener('auth', this.onAuth); - this.onAuth = null; - } - - return job; -}; - -/** - * Resolve the current wait job. - * @private - * @param {Object} result - */ - -BIP150.prototype.resolve = function resolve(result) { - const job = this.cleanup(); - job.resolve(result); -}; - -/** - * Reject the current wait job. - * @private - * @param {Error} err - */ - -BIP150.prototype.reject = function reject(err) { - const job = this.cleanup(); - job.reject(err); -}; - -/** - * Wait for handshake to complete. - * @param {Number} timeout - * @returns {Promise} - */ - -BIP150.prototype.wait = function wait(timeout) { - return new Promise((resolve, reject) => { - this._wait(timeout, resolve, reject); - }); -}; - -/** - * Wait for handshake to complete. - * @private - * @param {Number} timeout - * @param {Function} resolve - * @param {Function} reject - */ - -BIP150.prototype._wait = function _wait(timeout, resolve, reject) { - assert(!this.auth, 'Cannot wait for init after handshake.'); - - this.job = co.job(resolve, reject); - - if (this.outbound && !this.peerIdentity) { - this.reject(new Error(`No identity for ${this.hostname}.`)); - return; - } - - this.timeout = setTimeout(() => { - this.reject(new Error('BIP150 handshake timed out.')); - }, timeout); - - this.onAuth = this.resolve.bind(this); - this.once('auth', this.onAuth); -}; - -/** - * Serialize the peer's identity - * key as a BIP150 "address". - * @returns {Base58String} - */ - -BIP150.prototype.getAddress = function getAddress() { - assert(this.peerIdentity, 'Cannot serialize address.'); - return BIP150.address(this.peerIdentity); -}; - -/** - * Serialize an identity key as a - * BIP150 "address". - * @returns {Base58String} - */ - -BIP150.address = function address(key) { - const bw = new StaticWriter(27); - bw.writeU8(0x0f); - bw.writeU16BE(0xff01); - bw.writeBytes(digest.hash160(key)); - bw.writeChecksum(); - return base58.encode(bw.render()); -}; - -/** - * AuthDB - * @alias module:net.AuthDB - * @constructor - */ - -function AuthDB(options) { - if (!(this instanceof AuthDB)) - return new AuthDB(options); - - this.logger = Logger.global; - this.resolve = dns.lookup; - this.prefix = null; - this.dnsKnown = []; - - this.known = new Map(); - this.authorized = []; - - this._init(options); -} - -/** - * Initialize authdb with options. - * @param {Object} options - */ - -AuthDB.prototype._init = function _init(options) { - if (!options) - return; - - if (options.logger != null) { - assert(typeof options.logger === 'object'); - this.logger = options.logger.context('authdb'); - } - - if (options.resolve != null) { - assert(typeof options.resolve === 'function'); - this.resolve = options.resolve; - } - - if (options.knownPeers != null) { - assert(typeof options.knownPeers === 'object'); - this.setKnown(options.knownPeers); - } - - if (options.authPeers != null) { - assert(Array.isArray(options.authPeers)); - this.setAuthorized(options.authPeers); - } - - if (options.prefix != null) { - assert(typeof options.prefix === 'string'); - this.prefix = options.prefix; - } -}; - -/** - * Open auth database (lookup known peers). - * @method - * @returns {Promise} - */ - -AuthDB.prototype.open = async function open() { - await this.readKnown(); - await this.readAuth(); - await this.lookup(); -}; - -/** - * Close auth database. - * @method - * @returns {Promise} - */ - -AuthDB.prototype.close = async function close() { - ; -}; - -/** - * Add a known peer. - * @param {String} host - Peer Hostname - * @param {Buffer} key - Identity Key - */ - -AuthDB.prototype.addKnown = function addKnown(host, key) { - assert(typeof host === 'string', - 'Known host must be a string.'); - - assert(Buffer.isBuffer(key) && key.length === 33, - 'Invalid public key for known peer.'); - - const addr = IP.fromHostname(host); - - if (addr.type === IP.types.DNS) { - // Defer this for resolution. - this.dnsKnown.push([addr, key]); - return; - } - - this.known.set(host, key); -}; - -/** - * Add an authorized peer. - * @param {Buffer} key - Identity Key - */ - -AuthDB.prototype.addAuthorized = function addAuthorized(key) { - assert(Buffer.isBuffer(key) && key.length === 33, - 'Invalid public key for authorized peer.'); - this.authorized.push(key); -}; - -/** - * Initialize known peers with a host->key map. - * @param {Object} map - */ - -AuthDB.prototype.setKnown = function setKnown(map) { - this.known.clear(); - - for (const host of Object.keys(map)) { - const key = map[host]; - this.addKnown(host, key); - } -}; - -/** - * Initialize authorized peers with a list of keys. - * @param {Buffer[]} keys - */ - -AuthDB.prototype.setAuthorized = function setAuthorized(keys) { - this.authorized.length = 0; - - for (const key of keys) - this.addAuthorized(key); -}; - -/** - * Get a known peer key by hostname. - * @param {String} hostname - * @returns {Buffer|null} - */ - -AuthDB.prototype.getKnown = function getKnown(hostname) { - const known = this.known.get(hostname); - - if (known) - return known; - - const addr = IP.fromHostname(hostname); - - return this.known.get(addr.host); -}; - -/** - * Lookup known peers. - * @method - * @returns {Promise} - */ - -AuthDB.prototype.lookup = async function lookup() { - const jobs = []; - - for (const [addr, key] of this.dnsKnown) - jobs.push(this.populate(addr, key)); - - await Promise.all(jobs); -}; - -/** - * Populate known peers with hosts. - * @method - * @private - * @param {Object} addr - * @param {Buffer} key - * @returns {Promise} - */ - -AuthDB.prototype.populate = async function populate(addr, key) { - assert(addr.type === IP.types.DNS, 'Resolved host passed.'); - - this.logger.info('Resolving authorized hosts from: %s.', addr.host); - - let hosts; - try { - hosts = await this.resolve(addr.host); - } catch (e) { - this.logger.error(e); - return; - } - - for (let host of hosts) { - if (addr.port !== 0) - host = IP.toHostname(host, addr.port); - - this.known.set(host, key); - } -}; - -/** - * Parse known peers. - * @param {String} text - * @returns {Object} - */ - -AuthDB.prototype.readKnown = async function readKnown() { - if (fs.unsupported) - return; - - if (!this.prefix) - return; - - const file = path.join(this.prefix, 'known-peers'); - - let text; - try { - text = await fs.readFile(file, 'utf8'); - } catch (e) { - if (e.code === 'ENOENT') - return; - throw e; - } - - this.parseKnown(text); -}; - -/** - * Parse known peers. - * @param {String} text - * @returns {Object} - */ - -AuthDB.prototype.parseKnown = function parseKnown(text) { - assert(typeof text === 'string'); - - if (text.charCodeAt(0) === 0xfeff) - text = text.substring(1); - - text = text.replace(/\r\n/g, '\n'); - text = text.replace(/\r/g, '\n'); - - let num = 0; - - for (const chunk of text.split('\n')) { - const line = chunk.trim(); - - num += 1; - - if (line.length === 0) - continue; - - if (line[0] === '#') - continue; - - const parts = line.split(/\s+/); - - if (parts.length < 2) - throw new Error(`No key present on line ${num}: "${line}".`); - - const hosts = parts[0].split(','); - - let host, addr; - if (hosts.length >= 2) { - host = hosts[0]; - addr = hosts[1]; - } else { - host = null; - addr = hosts[0]; - } - - const key = Buffer.from(parts[1], 'hex'); - - if (key.length !== 33) - throw new Error(`Invalid key on line ${num}: "${parts[1]}".`); - - if (host && host.length > 0) - this.addKnown(host, key); - - if (addr.length === 0) - continue; - - this.addKnown(addr, key); - } -}; - -/** - * Parse known peers. - * @param {String} text - * @returns {Object} - */ - -AuthDB.prototype.readAuth = async function readAuth() { - if (fs.unsupported) - return; - - if (!this.prefix) - return; - - const file = path.join(this.prefix, 'authorized-peers'); - - let text; - try { - text = await fs.readFile(file, 'utf8'); - } catch (e) { - if (e.code === 'ENOENT') - return; - throw e; - } - - this.parseAuth(text); -}; - -/** - * Parse authorized peers. - * @param {String} text - * @returns {Buffer[]} keys - */ - -AuthDB.prototype.parseAuth = function parseAuth(text) { - assert(typeof text === 'string'); - - if (text.charCodeAt(0) === 0xfeff) - text = text.substring(1); - - text = text.replace(/\r\n/g, '\n'); - text = text.replace(/\r/g, '\n'); - - let num = 0; - - for (const chunk of text.split('\n')) { - const line = chunk.trim(); - - num += 1; - - if (line.length === 0) - continue; - - if (line[0] === '#') - continue; - - const key = Buffer.from(line, 'hex'); - - if (key.length !== 33) - throw new Error(`Invalid key on line ${num}: "${line}".`); - - this.addAuthorized(key); - } -}; - -/* - * Expose - */ - -exports = BIP150; - -exports.BIP150 = BIP150; -exports.AuthDB = AuthDB; - -module.exports = exports; diff --git a/lib/net/bip151.js b/lib/net/bip151.js deleted file mode 100644 index d6a91ec2c..000000000 --- a/lib/net/bip151.js +++ /dev/null @@ -1,754 +0,0 @@ -/*! - * bip151.js - peer-to-peer communication encryption. - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - * Resources: - * https://github.com/bitcoin/bips/blob/master/bip-0151.mediawiki - * https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.chacha20poly1305 - * https://github.com/openssh/openssh-portable/blob/master/cipher-chachapoly.c - * https://github.com/openssh/openssh-portable/blob/master/cipher.c - * https://github.com/openssh/openssh-portable/blob/master/packet.c - */ - -'use strict'; - -const assert = require('assert'); -const EventEmitter = require('events'); -const util = require('../utils/util'); -const co = require('../utils/co'); -const digest = require('../crypto/digest'); -const ChaCha20 = require('../crypto/chacha20'); -const Poly1305 = require('../crypto/poly1305'); -const AEAD = require('../crypto/aead'); -const hkdf = require('../crypto/hkdf'); -const secp256k1 = require('../crypto/secp256k1'); -const packets = require('./packets'); -const StaticWriter = require('../utils/staticwriter'); -const BufferReader = require('../utils/reader'); -const encoding = require('../utils/encoding'); -const EncinitPacket = packets.EncinitPacket; -const EncackPacket = packets.EncackPacket; - -/* - * Constants - */ - -const HKDF_SALT = Buffer.from('bitcoinecdh', 'ascii'); -const INFO_KEY1 = Buffer.from('BitcoinK1', 'ascii'); -const INFO_KEY2 = Buffer.from('BitcoinK2', 'ascii'); -const INFO_SID = Buffer.from('BitcoinSessionID', 'ascii'); -const HIGH_WATERMARK = 1024 * (1 << 20); - -/** - * Represents a BIP151 input or output stream. - * @alias module:net.BIP151Stream - * @constructor - * @param {Number} cipher - * @property {Buffer} publicKey - * @property {Buffer} privateKey - * @property {Number} cipher - * @property {Buffer} k1 - * @property {Buffer} k2 - * @property {Buffer} sid - * @property {ChaCha20} chacha - * @property {AEAD} aead - * @property {Buffer} tag - * @property {Number} seq - * @property {Number} processed - * @property {Number} lastKey - */ - -function BIP151Stream(cipher) { - if (!(this instanceof BIP151Stream)) - return new BIP151Stream(cipher); - - this.cipher = BIP151.ciphers.CHACHAPOLY; - this.privateKey = secp256k1.generatePrivateKey(); - this.publicKey = null; - this.k1 = null; - this.k2 = null; - this.sid = null; - - if (cipher != null) { - assert(cipher === BIP151.ciphers.CHACHAPOLY, 'Unknown cipher type.'); - this.cipher = cipher; - } - - this.chacha = new ChaCha20(); - this.aead = new AEAD(); - this.tag = null; - this.seq = 0; - this.iv = Buffer.allocUnsafe(8); - this.iv.fill(0); - - this.processed = 0; - this.lastRekey = 0; -} - -/** - * Initialize the stream with peer's public key. - * Computes ecdh secret and chacha keys. - * @param {Buffer} publicKey - */ - -BIP151Stream.prototype.init = function init(publicKey) { - assert(Buffer.isBuffer(publicKey)); - - this.publicKey = publicKey; - - const secret = secp256k1.ecdh(this.publicKey, this.privateKey); - - const bw = StaticWriter.pool(33); - - bw.writeBytes(secret); - bw.writeU8(this.cipher); - - const data = bw.render(); - const prk = hkdf.extract(data, HKDF_SALT, 'sha256'); - - this.k1 = hkdf.expand(prk, INFO_KEY1, 32, 'sha256'); - this.k2 = hkdf.expand(prk, INFO_KEY2, 32, 'sha256'); - this.sid = hkdf.expand(prk, INFO_SID, 32, 'sha256'); - - this.seq = 0; - - this.update(); - - this.chacha.init(this.k1, this.iv); - this.aead.init(this.k2, this.iv); - - this.lastRekey = util.now(); -}; - -/** - * Add buffer size to `processed`, - * check whether we need to rekey. - * @param {Buffer} packet - * @returns {Boolean} - */ - -BIP151Stream.prototype.shouldRekey = function shouldRekey(packet) { - const now = util.now(); - - this.processed += packet.length; - - if (now >= this.lastRekey + 10 - || this.processed >= HIGH_WATERMARK) { - this.lastRekey = now; - this.processed = 0; - return true; - } - - return false; -}; - -/** - * Generate new chacha keys with `key = HASH256(sid | key)`. - * This will reinitialize the state of both ciphers. - */ - -BIP151Stream.prototype.rekey = function rekey(k1, k2) { - assert(this.sid, 'Cannot rekey before initialization.'); - - if (!k1) { - this.k1 = digest.root256(this.sid, this.k1); - this.k2 = digest.root256(this.sid, this.k2); - } else { - this.k1 = k1; - this.k2 = k2; - } - - assert(this.k1); - assert(this.k2); - - // All state is reinitialized - // aside from the sequence number. - this.chacha.init(this.k1, this.iv); - this.aead.init(this.k2, this.iv); -}; - -/** - * Increment packet sequence number and update IVs - * (note, sequence number overflows after 2^64-1). - * The IV will be updated without reinitializing - * cipher state. - */ - -BIP151Stream.prototype.sequence = function sequence() { - // Wrap sequence number a la openssh. - if (++this.seq === 0x100000000) - this.seq = 0; - - this.update(); - - // State of the ciphers is - // unaltered aside from the iv. - this.chacha.init(null, this.iv); - this.aead.init(null, this.iv); -}; - -/** - * Render the IV necessary for cipher streams. - * @returns {Buffer} - */ - -BIP151Stream.prototype.update = function update() { - this.iv.writeUInt32LE(this.seq, 0, true); - return this.iv; -}; - -/** - * Get public key tied to private key - * (not the same as BIP151Stream#publicKey). - * @returns {Buffer} - */ - -BIP151Stream.prototype.getPublicKey = function getPublicKey() { - return secp256k1.publicKeyCreate(this.privateKey, true); -}; - -/** - * Encrypt a payload size with k1. - * @param {Buffer} data - * @returns {Buffer} - */ - -BIP151Stream.prototype.encryptSize = function encryptSize(data) { - return this.chacha.encrypt(data.slice(0, 4)); -}; - -/** - * Decrypt payload size with k1. - * @param {Buffer} data - * @returns {Number} - */ - -BIP151Stream.prototype.decryptSize = function decryptSize(data) { - this.chacha.encrypt(data); - return data.readUInt32LE(0, true); -}; - -/** - * Encrypt payload with AEAD (update cipher and mac). - * @param {Buffer} data - * @returns {Buffer} data - */ - -BIP151Stream.prototype.encrypt = function encrypt(data) { - return this.aead.encrypt(data); -}; - -/** - * Decrypt payload with AEAD (update cipher only). - * @param {Buffer} data - * @returns {Buffer} data - */ - -BIP151Stream.prototype.decrypt = function decrypt(data) { - return this.aead.chacha20.encrypt(data); -}; - -/** - * Authenticate payload with AEAD (update mac only). - * @param {Buffer} data - * @returns {Buffer} data - */ - -BIP151Stream.prototype.auth = function auth(data) { - return this.aead.auth(data); -}; - -/** - * Finalize AEAD and compute MAC. - * @returns {Buffer} - */ - -BIP151Stream.prototype.finish = function finish() { - this.tag = this.aead.finish(); - return this.tag; -}; - -/** - * Verify tag against mac in constant time. - * @param {Buffer} tag - * @returns {Boolean} - */ - -BIP151Stream.prototype.verify = function verify(tag) { - return Poly1305.verify(this.tag, tag); -}; - -/** - * Represents a BIP151 input and output stream. - * Holds state for peer communication. - * @alias module:net.BIP151 - * @constructor - * @param {Number} cipher - * @property {BIP151Stream} input - * @property {BIP151Stream} output - * @property {Boolean} initReceived - * @property {Boolean} ackReceived - * @property {Boolean} initSent - * @property {Boolean} ackSent - * @property {Object} timeout - * @property {Job} job - * @property {Boolean} completed - * @property {Boolean} handshake - */ - -function BIP151(cipher) { - if (!(this instanceof BIP151)) - return new BIP151(cipher); - - EventEmitter.call(this); - - this.input = new BIP151Stream(cipher); - this.output = new BIP151Stream(cipher); - - this.initReceived = false; - this.ackReceived = false; - this.initSent = false; - this.ackSent = false; - this.completed = false; - this.handshake = false; - - this.pending = []; - this.total = 0; - this.waiting = 4; - this.hasSize = false; - - this.timeout = null; - this.job = null; - this.onShake = null; - - this.bip150 = null; -} - -Object.setPrototypeOf(BIP151.prototype, EventEmitter.prototype); - -/** - * Cipher list. - * @enum {Number} - */ - -BIP151.ciphers = { - CHACHAPOLY: 0 -}; - -/** - * Max message size. - * @const {Number} - * @default - */ - -BIP151.MAX_MESSAGE = 12 * 1000 * 1000; - -/** - * Emit an error. - * @param {...String} msg - */ - -BIP151.prototype.error = function error() { - const msg = util.fmt.apply(util, arguments); - this.emit('error', new Error(msg)); -}; - -/** - * Test whether handshake has completed. - * @returns {Boolean} - */ - -BIP151.prototype.isReady = function isReady() { - return this.initSent - && this.ackReceived - && this.initReceived - && this.ackSent; -}; - -/** - * Render an `encinit` packet. Contains the - * input public key and cipher number. - * @returns {Buffer} - */ - -BIP151.prototype.toEncinit = function toEncinit() { - assert(!this.initSent, 'Cannot init twice.'); - this.initSent = true; - return new EncinitPacket(this.input.getPublicKey(), this.input.cipher); -}; - -/** - * Render `encack` packet. Contains the - * output stream public key. - * @returns {Buffer} - */ - -BIP151.prototype.toEncack = function toEncack() { - assert(this.output.sid, 'Cannot ack before init.'); - assert(!this.ackSent, 'Cannot ack twice.'); - this.ackSent = true; - - if (this.isReady()) { - assert(!this.completed, 'No encack after timeout.'); - this.handshake = true; - this.emit('handshake'); - } - - return new EncackPacket(this.output.getPublicKey()); -}; - -/** - * Render `encack` packet with an all - * zero public key, notifying of a rekey - * for the output stream. - * @returns {Buffer} - */ - -BIP151.prototype.toRekey = function toRekey() { - assert(this.handshake, 'Cannot rekey before handshake.'); - return new EncackPacket(encoding.ZERO_KEY); -}; - -/** - * Handle `encinit` from remote peer. - * @param {Buffer} - */ - -BIP151.prototype.encinit = function encinit(publicKey, cipher) { - assert(cipher === this.output.cipher, 'Cipher mismatch.'); - assert(!this.initReceived, 'Already initialized.'); - assert(!this.completed, 'No encinit after timeout.'); - this.initReceived = true; - this.output.init(publicKey); -}; - -/** - * Handle `encack` from remote peer. - * @param {Buffer} data - */ - -BIP151.prototype.encack = function encack(publicKey) { - assert(this.initSent, 'Unsolicited ACK.'); - - if (publicKey.equals(encoding.ZERO_KEY)) { - assert(this.handshake, 'No initialization before rekey.'); - - if (this.bip150 && this.bip150.auth) { - this.bip150.rekeyInput(); - return; - } - - this.input.rekey(); - - return; - } - - assert(!this.ackReceived, 'Already ACKed.'); - assert(!this.completed, 'No encack after timeout.'); - this.ackReceived = true; - - this.input.init(publicKey); - - if (this.isReady()) { - this.handshake = true; - this.emit('handshake'); - } -}; - -/** - * Cleanup handshake job. - * @returns {Job} - */ - -BIP151.prototype.cleanup = function cleanup() { - const job = this.job; - - assert(!this.completed, 'Already completed.'); - assert(job, 'No completion job.'); - - this.completed = true; - this.job = null; - - if (this.timeout != null) { - clearTimeout(this.timeout); - this.timeout = null; - } - - if (this.onShake) { - this.removeListener('handshake', this.onShake); - this.onShake = null; - } - - return job; -}; - -/** - * Complete the timeout for handshake. - * @param {Object} result - */ - -BIP151.prototype.resolve = function resolve(result) { - const job = this.cleanup(); - job.resolve(result); -}; - -/** - * Complete the timeout for handshake with error. - * @param {Error} err - */ - -BIP151.prototype.reject = function reject(err) { - const job = this.cleanup(); - job.reject(err); -}; - -/** - * Set a timeout and wait for handshake to complete. - * @param {Number} timeout - Timeout in ms. - * @returns {Promise} - */ - -BIP151.prototype.wait = function wait(timeout) { - return new Promise((resolve, reject) => { - this._wait(timeout, resolve, reject); - }); -}; - -/** - * Set a timeout and wait for handshake to complete. - * @private - * @param {Number} timeout - * @param {Function} resolve - * @param {Function} reject - */ - -BIP151.prototype._wait = function _wait(timeout, resolve, reject) { - assert(!this.handshake, 'Cannot wait for init after handshake.'); - - this.job = co.job(resolve, reject); - - this.timeout = setTimeout(() => { - this.reject(new Error('BIP151 handshake timed out.')); - }, timeout); - - this.onShake = this.resolve.bind(this); - this.once('handshake', this.onShake); -}; - -/** - * Destroy BIP151 state and streams. - */ - -BIP151.prototype.destroy = function destroy() { - if (!this.job) - return; - - this.reject(new Error('BIP151 stream was destroyed.')); -}; - -/** - * Add buffer size to `processed`, - * check whether we need to rekey. - * @param {Buffer} packet - */ - -BIP151.prototype.maybeRekey = function maybeRekey(packet) { - if (!this.output.shouldRekey(packet)) - return; - - this.emit('rekey'); - - if (this.bip150 && this.bip150.auth) { - this.bip150.rekeyOutput(); - return; - } - - this.output.rekey(); -}; - -/** - * Calculate packet size. - * @param {String} cmd - * @param {Buffer} body - * @returns {Number} - */ - -BIP151.prototype.packetSize = function packetSize(cmd, body) { - let size = 0; - size += 4; - size += encoding.sizeVarString(cmd, 'ascii'); - size += 4; - size += body.length; - size += 16; - return size; -}; - -/** - * Frame plaintext payload for the output stream. - * @param {String} cmd - * @param {Buffer} body - * @returns {Buffer} Ciphertext payload - */ - -BIP151.prototype.packet = function packet(cmd, body) { - const size = this.packetSize(cmd, body); - const bw = new StaticWriter(size); - const payloadSize = size - 20; - - bw.writeU32(payloadSize); - bw.writeVarString(cmd, 'ascii'); - bw.writeU32(body.length); - bw.writeBytes(body); - bw.seek(16); - - const msg = bw.render(); - const payload = msg.slice(4, 4 + payloadSize); - - this.maybeRekey(msg); - - this.output.encryptSize(msg); - this.output.encrypt(payload); - this.output.finish().copy(msg, 4 + payloadSize); - this.output.sequence(); - - return msg; -}; - -/** - * Feed ciphertext payload chunk - * to the input stream. Potentially - * emits a `packet` event. - * @param {Buffer} data - */ - -BIP151.prototype.feed = function feed(data) { - this.total += data.length; - this.pending.push(data); - - while (this.total >= this.waiting) { - const chunk = this.read(this.waiting); - this.parse(chunk); - } -}; - -/** - * Read and consume a number of bytes - * from the buffered stream. - * @param {Number} size - * @returns {Buffer} - */ - -BIP151.prototype.read = function read(size) { - assert(this.total >= size, 'Reading too much.'); - - if (size === 0) - return Buffer.alloc(0); - - const pending = this.pending[0]; - - if (pending.length > size) { - const chunk = pending.slice(0, size); - this.pending[0] = pending.slice(size); - this.total -= chunk.length; - return chunk; - } - - if (pending.length === size) { - const chunk = this.pending.shift(); - this.total -= chunk.length; - return chunk; - } - - const chunk = Buffer.allocUnsafe(size); - let off = 0; - - while (off < chunk.length) { - const pending = this.pending[0]; - const len = pending.copy(chunk, off); - if (len === pending.length) - this.pending.shift(); - else - this.pending[0] = pending.slice(len); - off += len; - } - - assert.strictEqual(off, chunk.length); - - this.total -= chunk.length; - - return chunk; -}; - -/** - * Parse a ciphertext payload chunk. - * Potentially emits a `packet` event. - * @param {Buffer} data - */ - -BIP151.prototype.parse = function parse(data) { - if (!this.hasSize) { - const size = this.input.decryptSize(data); - - assert(this.waiting === 4); - assert(data.length === 4); - - // Allow 3 batched packets of max message size (12mb). - // Not technically standard, but this protects us - // from buffering tons of data due to either an - // potential dos'er or a cipher state mismatch. - // Note that 6 is the minimum size: - // varint-cmdlen(1) str-cmd(1) u32-size(4) payload(0) - if (size < 6 || size > BIP151.MAX_MESSAGE) { - this.error('Bad packet size: %d.', util.mb(size)); - return; - } - - this.hasSize = true; - this.waiting = size + 16; - - return; - } - - const payload = data.slice(0, this.waiting - 16); - const tag = data.slice(this.waiting - 16, this.waiting); - - this.hasSize = false; - this.waiting = 4; - - // Authenticate payload before decrypting. - // This ensures the cipher state isn't altered - // if the payload integrity has been compromised. - this.input.auth(payload); - this.input.finish(); - - if (!this.input.verify(tag)) { - this.input.sequence(); - this.error('Bad tag: %s.', tag.toString('hex')); - return; - } - - this.input.decrypt(payload); - this.input.sequence(); - - const br = new BufferReader(payload); - - while (br.left()) { - let cmd, body; - - try { - cmd = br.readVarString('ascii'); - body = br.readBytes(br.readU32()); - } catch (e) { - this.emit('error', e); - return; - } - - this.emit('packet', cmd, body); - } -}; - -/* - * Expose - */ - -module.exports = BIP151; diff --git a/lib/net/bip152.js b/lib/net/bip152.js index 1a33af0d9..76794aed2 100644 --- a/lib/net/bip152.js +++ b/lib/net/bip152.js @@ -10,25 +10,23 @@ * @module net/bip152 */ -const assert = require('assert'); -const util = require('../utils/util'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); -const encoding = require('../utils/encoding'); +const assert = require('bsert'); +const bio = require('bufio'); const consensus = require('../protocol/consensus'); -const digest = require('../crypto/digest'); -const siphash256 = require('../crypto/siphash').siphash256; +const sha256 = require('bcrypto/lib/sha256'); +const {siphash} = require('bsip'); const AbstractBlock = require('../primitives/abstractblock'); const TX = require('../primitives/tx'); const Headers = require('../primitives/headers'); const Block = require('../primitives/block'); +const common = require('./common'); +const {encoding} = bio; /** + * Compact Block * Represents a compact block (bip152): `cmpctblock` packet. * @see https://github.com/bitcoin/bips/blob/master/bip-0152.mediawiki - * @constructor * @extends AbstractBlock - * @param {Object} options * @property {Buffer|null} keyNonce - Nonce for siphash key. * @property {Number[]} ids - Short IDs. * @property {Object[]} ptx - Prefilled transactions. @@ -38,913 +36,920 @@ const Block = require('../primitives/block'); * @property {Buffer|null} sipKey - Siphash key. */ -function CompactBlock(options) { - if (!(this instanceof CompactBlock)) - return new CompactBlock(options); +class CompactBlock extends AbstractBlock { + /** + * Create a compact block. + * @constructor + * @param {Object?} options + */ - AbstractBlock.call(this); + constructor(options) { + super(); - this.keyNonce = null; - this.ids = []; - this.ptx = []; + this.keyNonce = null; + this.ids = []; + this.ptx = []; - this.available = []; - this.idMap = new Map(); - this.count = 0; - this.sipKey = null; - this.totalTX = 0; - this.now = 0; + this.available = []; + this.idMap = new Map(); + this.count = 0; + this.sipKey = null; + this.totalTX = 0; + this.now = 0; - if (options) - this.fromOptions(options); -} - -Object.setPrototypeOf(CompactBlock.prototype, AbstractBlock.prototype); - -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -CompactBlock.prototype.fromOptions = function fromOptions(options) { - this.parseOptions(options); - - assert(Buffer.isBuffer(options.keyNonce)); - assert(Array.isArray(options.ids)); - assert(Array.isArray(options.ptx)); - - this.keyNonce = options.keyNonce; - this.ids = options.ids; - this.ptx = options.ptx; - - if (options.available) - this.available = options.available; - - if (options.idMap) - this.idMap = options.idMap; - - if (options.count) - this.count = options.count; - - if (options.totalTX != null) - this.totalTX = options.totalTX; - - this.sipKey = this.getKey(); - - return this; -}; - -/** - * Instantiate compact block from options. - * @param {Object} options - * @returns {CompactBlock} - */ + if (options) + this.fromOptions(options); + } -CompactBlock.fromOptions = function fromOptions(options) { - return new CompactBlock().fromOptions(options); -}; + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ -/** - * Verify the block. - * @returns {Boolean} - */ + fromOptions(options) { + this.parseOptions(options); -CompactBlock.prototype.verifyBody = function verifyBody() { - return true; -}; + assert(Buffer.isBuffer(options.keyNonce)); + assert(Array.isArray(options.ids)); + assert(Array.isArray(options.ptx)); -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + this.keyNonce = options.keyNonce; + this.ids = options.ids; + this.ptx = options.ptx; -CompactBlock.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); + if (options.available) + this.available = options.available; - this.readHead(br); + if (options.idMap) + this.idMap = options.idMap; - this.keyNonce = br.readBytes(8); - this.sipKey = this.getKey(); + if (options.count) + this.count = options.count; - const idCount = br.readVarint(); + if (options.totalTX != null) + this.totalTX = options.totalTX; - this.totalTX += idCount; + this.sipKey = this.getKey(); - for (let i = 0; i < idCount; i++) { - const lo = br.readU32(); - const hi = br.readU16(); - this.ids.push(hi * 0x100000000 + lo); + return this; } - const txCount = br.readVarint(); + /** + * Instantiate compact block from options. + * @param {Object} options + * @returns {CompactBlock} + */ - this.totalTX += txCount; - - for (let i = 0; i < txCount; i++) { - const index = br.readVarint(); - - assert(index <= 0xffff); - assert(index < this.totalTX); + static fromOptions(options) { + return new this().fromOptions(options); + } - const tx = TX.fromReader(br); + /** + * Verify the block. + * @returns {Boolean} + */ - this.ptx.push([index, tx]); + verifyBody() { + return true; } - return this; -}; - -/** - * Instantiate a block from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {CompactBlock} - */ + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -CompactBlock.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new CompactBlock().fromRaw(data); -}; + fromRaw(data) { + const br = bio.read(data); -/** - * Serialize compact block with witness data. - * @returns {Buffer} - */ + this.readHead(br); -CompactBlock.prototype.toRaw = function toRaw() { - return this.frameRaw(true); -}; + this.keyNonce = br.readBytes(8); + this.sipKey = this.getKey(); -/** - * Serialize compact block without witness data. - * @returns {Buffer} - */ + const idCount = br.readVarint(); -CompactBlock.prototype.toNormal = function toNormal() { - return this.frameRaw(false); -}; + this.totalTX += idCount; -/** - * Write serialized block to a buffer - * writer (includes witness data). - * @param {BufferWriter} bw - */ + for (let i = 0; i < idCount; i++) { + const lo = br.readU32(); + const hi = br.readU16(); + this.ids.push(hi * 0x100000000 + lo); + } -CompactBlock.prototype.toWriter = function toWriter(bw) { - return this.writeRaw(bw, true); -}; + const txCount = br.readVarint(); -/** - * Write serialized block to a buffer - * writer (excludes witness data). - * @param {BufferWriter} bw - */ + this.totalTX += txCount; -CompactBlock.prototype.toNormalWriter = function toNormalWriter(bw) { - return this.writeRaw(bw, false); -}; + for (let i = 0; i < txCount; i++) { + const index = br.readVarint(); -/** - * Serialize compact block. - * @private - * @param {Boolean} witness - * @returns {Buffer} - */ + assert(index <= 0xffff); + assert(index < this.totalTX); -CompactBlock.prototype.frameRaw = function frameRaw(witness) { - const size = this.getSize(witness); - return this.writeRaw(new StaticWriter(size), witness).render(); -}; + const tx = TX.fromReader(br); -/** - * Calculate block serialization size. - * @param {Boolean} witness - * @returns {Number} - */ + this.ptx.push([index, tx]); + } -CompactBlock.prototype.getSize = function getSize(witness) { - let size = 0; + return this; + } - size += 80; - size += 8; - size += encoding.sizeVarint(this.ids.length); - size += this.ids.length * 6; - size += encoding.sizeVarint(this.ptx.length); + /** + * Instantiate a block from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {CompactBlock} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } - for (const [index, tx] of this.ptx) { - size += encoding.sizeVarint(index); + /** + * Serialize compact block with witness data. + * @returns {Buffer} + */ - if (witness) - size += tx.getSize(); - else - size += tx.getBaseSize(); + toRaw() { + return this.frameRaw(true); } - return size; -}; + /** + * Serialize compact block without witness data. + * @returns {Buffer} + */ -/** - * Serialize block to buffer writer. - * @private - * @param {BufferWriter} bw - * @param {Boolean} witness - */ + toNormal() { + return this.frameRaw(false); + } -CompactBlock.prototype.writeRaw = function writeRaw(bw, witness) { - this.writeHead(bw); + /** + * Write serialized block to a buffer + * writer (includes witness data). + * @param {BufferWriter} bw + */ - bw.writeBytes(this.keyNonce); + toWriter(bw) { + return this.writeRaw(bw, true); + } - bw.writeVarint(this.ids.length); + /** + * Write serialized block to a buffer + * writer (excludes witness data). + * @param {BufferWriter} bw + */ - for (const id of this.ids) { - const lo = id % 0x100000000; - const hi = (id - lo) / 0x100000000; - assert(hi <= 0xffff); - bw.writeU32(lo); - bw.writeU16(hi); + toNormalWriter(bw) { + return this.writeRaw(bw, false); } - bw.writeVarint(this.ptx.length); + /** + * Serialize compact block. + * @private + * @param {Boolean} witness + * @returns {Buffer} + */ - for (const [index, tx] of this.ptx) { - bw.writeVarint(index); - - if (witness) - tx.toWriter(bw); - else - tx.toNormalWriter(bw); + frameRaw(witness) { + const size = this.getSize(witness); + return this.writeRaw(bio.write(size), witness).render(); } - return bw; -}; + /** + * Calculate block serialization size. + * @param {Boolean} witness + * @returns {Number} + */ -/** - * Convert block to a TXRequest - * containing missing indexes. - * @returns {TXRequest} - */ + getSize(witness) { + let size = 0; -CompactBlock.prototype.toRequest = function toRequest() { - return TXRequest.fromCompact(this); -}; + size += 80; + size += 8; + size += encoding.sizeVarint(this.ids.length); + size += this.ids.length * 6; + size += encoding.sizeVarint(this.ptx.length); -/** - * Attempt to fill missing transactions from mempool. - * @param {Boolean} witness - * @param {Mempool} mempool - * @returns {Boolean} - */ + for (const [index, tx] of this.ptx) { + size += encoding.sizeVarint(index); -CompactBlock.prototype.fillMempool = function fillMempool(witness, mempool) { - if (this.count === this.totalTX) - return true; + if (witness) + size += tx.getSize(); + else + size += tx.getBaseSize(); + } - const set = new Set(); + return size; + } - for (const {tx} of mempool.map.values()) { - let hash = tx.hash(); + /** + * Serialize block to buffer writer. + * @private + * @param {BufferWriter} bw + * @param {Boolean} witness + */ - if (witness) - hash = tx.witnessHash(); + writeRaw(bw, witness) { + this.writeHead(bw); - const id = this.sid(hash); - const index = this.idMap.get(id); + bw.writeBytes(this.keyNonce); - if (index == null) - continue; + bw.writeVarint(this.ids.length); - if (set.has(index)) { - // Siphash collision, just request it. - this.available[index] = null; - this.count--; - continue; + for (const id of this.ids) { + const lo = id % 0x100000000; + const hi = (id - lo) / 0x100000000; + assert(hi <= 0xffff); + bw.writeU32(lo); + bw.writeU16(hi); } - this.available[index] = tx; - set.add(index); - this.count++; - - // We actually may have a siphash collision - // here, but exit early anyway for perf. - if (this.count === this.totalTX) - return true; - } - - return false; -}; + bw.writeVarint(this.ptx.length); -/** - * Attempt to fill missing transactions from TXResponse. - * @param {TXResponse} res - * @returns {Boolean} - */ + for (const [index, tx] of this.ptx) { + bw.writeVarint(index); -CompactBlock.prototype.fillMissing = function fillMissing(res) { - let offset = 0; + if (witness) + tx.toWriter(bw); + else + tx.toNormalWriter(bw); + } - for (let i = 0; i < this.available.length; i++) { - if (this.available[i]) - continue; + return bw; + } - if (offset >= res.txs.length) - return false; + /** + * Convert block to a TXRequest + * containing missing indexes. + * @returns {TXRequest} + */ - this.available[i] = res.txs[offset++]; + toRequest() { + return TXRequest.fromCompact(this); } - return offset === res.txs.length; -}; - -/** - * Calculate a transaction short ID. - * @param {Hash} hash - * @returns {Number} - */ + /** + * Attempt to fill missing transactions from mempool. + * @param {Boolean} witness + * @param {Mempool} mempool + * @returns {Boolean} + */ -CompactBlock.prototype.sid = function sid(hash) { - if (typeof hash === 'string') - hash = Buffer.from(hash, 'hex'); + fillMempool(witness, mempool) { + if (this.count === this.totalTX) + return true; - const [hi, lo] = siphash256(hash, this.sipKey); + const set = new Set(); - return (hi & 0xffff) * 0x100000000 + (lo >>> 0); -}; + for (const {tx} of mempool.map.values()) { + let hash = tx.hash(); -/** - * Test whether an index is available. - * @param {Number} index - * @returns {Boolean} - */ + if (witness) + hash = tx.witnessHash(); -CompactBlock.prototype.hasIndex = function hasIndex(index) { - return this.available[index] != null; -}; + const id = this.sid(hash); + const index = this.idMap.get(id); -/** - * Initialize the siphash key. - * @private - * @returns {Buffer} - */ + if (index == null) + continue; -CompactBlock.prototype.getKey = function getKey() { - const data = Buffer.concat([this.toHead(), this.keyNonce]); - const hash = digest.sha256(data); - return hash.slice(0, 16); -}; + if (set.has(index)) { + // Siphash collision, just request it. + this.available[index] = null; + this.count -= 1; + continue; + } -/** - * Initialize compact block and short id map. - * @private - */ + this.available[index] = tx; + set.add(index); + this.count += 1; -CompactBlock.prototype.init = function init() { - if (this.totalTX === 0) - throw new Error('Empty vectors.'); + // We actually may have a siphash collision + // here, but exit early anyway for perf. + if (this.count === this.totalTX) + return true; + } - if (this.totalTX > consensus.MAX_BLOCK_SIZE / 10) - throw new Error('Compact block too big.'); + return false; + } - // Custom limit to avoid a hashdos. - // Min valid tx size: (4 + 1 + 41 + 1 + 9 + 4) = 60 - // Min block header size: 81 - // Max number of transactions: (1000000 - 81) / 60 = 16665 - if (this.totalTX > (consensus.MAX_BLOCK_SIZE - 81) / 60) - throw new Error('Compact block too big.'); + /** + * Attempt to fill missing transactions from TXResponse. + * @param {TXResponse} res + * @returns {Boolean} + */ - // No sparse arrays here, v8. - for (let i = 0; i < this.totalTX; i++) - this.available.push(null); + fillMissing(res) { + let offset = 0; - let last = -1; - let offset = 0; + for (let i = 0; i < this.available.length; i++) { + if (this.available[i]) + continue; - for (let i = 0; i < this.ptx.length; i++) { - const [index, tx] = this.ptx[i]; - last += index + 1; - assert(last <= 0xffff); - assert(last <= this.ids.length + i); - this.available[last] = tx; - this.count++; - } + if (offset >= res.txs.length) + return false; - for (let i = 0; i < this.ids.length; i++) { - const id = this.ids[i]; + this.available[i] = res.txs[offset++]; + } - while (this.available[i + offset]) - offset++; + return offset === res.txs.length; + } - // Fails on siphash collision. - if (this.idMap.has(id)) - return false; + /** + * Calculate a transaction short ID. + * @param {Hash} hash + * @returns {Number} + */ - this.idMap.set(id, i + offset); + sid(hash) { + const [hi, lo] = siphash(hash, this.sipKey); + return (hi & 0xffff) * 0x100000000 + (lo >>> 0); } - return true; -}; - -/** - * Convert completely filled compact - * block to a regular block. - * @returns {Block} - */ + /** + * Test whether an index is available. + * @param {Number} index + * @returns {Boolean} + */ -CompactBlock.prototype.toBlock = function toBlock() { - const block = new Block(); + hasIndex(index) { + return this.available[index] != null; + } - block.version = this.version; - block.prevBlock = this.prevBlock; - block.merkleRoot = this.merkleRoot; - block.time = this.time; - block.bits = this.bits; - block.nonce = this.nonce; - block._hash = this._hash; - block._hhash = this._hhash; + /** + * Initialize the siphash key. + * @private + * @returns {Buffer} + */ - for (const tx of this.available) { - assert(tx, 'Compact block is not full.'); - block.txs.push(tx); + getKey() { + const data = Buffer.concat([this.toHead(), this.keyNonce]); + const hash = sha256.digest(data); + return hash.slice(0, 16); } - return block; -}; - -/** - * Inject properties from block. - * @private - * @param {Block} block - * @param {Boolean} witness - * @param {Buffer?} nonce - * @returns {CompactBlock} - */ + /** + * Initialize compact block and short id map. + * @private + */ + + init() { + if (this.totalTX === 0) + throw new Error('Empty vectors.'); + + if (this.totalTX > consensus.MAX_BLOCK_SIZE / 10) + throw new Error('Compact block too big.'); + + // Custom limit to avoid a hashdos. + // Min valid tx size: (4 + 1 + 41 + 1 + 9 + 4) = 60 + // Min block header size: 81 + // Max number of transactions: (1000000 - 81) / 60 = 16665 + if (this.totalTX > (consensus.MAX_BLOCK_SIZE - 81) / 60) + throw new Error('Compact block too big.'); + + // No sparse arrays here, v8. + for (let i = 0; i < this.totalTX; i++) + this.available.push(null); + + let last = -1; + let offset = 0; + + for (let i = 0; i < this.ptx.length; i++) { + const [index, tx] = this.ptx[i]; + last += index + 1; + assert(last <= 0xffff); + assert(last <= this.ids.length + i); + this.available[last] = tx; + this.count += 1; + } -CompactBlock.prototype.fromBlock = function fromBlock(block, witness, nonce) { - this.version = block.version; - this.prevBlock = block.prevBlock; - this.merkleRoot = block.merkleRoot; - this.time = block.time; - this.bits = block.bits; - this.nonce = block.nonce; - this.totalTX = block.txs.length; - this._hash = block._hash; - this._hhash = block._hhash; + for (let i = 0; i < this.ids.length; i++) { + const id = this.ids[i]; - if (!nonce) - nonce = util.nonce(); + while (this.available[i + offset]) + offset += 1; - this.keyNonce = nonce; - this.sipKey = this.getKey(); + // Fails on siphash collision. + if (this.idMap.has(id)) + return false; - for (let i = 1; i < block.txs.length; i++) { - const tx = block.txs[i]; - let hash = tx.hash(); + this.idMap.set(id, i + offset); + } - if (witness) - hash = tx.witnessHash(); + return true; + } - const id = this.sid(hash); + /** + * Convert completely filled compact + * block to a regular block. + * @returns {Block} + */ + + toBlock() { + const block = new Block(); + + block.version = this.version; + block.prevBlock = this.prevBlock; + block.merkleRoot = this.merkleRoot; + block.time = this.time; + block.bits = this.bits; + block.nonce = this.nonce; + block._hash = this._hash; + block._hhash = this._hhash; + + for (const tx of this.available) { + assert(tx, 'Compact block is not full.'); + block.txs.push(tx); + } - this.ids.push(id); + return block; } - this.ptx.push([0, block.txs[0]]); + /** + * Inject properties from block. + * @private + * @param {Block} block + * @param {Boolean} witness + * @param {Buffer?} nonce + * @returns {CompactBlock} + */ + + fromBlock(block, witness, nonce) { + this.version = block.version; + this.prevBlock = block.prevBlock; + this.merkleRoot = block.merkleRoot; + this.time = block.time; + this.bits = block.bits; + this.nonce = block.nonce; + this.totalTX = block.txs.length; + this._hash = block._hash; + this._hhash = block._hhash; + + if (!nonce) + nonce = common.nonce(); + + this.keyNonce = nonce; + this.sipKey = this.getKey(); + + for (let i = 1; i < block.txs.length; i++) { + const tx = block.txs[i]; + let hash = tx.hash(); + + if (witness) + hash = tx.witnessHash(); + + const id = this.sid(hash); + + this.ids.push(id); + } - return this; -}; + this.ptx.push([0, block.txs[0]]); -/** - * Instantiate compact block from a block. - * @param {Block} block - * @param {Boolean} witness - * @param {Buffer?} nonce - * @returns {CompactBlock} - */ + return this; + } -CompactBlock.fromBlock = function fromBlock(block, witness, nonce) { - return new CompactBlock().fromBlock(block, witness, nonce); -}; + /** + * Instantiate compact block from a block. + * @param {Block} block + * @param {Boolean} witness + * @param {Buffer?} nonce + * @returns {CompactBlock} + */ -/** - * Convert block to headers. - * @returns {Headers} - */ + static fromBlock(block, witness, nonce) { + return new this().fromBlock(block, witness, nonce); + } -CompactBlock.prototype.toHeaders = function toHeaders() { - return Headers.fromBlock(this); -}; + /** + * Convert block to headers. + * @returns {Headers} + */ + + toHeaders() { + return Headers.fromBlock(this); + } +} /** + * TX Request * Represents a BlockTransactionsRequest (bip152): `getblocktxn` packet. * @see https://github.com/bitcoin/bips/blob/master/bip-0152.mediawiki - * @constructor - * @param {Object} options * @property {Hash} hash * @property {Number[]} indexes */ -function TXRequest(options) { - if (!(this instanceof TXRequest)) - return new TXRequest(options); +class TXRequest { + /** + * TX Request + * @constructor + * @param {Object?} options + */ - this.hash = encoding.NULL_HASH; - this.indexes = []; + constructor(options) { + this.hash = consensus.ZERO_HASH; + this.indexes = []; - if (options) - this.fromOptions(options); -} + if (options) + this.fromOptions(options); + } -/** - * Inject properties from options. - * @private - * @param {Object} options - * @returns {TXRequest} - */ + /** + * Inject properties from options. + * @private + * @param {Object} options + * @returns {TXRequest} + */ -TXRequest.prototype.fromOptions = function fromOptions(options) { - this.hash = options.hash; + fromOptions(options) { + this.hash = options.hash; - if (options.indexes) - this.indexes = options.indexes; + if (options.indexes) + this.indexes = options.indexes; - return this; -}; + return this; + } -/** - * Instantiate request from options. - * @param {Object} options - * @returns {TXRequest} - */ + /** + * Instantiate request from options. + * @param {Object} options + * @returns {TXRequest} + */ -TXRequest.fromOptions = function fromOptions(options) { - return new TXRequest().fromOptions(options); -}; + static fromOptions(options) { + return new this().fromOptions(options); + } -/** - * Inject properties from compact block. - * @private - * @param {CompactBlock} block - * @returns {TXRequest} - */ + /** + * Inject properties from compact block. + * @private + * @param {CompactBlock} block + * @returns {TXRequest} + */ + + fromCompact(block) { + this.hash = block.hash(); -TXRequest.prototype.fromCompact = function fromCompact(block) { - this.hash = block.hash('hex'); + for (let i = 0; i < block.available.length; i++) { + if (!block.available[i]) + this.indexes.push(i); + } - for (let i = 0; i < block.available.length; i++) { - if (!block.available[i]) - this.indexes.push(i); + return this; } - return this; -}; + /** + * Instantiate request from compact block. + * @param {CompactBlock} block + * @returns {TXRequest} + */ -/** - * Instantiate request from compact block. - * @param {CompactBlock} block - * @returns {TXRequest} - */ + static fromCompact(block) { + return new this().fromCompact(block); + } -TXRequest.fromCompact = function fromCompact(block) { - return new TXRequest().fromCompact(block); -}; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + * @returns {TXRequest} + */ -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - * @returns {TXRequest} - */ + fromReader(br) { + this.hash = br.readHash(); -TXRequest.prototype.fromReader = function fromReader(br) { - this.hash = br.readHash('hex'); + const count = br.readVarint(); - const count = br.readVarint(); + for (let i = 0; i < count; i++) { + const index = br.readVarint(); + assert(index <= 0xffff); + this.indexes.push(index); + } - for (let i = 0; i < count; i++) { - const index = br.readVarint(); - assert(index <= 0xffff); - this.indexes.push(index); - } + let offset = 0; - let offset = 0; + for (let i = 0; i < count; i++) { + let index = this.indexes[i]; + index += offset; + assert(index <= 0xffff); + this.indexes[i] = index; + offset = index + 1; + } - for (let i = 0; i < count; i++) { - let index = this.indexes[i]; - index += offset; - assert(index <= 0xffff); - this.indexes[i] = index; - offset = index + 1; + return this; } - return this; -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + * @returns {TXRequest} + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @returns {TXRequest} - */ + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -TXRequest.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + /** + * Instantiate request from buffer reader. + * @param {BufferReader} br + * @returns {TXRequest} + */ -/** - * Instantiate request from buffer reader. - * @param {BufferReader} br - * @returns {TXRequest} - */ + static fromReader(br) { + return new this().fromReader(br); + } -TXRequest.fromReader = function fromReader(br) { - return new TXRequest().fromReader(br); -}; + /** + * Instantiate request from serialized data. + * @param {Buffer} data + * @returns {TXRequest} + */ -/** - * Instantiate request from serialized data. - * @param {Buffer} data - * @returns {TXRequest} - */ + static fromRaw(data) { + return new this().fromRaw(data); + } -TXRequest.fromRaw = function fromRaw(data) { - return new TXRequest().fromRaw(data); -}; + /** + * Calculate request serialization size. + * @returns {Number} + */ -/** - * Calculate request serialization size. - * @returns {Number} - */ + getSize() { + let size = 0; -TXRequest.prototype.getSize = function getSize() { - let size = 0; + size += 32; + size += encoding.sizeVarint(this.indexes.length); - size += 32; - size += encoding.sizeVarint(this.indexes.length); + for (let i = 0; i < this.indexes.length; i++) { + let index = this.indexes[i]; - for (let i = 0; i < this.indexes.length; i++) { - let index = this.indexes[i]; + if (i > 0) + index -= this.indexes[i - 1] + 1; - if (i > 0) - index -= this.indexes[i - 1] + 1; + size += encoding.sizeVarint(index); + } - size += encoding.sizeVarint(index); + return size; } - return size; -}; + /** + * Write serialized request to buffer writer. + * @param {BufferWriter} bw + */ -/** - * Write serialized request to buffer writer. - * @param {BufferWriter} bw - */ + toWriter(bw) { + bw.writeHash(this.hash); -TXRequest.prototype.toWriter = function toWriter(bw) { - bw.writeHash(this.hash); + bw.writeVarint(this.indexes.length); - bw.writeVarint(this.indexes.length); + for (let i = 0; i < this.indexes.length; i++) { + let index = this.indexes[i]; - for (let i = 0; i < this.indexes.length; i++) { - let index = this.indexes[i]; + if (i > 0) + index -= this.indexes[i - 1] + 1; - if (i > 0) - index -= this.indexes[i - 1] + 1; + bw.writeVarint(index); + } - bw.writeVarint(index); + return bw; } - return bw; -}; - -/** - * Serialize request. - * @returns {Buffer} - */ + /** + * Serialize request. + * @returns {Buffer} + */ -TXRequest.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } +} /** + * TX Response * Represents BlockTransactions (bip152): `blocktxn` packet. * @see https://github.com/bitcoin/bips/blob/master/bip-0152.mediawiki - * @constructor - * @param {Object} options * @property {Hash} hash * @property {TX[]} txs */ -function TXResponse(options) { - if (!(this instanceof TXResponse)) - return new TXResponse(options); +class TXResponse { + /** + * Create a tx response. + * @constructor + * @param {Object?} options + */ - this.hash = encoding.NULL_HASH; - this.txs = []; + constructor(options) { + this.hash = consensus.ZERO_HASH; + this.txs = []; - if (options) - this.fromOptions(options); -} + if (options) + this.fromOptions(options); + } -/** - * Inject properties from options. - * @private - * @param {Object} options - * @returns {TXResponse} - */ + /** + * Inject properties from options. + * @private + * @param {Object} options + * @returns {TXResponse} + */ + + fromOptions(options) { + this.hash = options.hash; -TXResponse.prototype.fromOptions = function fromOptions(options) { - this.hash = options.hash; + if (options.txs) + this.txs = options.txs; - if (options.txs) - this.txs = options.txs; + return this; + } - return this; -}; + /** + * Instantiate response from options. + * @param {Object} options + * @returns {TXResponse} + */ -/** - * Instantiate response from options. - * @param {Object} options - * @returns {TXResponse} - */ + static fromOptions(options) { + return new this().fromOptions(options); + } -TXResponse.fromOptions = function fromOptions(options) { - return new TXResponse().fromOptions(options); -}; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + * @returns {TXResponse} + */ -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - * @returns {TXResponse} - */ + fromReader(br) { + this.hash = br.readHash(); -TXResponse.prototype.fromReader = function fromReader(br) { - this.hash = br.readHash('hex'); + const count = br.readVarint(); - const count = br.readVarint(); + for (let i = 0; i < count; i++) + this.txs.push(TX.fromReader(br)); - for (let i = 0; i < count; i++) - this.txs.push(TX.fromReader(br)); + return this; + } - return this; -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + * @returns {TXResponse} + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @returns {TXResponse} - */ + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -TXResponse.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + /** + * Instantiate response from buffer reader. + * @param {BufferReader} br + * @returns {TXResponse} + */ -/** - * Instantiate response from buffer reader. - * @param {BufferReader} br - * @returns {TXResponse} - */ + static fromReader(br) { + return new this().fromReader(br); + } -TXResponse.fromReader = function fromReader(br) { - return new TXResponse().fromReader(br); -}; + /** + * Instantiate response from serialized data. + * @param {Buffer} data + * @returns {TXResponse} + */ -/** - * Instantiate response from serialized data. - * @param {Buffer} data - * @returns {TXResponse} - */ + static fromRaw(data) { + return new this().fromRaw(data); + } -TXResponse.fromRaw = function fromRaw(data) { - return new TXResponse().fromRaw(data); -}; + /** + * Inject properties from block. + * @private + * @param {Block} block + * @returns {TXResponse} + */ -/** - * Inject properties from block. - * @private - * @param {Block} block - * @returns {TXResponse} - */ + fromBlock(block, req) { + this.hash = req.hash; -TXResponse.prototype.fromBlock = function fromBlock(block, req) { - this.hash = req.hash; + for (const index of req.indexes) { + if (index >= block.txs.length) + break; - for (const index of req.indexes) { - if (index >= block.txs.length) - break; + this.txs.push(block.txs[index]); + } - this.txs.push(block.txs[index]); + return this; } - return this; -}; + /** + * Instantiate response from block. + * @param {Block} block + * @returns {TXResponse} + */ -/** - * Instantiate response from block. - * @param {Block} block - * @returns {TXResponse} - */ + static fromBlock(block, req) { + return new this().fromBlock(block, req); + } -TXResponse.fromBlock = function fromBlock(block, req) { - return new TXResponse().fromBlock(block, req); -}; + /** + * Serialize response with witness data. + * @returns {Buffer} + */ -/** - * Serialize response with witness data. - * @returns {Buffer} - */ + toRaw() { + return this.frameRaw(true); + } -TXResponse.prototype.toRaw = function toRaw() { - return this.frameRaw(true); -}; + /** + * Serialize response without witness data. + * @returns {Buffer} + */ -/** - * Serialize response without witness data. - * @returns {Buffer} - */ + toNormal() { + return this.frameRaw(false); + } -TXResponse.prototype.toNormal = function toNormal() { - return this.frameRaw(false); -}; + /** + * Write serialized response to a buffer + * writer (includes witness data). + * @param {BufferWriter} bw + */ -/** - * Write serialized response to a buffer - * writer (includes witness data). - * @param {BufferWriter} bw - */ + toWriter(bw) { + return this.writeRaw(bw, true); + } -TXResponse.prototype.toWriter = function toWriter(bw) { - return this.writeRaw(bw, true); -}; + /** + * Write serialized response to a buffer + * writer (excludes witness data). + * @param {BufferWriter} bw + */ -/** - * Write serialized response to a buffer - * writer (excludes witness data). - * @param {BufferWriter} bw - */ + toNormalWriter(bw) { + return this.writeRaw(bw, false); + } -TXResponse.prototype.toNormalWriter = function toNormalWriter(bw) { - return this.writeRaw(bw, false); -}; + /** + * Calculate request serialization size. + * @returns {Number} + */ -/** - * Calculate request serialization size. - * @returns {Number} - */ + getSize(witness) { + let size = 0; -TXResponse.prototype.getSize = function getSize(witness) { - let size = 0; + size += 32; + size += encoding.sizeVarint(this.txs.length); - size += 32; - size += encoding.sizeVarint(this.txs.length); + for (const tx of this.txs) { + if (witness) + size += tx.getSize(); + else + size += tx.getBaseSize(); + } - for (const tx of this.txs) { - if (witness) - size += tx.getSize(); - else - size += tx.getBaseSize(); + return size; } - return size; -}; + /** + * Write serialized response to buffer writer. + * @private + * @param {BufferWriter} bw + * @param {Boolean} witness + */ -/** - * Write serialized response to buffer writer. - * @private - * @param {BufferWriter} bw - * @param {Boolean} witness - */ + writeRaw(bw, witness) { + bw.writeHash(this.hash); -TXResponse.prototype.writeRaw = function writeRaw(bw, witness) { - bw.writeHash(this.hash); + bw.writeVarint(this.txs.length); - bw.writeVarint(this.txs.length); + for (const tx of this.txs) { + if (witness) + tx.toWriter(bw); + else + tx.toNormalWriter(bw); + } - for (const tx of this.txs) { - if (witness) - tx.toWriter(bw); - else - tx.toNormalWriter(bw); + return bw; } - return bw; -}; - -/** - * Serialize response with witness data. - * @private - * @param {Boolean} witness - * @returns {Buffer} - */ + /** + * Serialize response with witness data. + * @private + * @param {Boolean} witness + * @returns {Buffer} + */ -TXResponse.prototype.frameRaw = function frameRaw(witness) { - const size = this.getSize(witness); - return this.writeRaw(new StaticWriter(size), witness).render(); -}; + frameRaw(witness) { + const size = this.getSize(witness); + return this.writeRaw(bio.write(size), witness).render(); + } +} /* * Expose diff --git a/lib/net/common.js b/lib/net/common.js index 614ac7d9a..c61db97e7 100644 --- a/lib/net/common.js +++ b/lib/net/common.js @@ -159,3 +159,71 @@ exports.BAN_TIME = 24 * 60 * 60; */ exports.BAN_SCORE = 100; + +/** + * Create a nonce. + * @returns {Buffer} + */ + +exports.nonce = function nonce() { + const data = Buffer.allocUnsafe(8); + data.writeUInt32LE((Math.random() * 0x100000000) >>> 0, 0, true); + data.writeUInt32LE((Math.random() * 0x100000000) >>> 0, 4, true); + return data; +}; + +/** + * A compressed pubkey of all zeroes. + * @const {Buffer} + * @default + */ + +exports.ZERO_KEY = Buffer.alloc(33, 0x00); + +/** + * A 64 byte signature of all zeroes. + * @const {Buffer} + * @default + */ + +exports.ZERO_SIG = Buffer.alloc(64, 0x00); + +/** + * 8 zero bytes. + * @const {Buffer} + * @default + */ + +exports.ZERO_NONCE = Buffer.alloc(8, 0x00); + +/** + * Maximum inv/getdata size. + * @const {Number} + * @default + */ + +exports.MAX_INV = 50000; + +/** + * Maximum number of requests. + * @const {Number} + * @default + */ + +exports.MAX_REQUEST = 5000; + +/** + * Maximum number of block requests. + * @const {Number} + * @default + */ + +exports.MAX_BLOCK_REQUEST = 100000 + 1000; + +/** + * Maximum number of tx requests. + * @const {Number} + * @default + */ + +exports.MAX_TX_REQUEST = 10000; diff --git a/lib/net/dns-browser.js b/lib/net/dns-browser.js deleted file mode 100644 index ac3096b2d..000000000 --- a/lib/net/dns-browser.js +++ /dev/null @@ -1,35 +0,0 @@ -/*! - * dns.js - dns backend for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * Resolve host (no getaddrinfo). - * @ignore - * @param {String} host - * @param {String?} proxy - Tor socks proxy. - * @returns {Promise} - */ - -exports.resolve = function resolve(host, proxy) { - return new Promise((resolve, reject) => { - reject(new Error('DNS not supported.')); - }); -}; - -/** - * Resolve host (getaddrinfo). - * @ignore - * @param {String} host - * @param {String?} proxy - Tor socks proxy. - * @returns {Promise} - */ - -exports.lookup = function lookup(host, proxy) { - return new Promise((resolve, reject) => { - reject(new Error('DNS not supported.')); - }); -}; diff --git a/lib/net/dns.js b/lib/net/dns.js deleted file mode 100644 index e2310d42e..000000000 --- a/lib/net/dns.js +++ /dev/null @@ -1,99 +0,0 @@ -/*! - * dns.js - dns backend for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module net/dns - */ - -const dns = require('dns'); -const socks = require('./socks'); - -const options = { - family: 4, - hints: dns.ADDRCONFIG | dns.V4MAPPED, - all: true -}; - -/** - * Resolve host (async w/ libcares). - * @param {String} host - * @param {String?} proxy - Tor socks proxy. - * @returns {Promise} - */ - -exports.resolve = function resolve(host, proxy) { - if (proxy) - return socks.resolve(proxy, host); - - return new Promise((resolve, reject) => { - dns.resolve(host, 'A', to((err, result) => { - if (err) { - reject(err); - return; - } - - if (result.length === 0) { - reject(new Error('No DNS results.')); - return; - } - - resolve(result); - })); - }); -}; - -/** - * Resolve host (getaddrinfo). - * @param {String} host - * @param {String?} proxy - Tor socks proxy. - * @returns {Promise} - */ - -exports.lookup = function lookup(host, proxy) { - if (proxy) - return socks.resolve(proxy, host); - - return new Promise((resolve, reject) => { - dns.lookup(host, options, to((err, result) => { - if (err) { - reject(err); - return; - } - - if (result.length === 0) { - reject(new Error('No DNS results.')); - return; - } - - const addrs = []; - - for (const addr of result) - addrs.push(addr.address); - - resolve(addrs); - })); - }); -}; - -/* - * Helpers - */ - -function to(callback) { - const timeout = setTimeout(() => { - callback(new Error('DNS request timed out.')); - callback = null; - }, 5000); - - return function(err, result) { - if (callback) { - clearTimeout(timeout); - callback(err, result); - } - }; -} diff --git a/lib/net/external-browser.js b/lib/net/external-browser.js deleted file mode 100644 index 17fbb76aa..000000000 --- a/lib/net/external-browser.js +++ /dev/null @@ -1,29 +0,0 @@ -/*! - * external.js - external ip address discovery for bcoin - * Copyright (c) 2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const external = exports; - -/** - * Attempt to retrieve external IP from icanhazip.com. - * @method - * @returns {Promise} - */ - -external.getIPv4 = async function getIPv4() { - throw new Error('Could not find IP.'); -}; - -/** - * Attempt to retrieve external IP from icanhazip.com. - * @method - * @returns {Promise} - */ - -external.getIPv6 = async function getIPv6() { - throw new Error('Could not find IP.'); -}; diff --git a/lib/net/external.js b/lib/net/external.js deleted file mode 100644 index b455e5cb1..000000000 --- a/lib/net/external.js +++ /dev/null @@ -1,95 +0,0 @@ -/*! - * external.js - external ip address discovery for bcoin - * Copyright (c) 2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const request = require('../http/request'); -const IP = require('../utils/ip'); - -/** - * @exports net/external - */ - -const external = exports; - -/** - * Attempt to retrieve external IP from icanhazip.com. - * @method - * @returns {Promise} - */ - -external.getIPv4 = async function getIPv4() { - try { - const res = await request({ - method: 'GET', - uri: 'http://ipv4.icanhazip.com', - expect: 'txt', - timeout: 2000 - }); - - const str = res.body.trim(); - const raw = IP.toBuffer(str); - - if (!IP.isIPv4(raw)) - throw new Error('Could not find IPv4.'); - - return IP.toString(raw); - } catch (e) { - return await external.getIPv42(); - } -}; - -/** - * Attempt to retrieve external IP from dyndns.org. - * @method - * @ignore - * @returns {Promise} - */ - -external.getIPv42 = async function getIPv42() { - const res = await request({ - method: 'GET', - uri: 'http://checkip.dyndns.org', - expect: 'html', - timeout: 2000 - }); - - const match = /IP Address:\s*([0-9a-f.:]+)/i.exec(res.body); - - if (!match) - throw new Error('Could not find IPv4.'); - - const str = match[1]; - const raw = IP.toBuffer(str); - - if (!IP.isIPv4(raw)) - throw new Error('Could not find IPv4.'); - - return IP.toString(raw); -}; - -/** - * Attempt to retrieve external IP from icanhazip.com. - * @method - * @returns {Promise} - */ - -external.getIPv6 = async function getIPv6() { - const res = await request({ - method: 'GET', - uri: 'http://ipv6.icanhazip.com', - expect: 'txt', - timeout: 2000 - }); - - const str = res.body.trim(); - const raw = IP.toBuffer(str); - - if (!IP.isIPv6(raw)) - throw new Error('Could not find IPv6.'); - - return IP.toString(raw); -}; diff --git a/lib/net/framer.js b/lib/net/framer.js index 7a568f72d..600707938 100644 --- a/lib/net/framer.js +++ b/lib/net/framer.js @@ -7,61 +7,64 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const Network = require('../protocol/network'); -const digest = require('../crypto/digest'); +const hash256 = require('bcrypto/lib/hash256'); /** - * Protocol packet framer + * Protocol Message Framer * @alias module:net.Framer - * @constructor - * @param {Network} network */ -function Framer(network) { - if (!(this instanceof Framer)) - return new Framer(network); +class Framer { + /** + * Create a framer. + * @constructor + * @param {Network} network + */ - this.network = Network.get(network); -} + constructor(network) { + this.network = Network.get(network); + } -/** - * Frame a payload with a header. - * @param {String} cmd - Packet type. - * @param {Buffer} payload - * @param {Buffer?} checksum - Precomputed checksum. - * @returns {Buffer} Payload with header prepended. - */ + /** + * Frame a payload with a header. + * @param {String} cmd - Packet type. + * @param {Buffer} payload + * @param {Buffer?} checksum - Precomputed checksum. + * @returns {Buffer} Payload with header prepended. + */ -Framer.prototype.packet = function packet(cmd, payload, checksum) { - assert(payload, 'No payload.'); - assert(cmd.length < 12); - assert(payload.length <= 0xffffffff); + packet(cmd, payload, checksum) { + assert(payload, 'No payload.'); + assert(cmd.length < 12); + assert(payload.length <= 0xffffffff); - const msg = Buffer.allocUnsafe(24 + payload.length); + const msg = Buffer.allocUnsafe(24 + payload.length); - // Magic value - msg.writeUInt32LE(this.network.magic, 0, true); + // Magic value + msg.writeUInt32LE(this.network.magic, 0, true); - // Command - msg.write(cmd, 4, 'ascii'); + // Command + msg.write(cmd, 4, 'ascii'); - for (let i = 4 + cmd.length; i < 16; i++) - msg[i] = 0; + for (let i = 4 + cmd.length; i < 16; i++) + msg[i] = 0; - // Payload length - msg.writeUInt32LE(payload.length, 16, true); + // Payload length + msg.writeUInt32LE(payload.length, 16, true); - if (!checksum) - checksum = digest.hash256(payload); + if (!checksum) + checksum = hash256.digest(payload); - // Checksum - checksum.copy(msg, 20, 0, 4); + // Checksum + checksum.copy(msg, 20, 0, 4); - payload.copy(msg, 24); + payload.copy(msg, 24); - return msg; -}; + return msg; + } +} /* * Expose diff --git a/lib/net/hostlist.js b/lib/net/hostlist.js index 3f0b8d502..ed3135035 100644 --- a/lib/net/hostlist.js +++ b/lib/net/hostlist.js @@ -6,1582 +6,1622 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const path = require('path'); +const fs = require('bfile'); +const IP = require('binet'); +const dns = require('bdns'); +const Logger = require('blgr'); +const murmur3 = require('mrmr'); +const List = require('blst'); const util = require('../utils/util'); -const IP = require('../utils/ip'); -const co = require('../utils/co'); const Network = require('../protocol/network'); -const NetAddress = require('../primitives/netaddress'); -const List = require('../utils/list'); -const murmur3 = require('../utils/murmur3'); +const NetAddress = require('./netaddress'); const common = require('./common'); const seeds = require('./seeds'); -const dns = require('./dns'); -const Logger = require('../node/logger'); -const fs = require('../utils/fs'); -const POOL32 = Buffer.allocUnsafe(32); - -/** - * Host List - * @alias module:net.HostList - * @constructor - * @param {Object} options - */ - -function HostList(options) { - if (!(this instanceof HostList)) - return new HostList(options); - - this.options = new HostListOptions(options); - this.network = this.options.network; - this.logger = this.options.logger.context('hostlist'); - this.address = this.options.address; - this.resolve = this.options.resolve; - - this.dnsSeeds = []; - this.dnsNodes = []; +const {inspectSymbol} = require('../utils'); - this.map = new Map(); - this.fresh = []; - this.totalFresh = 0; - this.used = []; - this.totalUsed = 0; - this.nodes = []; - this.local = new Map(); - this.banned = new Map(); - - this.timer = null; - this.needsFlush = false; - - this._init(); -} - -/** - * Number of days before considering - * an address stale. - * @const {Number} - * @default +/* + * Constants */ -HostList.HORIZON_DAYS = 30; +const POOL32 = Buffer.allocUnsafe(32); /** - * Number of retries (without success) - * before considering an address stale. - * @const {Number} - * @default + * Host List + * @alias module:net.HostList */ -HostList.RETRIES = 3; - -/** - * Number of days after reaching - * MAX_FAILURES to consider an - * address stale. - * @const {Number} - * @default - */ +class HostList { + /** + * Create a host list. + * @constructor + * @param {Object} options + */ + + constructor(options) { + this.options = new HostListOptions(options); + this.network = this.options.network; + this.logger = this.options.logger.context('hostlist'); + this.address = this.options.address; + this.resolve = this.options.resolve; + + this.dnsSeeds = []; + this.dnsNodes = []; + + this.map = new Map(); + this.fresh = []; + this.totalFresh = 0; + this.used = []; + this.totalUsed = 0; + this.nodes = []; + this.local = new Map(); + this.banned = new Map(); + + this.timer = null; + this.needsFlush = false; + this.flushing = false; + + this.init(); + } -HostList.MIN_FAIL_DAYS = 7; + /** + * Initialize list. + * @private + */ -/** - * Maximum number of failures - * allowed before considering - * an address stale. - * @const {Number} - * @default - */ + init() { + const options = this.options; + const scores = HostList.scores; -HostList.MAX_FAILURES = 10; + for (let i = 0; i < options.maxBuckets; i++) + this.fresh.push(new Map()); -/** - * Maximum number of references - * in fresh buckets. - * @const {Number} - * @default - */ + for (let i = 0; i < options.maxBuckets; i++) + this.used.push(new List()); -HostList.MAX_REFS = 8; + this.setSeeds(options.seeds); + this.setNodes(options.nodes); -/** - * Serialization version. - * @const {Number} - * @default - */ + this.pushLocal(this.address, scores.MANUAL); + this.addLocal(options.host, options.port, scores.BIND); -HostList.VERSION = 0; + const hosts = IP.getPublic(); + const port = this.address.port; -/** - * Local address scores. - * @enum {Number} - * @default - */ + for (const host of hosts) + this.addLocal(host, port, scores.IF); + } -HostList.scores = { - NONE: 0, - IF: 1, - BIND: 2, - UPNP: 3, - HTTP: 3, - MANUAL: 4, - MAX: 5 -}; + /** + * Open hostlist and read hosts file. + * @method + * @returns {Promise} + */ + + async open() { + try { + await this.loadFile(); + } catch (e) { + this.logger.warning('Hosts deserialization failed.'); + this.logger.error(e); + } -/** - * Initialize list. - * @private - */ + if (this.size() === 0) + this.injectSeeds(); -HostList.prototype._init = function _init() { - const options = this.options; - const scores = HostList.scores; - const hosts = IP.getPublic(); - const port = this.address.port; + await this.discoverNodes(); - for (let i = 0; i < this.options.maxBuckets; i++) - this.fresh.push(new Map()); + this.start(); + } - for (let i = 0; i < this.options.maxBuckets; i++) - this.used.push(new List()); + /** + * Close hostlist. + * @method + * @returns {Promise} + */ - this.setSeeds(options.seeds); - this.setNodes(options.nodes); + async close() { + this.stop(); + await this.flush(); + this.reset(); + } - this.pushLocal(this.address, scores.MANUAL); - this.addLocal(options.host, options.port, scores.BIND); + /** + * Start flush interval. + */ - for (const host of hosts) - this.addLocal(host, port, scores.IF); -}; + start() { + if (this.options.memory) + return; -/** - * Open hostlist and read hosts file. - * @method - * @returns {Promise} - */ + if (!this.options.filename) + return; -HostList.prototype.open = async function open() { - try { - await this.loadFile(); - } catch (e) { - this.logger.warning('Hosts deserialization failed.'); - this.logger.error(e); + assert(this.timer == null); + this.timer = setInterval(() => this.flush(), this.options.flushInterval); } - if (this.size() === 0) - this.injectSeeds(); - - await this.discoverNodes(); + /** + * Stop flush interval. + */ - this.start(); -}; + stop() { + if (this.options.memory) + return; -/** - * Close hostlist. - * @method - * @returns {Promise} - */ + if (!this.options.filename) + return; -HostList.prototype.close = async function close() { - this.stop(); - await this.flush(); - this.reset(); -}; + assert(this.timer != null); + clearInterval(this.timer); + this.timer = null; + } -/** - * Start flush interval. - */ + /** + * Read and initialize from hosts file. + * @method + * @returns {Promise} + */ -HostList.prototype.start = function start() { - if (!this.options.persistent) - return; + injectSeeds() { + const nodes = seeds.get(this.network.type); - if (!this.options.filename) - return; + for (const node of nodes) { + const addr = NetAddress.fromHostname(node, this.network); - assert(this.timer == null); - this.timer = co.setInterval(this.flush, this.options.flushInterval, this); -}; + if (!addr.isRoutable()) + continue; -/** - * Stop flush interval. - */ + if (!this.options.onion && addr.isOnion()) + continue; -HostList.prototype.stop = function stop() { - if (!this.options.persistent) - return; + if (addr.port === 0) + continue; - if (!this.options.filename) - return; + this.add(addr); + } + } - assert(this.timer != null); - co.clearInterval(this.timer); - this.timer = null; -}; + /** + * Read and initialize from hosts file. + * @method + * @returns {Promise} + */ -/** - * Read and initialize from hosts file. - * @method - * @returns {Promise} - */ + async loadFile() { + const filename = this.options.filename; -HostList.prototype.injectSeeds = function injectSeeds() { - const nodes = seeds.get(this.network.type); + if (fs.unsupported) + return; - for (const node of nodes) { - const addr = NetAddress.fromHostname(node, this.network); + if (this.options.memory) + return; - if (!addr.isRoutable()) - continue; + if (!filename) + return; - if (!this.options.onion && addr.isOnion()) - continue; + let data; + try { + data = await fs.readFile(filename, 'utf8'); + } catch (e) { + if (e.code === 'ENOENT') + return; + throw e; + } - if (addr.port === 0) - continue; + const json = JSON.parse(data); - this.add(addr); + this.fromJSON(json); } -}; -/** - * Read and initialize from hosts file. - * @method - * @returns {Promise} - */ + /** + * Flush addrs to hosts file. + * @method + * @returns {Promise} + */ -HostList.prototype.loadFile = async function loadFile() { - const filename = this.options.filename; + async flush() { + const filename = this.options.filename; - if (fs.unsupported) - return; - - if (!this.options.persistent) - return; - - if (!filename) - return; - - let data; - try { - data = await fs.readFile(filename, 'utf8'); - } catch (e) { - if (e.code === 'ENOENT') + if (fs.unsupported) return; - throw e; - } - const json = JSON.parse(data); + if (this.options.memory) + return; - this.fromJSON(json); -}; + if (!filename) + return; -/** - * Flush addrs to hosts file. - * @method - * @returns {Promise} - */ + if (!this.needsFlush) + return; -HostList.prototype.flush = async function flush() { - const filename = this.options.filename; + if (this.flushing) + return; - if (fs.unsupported) - return; + this.needsFlush = false; - if (!this.options.persistent) - return; + this.logger.debug('Writing hosts to %s.', filename); - if (!filename) - return; + const json = this.toJSON(); + const data = JSON.stringify(json); - if (!this.needsFlush) - return; + this.flushing = true; - this.needsFlush = false; + try { + await fs.writeFile(filename, data, 'utf8'); + } catch (e) { + this.logger.warning('Writing hosts failed.'); + this.logger.error(e); + } - this.logger.debug('Writing hosts to %s.', filename); + this.flushing = false; + } - const json = this.toJSON(); - const data = JSON.stringify(json); + /** + * Get list size. + * @returns {Number} + */ - try { - await fs.writeFile(filename, data, 'utf8'); - } catch (e) { - this.logger.warning('Writing hosts failed.'); - this.logger.error(e); + size() { + return this.totalFresh + this.totalUsed; } -}; -/** - * Get list size. - * @returns {Number} - */ + /** + * Test whether the host list is full. + * @returns {Boolean} + */ -HostList.prototype.size = function size() { - return this.totalFresh + this.totalUsed; -}; + isFull() { + const max = this.options.maxBuckets * this.options.maxEntries; + return this.size() >= max; + } -/** - * Test whether the host list is full. - * @returns {Boolean} - */ + /** + * Reset host list. + */ -HostList.prototype.isFull = function isFull() { - const max = this.options.maxBuckets * this.options.maxEntries; - return this.size() >= max; -}; + reset() { + this.map.clear(); -/** - * Reset host list. - */ + for (const bucket of this.fresh) + bucket.clear(); -HostList.prototype.reset = function reset() { - this.map.clear(); + for (const bucket of this.used) + bucket.reset(); - for (const bucket of this.fresh) - bucket.clear(); + this.totalFresh = 0; + this.totalUsed = 0; - for (const bucket of this.used) - bucket.reset(); + this.nodes.length = 0; + } - this.totalFresh = 0; - this.totalUsed = 0; + /** + * Mark a peer as banned. + * @param {String} host + */ - this.nodes.length = 0; -}; + ban(host) { + this.banned.set(host, util.now()); + } -/** - * Mark a peer as banned. - * @param {String} host - */ + /** + * Unban host. + * @param {String} host + */ -HostList.prototype.ban = function ban(host) { - this.banned.set(host, util.now()); -}; + unban(host) { + this.banned.delete(host); + } -/** - * Unban host. - * @param {String} host - */ + /** + * Clear banned hosts. + */ -HostList.prototype.unban = function unban(host) { - this.banned.delete(host); -}; - -/** - * Clear banned hosts. - */ + clearBanned() { + this.banned.clear(); + } -HostList.prototype.clearBanned = function clearBanned() { - this.banned.clear(); -}; + /** + * Test whether the host is banned. + * @param {String} host + * @returns {Boolean} + */ -/** - * Test whether the host is banned. - * @param {String} host - * @returns {Boolean} - */ + isBanned(host) { + const time = this.banned.get(host); -HostList.prototype.isBanned = function isBanned(host) { - const time = this.banned.get(host); + if (time == null) + return false; - if (time == null) - return false; + if (util.now() > time + this.options.banTime) { + this.banned.delete(host); + return false; + } - if (util.now() > time + this.options.banTime) { - this.banned.delete(host); - return false; + return true; } - return true; -}; - -/** - * Allocate a new host. - * @returns {HostEntry} - */ + /** + * Allocate a new host. + * @returns {HostEntry} + */ -HostList.prototype.getHost = function getHost() { - let buckets = null; + getHost() { + let buckets = null; - if (this.totalFresh > 0) - buckets = this.fresh; + if (this.totalFresh > 0) + buckets = this.fresh; - if (this.totalUsed > 0) { - if (this.totalFresh === 0 || util.random(0, 2) === 0) - buckets = this.used; - } - - if (!buckets) - return null; - - const now = this.network.now(); - let factor = 1; + if (this.totalUsed > 0) { + if (this.totalFresh === 0 || random(2) === 0) + buckets = this.used; + } - for (;;) { - let index = util.random(0, buckets.length); - const bucket = buckets[index]; + if (!buckets) + return null; - if (bucket.size === 0) - continue; + const now = this.network.now(); - index = util.random(0, bucket.size); + let factor = 1; - let entry; - if (buckets === this.used) { - entry = bucket.head; - while (index--) - entry = entry.next; - } else { - for (entry of bucket.values()) { - if (index === 0) - break; - index--; + for (;;) { + const i = random(buckets.length); + const bucket = buckets[i]; + + if (bucket.size === 0) + continue; + + let index = random(bucket.size); + let entry; + + if (buckets === this.used) { + entry = bucket.head; + while (index--) + entry = entry.next; + } else { + for (entry of bucket.values()) { + if (index === 0) + break; + index -= 1; + } } - } - const num = util.random(0, 1 << 30); + const num = random(1 << 30); - if (num < factor * entry.chance(now) * (1 << 30)) - return entry; + if (num < factor * entry.chance(now) * (1 << 30)) + return entry; - factor *= 1.2; + factor *= 1.2; + } } -}; -/** - * Get fresh bucket for host. - * @private - * @param {HostEntry} entry - * @returns {Map} - */ + /** + * Get fresh bucket for host. + * @private + * @param {HostEntry} entry + * @returns {Map} + */ + + freshBucket(entry) { + const addr = entry.addr; + const src = entry.src; + const data = concat32(addr.raw, src.raw); + const hash = murmur3.sum(data, 0xfba4c795); + const index = hash % this.fresh.length; + return this.fresh[index]; + } -HostList.prototype.freshBucket = function freshBucket(entry) { - const addr = entry.addr; - const src = entry.src; - const data = concat32(addr.raw, src.raw); - const hash = murmur3(data, 0xfba4c795); - const index = hash % this.fresh.length; - return this.fresh[index]; -}; + /** + * Get used bucket for host. + * @private + * @param {HostEntry} entry + * @returns {List} + */ + + usedBucket(entry) { + const addr = entry.addr; + const hash = murmur3.sum(addr.raw, 0xfba4c795); + const index = hash % this.used.length; + return this.used[index]; + } -/** - * Get used bucket for host. - * @private - * @param {HostEntry} entry - * @returns {List} - */ + /** + * Add host to host list. + * @param {NetAddress} addr + * @param {NetAddress?} src + * @returns {Boolean} + */ -HostList.prototype.usedBucket = function usedBucket(entry) { - const addr = entry.addr; - const hash = murmur3(addr.raw, 0xfba4c795); - const index = hash % this.used.length; - return this.used[index]; -}; + add(addr, src) { + assert(addr.port !== 0); -/** - * Add host to host list. - * @param {NetAddress} addr - * @param {NetAddress?} src - * @returns {Boolean} - */ + let entry = this.map.get(addr.hostname); -HostList.prototype.add = function add(addr, src) { - assert(addr.port !== 0); + if (entry) { + let penalty = 2 * 60 * 60; + let interval = 24 * 60 * 60; - let entry = this.map.get(addr.hostname); + // No source means we're inserting + // this ourselves. No penalty. + if (!src) + penalty = 0; - if (entry) { - const now = this.network.now(); - let penalty = 2 * 60 * 60; - let interval = 24 * 60 * 60; - - // No source means we're inserting - // this ourselves. No penalty. - if (!src) - penalty = 0; + // Update services. + entry.addr.services |= addr.services; + entry.addr.services >>>= 0; - // Update services. - entry.addr.services |= addr.services; - entry.addr.services >>>= 0; + // Online? + const now = this.network.now(); + if (now - addr.time < 24 * 60 * 60) + interval = 60 * 60; - // Online? - if (now - addr.time < 24 * 60 * 60) - interval = 60 * 60; + // Periodically update time. + if (entry.addr.time < addr.time - interval - penalty) { + entry.addr.time = addr.time; + this.needsFlush = true; + } - // Periodically update time. - if (entry.addr.time < addr.time - interval - penalty) { - entry.addr.time = addr.time; - this.needsFlush = true; - } + // Do not update if no new + // information is present. + if (entry.addr.time && addr.time <= entry.addr.time) + return false; - // Do not update if no new - // information is present. - if (entry.addr.time && addr.time <= entry.addr.time) - return false; + // Do not update if the entry was + // already in the "used" table. + if (entry.used) + return false; - // Do not update if the entry was - // already in the "used" table. - if (entry.used) - return false; + assert(entry.refCount > 0); - assert(entry.refCount > 0); + // Do not update if the max + // reference count is reached. + if (entry.refCount === HostList.MAX_REFS) + return false; - // Do not update if the max - // reference count is reached. - if (entry.refCount === HostList.MAX_REFS) - return false; + assert(entry.refCount < HostList.MAX_REFS); - assert(entry.refCount < HostList.MAX_REFS); + // Stochastic test: previous refCount + // N: 2^N times harder to increase it. + let factor = 1; + for (let i = 0; i < entry.refCount; i++) + factor *= 2; - // Stochastic test: previous refCount - // N: 2^N times harder to increase it. - let factor = 1; - for (let i = 0; i < entry.refCount; i++) - factor *= 2; + if (random(factor) !== 0) + return false; + } else { + if (this.isFull()) + return false; - if (util.random(0, factor) !== 0) - return false; - } else { - if (this.isFull()) - return false; + if (!src) + src = this.address; - if (!src) - src = this.address; + entry = new HostEntry(addr, src); - entry = new HostEntry(addr, src); + this.totalFresh += 1; + } - this.totalFresh++; - } + const bucket = this.freshBucket(entry); - const bucket = this.freshBucket(entry); + if (bucket.has(entry.key())) + return false; - if (bucket.has(entry.key())) - return false; + if (bucket.size >= this.options.maxEntries) + this.evictFresh(bucket); - if (bucket.size >= this.options.maxEntries) - this.evictFresh(bucket); + bucket.set(entry.key(), entry); + entry.refCount += 1; - bucket.set(entry.key(), entry); - entry.refCount++; + this.map.set(entry.key(), entry); + this.needsFlush = true; - this.map.set(entry.key(), entry); - this.needsFlush = true; + return true; + } - return true; -}; + /** + * Evict a host from fresh bucket. + * @param {Map} bucket + */ -/** - * Evict a host from fresh bucket. - * @param {Map} bucket - */ + evictFresh(bucket) { + let old = null; -HostList.prototype.evictFresh = function evictFresh(bucket) { - let old = null; + for (const entry of bucket.values()) { + if (this.isStale(entry)) { + bucket.delete(entry.key()); - for (const entry of bucket.values()) { - if (this.isStale(entry)) { - bucket.delete(entry.key()); + if (--entry.refCount === 0) { + this.map.delete(entry.key()); + this.totalFresh -= 1; + } - if (--entry.refCount === 0) { - this.map.delete(entry.key()); - this.totalFresh--; + continue; } - continue; - } + if (!old) { + old = entry; + continue; + } - if (!old) { - old = entry; - continue; + if (entry.addr.time < old.addr.time) + old = entry; } - if (entry.addr.time < old.addr.time) - old = entry; - } - - if (!old) - return; + if (!old) + return; - bucket.delete(old.key()); + bucket.delete(old.key()); - if (--old.refCount === 0) { - this.map.delete(old.key()); - this.totalFresh--; + if (--old.refCount === 0) { + this.map.delete(old.key()); + this.totalFresh -= 1; + } } -}; - -/** - * Test whether a host is evictable. - * @param {HostEntry} entry - * @returns {Boolean} - */ -HostList.prototype.isStale = function isStale(entry) { - const now = this.network.now(); + /** + * Test whether a host is evictable. + * @param {HostEntry} entry + * @returns {Boolean} + */ - if (entry.lastAttempt && entry.lastAttempt >= now - 60) - return false; + isStale(entry) { + const now = this.network.now(); - if (entry.addr.time > now + 10 * 60) - return true; + if (entry.lastAttempt && entry.lastAttempt >= now - 60) + return false; - if (entry.addr.time === 0) - return true; + if (entry.addr.time > now + 10 * 60) + return true; - if (now - entry.addr.time > HostList.HORIZON_DAYS * 24 * 60 * 60) - return true; + if (entry.addr.time === 0) + return true; - if (entry.lastSuccess === 0 && entry.attempts >= HostList.RETRIES) - return true; + if (now - entry.addr.time > HostList.HORIZON_DAYS * 24 * 60 * 60) + return true; - if (now - entry.lastSuccess > HostList.MIN_FAIL_DAYS * 24 * 60 * 60) { - if (entry.attempts >= HostList.MAX_FAILURES) + if (entry.lastSuccess === 0 && entry.attempts >= HostList.RETRIES) return true; + + if (now - entry.lastSuccess > HostList.MIN_FAIL_DAYS * 24 * 60 * 60) { + if (entry.attempts >= HostList.MAX_FAILURES) + return true; + } + + return false; } - return false; -}; + /** + * Remove host from host list. + * @param {String} hostname + * @returns {NetAddress} + */ -/** - * Remove host from host list. - * @param {String} hostname - * @returns {NetAddress} - */ + remove(hostname) { + const entry = this.map.get(hostname); -HostList.prototype.remove = function remove(hostname) { - const entry = this.map.get(hostname); + if (!entry) + return null; - if (!entry) - return null; + if (entry.used) { + let head = entry; - if (entry.used) { - let head = entry; + assert(entry.refCount === 0); - assert(entry.refCount === 0); + while (head.prev) + head = head.prev; - while (head.prev) - head = head.prev; + for (const bucket of this.used) { + if (bucket.head === head) { + bucket.remove(entry); + this.totalUsed -= 1; + head = null; + break; + } + } - for (const bucket of this.used) { - if (bucket.head === head) { - bucket.remove(entry); - this.totalUsed--; - head = null; - break; + assert(!head); + } else { + for (const bucket of this.fresh) { + if (bucket.delete(entry.key())) + entry.refCount -= 1; } - } - assert(!head); - } else { - for (const bucket of this.fresh) { - if (bucket.delete(entry.key())) - entry.refCount--; + this.totalFresh -= 1; + assert(entry.refCount === 0); } - this.totalFresh--; - assert(entry.refCount === 0); + this.map.delete(entry.key()); + + return entry.addr; } - this.map.delete(entry.key()); + /** + * Mark host as failed. + * @param {String} hostname + */ - return entry.addr; -}; + markAttempt(hostname) { + const entry = this.map.get(hostname); + const now = this.network.now(); -/** - * Mark host as failed. - * @param {String} hostname - */ + if (!entry) + return; -HostList.prototype.markAttempt = function markAttempt(hostname) { - const entry = this.map.get(hostname); - const now = this.network.now(); + entry.attempts += 1; + entry.lastAttempt = now; + } - if (!entry) - return; + /** + * Mark host as successfully connected. + * @param {String} hostname + */ - entry.attempts++; - entry.lastAttempt = now; -}; + markSuccess(hostname) { + const entry = this.map.get(hostname); + const now = this.network.now(); -/** - * Mark host as successfully connected. - * @param {String} hostname - */ + if (!entry) + return; -HostList.prototype.markSuccess = function markSuccess(hostname) { - const entry = this.map.get(hostname); - const now = this.network.now(); + if (now - entry.addr.time > 20 * 60) + entry.addr.time = now; + } - if (!entry) - return; + /** + * Mark host as successfully ack'd. + * @param {String} hostname + * @param {Number} services + */ - if (now - entry.addr.time > 20 * 60) - entry.addr.time = now; -}; + markAck(hostname, services) { + const entry = this.map.get(hostname); -/** - * Mark host as successfully ack'd. - * @param {String} hostname - * @param {Number} services - */ + if (!entry) + return; -HostList.prototype.markAck = function markAck(hostname, services) { - const entry = this.map.get(hostname); + const now = this.network.now(); - if (!entry) - return; + entry.addr.services |= services; + entry.addr.services >>>= 0; - const now = this.network.now(); + entry.lastSuccess = now; + entry.lastAttempt = now; + entry.attempts = 0; - entry.addr.services |= services; - entry.addr.services >>>= 0; + if (entry.used) + return; - entry.lastSuccess = now; - entry.lastAttempt = now; - entry.attempts = 0; + assert(entry.refCount > 0); - if (entry.used) - return; + // Remove from fresh. + let old = null; + for (const bucket of this.fresh) { + if (bucket.delete(entry.key())) { + entry.refCount -= 1; + old = bucket; + } + } + + assert(old); + assert(entry.refCount === 0); + this.totalFresh -= 1; - assert(entry.refCount > 0); + // Find room in used bucket. + const bucket = this.usedBucket(entry); - // Remove from fresh. - let old; - for (const bucket of this.fresh) { - if (bucket.delete(entry.key())) { - entry.refCount--; - old = bucket; + if (bucket.size < this.options.maxEntries) { + entry.used = true; + bucket.push(entry); + this.totalUsed += 1; + return; } - } - assert(old); - assert(entry.refCount === 0); - this.totalFresh--; + // No room. Evict. + const evicted = this.evictUsed(bucket); - // Find room in used bucket. - const bucket = this.usedBucket(entry); + let fresh = this.freshBucket(evicted); - if (bucket.size < this.options.maxEntries) { + // Move to entry's old bucket if no room. + if (fresh.size >= this.options.maxEntries) + fresh = old; + + // Swap to evicted's used bucket. entry.used = true; - bucket.push(entry); - this.totalUsed++; - return; + bucket.replace(evicted, entry); + + // Move evicted to fresh bucket. + evicted.used = false; + fresh.set(evicted.key(), evicted); + assert(evicted.refCount === 0); + evicted.refCount += 1; + this.totalFresh += 1; } - // No room. Evict. - const evicted = this.evictUsed(bucket); - let fresh = this.freshBucket(evicted); - - // Move to entry's old bucket if no room. - if (fresh.size >= this.options.maxEntries) - fresh = old; + /** + * Pick used for eviction. + * @param {List} bucket + */ - // Swap to evicted's used bucket. - entry.used = true; - bucket.replace(evicted, entry); + evictUsed(bucket) { + let old = bucket.head; - // Move evicted to fresh bucket. - evicted.used = false; - fresh.set(evicted.key(), evicted); - assert(evicted.refCount === 0); - evicted.refCount++; - this.totalFresh++; -}; + for (let entry = bucket.head; entry; entry = entry.next) { + if (entry.addr.time < old.addr.time) + old = entry; + } -/** - * Pick used for eviction. - * @param {List} bucket - */ + return old; + } -HostList.prototype.evictUsed = function evictUsed(bucket) { - let old = bucket.head; + /** + * Convert address list to array. + * @returns {NetAddress[]} + */ - for (let entry = bucket.head; entry; entry = entry.next) { - if (entry.addr.time < old.addr.time) - old = entry; - } + toArray() { + const out = []; - return old; -}; + for (const entry of this.map.values()) + out.push(entry.addr); -/** - * Convert address list to array. - * @returns {NetAddress[]} - */ + assert.strictEqual(out.length, this.size()); -HostList.prototype.toArray = function toArray() { - const out = []; + return out; + } - for (const entry of this.map.values()) - out.push(entry.addr); + /** + * Add a preferred seed. + * @param {String} host + */ - assert.strictEqual(out.length, this.size()); + addSeed(host) { + const ip = IP.fromHostname(host, this.network.port); - return out; -}; + if (ip.type === IP.types.DNS) { + // Defer for resolution. + this.dnsSeeds.push(ip); + return null; + } -/** - * Add a preferred seed. - * @param {String} host - */ + const addr = NetAddress.fromHost(ip.host, ip.port, this.network); -HostList.prototype.addSeed = function addSeed(host) { - const ip = IP.fromHostname(host, this.network.port); + this.add(addr); - if (ip.type === IP.types.DNS) { - // Defer for resolution. - this.dnsSeeds.push(ip); - return null; + return addr; } - const addr = NetAddress.fromHost(ip.host, ip.port, this.network); + /** + * Add a priority node. + * @param {String} host + * @returns {NetAddress} + */ - this.add(addr); + addNode(host) { + const ip = IP.fromHostname(host, this.network.port); - return addr; -}; + if (ip.type === IP.types.DNS) { + // Defer for resolution. + this.dnsNodes.push(ip); + return null; + } -/** - * Add a priority node. - * @param {String} host - * @returns {NetAddress} - */ + const addr = NetAddress.fromHost(ip.host, ip.port, this.network); -HostList.prototype.addNode = function addNode(host) { - const ip = IP.fromHostname(host, this.network.port); + this.nodes.push(addr); + this.add(addr); - if (ip.type === IP.types.DNS) { - // Defer for resolution. - this.dnsNodes.push(ip); - return null; + return addr; } - const addr = NetAddress.fromHost(ip.host, ip.port, this.network); - - this.nodes.push(addr); - this.add(addr); - - return addr; -}; + /** + * Remove a priority node. + * @param {String} host + * @returns {Boolean} + */ -/** - * Remove a priority node. - * @param {String} host - * @returns {Boolean} - */ + removeNode(host) { + const addr = IP.fromHostname(host, this.network.port); -HostList.prototype.removeNode = function removeNode(host) { - const addr = IP.fromHostname(host, this.network.port); + for (let i = 0; i < this.nodes.length; i++) { + const node = this.nodes[i]; - for (let i = 0; i < this.nodes.length; i++) { - const node = this.nodes[i]; + if (node.host !== addr.host) + continue; - if (node.host !== addr.host) - continue; + if (node.port !== addr.port) + continue; - if (node.port !== addr.port) - continue; + this.nodes.splice(i, 1); - this.nodes.splice(i, 1); + return true; + } - return true; + return false; } - return false; -}; + /** + * Set initial seeds. + * @param {String[]} seeds + */ -/** - * Set initial seeds. - * @param {String[]} seeds - */ + setSeeds(seeds) { + this.dnsSeeds.length = 0; -HostList.prototype.setSeeds = function setSeeds(seeds) { - this.dnsSeeds.length = 0; + for (const host of seeds) + this.addSeed(host); + } - for (const host of seeds) - this.addSeed(host); -}; + /** + * Set priority nodes. + * @param {String[]} nodes + */ -/** - * Set priority nodes. - * @param {String[]} nodes - */ + setNodes(nodes) { + this.dnsNodes.length = 0; + this.nodes.length = 0; -HostList.prototype.setNodes = function setNodes(nodes) { - this.dnsNodes.length = 0; - this.nodes.length = 0; + for (const host of nodes) + this.addNode(host); + } - for (const host of nodes) - this.addNode(host); -}; + /** + * Add a local address. + * @param {String} host + * @param {Number} port + * @param {Number} score + * @returns {Boolean} + */ + + addLocal(host, port, score) { + const addr = NetAddress.fromHost(host, port, this.network); + addr.services = this.options.services; + return this.pushLocal(addr, score); + } -/** - * Add a local address. - * @param {String} host - * @param {Number} port - * @param {Number} score - * @returns {Boolean} - */ + /** + * Add a local address. + * @param {NetAddress} addr + * @param {Number} score + * @returns {Boolean} + */ -HostList.prototype.addLocal = function addLocal(host, port, score) { - const addr = NetAddress.fromHost(host, port, this.network); - addr.services = this.options.services; - return this.pushLocal(addr, score); -}; + pushLocal(addr, score) { + if (!addr.isRoutable()) + return false; -/** - * Add a local address. - * @param {NetAddress} addr - * @param {Number} score - * @returns {Boolean} - */ + if (this.local.has(addr.hostname)) + return false; -HostList.prototype.pushLocal = function pushLocal(addr, score) { - if (!addr.isRoutable()) - return false; + const local = new LocalAddress(addr, score); - if (this.local.has(addr.hostname)) - return false; + this.local.set(addr.hostname, local); - const local = new LocalAddress(addr, score); + return true; + } - this.local.set(addr.hostname, local); + /** + * Get local address based on reachability. + * @param {NetAddress?} src + * @returns {NetAddress} + */ - return true; -}; + getLocal(src) { + let bestReach = -1; + let bestScore = -1; + let bestDest = null; -/** - * Get local address based on reachability. - * @param {NetAddress?} src - * @returns {NetAddress} - */ + if (!src) + src = this.address; -HostList.prototype.getLocal = function getLocal(src) { - let bestReach = -1; - let bestScore = -1; - let bestDest = null; + if (this.local.size === 0) + return null; - if (!src) - src = this.address; + for (const dest of this.local.values()) { + const reach = src.getReachability(dest.addr); - if (this.local.size === 0) - return null; + if (reach < bestReach) + continue; - for (const dest of this.local.values()) { - const reach = src.getReachability(dest.addr); + if (reach > bestReach || dest.score > bestScore) { + bestReach = reach; + bestScore = dest.score; + bestDest = dest.addr; + } + } - if (reach < bestReach) - continue; + bestDest.time = this.network.now(); - if (reach > bestReach || dest.score > bestScore) { - bestReach = reach; - bestScore = dest.score; - bestDest = dest.addr; - } + return bestDest; } - bestDest.time = this.network.now(); + /** + * Mark local address as seen during a handshake. + * @param {NetAddress} addr + * @returns {Boolean} + */ - return bestDest; -}; + markLocal(addr) { + const local = this.local.get(addr.hostname); -/** - * Mark local address as seen during a handshake. - * @param {NetAddress} addr - * @returns {Boolean} - */ + if (!local) + return false; -HostList.prototype.markLocal = function markLocal(addr) { - const local = this.local.get(addr.hostname); + local.score += 1; - if (!local) - return false; + return true; + } - local.score++; + /** + * Discover hosts from seeds. + * @method + * @returns {Promise} + */ - return true; -}; + async discoverSeeds() { + const jobs = []; -/** - * Discover hosts from seeds. - * @method - * @returns {Promise} - */ + for (const seed of this.dnsSeeds) + jobs.push(this.populateSeed(seed)); -HostList.prototype.discoverSeeds = async function discoverSeeds() { - const jobs = []; + return Promise.all(jobs); + } - for (const seed of this.dnsSeeds) - jobs.push(this.populateSeed(seed)); + /** + * Discover hosts from nodes. + * @method + * @returns {Promise} + */ - await Promise.all(jobs); -}; + async discoverNodes() { + const jobs = []; -/** - * Discover hosts from nodes. - * @method - * @returns {Promise} - */ + for (const node of this.dnsNodes) + jobs.push(this.populateNode(node)); -HostList.prototype.discoverNodes = async function discoverNodes() { - const jobs = []; + return Promise.all(jobs); + } - for (const node of this.dnsNodes) - jobs.push(this.populateNode(node)); + /** + * Lookup node's domain. + * @method + * @param {Object} addr + * @returns {Promise} + */ - await Promise.all(jobs); -}; + async populateNode(addr) { + const addrs = await this.populate(addr); -/** - * Lookup node's domain. - * @method - * @param {Object} addr - * @returns {Promise} - */ + if (addrs.length === 0) + return; -HostList.prototype.populateNode = async function populateNode(addr) { - const addrs = await this.populate(addr); + this.nodes.push(addrs[0]); + this.add(addrs[0]); + } - if (addrs.length === 0) - return; + /** + * Populate from seed. + * @method + * @param {Object} seed + * @returns {Promise} + */ - this.nodes.push(addrs[0]); - this.add(addrs[0]); -}; + async populateSeed(seed) { + const addrs = await this.populate(seed); -/** - * Populate from seed. - * @method - * @param {Object} seed - * @returns {Promise} - */ + for (const addr of addrs) + this.add(addr); + } -HostList.prototype.populateSeed = async function populateSeed(seed) { - const addrs = await this.populate(seed); + /** + * Lookup hosts from dns host. + * @method + * @param {Object} target + * @returns {Promise} + */ - for (const addr of addrs) - this.add(addr); -}; + async populate(target) { + const addrs = []; -/** - * Lookup hosts from dns host. - * @method - * @param {Object} target - * @returns {Promise} - */ + assert(target.type === IP.types.DNS, 'Resolved host passed.'); -HostList.prototype.populate = async function populate(target) { - const addrs = []; + this.logger.info('Resolving host: %s.', target.host); - assert(target.type === IP.types.DNS, 'Resolved host passed.'); + let hosts; + try { + hosts = await this.resolve(target.host); + } catch (e) { + this.logger.error(e); + return addrs; + } - this.logger.info('Resolving host: %s.', target.host); + for (const host of hosts) { + const addr = NetAddress.fromHost(host, target.port, this.network); + addrs.push(addr); + } - let hosts; - try { - hosts = await this.resolve(target.host); - } catch (e) { - this.logger.error(e); return addrs; } - for (const host of hosts) { - const addr = NetAddress.fromHost(host, target.port, this.network); - addrs.push(addr); - } + /** + * Convert host list to json-friendly object. + * @returns {Object} + */ - return addrs; -}; + toJSON() { + const addrs = []; + const fresh = []; + const used = []; -/** - * Convert host list to json-friendly object. - * @returns {Object} - */ + for (const entry of this.map.values()) + addrs.push(entry.toJSON()); -HostList.prototype.toJSON = function toJSON() { - const addrs = []; - const fresh = []; - const used = []; + for (const bucket of this.fresh) { + const keys = []; + for (const key of bucket.keys()) + keys.push(key); + fresh.push(keys); + } - for (const entry of this.map.values()) - addrs.push(entry.toJSON()); + for (const bucket of this.used) { + const keys = []; + for (let entry = bucket.head; entry; entry = entry.next) + keys.push(entry.key()); + used.push(keys); + } - for (const bucket of this.fresh) { - const keys = []; - for (const key of bucket.keys()) - keys.push(key); - fresh.push(keys); + return { + version: HostList.VERSION, + network: this.network.type, + addrs: addrs, + fresh: fresh, + used: used + }; } - for (const bucket of this.used) { - const keys = []; - for (let entry = bucket.head; entry; entry = entry.next) - keys.push(entry.key()); - used.push(keys); - } + /** + * Inject properties from json object. + * @private + * @param {Object} json + * @returns {HostList} + */ - return { - version: HostList.VERSION, - addrs: addrs, - fresh: fresh, - used: used - }; -}; + fromJSON(json) { + const sources = new Map(); + const map = new Map(); + const fresh = []; + const used = []; -/** - * Inject properties from json object. - * @private - * @param {Object} json - * @returns {HostList} - */ + let totalFresh = 0; + let totalUsed = 0; + + assert(json && typeof json === 'object'); -HostList.prototype.fromJSON = function fromJSON(json) { - const sources = new Map(); - const map = new Map(); - let totalFresh = 0; - let totalUsed = 0; - const fresh = []; - const used = []; + assert(!json.network || json.network === this.network.type, + 'Network mistmatch.'); - assert(json && typeof json === 'object'); + assert(json.version === HostList.VERSION, + 'Bad address serialization version.'); - assert(json.version === HostList.VERSION, - 'Bad address serialization version.'); + assert(Array.isArray(json.addrs)); - assert(Array.isArray(json.addrs)); + for (const addr of json.addrs) { + const entry = HostEntry.fromJSON(addr, this.network); - for (const addr of json.addrs) { - const entry = HostEntry.fromJSON(addr, this.network); - let src = sources.get(entry.src.hostname); + let src = sources.get(entry.src.hostname); - // Save some memory. - if (!src) { - src = entry.src; - sources.set(src.hostname, src); + // Save some memory. + if (!src) { + src = entry.src; + sources.set(src.hostname, src); + } + + entry.src = src; + + map.set(entry.key(), entry); } - entry.src = src; + assert(Array.isArray(json.fresh)); + assert(json.fresh.length <= this.options.maxBuckets, + 'Buckets mismatch.'); - map.set(entry.key(), entry); - } + for (const keys of json.fresh) { + const bucket = new Map(); - assert(Array.isArray(json.fresh)); - assert(json.fresh.length <= this.options.maxBuckets, - 'Buckets mismatch.'); + for (const key of keys) { + const entry = map.get(key); + assert(entry); + if (entry.refCount === 0) + totalFresh += 1; + entry.refCount += 1; + bucket.set(key, entry); + } - for (const keys of json.fresh) { - const bucket = new Map(); + assert(bucket.size <= this.options.maxEntries, + 'Bucket size mismatch.'); - for (const key of keys) { - const entry = map.get(key); - assert(entry); - if (entry.refCount === 0) - totalFresh++; - entry.refCount++; - bucket.set(key, entry); + fresh.push(bucket); } - assert(bucket.size <= this.options.maxEntries, - 'Bucket size mismatch.'); + assert(fresh.length === this.fresh.length, + 'Buckets mismatch.'); - fresh.push(bucket); - } + assert(Array.isArray(json.used)); + assert(json.used.length <= this.options.maxBuckets, + 'Buckets mismatch.'); - assert(fresh.length === this.fresh.length, - 'Buckets mismatch.'); + for (const keys of json.used) { + const bucket = new List(); - assert(Array.isArray(json.used)); - assert(json.used.length <= this.options.maxBuckets, - 'Buckets mismatch.'); + for (const key of keys) { + const entry = map.get(key); + assert(entry); + assert(entry.refCount === 0); + assert(!entry.used); + entry.used = true; + totalUsed += 1; + bucket.push(entry); + } - for (const keys of json.used) { - const bucket = new List(); + assert(bucket.size <= this.options.maxEntries, + 'Bucket size mismatch.'); - for (const key of keys) { - const entry = map.get(key); - assert(entry); - assert(entry.refCount === 0); - assert(!entry.used); - entry.used = true; - totalUsed++; - bucket.push(entry); + used.push(bucket); } - assert(bucket.size <= this.options.maxEntries, - 'Bucket size mismatch.'); + assert(used.length === this.used.length, + 'Buckets mismatch.'); - used.push(bucket); - } + for (const entry of map.values()) + assert(entry.used || entry.refCount > 0); - assert(used.length === this.used.length, - 'Buckets mismatch.'); + this.map = map; + this.fresh = fresh; + this.totalFresh = totalFresh; + this.used = used; + this.totalUsed = totalUsed; - for (const entry of map.values()) - assert(entry.used || entry.refCount > 0); + return this; + } - this.map = map; - this.fresh = fresh; - this.totalFresh = totalFresh; - this.used = used; - this.totalUsed = totalUsed; + /** + * Instantiate host list from json object. + * @param {Object} options + * @param {Object} json + * @returns {HostList} + */ - return this; -}; + static fromJSON(options, json) { + return new this(options).fromJSON(json); + } +} /** - * Instantiate host list from json object. - * @param {Object} options - * @param {Object} json - * @returns {HostList} + * Number of days before considering + * an address stale. + * @const {Number} + * @default */ -HostList.fromJSON = function fromJSON(options, json) { - return new HostEntry(options).fromJSON(json); -}; +HostList.HORIZON_DAYS = 30; /** - * HostEntry - * @alias module:net.HostEntry - * @constructor - * @param {NetAddress} addr - * @param {NetAddress} src + * Number of retries (without success) + * before considering an address stale. + * @const {Number} + * @default */ -function HostEntry(addr, src) { - if (!(this instanceof HostEntry)) - return new HostEntry(addr, src); - - this.addr = addr || new NetAddress(); - this.src = src || new NetAddress(); - this.prev = null; - this.next = null; - this.used = false; - this.refCount = 0; - this.attempts = 0; - this.lastSuccess = 0; - this.lastAttempt = 0; - - if (addr) - this.fromOptions(addr, src); -} +HostList.RETRIES = 3; /** - * Inject properties from options. - * @private - * @param {NetAddress} addr - * @param {NetAddress} src - * @returns {HostEntry} + * Number of days after reaching + * MAX_FAILURES to consider an + * address stale. + * @const {Number} + * @default */ -HostEntry.prototype.fromOptions = function fromOptions(addr, src) { - assert(addr instanceof NetAddress); - assert(src instanceof NetAddress); - this.addr = addr; - this.src = src; - return this; -}; +HostList.MIN_FAIL_DAYS = 7; /** - * Instantiate host entry from options. - * @param {NetAddress} addr - * @param {NetAddress} src - * @returns {HostEntry} + * Maximum number of failures + * allowed before considering + * an address stale. + * @const {Number} + * @default */ -HostEntry.fromOptions = function fromOptions(addr, src) { - return new HostEntry().fromOptions(addr, src); -}; +HostList.MAX_FAILURES = 10; /** - * Get key suitable for a hash table (hostname). - * @returns {String} + * Maximum number of references + * in fresh buckets. + * @const {Number} + * @default */ -HostEntry.prototype.key = function key() { - return this.addr.hostname; -}; +HostList.MAX_REFS = 8; /** - * Get host priority. - * @param {Number} now - * @returns {Number} + * Serialization version. + * @const {Number} + * @default */ -HostEntry.prototype.chance = function chance(now) { - let c = 1; - - if (now - this.lastAttempt < 60 * 10) - c *= 0.01; - - c *= Math.pow(0.66, Math.min(this.attempts, 8)); - - return c; -}; +HostList.VERSION = 0; /** - * Inspect host address. - * @returns {Object} + * Local address scores. + * @enum {Number} + * @default */ -HostEntry.prototype.inspect = function inspect() { - return { - addr: this.addr, - src: this.src, - used: this.used, - refCount: this.refCount, - attempts: this.attempts, - lastSuccess: util.date(this.lastSuccess), - lastAttempt: util.date(this.lastAttempt) - }; +HostList.scores = { + NONE: 0, + IF: 1, + BIND: 2, + UPNP: 3, + DNS: 3, + MANUAL: 4, + MAX: 5 }; /** - * Convert host entry to json-friendly object. - * @returns {Object} + * Host Entry + * @alias module:net.HostEntry */ -HostEntry.prototype.toJSON = function toJSON() { - return { - addr: this.addr.hostname, - src: this.src.hostname, - services: this.addr.services.toString(2), - time: this.addr.time, - attempts: this.attempts, - lastSuccess: this.lastSuccess, - lastAttempt: this.lastAttempt - }; -}; - -/** - * Inject properties from json object. - * @private - * @param {Object} json - * @param {Network} network - * @returns {HostEntry} - */ +class HostEntry { + /** + * Create a host entry. + * @constructor + * @param {NetAddress} addr + * @param {NetAddress} src + */ + + constructor(addr, src) { + this.addr = addr || new NetAddress(); + this.src = src || new NetAddress(); + this.prev = null; + this.next = null; + this.used = false; + this.refCount = 0; + this.attempts = 0; + this.lastSuccess = 0; + this.lastAttempt = 0; + + if (addr) + this.fromOptions(addr, src); + } -HostEntry.prototype.fromJSON = function fromJSON(json, network) { - assert(json && typeof json === 'object'); - assert(typeof json.addr === 'string'); - assert(typeof json.src === 'string'); + /** + * Inject properties from options. + * @private + * @param {NetAddress} addr + * @param {NetAddress} src + * @returns {HostEntry} + */ + + fromOptions(addr, src) { + assert(addr instanceof NetAddress); + assert(src instanceof NetAddress); + this.addr = addr; + this.src = src; + return this; + } - this.addr.fromHostname(json.addr, network); + /** + * Instantiate host entry from options. + * @param {NetAddress} addr + * @param {NetAddress} src + * @returns {HostEntry} + */ - if (json.services != null) { - assert(typeof json.services === 'string'); - assert(json.services.length > 0); - assert(json.services.length <= 32); - const services = parseInt(json.services, 2); - assert(util.isU32(services)); - this.addr.services = services; + static fromOptions(addr, src) { + return new this().fromOptions(addr, src); } - if (json.time != null) { - assert(util.isU64(json.time)); - this.addr.time = json.time; - } + /** + * Get key suitable for a hash table (hostname). + * @returns {String} + */ - if (json.src != null) { - assert(typeof json.src === 'string'); - this.src.fromHostname(json.src, network); + key() { + return this.addr.hostname; } - if (json.attempts != null) { - assert(util.isU64(json.attempts)); - this.attempts = json.attempts; + /** + * Get host priority. + * @param {Number} now + * @returns {Number} + */ + + chance(now) { + let c = 1; + + if (now - this.lastAttempt < 60 * 10) + c *= 0.01; + + c *= Math.pow(0.66, Math.min(this.attempts, 8)); + + return c; } - if (json.lastSuccess != null) { - assert(util.isU64(json.lastSuccess)); - this.lastSuccess = json.lastSuccess; + /** + * Inspect host address. + * @returns {Object} + */ + + [inspectSymbol]() { + return { + addr: this.addr, + src: this.src, + used: this.used, + refCount: this.refCount, + attempts: this.attempts, + lastSuccess: util.date(this.lastSuccess), + lastAttempt: util.date(this.lastAttempt) + }; } - if (json.lastAttempt != null) { - assert(util.isU64(json.lastAttempt)); - this.lastAttempt = json.lastAttempt; + /** + * Convert host entry to json-friendly object. + * @returns {Object} + */ + + toJSON() { + return { + addr: this.addr.hostname, + src: this.src.hostname, + services: this.addr.services.toString(2), + time: this.addr.time, + attempts: this.attempts, + lastSuccess: this.lastSuccess, + lastAttempt: this.lastAttempt + }; } - return this; -}; + /** + * Inject properties from json object. + * @private + * @param {Object} json + * @param {Network} network + * @returns {HostEntry} + */ + + fromJSON(json, network) { + assert(json && typeof json === 'object'); + assert(typeof json.addr === 'string'); + assert(typeof json.src === 'string'); -/** - * Instantiate host entry from json object. - * @param {Object} json - * @param {Network} network - * @returns {HostEntry} - */ + this.addr.fromHostname(json.addr, network); -HostEntry.fromJSON = function fromJSON(json, network) { - return new HostEntry().fromJSON(json, network); -}; + if (json.services != null) { + assert(typeof json.services === 'string'); + assert(json.services.length > 0); + assert(json.services.length <= 32); + const services = parseInt(json.services, 2); + assert((services >>> 0) === services); + this.addr.services = services; + } + + if (json.time != null) { + assert(Number.isSafeInteger(json.time)); + assert(json.time >= 0); + this.addr.time = json.time; + } + + if (json.src != null) { + assert(typeof json.src === 'string'); + this.src.fromHostname(json.src, network); + } + + if (json.attempts != null) { + assert((json.attempts >>> 0) === json.attempts); + this.attempts = json.attempts; + } + + if (json.lastSuccess != null) { + assert(Number.isSafeInteger(json.lastSuccess)); + assert(json.lastSuccess >= 0); + this.lastSuccess = json.lastSuccess; + } + + if (json.lastAttempt != null) { + assert(Number.isSafeInteger(json.lastAttempt)); + assert(json.lastAttempt >= 0); + this.lastAttempt = json.lastAttempt; + } + + return this; + } + + /** + * Instantiate host entry from json object. + * @param {Object} json + * @param {Network} network + * @returns {HostEntry} + */ + + static fromJSON(json, network) { + return new this().fromJSON(json, network); + } +} /** - * LocalAddress + * Local Address * @alias module:net.LocalAddress - * @constructor - * @param {NetAddress} addr - * @param {Number?} score */ -function LocalAddress(addr, score) { - this.addr = addr; - this.score = score || 0; +class LocalAddress { + /** + * Create a local address. + * @constructor + * @param {NetAddress} addr + * @param {Number?} score + */ + + constructor(addr, score) { + this.addr = addr; + this.score = score || 0; + } } /** * Host List Options * @alias module:net.HostListOptions - * @constructor - * @param {Object?} options */ -function HostListOptions(options) { - if (!(this instanceof HostListOptions)) - return new HostListOptions(options); +class HostListOptions { + /** + * Create host list options. + * @constructor + * @param {Object?} options + */ - this.network = Network.primary; - this.logger = Logger.global; - this.resolve = dns.lookup; - this.host = '0.0.0.0'; - this.port = this.network.port; - this.services = common.LOCAL_SERVICES; - this.onion = false; - this.banTime = common.BAN_TIME; + constructor(options) { + this.network = Network.primary; + this.logger = Logger.global; + this.resolve = dns.lookup; + this.host = '0.0.0.0'; + this.port = this.network.port; + this.services = common.LOCAL_SERVICES; + this.onion = false; + this.banTime = common.BAN_TIME; - this.address = new NetAddress(); - this.address.services = this.services; - this.address.time = this.network.now(); + this.address = new NetAddress(); + this.address.services = this.services; + this.address.time = this.network.now(); - this.seeds = this.network.seeds; - this.nodes = []; + this.seeds = this.network.seeds; + this.nodes = []; - this.maxBuckets = 20; - this.maxEntries = 50; + this.maxBuckets = 20; + this.maxEntries = 50; - this.prefix = null; - this.filename = null; - this.persistent = false; - this.flushInterval = 120000; + this.prefix = null; + this.filename = null; + this.memory = true; + this.flushInterval = 120000; - if (options) - this.fromOptions(options); -} + if (options) + this.fromOptions(options); + } -/** - * Inject properties from options. - * @private - * @param {Object} options - */ + /** + * Inject properties from options. + * @private + * @param {Object} options + */ -HostListOptions.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Options are required.'); + fromOptions(options) { + assert(options, 'Options are required.'); - if (options.network != null) { - this.network = Network.get(options.network); - this.seeds = this.network.seeds; - this.address.port = this.network.port; - this.port = this.network.port; - } + if (options.network != null) { + this.network = Network.get(options.network); + this.seeds = this.network.seeds; + this.address.port = this.network.port; + this.port = this.network.port; + } - if (options.logger != null) { - assert(typeof options.logger === 'object'); - this.logger = options.logger; - } + if (options.logger != null) { + assert(typeof options.logger === 'object'); + this.logger = options.logger; + } - if (options.resolve != null) { - assert(typeof options.resolve === 'function'); - this.resolve = options.resolve; - } + if (options.resolve != null) { + assert(typeof options.resolve === 'function'); + this.resolve = options.resolve; + } - if (options.banTime != null) { - assert(options.banTime >= 0); - this.banTime = options.banTime; - } + if (options.banTime != null) { + assert(options.banTime >= 0); + this.banTime = options.banTime; + } - if (options.seeds) { - assert(Array.isArray(options.seeds)); - this.seeds = options.seeds; - } + if (options.seeds) { + assert(Array.isArray(options.seeds)); + this.seeds = options.seeds; + } - if (options.nodes) { - assert(Array.isArray(options.nodes)); - this.nodes = options.nodes; - } + if (options.nodes) { + assert(Array.isArray(options.nodes)); + this.nodes = options.nodes; + } - if (options.host != null) { - assert(typeof options.host === 'string'); - const raw = IP.toBuffer(options.host); - this.host = IP.toString(raw); - if (IP.isRoutable(raw)) - this.address.setHost(this.host); - } + if (options.host != null) { + assert(typeof options.host === 'string'); + const raw = IP.toBuffer(options.host); + this.host = IP.toString(raw); + if (IP.isRoutable(raw)) + this.address.setHost(this.host); + } - if (options.port != null) { - assert(typeof options.port === 'number'); - assert(options.port > 0 && options.port <= 0xffff); - this.port = options.port; - this.address.setPort(this.port); - } + if (options.port != null) { + assert(typeof options.port === 'number'); + assert(options.port > 0 && options.port <= 0xffff); + this.port = options.port; + this.address.setPort(this.port); + } - if (options.publicHost != null) { - assert(typeof options.publicHost === 'string'); - this.address.setHost(options.publicHost); - } + if (options.publicHost != null) { + assert(typeof options.publicHost === 'string'); + this.address.setHost(options.publicHost); + } - if (options.publicPort != null) { - assert(typeof options.publicPort === 'number'); - assert(options.publicPort > 0 && options.publicPort <= 0xffff); - this.address.setPort(options.publicPort); - } + if (options.publicPort != null) { + assert(typeof options.publicPort === 'number'); + assert(options.publicPort > 0 && options.publicPort <= 0xffff); + this.address.setPort(options.publicPort); + } - if (options.services != null) { - assert(typeof options.services === 'number'); - this.services = options.services; - } + if (options.services != null) { + assert(typeof options.services === 'number'); + this.services = options.services; + } - if (options.onion != null) { - assert(typeof options.onion === 'boolean'); - this.onion = options.onion; - } + if (options.onion != null) { + assert(typeof options.onion === 'boolean'); + this.onion = options.onion; + } - if (options.maxBuckets != null) { - assert(typeof options.maxBuckets === 'number'); - this.maxBuckets = options.maxBuckets; - } + if (options.maxBuckets != null) { + assert(typeof options.maxBuckets === 'number'); + this.maxBuckets = options.maxBuckets; + } - if (options.maxEntries != null) { - assert(typeof options.maxEntries === 'number'); - this.maxEntries = options.maxEntries; - } + if (options.maxEntries != null) { + assert(typeof options.maxEntries === 'number'); + this.maxEntries = options.maxEntries; + } - if (options.persistent != null) { - assert(typeof options.persistent === 'boolean'); - this.persistent = options.persistent; - } + if (options.memory != null) { + assert(typeof options.memory === 'boolean'); + this.memory = options.memory; + } - if (options.prefix != null) { - assert(typeof options.prefix === 'string'); - this.prefix = options.prefix; - this.filename = path.join(this.prefix, 'hosts.json'); - } + if (options.prefix != null) { + assert(typeof options.prefix === 'string'); + this.prefix = options.prefix; + this.filename = path.join(this.prefix, 'hosts.json'); + } - if (options.filename != null) { - assert(typeof options.filename === 'string'); - this.filename = options.filename; - } + if (options.filename != null) { + assert(typeof options.filename === 'string'); + this.filename = options.filename; + } - if (options.flushInterval != null) { - assert(options.flushInterval >= 0); - this.flushInterval = options.flushInterval; - } + if (options.flushInterval != null) { + assert(options.flushInterval >= 0); + this.flushInterval = options.flushInterval; + } - this.address.time = this.network.now(); - this.address.services = this.services; + this.address.time = this.network.now(); + this.address.services = this.services; - return this; -}; + return this; + } +} /* * Helpers @@ -1594,6 +1634,10 @@ function concat32(left, right) { return data; } +function random(max) { + return Math.floor(Math.random() * max); +} + /* * Expose */ diff --git a/lib/net/index.js b/lib/net/index.js index 1396c6e76..26ad57102 100644 --- a/lib/net/index.js +++ b/lib/net/index.js @@ -10,18 +10,12 @@ * @module net */ -exports.BIP150 = require('./bip150'); -exports.BIP151 = require('./bip151'); exports.bip152 = require('./bip152'); exports.common = require('./common'); -exports.dns = require('./dns'); -exports.external = require('./external'); exports.Framer = require('./framer'); exports.HostList = require('./hostlist'); +exports.NetAddress = require('./netaddress'); exports.packets = require('./packets'); exports.Parser = require('./parser'); exports.Peer = require('./peer'); exports.Pool = require('./pool'); -exports.socks = require('./socks'); -exports.tcp = require('./tcp'); -exports.UPNP = require('./upnp'); diff --git a/lib/net/netaddress.js b/lib/net/netaddress.js new file mode 100644 index 000000000..efcf4a4a7 --- /dev/null +++ b/lib/net/netaddress.js @@ -0,0 +1,485 @@ +/*! + * netaddress.js - network address object for bcoin + * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const assert = require('bsert'); +const bio = require('bufio'); +const IP = require('binet'); +const Network = require('../protocol/network'); +const util = require('../utils/util'); +const common = require('./common'); +const {inspectSymbol} = require('../utils'); + +/** + * Net Address + * Represents a network address. + * @alias module:net.NetAddress + * @property {Host} host + * @property {Number} port + * @property {Number} services + * @property {Number} time + */ + +class NetAddress { + /** + * Create a network address. + * @constructor + * @param {Object} options + * @param {Number?} options.time - Timestamp. + * @param {Number?} options.services - Service bits. + * @param {String?} options.host - IP address (IPv6 or IPv4). + * @param {Number?} options.port - Port. + */ + + constructor(options) { + this.host = '0.0.0.0'; + this.port = 0; + this.services = 0; + this.time = 0; + this.hostname = '0.0.0.0:0'; + this.raw = IP.ZERO_IP; + + if (options) + this.fromOptions(options); + } + + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + assert(typeof options.host === 'string'); + assert(typeof options.port === 'number'); + + this.raw = IP.toBuffer(options.host); + this.host = IP.toString(this.raw); + this.port = options.port; + + if (options.services) { + assert(typeof options.services === 'number'); + this.services = options.services; + } + + if (options.time) { + assert(typeof options.time === 'number'); + this.time = options.time; + } + + this.hostname = IP.toHostname(this.host, this.port); + + return this; + } + + /** + * Instantiate network address from options. + * @param {Object} options + * @returns {NetAddress} + */ + + static fromOptions(options) { + return new this().fromOptions(options); + } + + /** + * Test whether required services are available. + * @param {Number} services + * @returns {Boolean} + */ + + hasServices(services) { + return (this.services & services) === services; + } + + /** + * Test whether the address is IPv4. + * @returns {Boolean} + */ + + isIPv4() { + return IP.isIPv4(this.raw); + } + + /** + * Test whether the address is IPv6. + * @returns {Boolean} + */ + + isIPv6() { + return IP.isIPv6(this.raw); + } + + /** + * Test whether the host is null. + * @returns {Boolean} + */ + + isNull() { + return IP.isNull(this.raw); + } + + /** + * Test whether the host is a local address. + * @returns {Boolean} + */ + + isLocal() { + return IP.isLocal(this.raw); + } + + /** + * Test whether the host is valid. + * @returns {Boolean} + */ + + isValid() { + return IP.isValid(this.raw); + } + + /** + * Test whether the host is routable. + * @returns {Boolean} + */ + + isRoutable() { + return IP.isRoutable(this.raw); + } + + /** + * Test whether the host is an onion address. + * @returns {Boolean} + */ + + isOnion() { + return IP.isOnion(this.raw); + } + + /** + * Compare against another network address. + * @returns {Boolean} + */ + + equal(addr) { + return this.compare(addr) === 0; + } + + /** + * Compare against another network address. + * @returns {Number} + */ + + compare(addr) { + const cmp = this.raw.compare(addr.raw); + + if (cmp !== 0) + return cmp; + + return this.port - addr.port; + } + + /** + * Get reachable score to destination. + * @param {NetAddress} dest + * @returns {Number} + */ + + getReachability(dest) { + return IP.getReachability(this.raw, dest.raw); + } + + /** + * Set null host. + */ + + setNull() { + this.raw = IP.ZERO_IP; + this.host = '0.0.0.0'; + this.hostname = IP.toHostname(this.host, this.port); + } + + /** + * Set host. + * @param {String} host + */ + + setHost(host) { + this.raw = IP.toBuffer(host); + this.host = IP.toString(this.raw); + this.hostname = IP.toHostname(this.host, this.port); + } + + /** + * Set port. + * @param {Number} port + */ + + setPort(port) { + assert(port >= 0 && port <= 0xffff); + this.port = port; + this.hostname = IP.toHostname(this.host, port); + } + + /** + * Inject properties from host, port, and network. + * @private + * @param {String} host + * @param {Number} port + * @param {(Network|NetworkType)?} network + */ + + fromHost(host, port, network) { + network = Network.get(network); + + assert(port >= 0 && port <= 0xffff); + + this.raw = IP.toBuffer(host); + this.host = IP.toString(this.raw); + this.port = port; + this.services = NetAddress.DEFAULT_SERVICES; + this.time = network.now(); + + this.hostname = IP.toHostname(this.host, this.port); + + return this; + } + + /** + * Instantiate a network address + * from a host and port. + * @param {String} host + * @param {Number} port + * @param {(Network|NetworkType)?} network + * @returns {NetAddress} + */ + + static fromHost(host, port, network) { + return new this().fromHost(host, port, network); + } + + /** + * Inject properties from hostname and network. + * @private + * @param {String} hostname + * @param {(Network|NetworkType)?} network + */ + + fromHostname(hostname, network) { + network = Network.get(network); + + const addr = IP.fromHostname(hostname, network.port); + + return this.fromHost(addr.host, addr.port, network); + } + + /** + * Instantiate a network address + * from a hostname (i.e. 127.0.0.1:8333). + * @param {String} hostname + * @param {(Network|NetworkType)?} network + * @returns {NetAddress} + */ + + static fromHostname(hostname, network) { + return new this().fromHostname(hostname, network); + } + + /** + * Inject properties from socket. + * @private + * @param {net.Socket} socket + */ + + fromSocket(socket, network) { + const host = socket.remoteAddress; + const port = socket.remotePort; + assert(typeof host === 'string'); + assert(typeof port === 'number'); + return this.fromHost(IP.normalize(host), port, network); + } + + /** + * Instantiate a network address + * from a socket. + * @param {net.Socket} socket + * @returns {NetAddress} + */ + + static fromSocket(hostname, network) { + return new this().fromSocket(hostname, network); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + * @param {Boolean?} full - Include timestamp. + */ + + fromReader(br, full) { + this.time = full ? br.readU32() : 0; + this.services = br.readU32(); + + // Note: hi service bits + // are currently unused. + br.readU32(); + + this.raw = br.readBytes(16); + this.host = IP.toString(this.raw); + this.port = br.readU16BE(); + this.hostname = IP.toHostname(this.host, this.port); + + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + * @param {Boolean?} full - Include timestamp. + */ + + fromRaw(data, full) { + return this.fromReader(bio.read(data), full); + } + + /** + * Insantiate a network address from buffer reader. + * @param {BufferReader} br + * @param {Boolean?} full - Include timestamp. + * @returns {NetAddress} + */ + + static fromReader(br, full) { + return new this().fromReader(br, full); + } + + /** + * Insantiate a network address from serialized data. + * @param {Buffer} data + * @param {Boolean?} full - Include timestamp. + * @returns {NetAddress} + */ + + static fromRaw(data, full) { + return new this().fromRaw(data, full); + } + + /** + * Write network address to a buffer writer. + * @param {BufferWriter} bw + * @param {Boolean?} full - Include timestamp. + * @returns {Buffer} + */ + + toWriter(bw, full) { + if (full) + bw.writeU32(this.time); + + bw.writeU32(this.services); + bw.writeU32(0); + bw.writeBytes(this.raw); + bw.writeU16BE(this.port); + + return bw; + } + + /** + * Calculate serialization size of address. + * @returns {Number} + */ + + getSize(full) { + return 26 + (full ? 4 : 0); + } + + /** + * Serialize network address. + * @param {Boolean?} full - Include timestamp. + * @returns {Buffer} + */ + + toRaw(full) { + const size = this.getSize(full); + return this.toWriter(bio.write(size), full).render(); + } + + /** + * Convert net address to json-friendly object. + * @returns {Object} + */ + + toJSON() { + return { + host: this.host, + port: this.port, + services: this.services, + time: this.time + }; + } + + /** + * Inject properties from json object. + * @private + * @param {Object} json + * @returns {NetAddress} + */ + + fromJSON(json) { + assert((json.port & 0xffff) === json.port); + assert((json.services >>> 0) === json.services); + assert((json.time >>> 0) === json.time); + this.raw = IP.toBuffer(json.host); + this.host = json.host; + this.port = json.port; + this.services = json.services; + this.time = json.time; + this.hostname = IP.toHostname(this.host, this.port); + return this; + } + + /** + * Instantiate net address from json object. + * @param {Object} json + * @returns {NetAddress} + */ + + static fromJSON(json) { + return new this().fromJSON(json); + } + + /** + * Inspect the network address. + * @returns {Object} + */ + + [inspectSymbol]() { + return ''; + } +} + +/** + * Default services for + * unknown outbound peers. + * @const {Number} + * @default + */ + +NetAddress.DEFAULT_SERVICES = 0 + | common.services.NETWORK + | common.services.WITNESS + | common.services.BLOOM; + +/* + * Expose + */ + +module.exports = NetAddress; diff --git a/lib/net/packets.js b/lib/net/packets.js index efb7ffbdd..fd77c3a6f 100644 --- a/lib/net/packets.js +++ b/lib/net/packets.js @@ -11,21 +11,22 @@ * @module net/packets */ +const assert = require('bsert'); +const bio = require('bufio'); +const {BloomFilter} = require('bfilter'); const common = require('./common'); const util = require('../utils/util'); -const assert = require('assert'); -const Bloom = require('../utils/bloom'); const bip152 = require('./bip152'); -const NetAddress = require('../primitives/netaddress'); +const NetAddress = require('./netaddress'); +const consensus = require('../protocol/consensus'); const Headers = require('../primitives/headers'); const InvItem = require('../primitives/invitem'); const MemBlock = require('../primitives/memblock'); const MerkleBlock = require('../primitives/merkleblock'); const TX = require('../primitives/tx'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); -const encoding = require('../utils/encoding'); +const {encoding} = bio; const DUMMY = Buffer.alloc(0); +const {inspectSymbol} = require('../utils'); /** * Packet types. @@ -60,15 +61,10 @@ exports.types = { CMPCTBLOCK: 23, GETBLOCKTXN: 24, BLOCKTXN: 25, - ENCINIT: 26, - ENCACK: 27, - AUTHCHALLENGE: 28, - AUTHREPLY: 29, - AUTHPROPOSE: 30, - UNKNOWN: 31, + UNKNOWN: 26, // Internal - INTERNAL: 100, - DATA: 101 + INTERNAL: 27, + DATA: 28 }; /** @@ -77,77 +73,103 @@ exports.types = { * @default */ -exports.typesByVal = util.reverse(exports.types); +exports.typesByVal = [ + 'VERSION', + 'VERACK', + 'PING', + 'PONG', + 'GETADDR', + 'ADDR', + 'INV', + 'GETDATA', + 'NOTFOUND', + 'GETBLOCKS', + 'GETHEADERS', + 'HEADERS', + 'SENDHEADERS', + 'BLOCK', + 'TX', + 'REJECT', + 'MEMPOOL', + 'FILTERLOAD', + 'FILTERADD', + 'FILTERCLEAR', + 'MERKLEBLOCK', + 'FEEFILTER', + 'SENDCMPCT', + 'CMPCTBLOCK', + 'GETBLOCKTXN', + 'BLOCKTXN', + 'UNKNOWN', + // Internal + 'INTERNAL', + 'DATA' +]; /** * Base Packet - * @constructor */ -function Packet() {} +class Packet { + /** + * Create a base packet. + * @constructor + */ -Packet.prototype.type = -1; -Packet.prototype.cmd = ''; + constructor() { + this.type = -1; + this.cmd = ''; + } -/** - * Get serialization size. - * @returns {Number} - */ + /** + * Get serialization size. + * @returns {Number} + */ -Packet.prototype.getSize = function getSize() { - return 0; -}; + getSize() { + return 0; + } -/** - * Serialize packet to writer. - * @param {BufferWriter} bw - */ + /** + * Serialize packet to writer. + * @param {BufferWriter} bw + */ -Packet.prototype.toWriter = function toWriter(bw) { - return bw; -}; + toWriter(bw) { + return bw; + } -/** - * Serialize packet. - * @returns {Buffer} - */ + /** + * Serialize packet. + * @returns {Buffer} + */ -Packet.prototype.toRaw = function toRaw() { - return DUMMY; -}; + toRaw() { + return DUMMY; + } -/** - * Inject properties from buffer reader. - * @param {BufferReader} br - */ + /** + * Inject properties from buffer reader. + * @param {BufferReader} br + */ -Packet.prototype.fromReader = function fromReader(br) { - return this; -}; + fromReader(br) { + return this; + } -/** - * Inject properties from serialized data. - * @param {Buffer} data - */ + /** + * Inject properties from serialized data. + * @param {Buffer} data + */ -Packet.prototype.fromRaw = function fromRaw(data) { - return this; -}; + fromRaw(data) { + return this; + } +} /** * Version Packet - * @constructor - * @param {Object?} options - * @param {Number} options.version - Protocol version. - * @param {Number} options.services - Service bits. - * @param {Number} options.time - Timestamp of discovery. - * @param {NetAddress} options.local - Our address. - * @param {NetAddress} options.remote - Their address. - * @param {Buffer} options.nonce - * @param {String} options.agent - User agent string. - * @param {Number} options.height - Chain height. - * @param {Boolean} options.noRelay - Whether transactions - * should be relayed immediately. + * @extends Packet * @property {Number} version - Protocol version. * @property {Number} services - Service bits. * @property {Number} time - Timestamp of discovery. @@ -160,1289 +182,1566 @@ Packet.prototype.fromRaw = function fromRaw(data) { * should be relayed immediately. */ -function VersionPacket(options) { - if (!(this instanceof VersionPacket)) - return new VersionPacket(options); - - Packet.call(this); +class VersionPacket extends Packet { + /** + * Create a version packet. + * @constructor + * @param {Object?} options + * @param {Number} options.version - Protocol version. + * @param {Number} options.services - Service bits. + * @param {Number} options.time - Timestamp of discovery. + * @param {NetAddress} options.local - Our address. + * @param {NetAddress} options.remote - Their address. + * @param {Buffer} options.nonce + * @param {String} options.agent - User agent string. + * @param {Number} options.height - Chain height. + * @param {Boolean} options.noRelay - Whether transactions + * should be relayed immediately. + */ + + constructor(options) { + super(); + + this.cmd = 'version'; + this.type = exports.types.VERSION; + + this.version = common.PROTOCOL_VERSION; + this.services = common.LOCAL_SERVICES; + this.time = util.now(); + this.remote = new NetAddress(); + this.local = new NetAddress(); + this.nonce = common.ZERO_NONCE; + this.agent = common.USER_AGENT; + this.height = 0; + this.noRelay = false; - this.version = common.PROTOCOL_VERSION; - this.services = common.LOCAL_SERVICES; - this.time = util.now(); - this.remote = new NetAddress(); - this.local = new NetAddress(); - this.nonce = encoding.ZERO_U64; - this.agent = common.USER_AGENT; - this.height = 0; - this.noRelay = false; + if (options) + this.fromOptions(options); + } - if (options) - this.fromOptions(options); -} + /** + * Inject properties from options. + * @private + * @param {Object} options + */ -Object.setPrototypeOf(VersionPacket.prototype, Packet.prototype); + fromOptions(options) { + if (options.version != null) + this.version = options.version; -VersionPacket.prototype.cmd = 'version'; -VersionPacket.prototype.type = exports.types.VERSION; + if (options.services != null) + this.services = options.services; -/** - * Inject properties from options. - * @private - * @param {Object} options - */ + if (options.time != null) + this.time = options.time; -VersionPacket.prototype.fromOptions = function fromOptions(options) { - if (options.version != null) - this.version = options.version; + if (options.remote) + this.remote.fromOptions(options.remote); - if (options.services != null) - this.services = options.services; + if (options.local) + this.local.fromOptions(options.local); - if (options.time != null) - this.time = options.time; + if (options.nonce) + this.nonce = options.nonce; - if (options.remote) - this.remote.fromOptions(options.remote); + if (options.agent) + this.agent = options.agent; - if (options.local) - this.local.fromOptions(options.local); + if (options.height != null) + this.height = options.height; - if (options.nonce) - this.nonce = options.nonce; + if (options.noRelay != null) + this.noRelay = options.noRelay; - if (options.agent) - this.agent = options.agent; + return this; + } - if (options.height != null) - this.height = options.height; + /** + * Instantiate version packet from options. + * @param {Object} options + * @returns {VersionPacket} + */ - if (options.noRelay != null) - this.noRelay = options.noRelay; + static fromOptions(options) { + return new this().fromOptions(options); + } - return this; -}; + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + let size = 0; + size += 20; + size += this.remote.getSize(false); + size += this.local.getSize(false); + size += 8; + size += encoding.sizeVarString(this.agent, 'ascii'); + size += 5; + return size; + } -/** - * Instantiate version packet from options. - * @param {Object} options - * @returns {VersionPacket} - */ + /** + * Write version packet to buffer writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + bw.writeI32(this.version); + bw.writeU32(this.services); + bw.writeU32(0); + bw.writeI64(this.time); + this.remote.toWriter(bw, false); + this.local.toWriter(bw, false); + bw.writeBytes(this.nonce); + bw.writeVarString(this.agent, 'ascii'); + bw.writeI32(this.height); + bw.writeU8(this.noRelay ? 0 : 1); + return bw; + } -VersionPacket.fromOptions = function fromOptions(options) { - return new VersionPacket().fromOptions(options); -}; + /** + * Serialize version packet. + * @returns {Buffer} + */ -/** - * Get serialization size. - * @returns {Number} - */ + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } -VersionPacket.prototype.getSize = function getSize() { - let size = 0; - size += 20; - size += this.remote.getSize(false); - size += this.local.getSize(false); - size += 8; - size += encoding.sizeVarString(this.agent, 'ascii'); - size += 5; - return size; -}; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -/** - * Write version packet to buffer writer. - * @param {BufferWriter} bw - */ + fromReader(br) { + this.version = br.readI32(); + this.services = br.readU32(); -VersionPacket.prototype.toWriter = function toWriter(bw) { - bw.writeI32(this.version); - bw.writeU32(this.services); - bw.writeU32(0); - bw.writeI64(this.time); - this.remote.toWriter(bw, false); - this.local.toWriter(bw, false); - bw.writeBytes(this.nonce); - bw.writeVarString(this.agent, 'ascii'); - bw.writeI32(this.height); - bw.writeU8(this.noRelay ? 0 : 1); - return bw; -}; + // Note: hi service bits + // are currently unused. + br.readU32(); -/** - * Serialize version packet. - * @returns {Buffer} - */ + this.time = br.readI64(); + this.remote.fromReader(br, false); -VersionPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + if (br.left() > 0) { + this.local.fromReader(br, false); + this.nonce = br.readBytes(8); + } -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + if (br.left() > 0) + this.agent = br.readVarString('ascii', 256); -VersionPacket.prototype.fromReader = function fromReader(br) { - this.version = br.readI32(); - this.services = br.readU32(); + if (br.left() > 0) + this.height = br.readI32(); - // Note: hi service bits - // are currently unused. - br.readU32(); + if (br.left() > 0) + this.noRelay = br.readU8() === 0; - this.time = br.readI64(); - this.remote.fromReader(br, false); + if (this.version === 10300) + this.version = 300; - if (br.left() > 0) { - this.local.fromReader(br, false); - this.nonce = br.readBytes(8); - } + assert(this.version >= 0, 'Version is negative.'); + assert(this.time >= 0, 'Timestamp is negative.'); - if (br.left() > 0) - this.agent = br.readVarString('ascii', 256); + // No idea why so many peers do this. + if (this.height < 0) + this.height = 0; - if (br.left() > 0) - this.height = br.readI32(); + return this; + } - if (br.left() > 0) - this.noRelay = br.readU8() === 0; + /** + * Instantiate version packet from buffer reader. + * @param {BufferReader} br + * @returns {VersionPacket} + */ - if (this.version === 10300) - this.version = 300; + static fromReader(br) { + return new this().fromReader(br); + } - assert(this.version >= 0, 'Version is negative.'); - assert(this.time >= 0, 'Timestamp is negative.'); + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ - // No idea why so many peers do this. - if (this.height < 0) - this.height = 0; + fromRaw(data) { + return this.fromReader(bio.read(data)); + } - return this; -}; + /** + * Instantiate version packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {VersionPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data, enc); + } +} /** - * Instantiate version packet from buffer reader. - * @param {BufferReader} br - * @returns {VersionPacket} + * Verack Packet + * @extends Packet */ -VersionPacket.fromReader = function fromReader(br) { - return new VersionPacket().fromReader(br); -}; +class VerackPacket extends Packet { + /** + * Create a `verack` packet. + * @constructor + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + constructor() { + super(); + this.cmd = 'verack'; + this.type = exports.types.VERACK; + } -VersionPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + /** + * Instantiate verack packet from serialized data. + * @param {BufferReader} br + * @returns {VerackPacket} + */ -/** - * Instantiate version packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {VersionPacket} - */ + static fromReader(br) { + return new this().fromReader(br); + } -VersionPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new VersionPacket().fromRaw(data, enc); -}; + /** + * Instantiate verack packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {VerackPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Represents a `verack` packet. - * @constructor + * Ping Packet + * @extends Packet + * @property {Buffer|null} nonce */ -function VerackPacket() { - if (!(this instanceof VerackPacket)) - return new VerackPacket(); - - Packet.call(this); -} +class PingPacket extends Packet { + /** + * Create a `ping` packet. + * @constructor + * @param {Buffer?} nonce + */ -Object.setPrototypeOf(VerackPacket.prototype, Packet.prototype); + constructor(nonce) { + super(); -VerackPacket.prototype.cmd = 'verack'; -VerackPacket.prototype.type = exports.types.VERACK; + this.cmd = 'ping'; + this.type = exports.types.PING; -/** - * Instantiate verack packet from serialized data. - * @param {BufferReader} br - * @returns {VerackPacket} - */ + this.nonce = nonce || null; + } -VerackPacket.fromReader = function fromReader(br) { - return new VerackPacket().fromReader(br); -}; + /** + * Get serialization size. + * @returns {Number} + */ -/** - * Instantiate verack packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {VerackPacket} - */ + getSize() { + return this.nonce ? 8 : 0; + } -VerackPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new VerackPacket().fromRaw(data); -}; + /** + * Serialize ping packet. + * @returns {Buffer} + */ -/** - * Represents a `ping` packet. - * @constructor - * @param {BN?} nonce - * @property {BN|null} nonce - */ + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } -function PingPacket(nonce) { - if (!(this instanceof PingPacket)) - return new PingPacket(nonce); + /** + * Serialize ping packet to writer. + * @param {BufferWriter} bw + */ - Packet.call(this); + toWriter(bw) { + if (this.nonce) + bw.writeBytes(this.nonce); + return bw; + } - this.nonce = nonce || null; -} + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -Object.setPrototypeOf(PingPacket.prototype, Packet.prototype); + fromReader(br) { + if (br.left() >= 8) + this.nonce = br.readBytes(8); + return this; + } -PingPacket.prototype.cmd = 'ping'; -PingPacket.prototype.type = exports.types.PING; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -/** - * Get serialization size. - * @returns {Number} - */ + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -PingPacket.prototype.getSize = function getSize() { - return this.nonce ? 8 : 0; -}; + /** + * Instantiate ping packet from serialized data. + * @param {BufferReader} br + * @returns {PingPacket} + */ -/** - * Serialize ping packet. - * @returns {Buffer} - */ + static fromReader(br) { + return new this().fromRaw(br); + } -PingPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + /** + * Instantiate ping packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {PingPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Serialize ping packet to writer. - * @param {BufferWriter} bw + * Pong Packet + * @extends Packet + * @property {BN} nonce */ -PingPacket.prototype.toWriter = function toWriter(bw) { - if (this.nonce) - bw.writeBytes(this.nonce); - return bw; -}; +class PongPacket extends Packet { + /** + * Create a `pong` packet. + * @constructor + * @param {BN?} nonce + */ -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + constructor(nonce) { + super(); -PingPacket.prototype.fromReader = function fromReader(br) { - if (br.left() >= 8) - this.nonce = br.readBytes(8); - return this; -}; + this.cmd = 'pong'; + this.type = exports.types.PONG; -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + this.nonce = nonce || common.ZERO_NONCE; + } -PingPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + /** + * Get serialization size. + * @returns {Number} + */ -/** - * Instantiate ping packet from serialized data. - * @param {BufferReader} br - * @returns {PingPacket} - */ + getSize() { + return 8; + } -PingPacket.fromReader = function fromReader(br) { - return new PingPacket().fromRaw(br); -}; + /** + * Serialize pong packet to writer. + * @param {BufferWriter} bw + */ -/** - * Instantiate ping packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {PingPacket} - */ + toWriter(bw) { + bw.writeBytes(this.nonce); + return bw; + } -PingPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new PingPacket().fromRaw(data); -}; + /** + * Serialize pong packet. + * @returns {Buffer} + */ -/** - * Represents a `pong` packet. - * @constructor - * @param {BN?} nonce - * @property {BN} nonce - */ + toRaw() { + return this.toWriter(bio.write(8)).render(); + } -function PongPacket(nonce) { - if (!(this instanceof PongPacket)) - return new PongPacket(nonce); + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ - Packet.call(this); + fromReader(br) { + this.nonce = br.readBytes(8); + return this; + } - this.nonce = nonce || encoding.ZERO_U64; -} + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -Object.setPrototypeOf(PongPacket.prototype, Packet.prototype); + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -PongPacket.prototype.cmd = 'pong'; -PongPacket.prototype.type = exports.types.PONG; + /** + * Instantiate pong packet from buffer reader. + * @param {BufferReader} br + * @returns {VerackPacket} + */ -/** - * Get serialization size. - * @returns {Number} - */ + static fromReader(br) { + return new this().fromReader(br); + } -PongPacket.prototype.getSize = function getSize() { - return 8; -}; + /** + * Instantiate pong packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {VerackPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Serialize pong packet to writer. - * @param {BufferWriter} bw + * GetAddr Packet + * @extends Packet */ -PongPacket.prototype.toWriter = function toWriter(bw) { - bw.writeBytes(this.nonce); - return bw; -}; +class GetAddrPacket extends Packet { + /** + * Create a `getaddr` packet. + * @constructor + */ -/** - * Serialize pong packet. - * @returns {Buffer} - */ + constructor() { + super(); + this.cmd = 'getaddr'; + this.type = exports.types.GETADDR; + } -PongPacket.prototype.toRaw = function toRaw() { - return this.toWriter(new StaticWriter(8)).render(); -}; + /** + * Instantiate getaddr packet from buffer reader. + * @param {BufferReader} br + * @returns {GetAddrPacket} + */ -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + static fromReader(br) { + return new this().fromReader(br); + } -PongPacket.prototype.fromReader = function fromReader(br) { - this.nonce = br.readBytes(8); - return this; -}; + /** + * Instantiate getaddr packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {GetAddrPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Inject properties from serialized data. - * @private - * @param {Buffer} data + * Addr Packet + * @extends Packet + * @property {NetAddress[]} items */ -PongPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate pong packet from buffer reader. - * @param {BufferReader} br - * @returns {VerackPacket} - */ +class AddrPacket extends Packet { + /** + * Create a `addr` packet. + * @constructor + * @param {(NetAddress[])?} items + */ -PongPacket.fromReader = function fromReader(br) { - return new PongPacket().fromReader(br); -}; + constructor(items) { + super(); -/** - * Instantiate pong packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {VerackPacket} - */ + this.cmd = 'addr'; + this.type = exports.types.ADDR; -PongPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new PongPacket().fromRaw(data); -}; + this.items = items || []; + } -/** - * Represents a `getaddr` packet. - * @constructor - */ + /** + * Get serialization size. + * @returns {Number} + */ -function GetAddrPacket() { - if (!(this instanceof GetAddrPacket)) - return new GetAddrPacket(); + getSize() { + let size = 0; + size += encoding.sizeVarint(this.items.length); + size += 30 * this.items.length; + return size; + } - Packet.call(this); -} + /** + * Serialize addr packet to writer. + * @param {BufferWriter} bw + */ -Object.setPrototypeOf(GetAddrPacket.prototype, Packet.prototype); + toWriter(bw) { + bw.writeVarint(this.items.length); -GetAddrPacket.prototype.cmd = 'getaddr'; -GetAddrPacket.prototype.type = exports.types.GETADDR; + for (const item of this.items) + item.toWriter(bw, true); -/** - * Instantiate getaddr packet from buffer reader. - * @param {BufferReader} br - * @returns {GetAddrPacket} - */ + return bw; + } -GetAddrPacket.fromReader = function fromReader(br) { - return new GetAddrPacket().fromReader(br); -}; + /** + * Serialize addr packet. + * @returns {Buffer} + */ -/** - * Instantiate getaddr packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {GetAddrPacket} - */ + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } -GetAddrPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new GetAddrPacket().fromRaw(data); -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -/** - * Represents a `addr` packet. - * @constructor - * @param {(NetAddress[])?} items - * @property {NetAddress[]} items - */ + fromRaw(data) { + const br = bio.read(data); + const count = br.readVarint(); -function AddrPacket(items) { - if (!(this instanceof AddrPacket)) - return new AddrPacket(items); + for (let i = 0; i < count; i++) + this.items.push(NetAddress.fromReader(br, true)); - Packet.call(this); + return this; + } - this.items = items || []; -} + /** + * Instantiate addr packet from Buffer reader. + * @param {BufferReader} br + * @returns {AddrPacket} + */ -Object.setPrototypeOf(AddrPacket.prototype, Packet.prototype); + static fromReader(br) { + return new this().fromReader(br); + } -AddrPacket.prototype.cmd = 'addr'; -AddrPacket.prototype.type = exports.types.ADDR; + /** + * Instantiate addr packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {AddrPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Get serialization size. - * @returns {Number} + * Inv Packet + * @extends Packet + * @property {InvItem[]} items */ -AddrPacket.prototype.getSize = function getSize() { - let size = 0; - size += encoding.sizeVarint(this.items.length); - size += 30 * this.items.length; - return size; -}; +class InvPacket extends Packet { + /** + * Create a `inv` packet. + * @constructor + * @param {(InvItem[])?} items + */ -/** - * Serialize addr packet to writer. - * @param {BufferWriter} bw - */ + constructor(items) { + super(); -AddrPacket.prototype.toWriter = function toWriter(bw) { - bw.writeVarint(this.items.length); + this.cmd = 'inv'; + this.type = exports.types.INV; - for (const item of this.items) - item.toWriter(bw, true); + this.items = items || []; + } - return bw; -}; + /** + * Get serialization size. + * @returns {Number} + */ -/** - * Serialize addr packet. - * @returns {Buffer} - */ + getSize() { + let size = 0; + size += encoding.sizeVarint(this.items.length); + size += 36 * this.items.length; + return size; + } -AddrPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + /** + * Serialize inv packet to writer. + * @param {Buffer} bw + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + toWriter(bw) { + assert(this.items.length <= common.MAX_INV); -AddrPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); - const count = br.readVarint(); + bw.writeVarint(this.items.length); - for (let i = 0; i < count; i++) - this.items.push(NetAddress.fromReader(br, true)); + for (const item of this.items) + item.toWriter(bw); - return this; -}; + return bw; + } -/** - * Instantiate addr packet from Buffer reader. - * @param {BufferReader} br - * @returns {AddrPacket} - */ + /** + * Serialize inv packet. + * @returns {Buffer} + */ -AddrPacket.fromReader = function fromReader(br) { - return new AddrPacket().fromReader(br); -}; + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } -/** - * Instantiate addr packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {AddrPacket} - */ + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -AddrPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new AddrPacket().fromRaw(data); -}; + fromReader(br) { + const count = br.readVarint(); -/** - * Represents a `inv` packet. - * @constructor - * @param {(InvItem[])?} items - * @property {InvItem[]} items - */ + assert(count <= common.MAX_INV, 'Inv item count too high.'); -function InvPacket(items) { - if (!(this instanceof InvPacket)) - return new InvPacket(items); + for (let i = 0; i < count; i++) + this.items.push(InvItem.fromReader(br)); - Packet.call(this); + return this; + } - this.items = items || []; -} + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -Object.setPrototypeOf(InvPacket.prototype, Packet.prototype); + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -InvPacket.prototype.cmd = 'inv'; -InvPacket.prototype.type = exports.types.INV; + /** + * Instantiate inv packet from buffer reader. + * @param {BufferReader} br + * @returns {InvPacket} + */ -/** - * Get serialization size. - * @returns {Number} - */ + static fromReader(br) { + return new this().fromRaw(br); + } -InvPacket.prototype.getSize = function getSize() { - let size = 0; - size += encoding.sizeVarint(this.items.length); - size += 36 * this.items.length; - return size; -}; + /** + * Instantiate inv packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {InvPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Serialize inv packet to writer. - * @param {Buffer} bw + * GetData Packet + * @extends InvPacket */ -InvPacket.prototype.toWriter = function toWriter(bw) { - assert(this.items.length <= 50000); - - bw.writeVarint(this.items.length); +class GetDataPacket extends InvPacket { + /** + * Create a `getdata` packet. + * @constructor + * @param {(InvItem[])?} items + */ - for (const item of this.items) - item.toWriter(bw); + constructor(items) { + super(items); + this.cmd = 'getdata'; + this.type = exports.types.GETDATA; + } - return bw; -}; + /** + * Instantiate getdata packet from buffer reader. + * @param {BufferReader} br + * @returns {GetDataPacket} + */ -/** - * Serialize inv packet. - * @returns {Buffer} - */ + static fromReader(br) { + return new this().fromReader(br); + } -InvPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + /** + * Instantiate getdata packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {GetDataPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br + * NotFound Packet + * @extends InvPacket */ -InvPacket.prototype.fromReader = function fromReader(br) { - const count = br.readVarint(); +class NotFoundPacket extends InvPacket { + /** + * Create a `notfound` packet. + * @constructor + * @param {(InvItem[])?} items + */ + + constructor(items) { + super(items); + this.cmd = 'notfound'; + this.type = exports.types.NOTFOUND; + } - assert(count <= 50000, 'Inv item count too high.'); + /** + * Instantiate notfound packet from buffer reader. + * @param {BufferReader} br + * @returns {NotFoundPacket} + */ - for (let i = 0; i < count; i++) - this.items.push(InvItem.fromReader(br)); + static fromReader(br) { + return new this().fromReader(br); + } - return this; -}; + /** + * Instantiate notfound packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {NotFoundPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Inject properties from serialized data. - * @private - * @param {Buffer} data + * GetBlocks Packet + * @extends Packet + * @property {Hash[]} locator + * @property {Hash|null} stop */ -InvPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; +class GetBlocksPacket extends Packet { + /** + * Create a `getblocks` packet. + * @constructor + * @param {Hash[]} locator + * @param {Hash?} stop + */ -/** - * Instantiate inv packet from buffer reader. - * @param {BufferReader} br - * @returns {InvPacket} - */ + constructor(locator, stop) { + super(); -InvPacket.fromReader = function fromReader(br) { - return new InvPacket().fromRaw(br); -}; + this.cmd = 'getblocks'; + this.type = exports.types.GETBLOCKS; -/** - * Instantiate inv packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {InvPacket} - */ + this.version = common.PROTOCOL_VERSION; + this.locator = locator || []; + this.stop = stop || null; + } -InvPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new InvPacket().fromRaw(data); -}; + /** + * Get serialization size. + * @returns {Number} + */ -/** - * Represents a `getdata` packet. - * @extends InvPacket - * @constructor - * @param {(InvItem[])?} items - */ + getSize() { + let size = 0; + size += 4; + size += encoding.sizeVarint(this.locator.length); + size += 32 * this.locator.length; + size += 32; + return size; + } -function GetDataPacket(items) { - if (!(this instanceof GetDataPacket)) - return new GetDataPacket(items); + /** + * Serialize getblocks packet to writer. + * @param {BufferWriter} bw + */ - InvPacket.call(this, items); -} + toWriter(bw) { + assert(this.locator.length <= common.MAX_INV, 'Too many block hashes.'); -Object.setPrototypeOf(GetDataPacket.prototype, InvPacket.prototype); + bw.writeU32(this.version); + bw.writeVarint(this.locator.length); -GetDataPacket.prototype.cmd = 'getdata'; -GetDataPacket.prototype.type = exports.types.GETDATA; + for (const hash of this.locator) + bw.writeHash(hash); -/** - * Instantiate getdata packet from buffer reader. - * @param {BufferReader} br - * @returns {GetDataPacket} - */ + bw.writeHash(this.stop || consensus.ZERO_HASH); -GetDataPacket.fromReader = function fromReader(br) { - return new GetDataPacket().fromReader(br); -}; + return bw; + } -/** - * Instantiate getdata packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {GetDataPacket} - */ + /** + * Serialize getblocks packet. + * @returns {Buffer} + */ -GetDataPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new GetDataPacket().fromRaw(data); -}; + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } -/** - * Represents a `notfound` packet. - * @extends InvPacket - * @constructor - * @param {(InvItem[])?} items - */ + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -function NotFoundPacket(items) { - if (!(this instanceof NotFoundPacket)) - return new NotFoundPacket(items); + fromReader(br) { + this.version = br.readU32(); - InvPacket.call(this, items); -} + const count = br.readVarint(); -Object.setPrototypeOf(NotFoundPacket.prototype, InvPacket.prototype); + assert(count <= common.MAX_INV, 'Too many block hashes.'); -NotFoundPacket.prototype.cmd = 'notfound'; -NotFoundPacket.prototype.type = exports.types.NOTFOUND; + for (let i = 0; i < count; i++) + this.locator.push(br.readHash()); -/** - * Instantiate notfound packet from buffer reader. - * @param {BufferReader} br - * @returns {NotFoundPacket} - */ + this.stop = br.readHash(); -NotFoundPacket.fromReader = function fromReader(br) { - return new NotFoundPacket().fromReader(br); -}; + if (this.stop.equals(consensus.ZERO_HASH)) + this.stop = null; -/** - * Instantiate notfound packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {NotFoundPacket} - */ + return this; + } -NotFoundPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new NotFoundPacket().fromRaw(data); -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(bio.read(data)); + } + + /** + * Instantiate getblocks packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {GetBlocksPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Represents a `getblocks` packet. - * @constructor - * @param {Hash[]} locator - * @param {Hash?} stop - * @property {Hash[]} locator - * @property {Hash|null} stop + * GetHeader Packets + * @extends GetBlocksPacket */ -function GetBlocksPacket(locator, stop) { - if (!(this instanceof GetBlocksPacket)) - return new GetBlocksPacket(locator, stop); +class GetHeadersPacket extends GetBlocksPacket { + /** + * Create a `getheaders` packet. + * @constructor + * @param {Hash[]} locator + * @param {Hash?} stop + */ - Packet.call(this); + constructor(locator, stop) { + super(locator, stop); + this.cmd = 'getheaders'; + this.type = exports.types.GETHEADERS; + } - this.version = common.PROTOCOL_VERSION; - this.locator = locator || []; - this.stop = stop || null; -} + /** + * Instantiate getheaders packet from buffer reader. + * @param {BufferReader} br + * @returns {GetHeadersPacket} + */ -Object.setPrototypeOf(GetBlocksPacket.prototype, Packet.prototype); + static fromReader(br) { + return new this().fromReader(br); + } -GetBlocksPacket.prototype.cmd = 'getblocks'; -GetBlocksPacket.prototype.type = exports.types.GETBLOCKS; + /** + * Instantiate getheaders packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {GetHeadersPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Get serialization size. - * @returns {Number} + * Headers Packet + * @extends Packet + * @property {Headers[]} items */ -GetBlocksPacket.prototype.getSize = function getSize() { - let size = 0; - size += 4; - size += encoding.sizeVarint(this.locator.length); - size += 32 * this.locator.length; - size += 32; - return size; -}; +class HeadersPacket extends Packet { + /** + * Create a `headers` packet. + * @constructor + * @param {(Headers[])?} items + */ -/** - * Serialize getblocks packet to writer. - * @param {BufferWriter} bw - */ + constructor(items) { + super(); -GetBlocksPacket.prototype.toWriter = function toWriter(bw) { - assert(this.locator.length <= 50000, 'Too many block hashes.'); + this.cmd = 'headers'; + this.type = exports.types.HEADERS; - bw.writeU32(this.version); - bw.writeVarint(this.locator.length); + this.items = items || []; + } - for (const hash of this.locator) - bw.writeHash(hash); + /** + * Get serialization size. + * @returns {Number} + */ - bw.writeHash(this.stop || encoding.ZERO_HASH); + getSize() { + let size = 0; - return bw; -}; + size += encoding.sizeVarint(this.items.length); -/** - * Serialize getblocks packet. - * @returns {Buffer} - */ + for (const item of this.items) + size += item.getSize(); -GetBlocksPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + return size; + } -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + /** + * Serialize headers packet to writer. + * @param {BufferWriter} bw + */ -GetBlocksPacket.prototype.fromReader = function fromReader(br) { - this.version = br.readU32(); + toWriter(bw) { + assert(this.items.length <= 2000, 'Too many headers.'); - const count = br.readVarint(); + bw.writeVarint(this.items.length); - assert(count <= 50000, 'Too many block hashes.'); + for (const item of this.items) + item.toWriter(bw); - for (let i = 0; i < count; i++) - this.locator.push(br.readHash('hex')); + return bw; + } - this.stop = br.readHash('hex'); + /** + * Serialize headers packet. + * @returns {Buffer} + */ - if (this.stop === encoding.NULL_HASH) - this.stop = null; + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } - return this; -}; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + fromReader(br) { + const count = br.readVarint(); -GetBlocksPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + assert(count <= 2000, 'Too many headers.'); -/** - * Instantiate getblocks packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {GetBlocksPacket} - */ + for (let i = 0; i < count; i++) + this.items.push(Headers.fromReader(br)); -GetBlocksPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new GetBlocksPacket().fromRaw(data); -}; + return this; + } -/** - * Represents a `getheaders` packet. - * @extends GetBlocksPacket - * @constructor - * @param {Hash[]} locator - * @param {Hash?} stop - */ + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -function GetHeadersPacket(locator, stop) { - if (!(this instanceof GetHeadersPacket)) - return new GetHeadersPacket(locator, stop); + fromRaw(data) { + return this.fromReader(bio.read(data)); + } - GetBlocksPacket.call(this, locator, stop); + /** + * Instantiate headers packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {VerackPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(GetHeadersPacket.prototype, GetBlocksPacket.prototype); - -GetHeadersPacket.prototype.cmd = 'getheaders'; -GetHeadersPacket.prototype.type = exports.types.GETHEADERS; - /** - * Instantiate getheaders packet from buffer reader. - * @param {BufferReader} br - * @returns {GetHeadersPacket} + * SendHeaders Packet + * @extends Packet */ -GetHeadersPacket.fromReader = function fromReader(br) { - return new GetHeadersPacket().fromReader(br); -}; +class SendHeadersPacket extends Packet { + /** + * Create a `sendheaders` packet. + * @constructor + */ -/** - * Instantiate getheaders packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {GetHeadersPacket} - */ + constructor() { + super(); + this.cmd = 'sendheaders'; + this.type = exports.types.SENDHEADERS; + } -GetHeadersPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new GetHeadersPacket().fromRaw(data); -}; + /** + * Instantiate sendheaders packet from buffer reader. + * @param {BufferReader} br + * @returns {SendHeadersPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate sendheaders packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {SendHeadersPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Represents a `headers` packet. - * @constructor - * @param {(Headers[])?} items - * @property {Headers[]} items + * Block Packet + * @extends Packet + * @property {Block} block + * @property {Boolean} witness */ -function HeadersPacket(items) { - if (!(this instanceof HeadersPacket)) - return new HeadersPacket(items); +class BlockPacket extends Packet { + /** + * Create a `block` packet. + * @constructor + * @param {Block|null} block + * @param {Boolean?} witness + */ - Packet.call(this); + constructor(block, witness) { + super(); - this.items = items || []; -} + this.cmd = 'block'; + this.type = exports.types.BLOCK; -Object.setPrototypeOf(HeadersPacket.prototype, Packet.prototype); + this.block = block || new MemBlock(); + this.witness = witness || false; + } -HeadersPacket.prototype.cmd = 'headers'; -HeadersPacket.prototype.type = exports.types.HEADERS; + /** + * Get serialization size. + * @returns {Number} + */ -/** - * Get serialization size. - * @returns {Number} - */ + getSize() { + if (this.witness) + return this.block.getSize(); + return this.block.getBaseSize(); + } -HeadersPacket.prototype.getSize = function getSize() { - let size = 0; + /** + * Serialize block packet to writer. + * @param {BufferWriter} bw + */ - size += encoding.sizeVarint(this.items.length); + toWriter(bw) { + if (this.witness) + return this.block.toWriter(bw); + return this.block.toNormalWriter(bw); + } - for (const item of this.items) - size += item.getSize(); + /** + * Serialize block packet. + * @returns {Buffer} + */ - return size; -}; + toRaw() { + if (this.witness) + return this.block.toRaw(); + return this.block.toNormal(); + } -/** - * Serialize headers packet to writer. - * @param {BufferWriter} bw - */ + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -HeadersPacket.prototype.toWriter = function toWriter(bw) { - assert(this.items.length <= 2000, 'Too many headers.'); + fromReader(br) { + this.block.fromReader(br); + return this; + } - bw.writeVarint(this.items.length); + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ - for (const item of this.items) - item.toWriter(bw); + fromRaw(data) { + this.block.fromRaw(data); + return this; + } - return bw; -}; + /** + * Instantiate block packet from buffer reader. + * @param {BufferReader} br + * @returns {BlockPacket} + */ -/** - * Serialize headers packet. - * @returns {Buffer} - */ + static fromReader(br) { + return new this().fromReader(br); + } -HeadersPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + /** + * Instantiate block packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {BlockPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br + * TX Packet + * @extends Packet + * @property {TX} block + * @property {Boolean} witness */ -HeadersPacket.prototype.fromReader = function fromReader(br) { - const count = br.readVarint(); +class TXPacket extends Packet { + /** + * Create a `tx` packet. + * @constructor + * @param {TX|null} tx + * @param {Boolean?} witness + */ - assert(count <= 2000, 'Too many headers.'); + constructor(tx, witness) { + super(); - for (let i = 0; i < count; i++) - this.items.push(Headers.fromReader(br)); + this.cmd = 'tx'; + this.type = exports.types.TX; - return this; -}; + this.tx = tx || new TX(); + this.witness = witness || false; + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + /** + * Get serialization size. + * @returns {Number} + */ -HeadersPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + getSize() { + if (this.witness) + return this.tx.getSize(); + return this.tx.getBaseSize(); + } -/** - * Instantiate headers packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {VerackPacket} - */ + /** + * Serialize tx packet to writer. + * @param {BufferWriter} bw + */ -HeadersPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new HeadersPacket().fromRaw(data); -}; + toWriter(bw) { + if (this.witness) + return this.tx.toWriter(bw); + return this.tx.toNormalWriter(bw); + } -/** - * Represents a `sendheaders` packet. - * @constructor - */ + /** + * Serialize tx packet. + * @returns {Buffer} + */ -function SendHeadersPacket() { - if (!(this instanceof SendHeadersPacket)) - return new SendHeadersPacket(); + toRaw() { + if (this.witness) + return this.tx.toRaw(); + return this.tx.toNormal(); + } - Packet.call(this); -} + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -Object.setPrototypeOf(SendHeadersPacket.prototype, Packet.prototype); + fromReader(br) { + this.tx.fromRaw(br); + return this; + } -SendHeadersPacket.prototype.cmd = 'sendheaders'; -SendHeadersPacket.prototype.type = exports.types.SENDHEADERS; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -/** - * Instantiate sendheaders packet from buffer reader. - * @param {BufferReader} br - * @returns {SendHeadersPacket} - */ + fromRaw(data) { + this.tx.fromRaw(data); + return this; + } -SendHeadersPacket.fromReader = function fromReader(br) { - return new SendHeadersPacket().fromReader(br); -}; + /** + * Instantiate tx packet from buffer reader. + * @param {BufferReader} br + * @returns {TXPacket} + */ -/** - * Instantiate sendheaders packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {SendHeadersPacket} - */ + static fromReader(br) { + return new this().fromReader(br); + } -SendHeadersPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new SendHeadersPacket().fromRaw(data); -}; + /** + * Instantiate tx packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {TXPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Represents a `block` packet. - * @constructor - * @param {Block|null} block - * @param {Boolean?} witness - * @property {Block} block - * @property {Boolean} witness + * Reject Packet + * @extends Packet + * @property {(Number|String)?} code - Code + * (see {@link RejectPacket.codes}). + * @property {String?} msg - Message. + * @property {String?} reason - Reason. + * @property {(Hash|Buffer)?} data - Transaction or block hash. */ -function BlockPacket(block, witness) { - if (!(this instanceof BlockPacket)) - return new BlockPacket(block, witness); +class RejectPacket extends Packet { + /** + * Create reject packet. + * @constructor + */ - Packet.call(this); + constructor(options) { + super(); - this.block = block || new MemBlock(); - this.witness = witness || false; -} + this.cmd = 'reject'; + this.type = exports.types.REJECT; -Object.setPrototypeOf(BlockPacket.prototype, Packet.prototype); + this.message = ''; + this.code = RejectPacket.codes.INVALID; + this.reason = ''; + this.hash = null; -BlockPacket.prototype.cmd = 'block'; -BlockPacket.prototype.type = exports.types.BLOCK; + if (options) + this.fromOptions(options); + } -/** - * Get serialization size. - * @returns {Number} - */ + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ -BlockPacket.prototype.getSize = function getSize() { - if (this.witness) - return this.block.getSize(); - return this.block.getBaseSize(); -}; + fromOptions(options) { + let code = options.code; -/** - * Serialize block packet to writer. - * @param {BufferWriter} bw - */ + if (options.message) + this.message = options.message; -BlockPacket.prototype.toWriter = function toWriter(bw) { - if (this.witness) - return this.block.toWriter(bw); - return this.block.toNormalWriter(bw); -}; + if (code != null) { + if (typeof code === 'string') + code = RejectPacket.codes[code.toUpperCase()]; -/** - * Serialize block packet. - * @returns {Buffer} - */ + if (code >= RejectPacket.codes.INTERNAL) + code = RejectPacket.codes.INVALID; -BlockPacket.prototype.toRaw = function toRaw() { - if (this.witness) - return this.block.toRaw(); - return this.block.toNormal(); -}; + this.code = code; + } -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + if (options.reason) + this.reason = options.reason; -BlockPacket.prototype.fromReader = function fromReader(br) { - this.block.fromReader(br); - return this; -}; + if (options.hash) + this.hash = options.hash; -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + return this; + } -BlockPacket.prototype.fromRaw = function fromRaw(data) { - this.block.fromRaw(data); - return this; -}; + /** + * Instantiate reject packet from options. + * @param {Object} options + * @returns {RejectPacket} + */ -/** - * Instantiate block packet from buffer reader. - * @param {BufferReader} br - * @returns {BlockPacket} - */ + static fromOptions(options) { + return new this().fromOptions(options); + } -BlockPacket.fromReader = function fromReader(br) { - return new BlockPacket().fromReader(br); -}; + /** + * Get uint256le hash if present. + * @returns {Hash} + */ -/** - * Instantiate block packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {BlockPacket} - */ + rhash() { + return this.hash ? util.revHex(this.hash) : null; + } -BlockPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new BlockPacket().fromRaw(data); -}; + /** + * Get symbolic code. + * @returns {String} + */ -/** - * Represents a `tx` packet. - * @constructor - * @param {TX|null} tx - * @param {Boolean?} witness - * @property {TX} block - * @property {Boolean} witness - */ + getCode() { + const code = RejectPacket.codesByVal[this.code]; -function TXPacket(tx, witness) { - if (!(this instanceof TXPacket)) - return new TXPacket(tx, witness); + if (!code) + return this.code.toString(10); - Packet.call(this); + return code.toLowerCase(); + } - this.tx = tx || new TX(); - this.witness = witness || false; -} + /** + * Get serialization size. + * @returns {Number} + */ -Object.setPrototypeOf(TXPacket.prototype, Packet.prototype); + getSize() { + let size = 0; -TXPacket.prototype.cmd = 'tx'; -TXPacket.prototype.type = exports.types.TX; + size += encoding.sizeVarString(this.message, 'ascii'); + size += 1; + size += encoding.sizeVarString(this.reason, 'ascii'); -/** - * Get serialization size. - * @returns {Number} - */ + if (this.hash) + size += 32; -TXPacket.prototype.getSize = function getSize() { - if (this.witness) - return this.tx.getSize(); - return this.tx.getBaseSize(); -}; + return size; + } -/** - * Serialize tx packet to writer. - * @param {BufferWriter} bw - */ + /** + * Serialize reject packet to writer. + * @param {BufferWriter} bw + */ -TXPacket.prototype.toWriter = function toWriter(bw) { - if (this.witness) - return this.tx.toWriter(bw); - return this.tx.toNormalWriter(bw); -}; + toWriter(bw) { + assert(this.message.length <= 12); + assert(this.reason.length <= 111); -/** - * Serialize tx packet. - * @returns {Buffer} - */ + bw.writeVarString(this.message, 'ascii'); + bw.writeU8(this.code); + bw.writeVarString(this.reason, 'ascii'); -TXPacket.prototype.toRaw = function toRaw() { - if (this.witness) - return this.tx.toRaw(); - return this.tx.toNormal(); -}; + if (this.hash) + bw.writeHash(this.hash); -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + return bw; + } -TXPacket.prototype.fromReader = function fromReader(br) { - this.tx.fromRaw(br); - return this; -}; + /** + * Serialize reject packet. + * @returns {Buffer} + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } -TXPacket.prototype.fromRaw = function fromRaw(data) { - this.tx.fromRaw(data); - return this; -}; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.message = br.readVarString('ascii', 12); + this.code = br.readU8(); + this.reason = br.readVarString('ascii', 111); + + switch (this.message) { + case 'block': + case 'tx': + this.hash = br.readHash(); + break; + default: + this.hash = null; + break; + } + + return this; + } -/** - * Instantiate tx packet from buffer reader. - * @param {BufferReader} br - * @returns {TXPacket} - */ + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -TXPacket.fromReader = function fromReader(br) { - return new TXPacket().fromReader(br); -}; + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -/** - * Instantiate tx packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {TXPacket} - */ + /** + * Instantiate reject packet from buffer reader. + * @param {BufferReader} br + * @returns {RejectPacket} + */ -TXPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new TXPacket().fromRaw(data); -}; + static fromReader(br) { + return new this().fromReader(br); + } -/** - * Reject Packet - * @constructor - * @property {(Number|String)?} code - Code - * (see {@link RejectPacket.codes}). - * @property {String?} msg - Message. - * @property {String?} reason - Reason. - * @property {(Hash|Buffer)?} data - Transaction or block hash. - */ + /** + * Instantiate reject packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {RejectPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data, enc); + } + + /** + * Inject properties from reason message and object. + * @private + * @param {Number|String} code + * @param {String} reason + * @param {String?} msg + * @param {Hash?} hash + */ + + fromReason(code, reason, msg, hash) { + if (typeof code === 'string') + code = RejectPacket.codes[code.toUpperCase()]; + + if (!code) + code = RejectPacket.codes.INVALID; -function RejectPacket(options) { - if (!(this instanceof RejectPacket)) - return new RejectPacket(options); + if (code >= RejectPacket.codes.INTERNAL) + code = RejectPacket.codes.INVALID; - Packet.call(this); + this.message = ''; + this.code = code; + this.reason = reason; - this.message = ''; - this.code = RejectPacket.codes.INVALID; - this.reason = ''; - this.hash = null; + if (msg) { + assert(hash); + this.message = msg; + this.hash = hash; + } - if (options) - this.fromOptions(options); -} + return this; + } + + /** + * Instantiate reject packet from reason message. + * @param {Number} code + * @param {String} reason + * @param {String?} msg + * @param {Hash?} hash + * @returns {RejectPacket} + */ + + static fromReason(code, reason, msg, hash) { + return new this().fromReason(code, reason, msg, hash); + } + + /** + * Instantiate reject packet from verify error. + * @param {VerifyError} err + * @param {(TX|Block)?} obj + * @returns {RejectPacket} + */ -Object.setPrototypeOf(RejectPacket.prototype, Packet.prototype); + static fromError(err, obj) { + return this.fromReason(err.code, err.reason, obj); + } + + /** + * Inspect reject packet. + * @returns {String} + */ + + [inspectSymbol]() { + const code = RejectPacket.codesByVal[this.code] || this.code; + const hash = this.hash ? util.revHex(this.hash) : null; + return ''; + } +} /** * Reject codes. Note that `internal` and higher @@ -1462,1644 +1761,954 @@ RejectPacket.codes = { CHECKPOINT: 0x43, // Internal codes (NOT FOR USE ON NETWORK) INTERNAL: 0x100, - HIGHFEE: 0x100, - ALREADYKNOWN: 0x101, - CONFLICT: 0x102 + HIGHFEE: 0x101, + ALREADYKNOWN: 0x102, + CONFLICT: 0x103 }; /** * Reject codes by value. - * @const {RevMap} + * @const {Object} */ -RejectPacket.codesByVal = util.reverse(RejectPacket.codes); - -RejectPacket.prototype.cmd = 'reject'; -RejectPacket.prototype.type = exports.types.REJECT; +RejectPacket.codesByVal = { + 0x01: 'MALFORMED', + 0x10: 'INVALID', + 0x11: 'OBSOLETE', + 0x12: 'DUPLICATE', + 0x40: 'NONSTANDARD', + 0x41: 'DUST', + 0x42: 'INSUFFICIENTFEE', + 0x43: 'CHECKPOINT', + // Internal codes (NOT FOR USE ON NETWORK) + 0x100: 'INTERNAL', + 0x101: 'HIGHFEE', + 0x102: 'ALREADYKNOWN', + 0x103: 'CONFLICT' +}; /** - * Inject properties from options object. - * @private - * @param {Object} options + * Mempool Packet + * @extends Packet */ -RejectPacket.prototype.fromOptions = function fromOptions(options) { - let code = options.code; - - if (options.message) - this.message = options.message; - - if (code != null) { - if (typeof code === 'string') - code = RejectPacket.codes[code.toUpperCase()]; - - if (code >= RejectPacket.codes.INTERNAL) - code = RejectPacket.codes.INVALID; +class MempoolPacket extends Packet { + /** + * Create a `mempool` packet. + * @constructor + */ - this.code = code; + constructor() { + super(); + this.cmd = 'mempool'; + this.type = exports.types.MEMPOOL; } - if (options.reason) - this.reason = options.reason; + /** + * Instantiate mempool packet from buffer reader. + * @param {BufferReader} br + * @returns {VerackPacket} + */ - if (options.hash) - this.hash = options.hash; + static fromReader(br) { + return new this().fromReader(br); + } - return this; -}; + /** + * Instantiate mempool packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {VerackPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Instantiate reject packet from options. - * @param {Object} options - * @returns {RejectPacket} + * FilterLoad Packet + * @extends Packet */ -RejectPacket.fromOptions = function fromOptions(options) { - return new RejectPacket().fromOptions(options); -}; +class FilterLoadPacket extends Packet { + /** + * Create a `filterload` packet. + * @constructor + * @param {BloomFilter|null} filter + */ -/** - * Get uint256le hash if present. - * @returns {Hash} - */ + constructor(filter) { + super(); -RejectPacket.prototype.rhash = function rhash() { - return this.hash ? util.revHex(this.hash) : null; -}; + this.cmd = 'filterload'; + this.type = exports.types.FILTERLOAD; -/** - * Get symbolic code. - * @returns {String} - */ + this.filter = filter || new BloomFilter(); + } -RejectPacket.prototype.getCode = function getCode() { - const code = RejectPacket.codesByVal[this.code]; + /** + * Get serialization size. + * @returns {Number} + */ - if (!code) - return this.code.toString(10); + getSize() { + return this.filter.getSize(); + } - return code.toLowerCase(); -}; + /** + * Serialize filterload packet to writer. + * @param {BufferWriter} bw + */ -/** - * Get serialization size. - * @returns {Number} - */ + toWriter(bw) { + return this.filter.toWriter(bw); + } -RejectPacket.prototype.getSize = function getSize() { - let size = 0; + /** + * Serialize filterload packet. + * @returns {Buffer} + */ - size += encoding.sizeVarString(this.message, 'ascii'); - size += 1; - size += encoding.sizeVarString(this.reason, 'ascii'); + toRaw() { + return this.filter.toRaw(); + } - if (this.hash) - size += 32; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ - return size; -}; + fromReader(br) { + this.filter.fromReader(br); + return this; + } -/** - * Serialize reject packet to writer. - * @param {BufferWriter} bw - */ + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -RejectPacket.prototype.toWriter = function toWriter(bw) { - assert(this.message.length <= 12); - assert(this.reason.length <= 111); + fromRaw(data) { + this.filter.fromRaw(data); + return this; + } - bw.writeVarString(this.message, 'ascii'); - bw.writeU8(this.code); - bw.writeVarString(this.reason, 'ascii'); + /** + * Instantiate filterload packet from buffer reader. + * @param {BufferReader} br + * @returns {FilterLoadPacket} + */ - if (this.hash) - bw.writeHash(this.hash); + static fromReader(br) { + return new this().fromReader(br); + } - return bw; -}; + /** + * Instantiate filterload packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {FilterLoadPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } -/** - * Serialize reject packet. - * @returns {Buffer} - */ + /** + * Ensure the filter is within the size limits. + * @returns {Boolean} + */ -RejectPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + isWithinConstraints() { + return this.filter.isWithinConstraints(); + } +} /** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br + * FilterAdd Packet + * @extends Packet + * @property {Buffer} data */ -RejectPacket.prototype.fromReader = function fromReader(br) { - this.message = br.readVarString('ascii', 12); - this.code = br.readU8(); - this.reason = br.readVarString('ascii', 111); - - switch (this.message) { - case 'block': - case 'tx': - this.hash = br.readHash('hex'); - break; - default: - this.hash = null; - break; - } +class FilterAddPacket extends Packet { + /** + * Create a `filteradd` packet. + * @constructor + * @param {Buffer?} data + */ - return this; -}; + constructor(data) { + super(); -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + this.cmd = 'filteradd'; + this.type = exports.types.FILTERADD; -RejectPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + this.data = data || DUMMY; + } -/** - * Instantiate reject packet from buffer reader. - * @param {BufferReader} br - * @returns {RejectPacket} - */ + /** + * Get serialization size. + * @returns {Number} + */ -RejectPacket.fromReader = function fromReader(br) { - return new RejectPacket().fromReader(br); -}; + getSize() { + return encoding.sizeVarBytes(this.data); + } -/** - * Instantiate reject packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {RejectPacket} - */ + /** + * Serialize filteradd packet to writer. + * @returns {BufferWriter} bw + */ -RejectPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new RejectPacket().fromRaw(data, enc); -}; + toWriter(bw) { + bw.writeVarBytes(this.data); + return bw; + } -/** - * Inject properties from reason message and object. - * @private - * @param {Number} code - * @param {String} reason - * @param {String?} msg - * @param {Hash?} hash - */ + /** + * Serialize filteradd packet. + * @returns {Buffer} + */ -RejectPacket.prototype.fromReason = function fromReason(code, reason, msg, hash) { - if (typeof code === 'string') - code = RejectPacket.codes[code.toUpperCase()]; + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } - if (!code) - code = RejectPacket.codes.INVALID; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ - if (code >= RejectPacket.codes.INTERNAL) - code = RejectPacket.codes.INVALID; + fromReader(br) { + this.data = br.readVarBytes(); + return this; + } - this.message = ''; - this.code = code; - this.reason = reason; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ - if (msg) { - assert(hash); - this.message = msg; - this.hash = hash; + fromRaw(data) { + return this.fromReader(bio.read(data)); } - return this; -}; + /** + * Instantiate filteradd packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {FilterAddPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Instantiate reject packet from reason message. - * @param {Number} code - * @param {String} reason - * @param {String?} msg - * @param {Hash?} hash - * @returns {RejectPacket} + * FilterClear Packet + * @extends Packet */ -RejectPacket.fromReason = function fromReason(code, reason, msg, hash) { - return new RejectPacket().fromReason(code, reason, msg, hash); -}; +class FilterClearPacket extends Packet { + /** + * Create a `filterclear` packet. + * @constructor + */ -/** - * Instantiate reject packet from verify error. - * @param {VerifyError} err - * @param {(TX|Block)?} obj - * @returns {RejectPacket} - */ + constructor() { + super(); + this.cmd = 'filterclear'; + this.type = exports.types.FILTERCLEAR; + } -RejectPacket.fromError = function fromError(err, obj) { - return RejectPacket.fromReason(err.code, err.reason, obj); -}; + /** + * Instantiate filterclear packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {FilterClearPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Inspect reject packet. - * @returns {String} + * MerkleBlock Packet + * @extends Packet + * @property {MerkleBlock} block */ -RejectPacket.prototype.inspect = function inspect() { - const code = RejectPacket.codesByVal[this.code] || this.code; - const hash = this.hash ? util.revHex(this.hash) : null; - return ''; -}; - -/** - * Represents a `mempool` packet. - * @constructor - */ +class MerkleBlockPacket extends Packet { + /** + * Create a `merkleblock` packet. + * @constructor + * @param {MerkleBlock?} block + */ -function MempoolPacket() { - if (!(this instanceof MempoolPacket)) - return new MempoolPacket(); + constructor(block) { + super(); - Packet.call(this); -} + this.cmd = 'merkleblock'; + this.type = exports.types.MERKLEBLOCK; -Object.setPrototypeOf(MempoolPacket.prototype, Packet.prototype); + this.block = block || new MerkleBlock(); + } -MempoolPacket.prototype.cmd = 'mempool'; -MempoolPacket.prototype.type = exports.types.MEMPOOL; + /** + * Get serialization size. + * @returns {Number} + */ -/** - * Instantiate mempool packet from buffer reader. - * @param {BufferReader} br - * @returns {VerackPacket} - */ + getSize() { + return this.block.getSize(); + } -MempoolPacket.fromReader = function fromReader(br) { - return new MempoolPacket().fromReader(br); -}; + /** + * Serialize merkleblock packet to writer. + * @param {BufferWriter} bw + */ -/** - * Instantiate mempool packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {VerackPacket} - */ + toWriter(bw) { + return this.block.toWriter(bw); + } -MempoolPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new MempoolPacket().fromRaw(data); -}; + /** + * Serialize merkleblock packet. + * @returns {Buffer} + */ -/** - * Represents a `filterload` packet. - * @constructor - * @param {Bloom|null} filter - */ + toRaw() { + return this.block.toRaw(); + } -function FilterLoadPacket(filter) { - if (!(this instanceof FilterLoadPacket)) - return new FilterLoadPacket(filter); + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ - Packet.call(this); + fromReader(br) { + this.block.fromReader(br); + return this; + } - this.filter = filter || new Bloom(); -} + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -Object.setPrototypeOf(FilterLoadPacket.prototype, Packet.prototype); + fromRaw(data) { + this.block.fromRaw(data); + return this; + } -FilterLoadPacket.prototype.cmd = 'filterload'; -FilterLoadPacket.prototype.type = exports.types.FILTERLOAD; + /** + * Instantiate merkleblock packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {MerkleBlockPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Get serialization size. - * @returns {Number} + * FeeFilter Packet + * @extends Packet + * @property {Rate} rate */ -FilterLoadPacket.prototype.getSize = function getSize() { - return this.filter.getSize(); -}; +class FeeFilterPacket extends Packet { + /** + * Create a `feefilter` packet. + * @constructor + * @param {Rate?} rate + */ -/** - * Serialize filterload packet to writer. - * @param {BufferWriter} bw - */ + constructor(rate) { + super(); -FilterLoadPacket.prototype.toWriter = function toWriter(bw) { - return this.filter.toWriter(bw); -}; + this.cmd = 'feefilter'; + this.type = exports.types.FEEFILTER; -/** - * Serialize filterload packet. - * @returns {Buffer} - */ + this.rate = rate || 0; + } -FilterLoadPacket.prototype.toRaw = function toRaw() { - return this.filter.toRaw(); -}; + /** + * Get serialization size. + * @returns {Number} + */ -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + getSize() { + return 8; + } -FilterLoadPacket.prototype.fromReader = function fromReader(br) { - this.filter.fromReader(br); - return this; -}; + /** + * Serialize feefilter packet to writer. + * @param {BufferWriter} bw + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + toWriter(bw) { + bw.writeI64(this.rate); + return bw; + } -FilterLoadPacket.prototype.fromRaw = function fromRaw(data) { - this.filter.fromRaw(data); - return this; -}; + /** + * Serialize feefilter packet. + * @returns {Buffer} + */ -/** - * Instantiate filterload packet from buffer reader. - * @param {BufferReader} br - * @returns {FilterLoadPacket} - */ + toRaw() { + return this.toWriter(bio.write(8)).render(); + } -FilterLoadPacket.fromReader = function fromReader(br) { - return new FilterLoadPacket().fromReader(br); -}; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -/** - * Instantiate filterload packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {FilterLoadPacket} - */ + fromReader(br) { + this.rate = br.readI64(); + return this; + } -FilterLoadPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new FilterLoadPacket().fromRaw(data); -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -/** - * Ensure the filter is within the size limits. - * @returns {Boolean} - */ + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -FilterLoadPacket.prototype.isWithinConstraints = function isWithinConstraints() { - return this.filter.isWithinConstraints(); -}; + /** + * Instantiate feefilter packet from buffer reader. + * @param {BufferReader} br + * @returns {FeeFilterPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate feefilter packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {FeeFilterPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Represents a `filteradd` packet. - * @constructor - * @param {Buffer?} data - * @property {Buffer} data + * SendCmpct Packet + * @extends Packet + * @property {Number} mode + * @property {Number} version */ -function FilterAddPacket(data) { - if (!(this instanceof FilterAddPacket)) - return new FilterAddPacket(data); +class SendCmpctPacket extends Packet { + /** + * Create a `sendcmpct` packet. + * @constructor + * @param {Number|null} mode + * @param {Number|null} version + */ - Packet.call(this); + constructor(mode, version) { + super(); - this.data = data || DUMMY; -} + this.cmd = 'sendcmpct'; + this.type = exports.types.SENDCMPCT; -Object.setPrototypeOf(FilterAddPacket.prototype, Packet.prototype); + this.mode = mode || 0; + this.version = version || 1; + } -FilterAddPacket.prototype.cmd = 'filteradd'; -FilterAddPacket.prototype.type = exports.types.FILTERADD; + /** + * Get serialization size. + * @returns {Number} + */ -/** - * Get serialization size. - * @returns {Number} - */ + getSize() { + return 9; + } -FilterAddPacket.prototype.getSize = function getSize() { - return encoding.sizeVarBytes(this.data); -}; + /** + * Serialize sendcmpct packet to writer. + * @param {BufferWriter} bw + */ -/** - * Serialize filteradd packet to writer. - * @returns {BufferWriter} bw - */ + toWriter(bw) { + bw.writeU8(this.mode); + bw.writeU64(this.version); + return bw; + } -FilterAddPacket.prototype.toWriter = function toWriter(bw) { - bw.writeVarBytes(this.data); - return bw; -}; + /** + * Serialize sendcmpct packet. + * @returns {Buffer} + */ -/** - * Serialize filteradd packet. - * @returns {Buffer} - */ + toRaw() { + return this.toWriter(bio.write(9)).render(); + } -FilterAddPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + fromReader(br) { + this.mode = br.readU8(); + this.version = br.readU64(); + return this; + } -FilterAddPacket.prototype.fromReader = function fromReader(br) { - this.data = br.readVarBytes(); - return this; -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -FilterAddPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + /** + * Instantiate sendcmpct packet from buffer reader. + * @param {BufferReader} br + * @returns {SendCmpctPacket} + */ -/** - * Instantiate filteradd packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {FilterAddPacket} - */ + static fromReader(br) { + return new this().fromReader(br); + } -FilterAddPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new FilterAddPacket().fromRaw(data); -}; + /** + * Instantiate sendcmpct packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {SendCmpctPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Represents a `filterclear` packet. - * @constructor + * CmpctBlock Packet + * @extends Packet + * @property {Block} block + * @property {Boolean} witness */ -function FilterClearPacket() { - if (!(this instanceof FilterClearPacket)) - return new FilterClearPacket(); +class CmpctBlockPacket extends Packet { + /** + * Create a `cmpctblock` packet. + * @constructor + * @param {Block|null} block + * @param {Boolean|null} witness + */ - Packet.call(this); -} - -Object.setPrototypeOf(FilterClearPacket.prototype, Packet.prototype); + constructor(block, witness) { + super(); -FilterClearPacket.prototype.cmd = 'filterclear'; -FilterClearPacket.prototype.type = exports.types.FILTERCLEAR; + this.cmd = 'cmpctblock'; + this.type = exports.types.CMPCTBLOCK; -/** - * Instantiate filterclear packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {FilterClearPacket} - */ + this.block = block || new bip152.CompactBlock(); + this.witness = witness || false; + } -FilterClearPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new FilterClearPacket().fromRaw(data); -}; + /** + * Serialize cmpctblock packet. + * @returns {Buffer} + */ -/** - * Represents a `merkleblock` packet. - * @constructor - * @param {MerkleBlock?} block - * @property {MerkleBlock} block - */ + getSize() { + if (this.witness) + return this.block.getSize(true); + return this.block.getSize(false); + } -function MerkleBlockPacket(block) { - if (!(this instanceof MerkleBlockPacket)) - return new MerkleBlockPacket(block); + /** + * Serialize cmpctblock packet to writer. + * @param {BufferWriter} bw + */ - Packet.call(this); + toWriter(bw) { + if (this.witness) + return this.block.toWriter(bw); + return this.block.toNormalWriter(bw); + } - this.block = block || new MerkleBlock(); -} + /** + * Serialize cmpctblock packet. + * @returns {Buffer} + */ -Object.setPrototypeOf(MerkleBlockPacket.prototype, Packet.prototype); + toRaw() { + if (this.witness) + return this.block.toRaw(); + return this.block.toNormal(); + } -MerkleBlockPacket.prototype.cmd = 'merkleblock'; -MerkleBlockPacket.prototype.type = exports.types.MERKLEBLOCK; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -/** - * Get serialization size. - * @returns {Number} - */ + fromReader(br) { + this.block.fromReader(br); + return this; + } -MerkleBlockPacket.prototype.getSize = function getSize() { - return this.block.getSize(); -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -/** - * Serialize merkleblock packet to writer. - * @param {BufferWriter} bw - */ + fromRaw(data) { + this.block.fromRaw(data); + return this; + } -MerkleBlockPacket.prototype.toWriter = function toWriter(bw) { - return this.block.toWriter(bw); -}; + /** + * Instantiate cmpctblock packet from buffer reader. + * @param {BufferReader} br + * @returns {CmpctBlockPacket} + */ -/** - * Serialize merkleblock packet. - * @returns {Buffer} - */ + static fromReader(br) { + return new this().fromRaw(br); + } -MerkleBlockPacket.prototype.toRaw = function toRaw() { - return this.block.toRaw(); -}; + /** + * Instantiate cmpctblock packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {CmpctBlockPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br + * GetBlockTxn Packet + * @extends Packet + * @property {TXRequest} request */ -MerkleBlockPacket.prototype.fromReader = function fromReader(br) { - this.block.fromReader(br); - return this; -}; +class GetBlockTxnPacket extends Packet { + /** + * Create a `getblocktxn` packet. + * @constructor + * @param {TXRequest?} request + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + constructor(request) { + super(); -MerkleBlockPacket.prototype.fromRaw = function fromRaw(data) { - this.block.fromRaw(data); - return this; -}; + this.cmd = 'getblocktxn'; + this.type = exports.types.GETBLOCKTXN; -/** - * Instantiate merkleblock packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {MerkleBlockPacket} - */ + this.request = request || new bip152.TXRequest(); + } -MerkleBlockPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new MerkleBlockPacket().fromRaw(data); -}; + /** + * Get serialization size. + * @returns {Number} + */ -/** - * Represents a `feefilter` packet. - * @constructor - * @param {Rate?} rate - * @property {Rate} rate - */ + getSize() { + return this.request.getSize(); + } -function FeeFilterPacket(rate) { - if (!(this instanceof FeeFilterPacket)) - return new FeeFilterPacket(rate); + /** + * Serialize getblocktxn packet to writer. + * @param {BufferWriter} bw + */ - Packet.call(this); + toWriter(bw) { + return this.request.toWriter(bw); + } - this.rate = rate || 0; -} + /** + * Serialize getblocktxn packet. + * @returns {Buffer} + */ -Object.setPrototypeOf(FeeFilterPacket.prototype, Packet.prototype); + toRaw() { + return this.request.toRaw(); + } -FeeFilterPacket.prototype.cmd = 'feefilter'; -FeeFilterPacket.prototype.type = exports.types.FEEFILTER; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -/** - * Get serialization size. - * @returns {Number} - */ + fromReader(br) { + this.request.fromReader(br); + return this; + } -FeeFilterPacket.prototype.getSize = function getSize() { - return 8; -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -/** - * Serialize feefilter packet to writer. - * @param {BufferWriter} bw - */ + fromRaw(data) { + this.request.fromRaw(data); + return this; + } -FeeFilterPacket.prototype.toWriter = function toWriter(bw) { - bw.writeI64(this.rate); - return bw; -}; + /** + * Instantiate getblocktxn packet from buffer reader. + * @param {BufferReader} br + * @returns {GetBlockTxnPacket} + */ -/** - * Serialize feefilter packet. - * @returns {Buffer} - */ + static fromReader(br) { + return new this().fromReader(br); + } -FeeFilterPacket.prototype.toRaw = function toRaw() { - return this.toWriter(new StaticWriter(8)).render(); -}; + /** + * Instantiate getblocktxn packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {GetBlockTxnPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br + * BlockTxn Packet + * @extends Packet + * @property {TXResponse} response + * @property {Boolean} witness */ -FeeFilterPacket.prototype.fromReader = function fromReader(br) { - this.rate = br.readI64(); - return this; -}; +class BlockTxnPacket extends Packet { + /** + * Create a `blocktxn` packet. + * @constructor + * @param {TXResponse?} response + * @param {Boolean?} witness + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + constructor(response, witness) { + super(); -FeeFilterPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + this.cmd = 'blocktxn'; + this.type = exports.types.BLOCKTXN; -/** - * Instantiate feefilter packet from buffer reader. - * @param {BufferReader} br - * @returns {FeeFilterPacket} - */ + this.response = response || new bip152.TXResponse(); + this.witness = witness || false; + } -FeeFilterPacket.fromReader = function fromReader(br) { - return new FeeFilterPacket().fromReader(br); -}; + /** + * Get serialization size. + * @returns {Number} + */ -/** - * Instantiate feefilter packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {FeeFilterPacket} - */ + getSize() { + if (this.witness) + return this.response.getSize(true); + return this.response.getSize(false); + } -FeeFilterPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new FeeFilterPacket().fromRaw(data); -}; + /** + * Serialize blocktxn packet to writer. + * @param {BufferWriter} bw + */ -/** - * Represents a `sendcmpct` packet. - * @constructor - * @param {Number|null} mode - * @param {Number|null} version - * @property {Number} mode - * @property {Number} version - */ + toWriter(bw) { + if (this.witness) + return this.response.toWriter(bw); + return this.response.toNormalWriter(bw); + } -function SendCmpctPacket(mode, version) { - if (!(this instanceof SendCmpctPacket)) - return new SendCmpctPacket(mode, version); + /** + * Serialize blocktxn packet. + * @returns {Buffer} + */ - Packet.call(this); + toRaw() { + if (this.witness) + return this.response.toRaw(); + return this.response.toNormal(); + } - this.mode = mode || 0; - this.version = version || 1; -} + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -Object.setPrototypeOf(SendCmpctPacket.prototype, Packet.prototype); + fromReader(br) { + this.response.fromReader(br); + return this; + } -SendCmpctPacket.prototype.cmd = 'sendcmpct'; -SendCmpctPacket.prototype.type = exports.types.SENDCMPCT; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -/** - * Get serialization size. - * @returns {Number} - */ + fromRaw(data) { + this.response.fromRaw(data); + return this; + } -SendCmpctPacket.prototype.getSize = function getSize() { - return 9; -}; + /** + * Instantiate blocktxn packet from buffer reader. + * @param {BufferReader} br + * @returns {BlockTxnPacket} + */ -/** - * Serialize sendcmpct packet to writer. - * @param {BufferWriter} bw - */ + static fromReader(br) { + return new this().fromReader(br); + } -SendCmpctPacket.prototype.toWriter = function toWriter(bw) { - bw.writeU8(this.mode); - bw.writeU64(this.version); - return bw; -}; + /** + * Instantiate blocktxn packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {BlockTxnPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Serialize sendcmpct packet. - * @returns {Buffer} + * Unknown Packet + * @extends Packet + * @property {String} cmd + * @property {Buffer} data */ -SendCmpctPacket.prototype.toRaw = function toRaw() { - return this.toWriter(new StaticWriter(9)).render(); -}; +class UnknownPacket extends Packet { + /** + * Create an unknown packet. + * @constructor + * @param {String|null} cmd + * @param {Buffer|null} data + */ -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + constructor(cmd, data) { + super(); -SendCmpctPacket.prototype.fromReader = function fromReader(br) { - this.mode = br.readU8(); - this.version = br.readU64(); - return this; -}; + this.cmd = cmd; + this.type = exports.types.UNKNOWN; + this.data = data; + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + /** + * Get serialization size. + * @returns {Number} + */ -SendCmpctPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + getSize() { + return this.data.length; + } -/** - * Instantiate sendcmpct packet from buffer reader. - * @param {BufferReader} br - * @returns {SendCmpctPacket} - */ + /** + * Serialize unknown packet to writer. + * @param {BufferWriter} bw + */ -SendCmpctPacket.fromReader = function fromReader(br) { - return new SendCmpctPacket().fromReader(br); -}; + toWriter(bw) { + bw.writeBytes(this.data); + return bw; + } -/** - * Instantiate sendcmpct packet from buffer reader. - * @param {BufferReader} br - * @returns {SendCmpctPacket} - */ + /** + * Serialize unknown packet. + * @returns {Buffer} + */ -SendCmpctPacket.fromReader = function fromReader(br) { - return new SendCmpctPacket().fromReader(br); -}; - -/** - * Instantiate sendcmpct packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {SendCmpctPacket} - */ - -SendCmpctPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new SendCmpctPacket().fromRaw(data); -}; - -/** - * Represents a `cmpctblock` packet. - * @constructor - * @param {Block|null} block - * @param {Boolean|null} witness - * @property {Block} block - * @property {Boolean} witness - */ - -function CmpctBlockPacket(block, witness) { - if (!(this instanceof CmpctBlockPacket)) - return new CmpctBlockPacket(block, witness); - - Packet.call(this); - - this.block = block || new bip152.CompactBlock(); - this.witness = witness || false; -} - -Object.setPrototypeOf(CmpctBlockPacket.prototype, Packet.prototype); - -CmpctBlockPacket.prototype.cmd = 'cmpctblock'; -CmpctBlockPacket.prototype.type = exports.types.CMPCTBLOCK; - -/** - * Serialize cmpctblock packet. - * @returns {Buffer} - */ - -CmpctBlockPacket.prototype.getSize = function getSize() { - if (this.witness) - return this.block.getSize(true); - return this.block.getSize(false); -}; - -/** - * Serialize cmpctblock packet to writer. - * @param {BufferWriter} bw - */ - -CmpctBlockPacket.prototype.toWriter = function toWriter(bw) { - if (this.witness) - return this.block.toWriter(bw); - return this.block.toNormalWriter(bw); -}; - -/** - * Serialize cmpctblock packet. - * @returns {Buffer} - */ - -CmpctBlockPacket.prototype.toRaw = function toRaw() { - if (this.witness) - return this.block.toRaw(); - return this.block.toNormal(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -CmpctBlockPacket.prototype.fromReader = function fromReader(br) { - this.block.fromReader(br); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -CmpctBlockPacket.prototype.fromRaw = function fromRaw(data) { - this.block.fromRaw(data); - return this; -}; - -/** - * Instantiate cmpctblock packet from buffer reader. - * @param {BufferReader} br - * @returns {CmpctBlockPacket} - */ - -CmpctBlockPacket.fromReader = function fromReader(br) { - return new CmpctBlockPacket().fromRaw(br); -}; - -/** - * Instantiate cmpctblock packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {CmpctBlockPacket} - */ - -CmpctBlockPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new CmpctBlockPacket().fromRaw(data); -}; - -/** - * Represents a `getblocktxn` packet. - * @constructor - * @param {TXRequest?} request - * @property {TXRequest} request - */ - -function GetBlockTxnPacket(request) { - if (!(this instanceof GetBlockTxnPacket)) - return new GetBlockTxnPacket(request); - - Packet.call(this); - - this.request = request || new bip152.TXRequest(); -} - -Object.setPrototypeOf(GetBlockTxnPacket.prototype, Packet.prototype); - -GetBlockTxnPacket.prototype.cmd = 'getblocktxn'; -GetBlockTxnPacket.prototype.type = exports.types.GETBLOCKTXN; - -/** - * Get serialization size. - * @returns {Number} - */ - -GetBlockTxnPacket.prototype.getSize = function getSize() { - return this.request.getSize(); -}; - -/** - * Serialize getblocktxn packet to writer. - * @param {BufferWriter} bw - */ - -GetBlockTxnPacket.prototype.toWriter = function toWriter(bw) { - return this.request.toWriter(bw); -}; - -/** - * Serialize getblocktxn packet. - * @returns {Buffer} - */ - -GetBlockTxnPacket.prototype.toRaw = function toRaw() { - return this.request.toRaw(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -GetBlockTxnPacket.prototype.fromReader = function fromReader(br) { - this.request.fromReader(br); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -GetBlockTxnPacket.prototype.fromRaw = function fromRaw(data) { - this.request.fromRaw(data); - return this; -}; - -/** - * Instantiate getblocktxn packet from buffer reader. - * @param {BufferReader} br - * @returns {GetBlockTxnPacket} - */ - -GetBlockTxnPacket.fromReader = function fromReader(br) { - return new GetBlockTxnPacket().fromReader(br); -}; - -/** - * Instantiate getblocktxn packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {GetBlockTxnPacket} - */ - -GetBlockTxnPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new GetBlockTxnPacket().fromRaw(data); -}; - -/** - * Represents a `blocktxn` packet. - * @constructor - * @param {TXResponse?} response - * @param {Boolean?} witness - * @property {TXResponse} response - * @property {Boolean} witness - */ - -function BlockTxnPacket(response, witness) { - if (!(this instanceof BlockTxnPacket)) - return new BlockTxnPacket(response, witness); - - Packet.call(this); - - this.response = response || new bip152.TXResponse(); - this.witness = witness || false; -} - -Object.setPrototypeOf(BlockTxnPacket.prototype, Packet.prototype); - -BlockTxnPacket.prototype.cmd = 'blocktxn'; -BlockTxnPacket.prototype.type = exports.types.BLOCKTXN; - -/** - * Get serialization size. - * @returns {Number} - */ - -BlockTxnPacket.prototype.getSize = function getSize() { - if (this.witness) - return this.response.getSize(true); - return this.response.getSize(false); -}; - -/** - * Serialize blocktxn packet to writer. - * @param {BufferWriter} bw - */ - -BlockTxnPacket.prototype.toWriter = function toWriter(bw) { - if (this.witness) - return this.response.toWriter(bw); - return this.response.toNormalWriter(bw); -}; - -/** - * Serialize blocktxn packet. - * @returns {Buffer} - */ - -BlockTxnPacket.prototype.toRaw = function toRaw() { - if (this.witness) - return this.response.toRaw(); - return this.response.toNormal(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -BlockTxnPacket.prototype.fromReader = function fromReader(br) { - this.response.fromReader(br); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -BlockTxnPacket.prototype.fromRaw = function fromRaw(data) { - this.response.fromRaw(data); - return this; -}; - -/** - * Instantiate blocktxn packet from buffer reader. - * @param {BufferReader} br - * @returns {BlockTxnPacket} - */ - -BlockTxnPacket.fromReader = function fromReader(br) { - return new BlockTxnPacket().fromReader(br); -}; - -/** - * Instantiate blocktxn packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {BlockTxnPacket} - */ - -BlockTxnPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new BlockTxnPacket().fromRaw(data); -}; - -/** - * Represents a `encinit` packet. - * @constructor - * @param {Buffer|null} publicKey - * @param {Number|null} cipher - * @property {Buffer} publicKey - * @property {Number} cipher - */ - -function EncinitPacket(publicKey, cipher) { - if (!(this instanceof EncinitPacket)) - return new EncinitPacket(publicKey, cipher); - - Packet.call(this); - - this.publicKey = publicKey || encoding.ZERO_KEY; - this.cipher = cipher || 0; -} - -Object.setPrototypeOf(EncinitPacket.prototype, Packet.prototype); - -EncinitPacket.prototype.cmd = 'encinit'; -EncinitPacket.prototype.type = exports.types.ENCINIT; - -/** - * Get serialization size. - * @returns {Number} - */ - -EncinitPacket.prototype.getSize = function getSize() { - return 34; -}; - -/** - * Serialize encinit packet to writer. - * @param {BufferWriter} bw - */ - -EncinitPacket.prototype.toWriter = function toWriter(bw) { - bw.writeBytes(this.publicKey); - bw.writeU8(this.cipher); - return bw; -}; - -/** - * Serialize encinit packet. - * @returns {Buffer} - */ - -EncinitPacket.prototype.toRaw = function toRaw() { - return this.toWriter(new StaticWriter(34)).render(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -EncinitPacket.prototype.fromReader = function fromReader(br) { - this.publicKey = br.readBytes(33); - this.cipher = br.readU8(); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -EncinitPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate getblocks packet from buffer reader. - * @param {BufferReader} br - * @returns {EncinitPacket} - */ - -EncinitPacket.fromReader = function fromReader(br) { - return new EncinitPacket().fromReader(br); -}; - -/** - * Instantiate getblocks packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {EncinitPacket} - */ - -EncinitPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new EncinitPacket().fromRaw(data); -}; - -/** - * Represents a `encack` packet. - * @constructor - * @param {Buffer?} publicKey - * @property {Buffer} publicKey - */ - -function EncackPacket(publicKey) { - if (!(this instanceof EncackPacket)) - return new EncackPacket(publicKey); - - Packet.call(this); - - this.publicKey = publicKey || encoding.ZERO_KEY; -} - -Object.setPrototypeOf(EncackPacket.prototype, Packet.prototype); - -EncackPacket.prototype.cmd = 'encack'; -EncackPacket.prototype.type = exports.types.ENCACK; - -/** - * Get serialization size. - * @returns {Number} - */ - -EncackPacket.prototype.getSize = function getSize() { - return 33; -}; - -/** - * Serialize encack packet to writer. - * @param {BufferWriter} bw - */ - -EncackPacket.prototype.toWriter = function toWriter(bw) { - bw.writeBytes(this.publicKey); - return bw; -}; - -/** - * Serialize encack packet. - * @returns {Buffer} - */ - -EncackPacket.prototype.toRaw = function toRaw() { - return this.publicKey; -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -EncackPacket.prototype.fromReader = function fromReader(br) { - this.publicKey = br.readBytes(33); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -EncackPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate encack packet from buffer reader. - * @param {BufferReader} br - * @returns {EncackPacket} - */ - -EncackPacket.fromReader = function fromReader(br) { - return new EncackPacket().fromReader(br); -}; - -/** - * Instantiate encack packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {EncackPacket} - */ - -EncackPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new EncackPacket().fromRaw(data); -}; - -/** - * Represents a `authchallenge` packet. - * @constructor - * @param {Buffer?} hash - * @property {Buffer} hash - */ - -function AuthChallengePacket(hash) { - if (!(this instanceof AuthChallengePacket)) - return new AuthChallengePacket(hash); - - Packet.call(this); - - this.hash = hash || encoding.ZERO_HASH; -} - -Object.setPrototypeOf(AuthChallengePacket.prototype, Packet.prototype); - -AuthChallengePacket.prototype.cmd = 'authchallenge'; -AuthChallengePacket.prototype.type = exports.types.AUTHCHALLENGE; - -/** - * Get serialization size. - * @returns {Number} - */ - -EncackPacket.prototype.getSize = function getSize() { - return 32; -}; - -/** - * Serialize authchallenge packet to writer. - * @param {BufferWriter} bw - */ - -AuthChallengePacket.prototype.toWriter = function toWriter(bw) { - bw.writeBytes(this.hash); - return bw; -}; - -/** - * Serialize authchallenge packet. - * @returns {Buffer} - */ - -AuthChallengePacket.prototype.toRaw = function toRaw() { - return this.hash; -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -AuthChallengePacket.prototype.fromReader = function fromReader(br) { - this.hash = br.readHash(); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -AuthChallengePacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate authchallenge packet from buffer reader. - * @param {BufferReader} br - * @returns {AuthChallengePacket} - */ - -AuthChallengePacket.fromReader = function fromReader(br) { - return new AuthChallengePacket().fromReader(br); -}; - -/** - * Instantiate authchallenge packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {AuthChallengePacket} - */ - -AuthChallengePacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new AuthChallengePacket().fromRaw(data); -}; - -/** - * Represents a `authreply` packet. - * @constructor - * @param {Buffer?} signature - * @property {Buffer} signature - */ - -function AuthReplyPacket(signature) { - if (!(this instanceof AuthReplyPacket)) - return new AuthReplyPacket(signature); - - Packet.call(this); - - this.signature = signature || encoding.ZERO_SIG64; -} - -Object.setPrototypeOf(AuthReplyPacket.prototype, Packet.prototype); - -AuthReplyPacket.prototype.cmd = 'authreply'; -AuthReplyPacket.prototype.type = exports.types.AUTHREPLY; - -/** - * Get serialization size. - * @returns {Number} - */ - -AuthReplyPacket.prototype.getSize = function getSize() { - return 64; -}; - -/** - * Serialize authreply packet to writer. - * @param {BufferWriter} bw - */ - -AuthReplyPacket.prototype.toWriter = function toWriter(bw) { - bw.writeBytes(this.signature); - return bw; -}; - -/** - * Serialize authreply packet. - * @returns {Buffer} - */ - -AuthReplyPacket.prototype.toRaw = function toRaw() { - return this.signature; -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -AuthReplyPacket.prototype.fromReader = function fromReader(br) { - this.signature = br.readBytes(64); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -AuthReplyPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate authreply packet from buffer reader. - * @param {BufferReader} br - * @returns {AuthReplyPacket} - */ - -AuthReplyPacket.fromReader = function fromReader(br) { - return new AuthReplyPacket().fromReader(br); -}; - -/** - * Instantiate authreply packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {AuthReplyPacket} - */ - -AuthReplyPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new AuthReplyPacket().fromRaw(data); -}; - -/** - * Represents a `authpropose` packet. - * @constructor - * @param {Hash?} hash - * @property {Hash} hash - */ - -function AuthProposePacket(hash) { - if (!(this instanceof AuthProposePacket)) - return new AuthProposePacket(hash); - - Packet.call(this); - - this.hash = hash || encoding.ZERO_HASH; -} - -Object.setPrototypeOf(AuthProposePacket.prototype, Packet.prototype); - -AuthProposePacket.prototype.cmd = 'authpropose'; -AuthProposePacket.prototype.type = exports.types.AUTHPROPOSE; - -/** - * Get serialization size. - * @returns {Number} - */ - -AuthProposePacket.prototype.getSize = function getSize() { - return 32; -}; - -/** - * Serialize authpropose packet to writer. - * @param {BufferWriter} bw - */ - -AuthProposePacket.prototype.toWriter = function toWriter(bw) { - bw.writeBytes(this.hash); - return bw; -}; - -/** - * Serialize authpropose packet. - * @returns {Buffer} - */ - -AuthProposePacket.prototype.toRaw = function toRaw() { - return this.hash; -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -AuthProposePacket.prototype.fromReader = function fromReader(br) { - this.hash = br.readHash(); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -AuthProposePacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate authpropose packet from buffer reader. - * @param {BufferReader} br - * @returns {AuthProposePacket} - */ - -AuthProposePacket.fromReader = function fromReader(br) { - return new AuthProposePacket().fromReader(br); -}; - -/** - * Instantiate authpropose packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {AuthProposePacket} - */ - -AuthProposePacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new AuthProposePacket().fromRaw(data); -}; - -/** - * Represents an unknown packet. - * @constructor - * @param {String|null} cmd - * @param {Buffer|null} data - * @property {String} cmd - * @property {Buffer} data - */ - -function UnknownPacket(cmd, data) { - if (!(this instanceof UnknownPacket)) - return new UnknownPacket(cmd, data); + toRaw() { + return this.data; + } - Packet.call(this); + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(cmd, data) { + assert(Buffer.isBuffer(data)); + this.cmd = cmd; + this.data = data; + return this; + } - this.cmd = cmd; - this.data = data; + /** + * Instantiate unknown packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {UnknownPacket} + */ + + static fromRaw(cmd, data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(cmd, data); + } } -Object.setPrototypeOf(UnknownPacket.prototype, Packet.prototype); - -UnknownPacket.prototype.type = exports.types.UNKNOWN; - -/** - * Get serialization size. - * @returns {Number} - */ - -UnknownPacket.prototype.getSize = function getSize() { - return this.data.length; -}; - -/** - * Serialize unknown packet to writer. - * @param {BufferWriter} bw - */ - -UnknownPacket.prototype.toWriter = function toWriter(bw) { - bw.writeBytes(this.data); - return bw; -}; - -/** - * Serialize unknown packet. - * @returns {Buffer} - */ - -UnknownPacket.prototype.toRaw = function toRaw() { - return this.data; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -UnknownPacket.prototype.fromRaw = function fromRaw(cmd, data) { - assert(Buffer.isBuffer(data)); - this.cmd = cmd; - this.data = data; - return this; -}; - -/** - * Instantiate unknown packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {UnknownPacket} - */ - -UnknownPacket.fromRaw = function fromRaw(cmd, data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new UnknownPacket().fromRaw(cmd, data); -}; - /** * Parse a payload. * @param {String} cmd @@ -3161,16 +2770,6 @@ exports.fromRaw = function fromRaw(cmd, data) { return GetBlockTxnPacket.fromRaw(data); case 'blocktxn': return BlockTxnPacket.fromRaw(data); - case 'encinit': - return EncinitPacket.fromRaw(data); - case 'encack': - return EncackPacket.fromRaw(data); - case 'authchallenge': - return AuthChallengePacket.fromRaw(data); - case 'authreply': - return AuthReplyPacket.fromRaw(data); - case 'authpropose': - return AuthProposePacket.fromRaw(data); default: return UnknownPacket.fromRaw(cmd, data); } @@ -3207,9 +2806,4 @@ exports.SendCmpctPacket = SendCmpctPacket; exports.CmpctBlockPacket = CmpctBlockPacket; exports.GetBlockTxnPacket = GetBlockTxnPacket; exports.BlockTxnPacket = BlockTxnPacket; -exports.EncinitPacket = EncinitPacket; -exports.EncackPacket = EncackPacket; -exports.AuthChallengePacket = AuthChallengePacket; -exports.AuthReplyPacket = AuthReplyPacket; -exports.AuthProposePacket = AuthProposePacket; exports.UnknownPacket = UnknownPacket; diff --git a/lib/net/parser.js b/lib/net/parser.js index 94d1ff2f0..accc1ce33 100644 --- a/lib/net/parser.js +++ b/lib/net/parser.js @@ -9,178 +9,187 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const EventEmitter = require('events'); +const {format} = require('util'); const Network = require('../protocol/network'); -const util = require('../utils/util'); -const digest = require('../crypto/digest'); +const hash256 = require('bcrypto/lib/hash256'); const common = require('./common'); const packets = require('./packets'); /** - * Protocol packet parser + * Protocol Message Parser * @alias module:net.Parser - * @constructor - * @param {Network} network + * @extends EventEmitter * @emits Parser#error * @emits Parser#packet */ -function Parser(network) { - if (!(this instanceof Parser)) - return new Parser(network); +class Parser extends EventEmitter { + /** + * Create a parser. + * @constructor + * @param {Network} network + */ - EventEmitter.call(this); + constructor(network) { + super(); - this.network = Network.get(network); + this.network = Network.get(network); - this.pending = []; - this.total = 0; - this.waiting = 24; - this.header = null; -} + this.pending = []; + this.total = 0; + this.waiting = 24; + this.header = null; + } -Object.setPrototypeOf(Parser.prototype, EventEmitter.prototype); + /** + * Emit an error. + * @private + * @param {...String} msg + */ -/** - * Emit an error. - * @private - * @param {...String} msg - */ + error() { + const msg = format.apply(null, arguments); + this.emit('error', new Error(msg)); + } -Parser.prototype.error = function error() { - const msg = util.fmt.apply(util, arguments); - this.emit('error', new Error(msg)); -}; + /** + * Feed data to the parser. + * @param {Buffer} data + */ -/** - * Feed data to the parser. - * @param {Buffer} data - */ + feed(data) { + this.total += data.length; + this.pending.push(data); -Parser.prototype.feed = function feed(data) { - this.total += data.length; - this.pending.push(data); - - while (this.total >= this.waiting) { - const chunk = Buffer.allocUnsafe(this.waiting); - let off = 0; - - while (off < chunk.length) { - const len = this.pending[0].copy(chunk, off); - if (len === this.pending[0].length) - this.pending.shift(); - else - this.pending[0] = this.pending[0].slice(len); - off += len; - } + while (this.total >= this.waiting) { + const chunk = Buffer.allocUnsafe(this.waiting); + let off = 0; + + while (off < chunk.length) { + const len = this.pending[0].copy(chunk, off); + if (len === this.pending[0].length) + this.pending.shift(); + else + this.pending[0] = this.pending[0].slice(len); + off += len; + } - assert.strictEqual(off, chunk.length); + assert.strictEqual(off, chunk.length); - this.total -= chunk.length; - this.parse(chunk); + this.total -= chunk.length; + this.parse(chunk); + } } -}; -/** - * Parse a fully-buffered chunk. - * @param {Buffer} chunk - */ + /** + * Parse a fully-buffered chunk. + * @param {Buffer} chunk + */ -Parser.prototype.parse = function parse(data) { - assert(data.length <= common.MAX_MESSAGE); + parse(data) { + assert(data.length <= common.MAX_MESSAGE); - if (!this.header) { - this.header = this.parseHeader(data); - return; - } + if (!this.header) { + this.header = this.parseHeader(data); + return; + } - const checksum = digest.hash256(data).readUInt32LE(0, true); + const hash = hash256.digest(data); + const checksum = hash.readUInt32LE(0, true); - if (checksum !== this.header.checksum) { - this.waiting = 24; - this.header = null; - this.error('Invalid checksum: %s.', util.hex32(checksum)); - return; - } + if (checksum !== this.header.checksum) { + this.waiting = 24; + this.header = null; + this.error('Invalid checksum: %s.', checksum.toString(16)); + return; + } + + let payload; + try { + payload = this.parsePayload(this.header.cmd, data); + } catch (e) { + this.waiting = 24; + this.header = null; + this.emit('error', e); + return; + } - let payload; - try { - payload = this.parsePayload(this.header.cmd, data); - } catch (e) { this.waiting = 24; this.header = null; - this.emit('error', e); - return; - } - - this.waiting = 24; - this.header = null; - this.emit('packet', payload); -}; + this.emit('packet', payload); + } -/** - * Parse buffered packet header. - * @param {Buffer} data - Header. - * @returns {Header} - */ + /** + * Parse buffered packet header. + * @param {Buffer} data - Header. + * @returns {Header} + */ -Parser.prototype.parseHeader = function parseHeader(data) { - const magic = data.readUInt32LE(0, true); + parseHeader(data) { + const magic = data.readUInt32LE(0, true); - if (magic !== this.network.magic) { - this.error('Invalid magic value: %s.', util.hex32(magic)); - return null; - } + if (magic !== this.network.magic) { + this.error('Invalid magic value: %s.', magic.toString(16)); + return null; + } - // Count length of the cmd. - let i = 0; - for (; data[i + 4] !== 0 && i < 12; i++); + // Count length of the cmd. + let i = 0; + for (; data[i + 4] !== 0 && i < 12; i++); - if (i === 12) { - this.error('Non NULL-terminated command.'); - return null; - } + if (i === 12) { + this.error('Non NULL-terminated command.'); + return null; + } - const cmd = data.toString('ascii', 4, 4 + i); + const cmd = data.toString('ascii', 4, 4 + i); - const size = data.readUInt32LE(16, true); + const size = data.readUInt32LE(16, true); - if (size > common.MAX_MESSAGE) { - this.waiting = 24; - this.error('Packet length too large: %dmb.', util.mb(size)); - return null; - } + if (size > common.MAX_MESSAGE) { + this.waiting = 24; + this.error('Packet length too large: %d.', size); + return null; + } - this.waiting = size; + this.waiting = size; - const checksum = data.readUInt32LE(20, true); + const checksum = data.readUInt32LE(20, true); - return new Header(cmd, size, checksum); -}; + return new Header(cmd, size, checksum); + } -/** - * Parse a payload. - * @param {String} cmd - Packet type. - * @param {Buffer} data - Payload. - * @returns {Object} - */ + /** + * Parse a payload. + * @param {String} cmd - Packet type. + * @param {Buffer} data - Payload. + * @returns {Object} + */ -Parser.prototype.parsePayload = function parsePayload(cmd, data) { - return packets.fromRaw(cmd, data); -}; + parsePayload(cmd, data) { + return packets.fromRaw(cmd, data); + } +} /** * Packet Header - * @constructor * @ignore */ -function Header(cmd, size, checksum) { - this.cmd = cmd; - this.size = size; - this.checksum = checksum; +class Header { + /** + * Create a header. + * @constructor + */ + + constructor(cmd, size, checksum) { + this.cmd = cmd; + this.size = size; + this.checksum = checksum; + } } /* diff --git a/lib/net/peer.js b/lib/net/peer.js index b9f1fd27b..aa77cf9e9 100644 --- a/lib/net/peer.js +++ b/lib/net/peer.js @@ -7,37 +7,35 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const EventEmitter = require('events'); -const util = require('../utils/util'); -const co = require('../utils/co'); +const {Lock} = require('bmutex'); +const {format} = require('util'); +const tcp = require('btcp'); +const dns = require('bdns'); +const Logger = require('blgr'); +const {RollingFilter} = require('bfilter'); +const {BufferMap} = require('buffer-map'); const Parser = require('./parser'); const Framer = require('./framer'); const packets = require('./packets'); const consensus = require('../protocol/consensus'); const common = require('./common'); const InvItem = require('../primitives/invitem'); -const Lock = require('../utils/lock'); -const RollingFilter = require('../utils/rollingfilter'); -const BIP151 = require('./bip151'); -const BIP150 = require('./bip150'); const BIP152 = require('./bip152'); const Block = require('../primitives/block'); const TX = require('../primitives/tx'); -const encoding = require('../utils/encoding'); -const NetAddress = require('../primitives/netaddress'); +const NetAddress = require('./netaddress'); const Network = require('../protocol/network'); -const Logger = require('../node/logger'); -const tcp = require('./tcp'); const services = common.services; const invTypes = InvItem.types; const packetTypes = packets.types; +const {inspectSymbol} = require('../utils'); /** - * Represents a remote peer. + * Represents a network peer. * @alias module:net.Peer - * @constructor - * @param {PeerOptions} options + * @extends EventEmitter * @property {net.Socket} socket * @property {NetAddress} address * @property {Parser} parser @@ -61,2401 +59,2176 @@ const packetTypes = packets.types; * sent (unix time). * @property {Number} minPing - Lowest ping time seen. * @property {Number} banScore - * @emits Peer#ack */ -function Peer(options) { - if (!(this instanceof Peer)) - return new Peer(options); - - EventEmitter.call(this); - - this.options = options; - this.network = this.options.network; - this.logger = this.options.logger.context('peer'); - this.locker = new Lock(); - - this.parser = new Parser(this.network); - this.framer = new Framer(this.network); - - this.id = -1; - this.socket = null; - this.opened = false; - this.outbound = false; - this.loader = false; - this.address = new NetAddress(); - this.local = new NetAddress(); - this.connected = false; - this.destroyed = false; - this.ack = false; - this.handshake = false; - this.time = 0; - this.lastSend = 0; - this.lastRecv = 0; - this.drainSize = 0; - this.drainQueue = []; - this.banScore = 0; - this.invQueue = []; - this.onPacket = null; - - this.next = null; - this.prev = null; - - this.version = -1; - this.services = 0; - this.height = -1; - this.agent = null; - this.noRelay = false; - this.preferHeaders = false; - this.hashContinue = null; - this.spvFilter = null; - this.feeRate = -1; - this.bip151 = null; - this.bip150 = null; - this.compactMode = -1; - this.compactWitness = false; - this.merkleBlock = null; - this.merkleTime = -1; - this.merkleMatches = 0; - this.merkleMap = null; - this.syncing = false; - this.sentAddr = false; - this.sentGetAddr = false; - this.challenge = null; - this.lastPong = -1; - this.lastPing = -1; - this.minPing = -1; - this.blockTime = -1; - - this.bestHash = null; - this.bestHeight = -1; - - this.connectTimeout = null; - this.pingTimer = null; - this.invTimer = null; - this.stallTimer = null; - - this.addrFilter = new RollingFilter(5000, 0.001); - this.invFilter = new RollingFilter(50000, 0.000001); - - this.blockMap = new Map(); - this.txMap = new Map(); - this.responseMap = new Map(); - this.compactBlocks = new Map(); - - this._init(); -} +class Peer extends EventEmitter { + /** + * Create a peer. + * @alias module:net.Peer + * @constructor + * @param {PeerOptions|PoolOptions} options + */ + + constructor(options) { + super(); + + this.options = options; + this.network = this.options.network; + this.logger = this.options.logger.context('peer'); + this.locker = new Lock(); + + this.parser = new Parser(this.network); + this.framer = new Framer(this.network); + + this.id = -1; + this.socket = null; + this.opened = false; + this.outbound = false; + this.loader = false; + this.address = new NetAddress(); + this.local = new NetAddress(); + this.name = null; + this.connected = false; + this.destroyed = false; + this.ack = false; + this.handshake = false; + this.time = 0; + this.lastSend = 0; + this.lastRecv = 0; + this.drainSize = 0; + this.drainQueue = []; + this.banScore = 0; + this.invQueue = []; + this.onPacket = null; + + this.next = null; + this.prev = null; + + this.version = -1; + this.services = 0; + this.height = -1; + this.agent = null; + this.noRelay = false; + this.preferHeaders = false; + this.hashContinue = null; + this.spvFilter = null; + this.feeRate = -1; + this.compactMode = -1; + this.compactWitness = false; + this.merkleBlock = null; + this.merkleTime = -1; + this.merkleMatches = 0; + this.merkleMap = null; + this.syncing = false; + this.sentAddr = false; + this.sentGetAddr = false; + this.challenge = null; + this.lastPong = -1; + this.lastPing = -1; + this.minPing = -1; + this.blockTime = -1; + + this.bestHash = null; + this.bestHeight = -1; -Object.setPrototypeOf(Peer.prototype, EventEmitter.prototype); + this.connectTimeout = null; + this.pingTimer = null; + this.invTimer = null; + this.stallTimer = null; -/** - * Max output bytes buffered before - * invoking stall behavior for peer. - * @const {Number} - * @default - */ + this.addrFilter = new RollingFilter(5000, 0.001); + this.invFilter = new RollingFilter(50000, 0.000001); -Peer.DRAIN_MAX = 10 << 20; + this.blockMap = new BufferMap(); + this.txMap = new BufferMap(); + this.responseMap = new Map(); + this.compactBlocks = new BufferMap(); -/** - * Interval to check for drainage - * and required responses from peer. - * @const {Number} - * @default - */ + this.init(); + } -Peer.STALL_INTERVAL = 5000; + /** + * Create inbound peer from socket. + * @param {PeerOptions} options + * @param {net.Socket} socket + * @returns {Peer} + */ -/** - * Interval for pinging peers. - * @const {Number} - * @default - */ + static fromInbound(options, socket) { + const peer = new this(options); + peer.accept(socket); + return peer; + } -Peer.PING_INTERVAL = 30000; + /** + * Create outbound peer from net address. + * @param {PeerOptions} options + * @param {NetAddress} addr + * @returns {Peer} + */ -/** - * Interval to flush invs. - * Higher means more invs (usually - * txs) will be accumulated before - * flushing. - * @const {Number} - * @default - */ + static fromOutbound(options, addr) { + const peer = new this(options); + peer.connect(addr); + return peer; + } -Peer.INV_INTERVAL = 5000; + /** + * Create a peer from options. + * @param {Object} options + * @returns {Peer} + */ -/** - * Required time for peers to - * respond to messages (i.e. - * getblocks/getdata). - * @const {Number} - * @default - */ + static fromOptions(options) { + return new this(new PeerOptions(options)); + } -Peer.RESPONSE_TIMEOUT = 30000; + /** + * Begin peer initialization. + * @private + */ -/** - * Required time for loader to - * respond with block/merkleblock. - * @const {Number} - * @default - */ + init() { + this.parser.on('packet', async (packet) => { + try { + await this.readPacket(packet); + } catch (e) { + this.error(e); + this.destroy(); + } + }); -Peer.BLOCK_TIMEOUT = 120000; + this.parser.on('error', (err) => { + if (this.destroyed) + return; -/** - * Required time for loader to - * respond with a tx. - * @const {Number} - * @default - */ + this.error(err); + this.sendReject('malformed', 'error parsing message'); + this.increaseBan(10); + }); + } -Peer.TX_TIMEOUT = 120000; + /** + * Getter to retrieve hostname. + * @returns {String} + */ -/** - * Generic timeout interval. - * @const {Number} - * @default - */ + hostname() { + return this.address.hostname; + } -Peer.TIMEOUT_INTERVAL = 20 * 60000; + /** + * Frame a payload with a header. + * @param {String} cmd - Packet type. + * @param {Buffer} payload + * @returns {Buffer} Payload with header prepended. + */ -/** - * Create inbound peer from socket. - * @param {PeerOptions} options - * @param {net.Socket} socket - * @returns {Peer} - */ + framePacket(cmd, payload, checksum) { + return this.framer.packet(cmd, payload, checksum); + } -Peer.fromInbound = function fromInbound(options, socket) { - const peer = new Peer(options); - peer.accept(socket); - return peer; -}; + /** + * Feed data to the parser. + * @param {Buffer} data + */ -/** - * Create outbound peer from net address. - * @param {PeerOptions} options - * @param {NetAddress} addr - * @returns {Peer} - */ + feedParser(data) { + return this.parser.feed(data); + } -Peer.fromOutbound = function fromOutbound(options, addr) { - const peer = new Peer(options); - peer.connect(addr); - return peer; -}; + /** + * Bind to socket. + * @param {net.Socket} socket + */ -/** - * Create a peer from options. - * @param {Object} options - * @returns {Peer} - */ + _bind(socket) { + assert(!this.socket); -Peer.fromOptions = function fromOptions(options) { - return new Peer(new PeerOptions(options)); -}; + this.socket = socket; -/** - * Begin peer initialization. - * @private - */ + this.socket.once('error', (err) => { + if (!this.connected) + return; -Peer.prototype._init = function _init() { - this.parser.on('packet', async (packet) => { - try { - await this.readPacket(packet); - } catch (e) { - this.error(e); + this.error(err); this.destroy(); - } - }); + }); - this.parser.on('error', (err) => { - if (this.destroyed) - return; + this.socket.once('close', () => { + this.error('Socket hangup.'); + this.destroy(); + }); - this.error(err); - this.sendReject('malformed', 'error parsing message'); - this.increaseBan(10); - }); -}; + this.socket.on('drain', () => { + this.handleDrain(); + }); -/** - * Getter to retrieve hostname. - * @returns {String} - */ + this.socket.on('data', (chunk) => { + this.lastRecv = Date.now(); + this.feedParser(chunk); + }); -Peer.prototype.hostname = function hostname() { - return this.address.hostname; -}; + this.socket.setNoDelay(true); + } -/** - * Frame a payload with a header. - * @param {String} cmd - Packet type. - * @param {Buffer} payload - * @returns {Buffer} Payload with header prepended. - */ + /** + * Accept an inbound socket. + * @param {net.Socket} socket + * @returns {net.Socket} + */ -Peer.prototype.framePacket = function framePacket(cmd, payload, checksum) { - if (this.bip151 && this.bip151.handshake) - return this.bip151.packet(cmd, payload); - return this.framer.packet(cmd, payload, checksum); -}; + accept(socket) { + assert(!this.socket); -/** - * Feed data to the parser. - * @param {Buffer} data - */ + this.address = NetAddress.fromSocket(socket, this.network); + this.address.services = 0; + this.time = Date.now(); + this.outbound = false; + this.connected = true; -Peer.prototype.feedParser = function feedParser(data) { - if (this.bip151 && this.bip151.handshake) - return this.bip151.feed(data); - return this.parser.feed(data); -}; + this._bind(socket); -/** - * Set BIP151 cipher type. - * @param {Number} cipher - */ + return socket; + } -Peer.prototype.setCipher = function setCipher(cipher) { - assert(!this.bip151, 'BIP151 already set.'); - assert(this.socket, 'Peer must be initialized with a socket.'); - assert(!this.opened, 'Cannot set cipher after open.'); + /** + * Create the socket and begin connecting. This method + * will use `options.createSocket` if provided. + * @param {NetAddress} addr + * @returns {net.Socket} + */ - this.bip151 = new BIP151(cipher); + connect(addr) { + assert(!this.socket); - this.bip151.on('error', (err) => { - this.error(err); - this.destroy(); - }); + const socket = this.options.createSocket(addr.port, addr.host); - this.bip151.on('rekey', () => { - if (this.destroyed) - return; + this.address = addr; + this.outbound = true; + this.connected = false; + + this._bind(socket); - this.logger.debug('Rekeying with peer (%s).', this.hostname()); - this.send(this.bip151.toRekey()); - }); + return socket; + } + + /** + * Do a reverse dns lookup on peer's addr. + * @returns {Promise} + */ - this.bip151.on('packet', (cmd, body) => { - let payload = null; + async getName() { try { - payload = this.parser.parsePayload(cmd, body); + if (!this.name) { + const {host, port} = this.address; + const {hostname} = await dns.lookupService(host, port); + this.name = hostname; + } } catch (e) { - this.parser.error(e); - return; + ; } - this.parser.emit('packet', payload); - }); -}; - -/** - * Set BIP150 auth. - * @param {AuthDB} db - * @param {Buffer} key - */ - -Peer.prototype.setAuth = function setAuth(db, key) { - const bip151 = this.bip151; - const hostname = this.hostname(); - const outbound = this.outbound; - - assert(this.bip151, 'BIP151 not set.'); - assert(!this.bip150, 'BIP150 already set.'); - assert(this.socket, 'Peer must be initialized with a socket.'); - assert(!this.opened, 'Cannot set auth after open.'); - - this.bip150 = new BIP150(bip151, hostname, outbound, db, key); - this.bip151.bip150 = this.bip150; -}; - -/** - * Bind to socket. - * @param {net.Socket} socket - */ - -Peer.prototype.bind = function bind(socket) { - assert(!this.socket); - - this.socket = socket; - - this.socket.once('error', (err) => { - if (!this.connected) - return; - - this.error(err); - this.destroy(); - }); - - this.socket.once('close', () => { - this.error('Socket hangup.'); - this.destroy(); - }); - - this.socket.on('drain', () => { - this.handleDrain(); - }); - - this.socket.on('data', (chunk) => { - this.lastRecv = util.ms(); - this.feedParser(chunk); - }); - - this.socket.setNoDelay(true); -}; - -/** - * Accept an inbound socket. - * @param {net.Socket} socket - * @returns {net.Socket} - */ - -Peer.prototype.accept = function accept(socket) { - assert(!this.socket); - - this.address = NetAddress.fromSocket(socket, this.network); - this.address.services = 0; - this.time = util.ms(); - this.outbound = false; - this.connected = true; - - this.bind(socket); - - return socket; -}; - -/** - * Create the socket and begin connecting. This method - * will use `options.createSocket` if provided. - * @param {NetAddress} addr - * @returns {net.Socket} - */ - -Peer.prototype.connect = function connect(addr) { - assert(!this.socket); - - const socket = this.options.createSocket(addr.port, addr.host); - - this.address = addr; - this.outbound = true; - this.connected = false; - - this.bind(socket); + return this.name; + } - return socket; -}; + /** + * Open and perform initial handshake (without rejection). + * @method + * @returns {Promise} + */ -/** - * Open and perform initial handshake (without rejection). - * @method - * @returns {Promise} - */ - -Peer.prototype.tryOpen = async function tryOpen() { - try { - await this.open(); - } catch (e) { - ; + async tryOpen() { + try { + await this.open(); + } catch (e) { + ; + } } -}; -/** - * Open and perform initial handshake. - * @method - * @returns {Promise} - */ + /** + * Open and perform initial handshake. + * @method + * @returns {Promise} + */ -Peer.prototype.open = async function open() { - try { - await this._open(); - } catch (e) { - this.error(e); - this.destroy(); - throw e; + async open() { + try { + await this._open(); + } catch (e) { + this.error(e); + this.destroy(); + throw e; + } } -}; -/** - * Open and perform initial handshake. - * @method - * @returns {Promise} - */ + /** + * Open and perform initial handshake. + * @method + * @returns {Promise} + */ -Peer.prototype._open = async function _open() { - this.opened = true; + async _open() { + this.opened = true; - // Connect to peer. - await this.initConnect(); - await this.initStall(); - await this.initBIP151(); - await this.initBIP150(); - await this.initVersion(); - await this.finalize(); + // Connect to peer. + await this.initConnect(); + await this.initStall(); + await this.initVersion(); + await this.finalize(); - assert(!this.destroyed); + assert(!this.destroyed); - // Finally we can let the pool know - // that this peer is ready to go. - this.emit('open'); -}; + // Finally we can let the pool know + // that this peer is ready to go. + this.emit('open'); + } -/** - * Wait for connection. - * @private - * @returns {Promise} - */ + /** + * Wait for connection. + * @private + * @returns {Promise} + */ -Peer.prototype.initConnect = function initConnect() { - if (this.connected) { - assert(!this.outbound); - return Promise.resolve(); - } + initConnect() { + if (this.connected) { + assert(!this.outbound); + return Promise.resolve(); + } - return new Promise((resolve, reject) => { - const cleanup = () => { - if (this.connectTimeout != null) { - clearTimeout(this.connectTimeout); + return new Promise((resolve, reject) => { + const cleanup = () => { + if (this.connectTimeout != null) { + clearTimeout(this.connectTimeout); + this.connectTimeout = null; + } + // eslint-disable-next-line no-use-before-define + this.socket.removeListener('error', onError); + }; + + const onError = (err) => { + cleanup(); + reject(err); + }; + + this.socket.once('connect', () => { + this.time = Date.now(); + this.connected = true; + this.emit('connect'); + + cleanup(); + resolve(); + }); + + this.socket.once('error', onError); + + this.connectTimeout = setTimeout(() => { this.connectTimeout = null; - } - // eslint-disable-next-line no-use-before-define - this.socket.removeListener('error', onError); - }; - - const onError = (err) => { - cleanup(); - reject(err); - }; - - this.socket.once('connect', () => { - this.time = util.ms(); - this.connected = true; - this.emit('connect'); - - cleanup(); - resolve(); + cleanup(); + reject(new Error('Connection timed out.')); + }, 10000); }); - - this.socket.once('error', onError); - - this.connectTimeout = setTimeout(() => { - this.connectTimeout = null; - cleanup(); - reject(new Error('Connection timed out.')); - }, 10000); - }); -}; - -/** - * Setup stall timer. - * @private - * @returns {Promise} - */ - -Peer.prototype.initStall = function initStall() { - assert(!this.stallTimer); - assert(!this.destroyed); - this.stallTimer = setInterval(() => { - this.maybeTimeout(); - }, Peer.STALL_INTERVAL); - return Promise.resolve(); -}; - -/** - * Handle `connect` event (called immediately - * if a socket was passed into peer). - * @method - * @private - * @returns {Promise} - */ - -Peer.prototype.initBIP151 = async function initBIP151() { - assert(!this.destroyed); - - // Send encinit. Wait for handshake to complete. - if (!this.bip151) - return; - - assert(!this.bip151.completed); - - this.logger.info('Attempting BIP151 handshake (%s).', this.hostname()); - - this.send(this.bip151.toEncinit()); - - try { - await this.bip151.wait(3000); - } catch (err) { - this.error(err); } - if (this.destroyed) - throw new Error('Peer was destroyed during BIP151 handshake.'); + /** + * Setup stall timer. + * @private + * @returns {Promise} + */ - assert(this.bip151.completed); - - if (this.bip151.handshake) { - this.logger.info('BIP151 handshake complete (%s).', this.hostname()); - this.logger.info('Connection is encrypted (%s).', this.hostname()); + initStall() { + assert(!this.stallTimer); + assert(!this.destroyed); + this.stallTimer = setInterval(() => { + this.maybeTimeout(); + }, Peer.STALL_INTERVAL); + return Promise.resolve(); } -}; -/** - * Handle post bip151-handshake. - * @method - * @private - * @returns {Promise} - */ + /** + * Handle post handshake. + * @method + * @private + * @returns {Promise} + */ -Peer.prototype.initBIP150 = async function initBIP150() { - assert(!this.destroyed); + async initVersion() { + assert(!this.destroyed); - if (!this.bip150) - return; + // Say hello. + this.sendVersion(); - assert(this.bip151); - assert(!this.bip150.completed); + if (!this.ack) { + await this.wait(packetTypes.VERACK, 10000); + assert(this.ack); + } - if (!this.bip151.handshake) - throw new Error('BIP151 handshake was not completed for BIP150.'); + // Wait for _their_ version. + if (this.version === -1) { + this.logger.debug( + 'Peer sent a verack without a version (%s).', + this.hostname()); - this.logger.info('Attempting BIP150 handshake (%s).', this.hostname()); + await this.wait(packetTypes.VERSION, 10000); - if (this.bip150.outbound) { - if (!this.bip150.peerIdentity) - throw new Error('No known identity for peer.'); - this.send(this.bip150.toChallenge()); - } + assert(this.version !== -1); + } - await this.bip150.wait(3000); + if (this.destroyed) + throw new Error('Peer was destroyed during handshake.'); - assert(!this.destroyed); - assert(this.bip150.completed); + this.handshake = true; - if (this.bip150.auth) { - this.logger.info('BIP150 handshake complete (%s).', this.hostname()); - this.logger.info('Peer is authed (%s): %s.', - this.hostname(), this.bip150.getAddress()); + this.logger.debug('Version handshake complete (%s).', this.hostname()); } -}; - -/** - * Handle post handshake. - * @method - * @private - * @returns {Promise} - */ - -Peer.prototype.initVersion = async function initVersion() { - assert(!this.destroyed); - // Say hello. - this.sendVersion(); - - if (!this.ack) { - await this.wait(packetTypes.VERACK, 10000); - assert(this.ack); - } + /** + * Finalize peer after handshake. + * @method + * @private + * @returns {Promise} + */ - // Wait for _their_ version. - if (this.version === -1) { - this.logger.debug( - 'Peer sent a verack without a version (%s).', - this.hostname()); + async finalize() { + assert(!this.destroyed); - await this.wait(packetTypes.VERSION, 10000); + // Setup the ping interval. + this.pingTimer = setInterval(() => { + this.sendPing(); + }, Peer.PING_INTERVAL); - assert(this.version !== -1); + // Setup the inv flusher. + this.invTimer = setInterval(() => { + this.flushInv(); + }, Peer.INV_INTERVAL); } - if (this.destroyed) - throw new Error('Peer was destroyed during handshake.'); - - this.handshake = true; - - this.logger.debug('Version handshake complete (%s).', this.hostname()); -}; + /** + * Broadcast blocks to peer. + * @param {Block[]} blocks + */ -/** - * Finalize peer after handshake. - * @method - * @private - * @returns {Promise} - */ - -Peer.prototype.finalize = async function finalize() { - assert(!this.destroyed); - - // Setup the ping interval. - this.pingTimer = setInterval(() => { - this.sendPing(); - }, Peer.PING_INTERVAL); - - // Setup the inv flusher. - this.invTimer = setInterval(() => { - this.flushInv(); - }, Peer.INV_INTERVAL); -}; + announceBlock(blocks) { + if (!this.handshake) + return; -/** - * Broadcast blocks to peer. - * @param {Block[]} blocks - */ + if (this.destroyed) + return; -Peer.prototype.announceBlock = function announceBlock(blocks) { - if (!this.handshake) - return; + if (!Array.isArray(blocks)) + blocks = [blocks]; - if (this.destroyed) - return; + const inv = []; - if (!Array.isArray(blocks)) - blocks = [blocks]; + for (const block of blocks) { + assert(block instanceof Block); - const inv = []; + // Don't send if they already have it. + if (this.invFilter.test(block.hash())) + continue; - for (const block of blocks) { - assert(block instanceof Block); + // Send them the block immediately if + // they're using compact block mode 1. + if (this.compactMode === 1) { + this.invFilter.add(block.hash()); + this.sendCompactBlock(block); + continue; + } - // Don't send if they already have it. - if (this.invFilter.test(block.hash())) - continue; + // Convert item to block headers + // for peers that request it. + if (this.preferHeaders) { + inv.push(block.toHeaders()); + continue; + } - // Send them the block immediately if - // they're using compact block mode 1. - if (this.compactMode === 1) { - this.invFilter.add(block.hash()); - this.sendCompactBlock(block); - continue; + inv.push(block.toInv()); } - // Convert item to block headers - // for peers that request it. if (this.preferHeaders) { - inv.push(block.toHeaders()); - continue; + this.sendHeaders(inv); + return; } - inv.push(block.toInv()); - } - - if (this.preferHeaders) { - this.sendHeaders(inv); - return; + this.queueInv(inv); } - this.queueInv(inv); -}; + /** + * Broadcast transactions to peer. + * @param {TX[]} txs + */ -/** - * Broadcast transactions to peer. - * @param {TX[]} txs - */ + announceTX(txs) { + if (!this.handshake) + return; -Peer.prototype.announceTX = function announceTX(txs) { - if (!this.handshake) - return; + if (this.destroyed) + return; - if (this.destroyed) - return; + // Do not send txs to spv clients + // that have relay unset. + if (this.noRelay) + return; - // Do not send txs to spv clients - // that have relay unset. - if (this.noRelay) - return; + if (!Array.isArray(txs)) + txs = [txs]; - if (!Array.isArray(txs)) - txs = [txs]; + const inv = []; - const inv = []; + for (const tx of txs) { + assert(tx instanceof TX); - for (const tx of txs) { - assert(tx instanceof TX); + // Don't send if they already have it. + if (this.invFilter.test(tx.hash())) + continue; - // Don't send if they already have it. - if (this.invFilter.test(tx.hash())) - continue; + // Check the peer's bloom + // filter if they're using spv. + if (this.spvFilter) { + if (!tx.isWatched(this.spvFilter)) + continue; + } - // Check the peer's bloom - // filter if they're using spv. - if (this.spvFilter) { - if (!tx.isWatched(this.spvFilter)) - continue; - } + // Check the fee filter. + if (this.feeRate !== -1) { + const hash = tx.hash(); + const rate = this.options.getRate(hash); + if (rate !== -1 && rate < this.feeRate) + continue; + } - // Check the fee filter. - if (this.feeRate !== -1) { - const hash = tx.hash('hex'); - const rate = this.options.getRate(hash); - if (rate !== -1 && rate < this.feeRate) - continue; + inv.push(tx.toInv()); } - inv.push(tx.toInv()); + this.queueInv(inv); } - this.queueInv(inv); -}; + /** + * Send inv to a peer. + * @param {InvItem[]} items + */ -/** - * Send inv to a peer. - * @param {InvItem[]} items - */ + queueInv(items) { + if (!this.handshake) + return; -Peer.prototype.queueInv = function queueInv(items) { - if (!this.handshake) - return; + if (this.destroyed) + return; - if (this.destroyed) - return; + if (!Array.isArray(items)) + items = [items]; - if (!Array.isArray(items)) - items = [items]; + let hasBlock = false; - let hasBlock = false; + for (const item of items) { + if (item.type === invTypes.BLOCK) + hasBlock = true; + this.invQueue.push(item); + } - for (const item of items) { - if (item.type === invTypes.BLOCK) - hasBlock = true; - this.invQueue.push(item); + if (this.invQueue.length >= 500 || hasBlock) + this.flushInv(); } - if (this.invQueue.length >= 500 || hasBlock) - this.flushInv(); -}; + /** + * Flush inv queue. + * @private + */ -/** - * Flush inv queue. - * @private - */ - -Peer.prototype.flushInv = function flushInv() { - if (this.destroyed) - return; + flushInv() { + if (this.destroyed) + return; - const queue = this.invQueue; + const queue = this.invQueue; - if (queue.length === 0) - return; + if (queue.length === 0) + return; - this.invQueue = []; + this.invQueue = []; - this.logger.spam('Serving %d inv items to %s.', - queue.length, this.hostname()); + this.logger.spam('Serving %d inv items to %s.', + queue.length, this.hostname()); - const items = []; + const items = []; - for (const item of queue) { - if (!this.invFilter.added(item.hash, 'hex')) - continue; + for (const item of queue) { + if (!this.invFilter.added(item.hash)) + continue; - items.push(item); - } + items.push(item); + } - for (let i = 0; i < items.length; i += 1000) { - const chunk = items.slice(i, i + 1000); - this.send(new packets.InvPacket(chunk)); + for (let i = 0; i < items.length; i += 1000) { + const chunk = items.slice(i, i + 1000); + this.send(new packets.InvPacket(chunk)); + } } -}; -/** - * Force send an inv (no filter check). - * @param {InvItem[]} items - */ + /** + * Force send an inv (no filter check). + * @param {InvItem[]} items + */ -Peer.prototype.sendInv = function sendInv(items) { - if (!this.handshake) - return; + sendInv(items) { + if (!this.handshake) + return; - if (this.destroyed) - return; + if (this.destroyed) + return; - if (!Array.isArray(items)) - items = [items]; + if (!Array.isArray(items)) + items = [items]; - for (const item of items) - this.invFilter.add(item.hash, 'hex'); + for (const item of items) + this.invFilter.add(item.hash); - if (items.length === 0) - return; + if (items.length === 0) + return; - this.logger.spam('Serving %d inv items to %s.', - items.length, this.hostname()); + this.logger.spam('Serving %d inv items to %s.', + items.length, this.hostname()); - for (let i = 0; i < items.length; i += 1000) { - const chunk = items.slice(i, i + 1000); - this.send(new packets.InvPacket(chunk)); + for (let i = 0; i < items.length; i += 1000) { + const chunk = items.slice(i, i + 1000); + this.send(new packets.InvPacket(chunk)); + } } -}; -/** - * Send headers to a peer. - * @param {Headers[]} items - */ + /** + * Send headers to a peer. + * @param {Headers[]} items + */ -Peer.prototype.sendHeaders = function sendHeaders(items) { - if (!this.handshake) - return; + sendHeaders(items) { + if (!this.handshake) + return; - if (this.destroyed) - return; + if (this.destroyed) + return; - if (!Array.isArray(items)) - items = [items]; + if (!Array.isArray(items)) + items = [items]; - for (const item of items) - this.invFilter.add(item.hash()); + for (const item of items) + this.invFilter.add(item.hash()); - if (items.length === 0) - return; + if (items.length === 0) + return; - this.logger.spam('Serving %d headers to %s.', - items.length, this.hostname()); + this.logger.spam('Serving %d headers to %s.', + items.length, this.hostname()); - for (let i = 0; i < items.length; i += 2000) { - const chunk = items.slice(i, i + 2000); - this.send(new packets.HeadersPacket(chunk)); + for (let i = 0; i < items.length; i += 2000) { + const chunk = items.slice(i, i + 2000); + this.send(new packets.HeadersPacket(chunk)); + } } -}; - -/** - * Send a compact block. - * @private - * @param {Block} block - * @returns {Boolean} - */ -Peer.prototype.sendCompactBlock = function sendCompactBlock(block) { - const witness = this.compactWitness; - const compact = BIP152.CompactBlock.fromBlock(block, witness); - this.send(new packets.CmpctBlockPacket(compact, witness)); -}; - -/** - * Send a `version` packet. - */ + /** + * Send a compact block. + * @private + * @param {Block} block + * @returns {Boolean} + */ + + sendCompactBlock(block) { + const witness = this.compactWitness; + const compact = BIP152.CompactBlock.fromBlock(block, witness); + this.send(new packets.CmpctBlockPacket(compact, witness)); + } + + /** + * Send a `version` packet. + */ + + sendVersion() { + const packet = new packets.VersionPacket(); + packet.version = this.options.version; + packet.services = this.options.services; + packet.time = this.network.now(); + packet.remote = this.address; + packet.local.setNull(); + packet.local.services = this.options.services; + packet.nonce = this.options.createNonce(this.hostname()); + packet.agent = this.options.agent; + packet.height = this.options.getHeight(); + packet.noRelay = this.options.noRelay; + this.send(packet); + } + + /** + * Send a `getaddr` packet. + */ + + sendGetAddr() { + if (this.sentGetAddr) + return; -Peer.prototype.sendVersion = function sendVersion() { - const packet = new packets.VersionPacket(); - packet.version = this.options.version; - packet.services = this.options.services; - packet.time = this.network.now(); - packet.remote = this.address; - packet.local.setNull(); - packet.local.services = this.options.services; - packet.nonce = this.options.createNonce(this.hostname()); - packet.agent = this.options.agent; - packet.height = this.options.getHeight(); - packet.noRelay = this.options.noRelay; - this.send(packet); -}; + this.sentGetAddr = true; + this.send(new packets.GetAddrPacket()); + } -/** - * Send a `getaddr` packet. - */ + /** + * Send a `ping` packet. + */ -Peer.prototype.sendGetAddr = function sendGetAddr() { - if (this.sentGetAddr) - return; + sendPing() { + if (!this.handshake) + return; - this.sentGetAddr = true; - this.send(new packets.GetAddrPacket()); -}; + if (this.version <= common.PONG_VERSION) { + this.send(new packets.PingPacket()); + return; + } -/** - * Send a `ping` packet. - */ + if (this.challenge) { + this.logger.debug( + 'Peer has not responded to ping (%s).', + this.hostname()); + return; + } -Peer.prototype.sendPing = function sendPing() { - if (!this.handshake) - return; + this.lastPing = Date.now(); + this.challenge = common.nonce(); - if (this.version <= common.PONG_VERSION) { - this.send(new packets.PingPacket()); - return; + this.send(new packets.PingPacket(this.challenge)); } - if (this.challenge) { - this.logger.debug('Peer has not responded to ping (%s).', this.hostname()); - return; - } + /** + * Send `filterload` to update the local bloom filter. + */ - this.lastPing = util.ms(); - this.challenge = util.nonce(); + sendFilterLoad(filter) { + if (!this.handshake) + return; - this.send(new packets.PingPacket(this.challenge)); -}; + if (!this.options.spv) + return; -/** - * Send `filterload` to update the local bloom filter. - */ + if (!(this.services & services.BLOOM)) + return; -Peer.prototype.sendFilterLoad = function sendFilterLoad(filter) { - if (!this.handshake) - return; + this.send(new packets.FilterLoadPacket(filter)); + } - if (!this.options.spv) - return; + /** + * Set a fee rate filter for the peer. + * @param {Rate} rate + */ - if (!(this.services & services.BLOOM)) - return; + sendFeeRate(rate) { + if (!this.handshake) + return; - this.send(new packets.FilterLoadPacket(filter)); -}; + this.send(new packets.FeeFilterPacket(rate)); + } -/** - * Set a fee rate filter for the peer. - * @param {Rate} rate - */ + /** + * Disconnect from and destroy the peer. + */ -Peer.prototype.sendFeeRate = function sendFeeRate(rate) { - if (!this.handshake) - return; + destroy() { + const connected = this.connected; - this.send(new packets.FeeFilterPacket(rate)); -}; + if (this.destroyed) + return; -/** - * Disconnect from and destroy the peer. - */ + this.destroyed = true; + this.connected = false; -Peer.prototype.destroy = function destroy() { - const connected = this.connected; + this.socket.destroy(); + this.socket = null; - if (this.destroyed) - return; + if (this.pingTimer != null) { + clearInterval(this.pingTimer); + this.pingTimer = null; + } - this.destroyed = true; - this.connected = false; + if (this.invTimer != null) { + clearInterval(this.invTimer); + this.invTimer = null; + } - this.socket.destroy(); - this.socket = null; + if (this.stallTimer != null) { + clearInterval(this.stallTimer); + this.stallTimer = null; + } - if (this.bip151) - this.bip151.destroy(); + if (this.connectTimeout != null) { + clearTimeout(this.connectTimeout); + this.connectTimeout = null; + } - if (this.bip150) - this.bip150.destroy(); + const jobs = this.drainQueue; - if (this.pingTimer != null) { - clearInterval(this.pingTimer); - this.pingTimer = null; - } + this.drainSize = 0; + this.drainQueue = []; - if (this.invTimer != null) { - clearInterval(this.invTimer); - this.invTimer = null; - } + for (const job of jobs) + job.reject(new Error('Peer was destroyed.')); - if (this.stallTimer != null) { - clearInterval(this.stallTimer); - this.stallTimer = null; - } + for (const [cmd, entry] of this.responseMap) { + this.responseMap.delete(cmd); + entry.reject(new Error('Peer was destroyed.')); + } - if (this.connectTimeout != null) { - clearTimeout(this.connectTimeout); - this.connectTimeout = null; + this.locker.destroy(); + + this.emit('close', connected); } - const jobs = this.drainQueue; + /** + * Write data to the peer's socket. + * @param {Buffer} data + */ - this.drainSize = 0; - this.drainQueue = []; + write(data) { + if (this.destroyed) + throw new Error('Peer is destroyed (write).'); - for (const job of jobs) - job.reject(new Error('Peer was destroyed.')); + this.lastSend = Date.now(); - for (const [cmd, entry] of this.responseMap) { - this.responseMap.delete(cmd); - entry.reject(new Error('Peer was destroyed.')); + if (this.socket.write(data) === false) + this.needsDrain(data.length); } - this.locker.destroy(); - - this.emit('close', connected); -}; - -/** - * Write data to the peer's socket. - * @param {Buffer} data - */ + /** + * Send a packet. + * @param {Packet} packet + */ -Peer.prototype.write = function write(data) { - if (this.destroyed) - throw new Error('Peer is destroyed (write).'); + send(packet) { + if (this.destroyed) + throw new Error('Peer is destroyed (send).'); + + // Used cached hashes as the + // packet checksum for speed. + let checksum = null; + if (packet.type === packetTypes.TX) { + const tx = packet.tx; + if (packet.witness) { + if (!tx.isCoinbase()) + checksum = tx.witnessHash(); + } else { + checksum = tx.hash(); + } + } - this.lastSend = util.ms(); + this.sendRaw(packet.cmd, packet.toRaw(), checksum); - if (this.socket.write(data) === false) - this.needsDrain(data.length); -}; + this.addTimeout(packet); + } -/** - * Send a packet. - * @param {Packet} packet - */ + /** + * Send a packet. + * @param {Packet} packet + */ -Peer.prototype.send = function send(packet) { - if (this.destroyed) - throw new Error('Peer is destroyed (send).'); - - // Used cached hashes as the - // packet checksum for speed. - let checksum = null; - if (packet.type === packetTypes.TX) { - const tx = packet.tx; - if (packet.witness) { - if (!tx.isCoinbase()) - checksum = tx.witnessHash(); - } else { - checksum = tx.hash(); - } + sendRaw(cmd, body, checksum) { + const payload = this.framePacket(cmd, body, checksum); + this.write(payload); } - this.sendRaw(packet.cmd, packet.toRaw(), checksum); + /** + * Wait for a drain event. + * @returns {Promise} + */ - this.addTimeout(packet); -}; + drain() { + if (this.destroyed) + return Promise.reject(new Error('Peer is destroyed.')); -/** - * Send a packet. - * @param {Packet} packet - */ + if (this.drainSize === 0) + return Promise.resolve(); -Peer.prototype.sendRaw = function sendRaw(cmd, body, checksum) { - const payload = this.framePacket(cmd, body, checksum); - this.write(payload); -}; + return new Promise((resolve, reject) => { + this.drainQueue.push({ resolve, reject }); + }); + } -/** - * Wait for a drain event. - * @returns {Promise} - */ + /** + * Handle drain event. + * @private + */ -Peer.prototype.drain = function drain() { - if (this.destroyed) - return Promise.reject(new Error('Peer is destroyed.')); + handleDrain() { + const jobs = this.drainQueue; - if (this.drainSize === 0) - return Promise.resolve(); + this.drainSize = 0; - return new Promise((resolve, reject) => { - this.drainQueue.push(co.job(resolve, reject)); - }); -}; + if (jobs.length === 0) + return; -/** - * Handle drain event. - * @private - */ + this.drainQueue = []; -Peer.prototype.handleDrain = function handleDrain() { - const jobs = this.drainQueue; + for (const job of jobs) + job.resolve(); + } - this.drainSize = 0; + /** + * Add to drain counter. + * @private + * @param {Number} size + */ - if (jobs.length === 0) - return; + needsDrain(size) { + this.drainSize += size; - this.drainQueue = []; + if (this.drainSize >= Peer.DRAIN_MAX) { + this.logger.warning( + 'Peer is not reading: %dmb buffered (%s).', + this.drainSize / (1 << 20), + this.hostname()); + this.error('Peer stalled (drain).'); + this.destroy(); + } + } - for (const job of jobs) - job.resolve(); -}; + /** + * Potentially add response timeout. + * @private + * @param {Packet} packet + */ -/** - * Add to drain counter. - * @private - * @param {Number} size - */ + addTimeout(packet) { + const timeout = Peer.RESPONSE_TIMEOUT; -Peer.prototype.needsDrain = function needsDrain(size) { - this.drainSize += size; + if (!this.outbound) + return; - if (this.drainSize >= Peer.DRAIN_MAX) { - this.logger.warning( - 'Peer is not reading: %dmb buffered (%s).', - util.mb(this.drainSize), - this.hostname()); - this.error('Peer stalled (drain).'); - this.destroy(); + switch (packet.type) { + case packetTypes.MEMPOOL: + this.request(packetTypes.INV, timeout); + break; + case packetTypes.GETBLOCKS: + if (!this.options.isFull()) + this.request(packetTypes.INV, timeout); + break; + case packetTypes.GETHEADERS: + this.request(packetTypes.HEADERS, timeout * 2); + break; + case packetTypes.GETDATA: + this.request(packetTypes.DATA, timeout * 2); + break; + case packetTypes.GETBLOCKTXN: + this.request(packetTypes.BLOCKTXN, timeout); + break; + } } -}; -/** - * Potentially add response timeout. - * @private - * @param {Packet} packet - */ + /** + * Potentially finish response timeout. + * @private + * @param {Packet} packet + */ + + fulfill(packet) { + switch (packet.type) { + case packetTypes.BLOCK: + case packetTypes.CMPCTBLOCK: + case packetTypes.MERKLEBLOCK: + case packetTypes.TX: + case packetTypes.NOTFOUND: { + const entry = this.response(packetTypes.DATA, packet); + assert(!entry || entry.jobs.length === 0); + break; + } + } -Peer.prototype.addTimeout = function addTimeout(packet) { - const timeout = Peer.RESPONSE_TIMEOUT; + return this.response(packet.type, packet); + } - if (!this.outbound) - return; + /** + * Potentially timeout peer if it hasn't responded. + * @private + */ - switch (packet.type) { - case packetTypes.MEMPOOL: - this.request(packetTypes.INV, timeout); - break; - case packetTypes.GETBLOCKS: - if (!this.options.isFull()) - this.request(packetTypes.INV, timeout); - break; - case packetTypes.GETHEADERS: - this.request(packetTypes.HEADERS, timeout * 2); - break; - case packetTypes.GETDATA: - this.request(packetTypes.DATA, timeout * 2); - break; - case packetTypes.GETBLOCKTXN: - this.request(packetTypes.BLOCKTXN, timeout); - break; - } -}; + maybeTimeout() { + const now = Date.now(); -/** - * Potentially finish response timeout. - * @private - * @param {Packet} packet - */ + for (const [key, entry] of this.responseMap) { + if (now > entry.timeout) { + const name = packets.typesByVal[key]; + this.error('Peer is stalling (%s).', name.toLowerCase()); + this.destroy(); + return; + } + } -Peer.prototype.fulfill = function fulfill(packet) { - switch (packet.type) { - case packetTypes.BLOCK: - case packetTypes.CMPCTBLOCK: - case packetTypes.MERKLEBLOCK: - case packetTypes.TX: - case packetTypes.NOTFOUND: { - const entry = this.response(packetTypes.DATA, packet); - assert(!entry || entry.jobs.length === 0); - break; + if (this.merkleBlock) { + assert(this.merkleTime !== -1); + if (now > this.merkleTime + Peer.BLOCK_TIMEOUT) { + this.error('Peer is stalling (merkleblock).'); + this.destroy(); + return; + } } - } - return this.response(packet.type, packet); -}; + if (this.syncing && this.loader && !this.options.isFull()) { + if (now > this.blockTime + Peer.BLOCK_TIMEOUT) { + this.error('Peer is stalling (block).'); + this.destroy(); + return; + } + } -/** - * Potentially timeout peer if it hasn't responded. - * @private - */ + if (this.options.isFull() || !this.syncing) { + for (const time of this.blockMap.values()) { + if (now > time + Peer.BLOCK_TIMEOUT) { + this.error('Peer is stalling (block).'); + this.destroy(); + return; + } + } -Peer.prototype.maybeTimeout = function maybeTimeout() { - const now = util.ms(); + for (const time of this.txMap.values()) { + if (now > time + Peer.TX_TIMEOUT) { + this.error('Peer is stalling (tx).'); + this.destroy(); + return; + } + } - for (const [key, entry] of this.responseMap) { - if (now > entry.timeout) { - const name = packets.typesByVal[key]; - this.error('Peer is stalling (%s).', name.toLowerCase()); - this.destroy(); - return; + for (const block of this.compactBlocks.values()) { + if (now > block.now + Peer.RESPONSE_TIMEOUT) { + this.error('Peer is stalling (blocktxn).'); + this.destroy(); + return; + } + } } - } - if (this.merkleBlock) { - assert(this.merkleTime !== -1); - if (now > this.merkleTime + Peer.BLOCK_TIMEOUT) { - this.error('Peer is stalling (merkleblock).'); - this.destroy(); - return; - } - } + if (now > this.time + 60000) { + assert(this.time !== 0); - if (this.syncing && this.loader && !this.options.isFull()) { - if (now > this.blockTime + Peer.BLOCK_TIMEOUT) { - this.error('Peer is stalling (block).'); - this.destroy(); - return; - } - } + if (this.lastRecv === 0 || this.lastSend === 0) { + this.error('Peer is stalling (no message).'); + this.destroy(); + return; + } - if (this.options.isFull() || !this.syncing) { - for (const time of this.blockMap.values()) { - if (now > time + Peer.BLOCK_TIMEOUT) { - this.error('Peer is stalling (block).'); + if (now > this.lastSend + Peer.TIMEOUT_INTERVAL) { + this.error('Peer is stalling (send).'); this.destroy(); return; } - } - for (const time of this.txMap.values()) { - if (now > time + Peer.TX_TIMEOUT) { - this.error('Peer is stalling (tx).'); + const mult = this.version <= common.PONG_VERSION ? 4 : 1; + + if (now > this.lastRecv + Peer.TIMEOUT_INTERVAL * mult) { + this.error('Peer is stalling (recv).'); this.destroy(); return; } - } - for (const block of this.compactBlocks.values()) { - if (now > block.now + Peer.RESPONSE_TIMEOUT) { - this.error('Peer is stalling (blocktxn).'); + if (this.challenge && now > this.lastPing + Peer.TIMEOUT_INTERVAL) { + this.error('Peer is stalling (ping).'); this.destroy(); return; } } } - if (now > this.time + 60000) { - assert(this.time !== 0); + /** + * Wait for a packet to be received from peer. + * @private + * @param {Number} type - Packet type. + * @param {Number} timeout + * @returns {RequestEntry} + */ - if (this.lastRecv === 0 || this.lastSend === 0) { - this.error('Peer is stalling (no message).'); - this.destroy(); - return; - } + request(type, timeout) { + if (this.destroyed) + return null; - if (now > this.lastSend + Peer.TIMEOUT_INTERVAL) { - this.error('Peer is stalling (send).'); - this.destroy(); - return; - } + let entry = this.responseMap.get(type); - const mult = this.version <= common.PONG_VERSION ? 4 : 1; + if (!entry) { + entry = new RequestEntry(); - if (now > this.lastRecv + Peer.TIMEOUT_INTERVAL * mult) { - this.error('Peer is stalling (recv).'); - this.destroy(); - return; - } + this.responseMap.set(type, entry); - if (this.challenge && now > this.lastPing + Peer.TIMEOUT_INTERVAL) { - this.error('Peer is stalling (ping).'); - this.destroy(); - return; + if (this.responseMap.size >= common.MAX_REQUEST) { + this.destroy(); + return null; + } } + + entry.setTimeout(timeout); + + return entry; } -}; -/** - * Wait for a packet to be received from peer. - * @private - * @param {Number} type - Packet type. - * @param {Number} timeout - * @returns {RequestEntry} - */ + /** + * Fulfill awaiting requests created with {@link Peer#request}. + * @private + * @param {Number} type - Packet type. + * @param {Object} payload + */ + + response(type, payload) { + const entry = this.responseMap.get(type); -Peer.prototype.request = function request(type, timeout) { - if (this.destroyed) - return null; + if (!entry) + return null; - let entry = this.responseMap.get(type); + this.responseMap.delete(type); - if (!entry) { - entry = new RequestEntry(); - this.responseMap.set(type, entry); + return entry; } - entry.setTimeout(timeout); + /** + * Wait for a packet to be received from peer. + * @private + * @param {Number} type - Packet type. + * @returns {Promise} - Returns Object(payload). + * Executed on timeout or once packet is received. + */ - return entry; -}; + wait(type, timeout) { + return new Promise((resolve, reject) => { + const entry = this.request(type); -/** - * Fulfill awaiting requests created with {@link Peer#request}. - * @private - * @param {Number} type - Packet type. - * @param {Object} payload - */ + if (!entry) { + reject(new Error('Peer is destroyed (request).')); + return; + } -Peer.prototype.response = function response(type, payload) { - const entry = this.responseMap.get(type); + entry.setTimeout(timeout); + entry.addJob(resolve, reject); + }); + } - if (!entry) - return null; + /** + * Emit an error and destroy the peer. + * @private + * @param {...String|Error} err + */ - this.responseMap.delete(type); + error(err) { + if (this.destroyed) + return; - return entry; -}; + if (typeof err === 'string') { + const msg = format.apply(null, arguments); + err = new Error(msg); + } -/** - * Wait for a packet to be received from peer. - * @private - * @param {Number} type - Packet type. - * @returns {Promise} - Returns Object(payload). - * Executed on timeout or once packet is received. - */ + if (typeof err.code === 'string' && err.code[0] === 'E') { + const msg = err.code; + err = new Error(msg); + err.code = msg; + err.message = `Socket Error: ${msg}`; + } -Peer.prototype.wait = function wait(type, timeout) { - return new Promise((resolve, reject) => { - if (this.destroyed) { - reject(new Error('Peer is destroyed (request).')); - return; + err.message += ` (${this.hostname()})`; + + this.emit('error', err); + } + + /** + * Calculate peer block inv type (filtered, + * compact, witness, or non-witness). + * @returns {Number} + */ + + blockType() { + if (this.options.spv) + return invTypes.FILTERED_BLOCK; + + if (this.options.compact + && this.hasCompactSupport() + && this.hasCompact()) { + return invTypes.CMPCT_BLOCK; } - const entry = this.request(type); + if (this.hasWitness()) + return invTypes.WITNESS_BLOCK; - entry.setTimeout(timeout); - entry.addJob(resolve, reject); - }); -}; + return invTypes.BLOCK; + } -/** - * Emit an error and destroy the peer. - * @private - * @param {...String|Error} err - */ + /** + * Calculate peer tx inv type (witness or non-witness). + * @returns {Number} + */ -Peer.prototype.error = function error(err) { - if (this.destroyed) - return; + txType() { + if (this.hasWitness()) + return invTypes.WITNESS_TX; - if (typeof err === 'string') { - const msg = util.fmt.apply(util, arguments); - err = new Error(msg); + return invTypes.TX; } - if (typeof err.code === 'string' && err.code[0] === 'E') { - const msg = err.code; - err = new Error(msg); - err.code = msg; - err.message = `Socket Error: ${msg}`; + /** + * Send `getdata` to peer. + * @param {InvItem[]} items + */ + + getData(items) { + this.send(new packets.GetDataPacket(items)); } - err.message += ` (${this.hostname()})`; + /** + * Send batched `getdata` to peer. + * @param {InvType} type + * @param {Hash[]} hashes + */ - this.emit('error', err); -}; + getItems(type, hashes) { + const items = []; -/** - * Calculate peer block inv type (filtered, - * compact, witness, or non-witness). - * @returns {Number} - */ + for (const hash of hashes) + items.push(new InvItem(type, hash)); -Peer.prototype.blockType = function blockType() { - if (this.options.spv) - return invTypes.FILTERED_BLOCK; + if (items.length === 0) + return; - if (this.options.compact - && this.hasCompactSupport() - && this.hasCompact()) { - return invTypes.CMPCT_BLOCK; + this.getData(items); } - if (this.hasWitness()) - return invTypes.WITNESS_BLOCK; + /** + * Send batched `getdata` to peer (blocks). + * @param {Hash[]} hashes + */ - return invTypes.BLOCK; -}; + getBlock(hashes) { + this.getItems(this.blockType(), hashes); + } -/** - * Calculate peer tx inv type (witness or non-witness). - * @returns {Number} - */ + /** + * Send batched `getdata` to peer (txs). + * @param {Hash[]} hashes + */ -Peer.prototype.txType = function txType() { - if (this.hasWitness()) - return invTypes.WITNESS_TX; + getTX(hashes) { + this.getItems(this.txType(), hashes); + } - return invTypes.TX; -}; + /** + * Send `getdata` to peer for a single block. + * @param {Hash} hash + */ -/** - * Send `getdata` to peer. - * @param {InvItem[]} items - */ + getFullBlock(hash) { + assert(!this.options.spv); -Peer.prototype.getData = function getData(items) { - this.send(new packets.GetDataPacket(items)); -}; + let type = invTypes.BLOCK; -/** - * Send batched `getdata` to peer. - * @param {InvType} type - * @param {Hash[]} hashes - */ + if (this.hasWitness()) + type |= InvItem.WITNESS_FLAG; -Peer.prototype.getItems = function getItems(type, hashes) { - const items = []; + this.getItems(type, [hash]); + } - for (const hash of hashes) - items.push(new InvItem(type, hash)); + /** + * Handle a packet payload. + * @method + * @private + * @param {Packet} packet + */ - if (items.length === 0) - return; + async readPacket(packet) { + if (this.destroyed) + return; - this.getData(items); -}; + // The "pre-handshake" packets get + // to bypass the lock, since they + // are meant to change the way input + // is handled at a low level. They + // must be handled immediately. + switch (packet.type) { + case packetTypes.PONG: { + try { + this.socket.pause(); + await this.handlePacket(packet); + } finally { + if (!this.destroyed) + this.socket.resume(); + } + break; + } + default: { + const unlock = await this.locker.lock(); + try { + this.socket.pause(); + await this.handlePacket(packet); + } finally { + if (!this.destroyed) + this.socket.resume(); + unlock(); + } + break; + } + } + } -/** - * Send batched `getdata` to peer (blocks). - * @param {Hash[]} hashes - */ + /** + * Handle a packet payload without a lock. + * @method + * @private + * @param {Packet} packet + */ -Peer.prototype.getBlock = function getBlock(hashes) { - this.getItems(this.blockType(), hashes); -}; + async handlePacket(packet) { + if (this.destroyed) + throw new Error('Destroyed peer sent a packet.'); + + const entry = this.fulfill(packet); + + switch (packet.type) { + case packetTypes.VERSION: + await this.handleVersion(packet); + break; + case packetTypes.VERACK: + await this.handleVerack(packet); + break; + case packetTypes.PING: + await this.handlePing(packet); + break; + case packetTypes.PONG: + await this.handlePong(packet); + break; + case packetTypes.SENDHEADERS: + await this.handleSendHeaders(packet); + break; + case packetTypes.FILTERLOAD: + await this.handleFilterLoad(packet); + break; + case packetTypes.FILTERADD: + await this.handleFilterAdd(packet); + break; + case packetTypes.FILTERCLEAR: + await this.handleFilterClear(packet); + break; + case packetTypes.FEEFILTER: + await this.handleFeeFilter(packet); + break; + case packetTypes.SENDCMPCT: + await this.handleSendCmpct(packet); + break; + } -/** - * Send batched `getdata` to peer (txs). - * @param {Hash[]} hashes - */ + if (this.onPacket) + await this.onPacket(packet); -Peer.prototype.getTX = function getTX(hashes) { - this.getItems(this.txType(), hashes); -}; + this.emit('packet', packet); -/** - * Send `getdata` to peer for a single block. - * @param {Hash} hash - */ + if (entry) + entry.resolve(packet); + } -Peer.prototype.getFullBlock = function getFullBlock(hash) { - assert(!this.options.spv); + /** + * Handle `version` packet. + * @method + * @private + * @param {VersionPacket} packet + */ - let type = invTypes.BLOCK; + async handleVersion(packet) { + if (this.version !== -1) + throw new Error('Peer sent a duplicate version.'); - if (this.hasWitness()) - type |= InvItem.WITNESS_FLAG; + this.version = packet.version; + this.services = packet.services; + this.height = packet.height; + this.agent = packet.agent; + this.noRelay = packet.noRelay; + this.local = packet.remote; - this.getItems(type, [hash]); -}; + if (!this.network.selfConnect) { + if (this.options.hasNonce(packet.nonce)) + throw new Error('We connected to ourself. Oops.'); + } -/** - * Handle a packet payload. - * @method - * @private - * @param {Packet} packet - */ + if (this.version < common.MIN_VERSION) + throw new Error('Peer does not support required protocol version.'); -Peer.prototype.readPacket = async function readPacket(packet) { - if (this.destroyed) - return; - - // The "pre-handshake" packets get - // to bypass the lock, since they - // are meant to change the way input - // is handled at a low level. They - // must be handled immediately. - switch (packet.type) { - case packetTypes.ENCINIT: - case packetTypes.ENCACK: - case packetTypes.AUTHCHALLENGE: - case packetTypes.AUTHREPLY: - case packetTypes.AUTHPROPOSE: - case packetTypes.PONG: { - try { - this.socket.pause(); - await this.handlePacket(packet); - } finally { - if (!this.destroyed) - this.socket.resume(); + if (this.outbound) { + if (!(this.services & services.NETWORK)) + throw new Error('Peer does not support network services.'); + + if (this.options.headers) { + if (this.version < common.HEADERS_VERSION) + throw new Error('Peer does not support getheaders.'); } - break; - } - default: { - const unlock = await this.locker.lock(); - try { - this.socket.pause(); - await this.handlePacket(packet); - } finally { - if (!this.destroyed) - this.socket.resume(); - unlock(); + + if (this.options.spv) { + if (!(this.services & services.BLOOM)) + throw new Error('Peer does not support BIP37.'); + + if (this.version < common.BLOOM_VERSION) + throw new Error('Peer does not support BIP37.'); + } + + if (this.options.hasWitness()) { + if (!(this.services & services.WITNESS)) + throw new Error('Peer does not support segregated witness.'); + } + + if (this.options.compact) { + if (!this.hasCompactSupport()) { + this.logger.debug( + 'Peer does not support compact blocks (%s).', + this.hostname()); + } } - break; } + + this.send(new packets.VerackPacket()); } -}; -/** - * Handle a packet payload without a lock. - * @method - * @private - * @param {Packet} packet - */ + /** + * Handle `verack` packet. + * @method + * @private + * @param {VerackPacket} packet + */ -Peer.prototype.handlePacket = async function handlePacket(packet) { - if (this.destroyed) - throw new Error('Destroyed peer sent a packet.'); - - if (this.bip151 - && this.bip151.job - && !this.bip151.completed - && packet.type !== packetTypes.ENCINIT - && packet.type !== packetTypes.ENCACK) { - this.bip151.reject(new Error('Message before BIP151 handshake.')); - } - - if (this.bip150 - && this.bip150.job - && !this.bip150.completed - && packet.type !== packetTypes.AUTHCHALLENGE - && packet.type !== packetTypes.AUTHREPLY - && packet.type !== packetTypes.AUTHPROPOSE) { - this.bip150.reject(new Error('Message before BIP150 auth.')); - } - - const entry = this.fulfill(packet); - - switch (packet.type) { - case packetTypes.VERSION: - await this.handleVersion(packet); - break; - case packetTypes.VERACK: - await this.handleVerack(packet); - break; - case packetTypes.PING: - await this.handlePing(packet); - break; - case packetTypes.PONG: - await this.handlePong(packet); - break; - case packetTypes.SENDHEADERS: - await this.handleSendHeaders(packet); - break; - case packetTypes.FILTERLOAD: - await this.handleFilterLoad(packet); - break; - case packetTypes.FILTERADD: - await this.handleFilterAdd(packet); - break; - case packetTypes.FILTERCLEAR: - await this.handleFilterClear(packet); - break; - case packetTypes.FEEFILTER: - await this.handleFeeFilter(packet); - break; - case packetTypes.SENDCMPCT: - await this.handleSendCmpct(packet); - break; - case packetTypes.ENCINIT: - await this.handleEncinit(packet); - break; - case packetTypes.ENCACK: - await this.handleEncack(packet); - break; - case packetTypes.AUTHCHALLENGE: - await this.handleAuthChallenge(packet); - break; - case packetTypes.AUTHREPLY: - await this.handleAuthReply(packet); - break; - case packetTypes.AUTHPROPOSE: - await this.handleAuthPropose(packet); - break; - } - - if (this.onPacket) - await this.onPacket(packet); - - this.emit('packet', packet); - - if (entry) - entry.resolve(packet); -}; + async handleVerack(packet) { + if (this.ack) { + this.logger.debug('Peer sent duplicate ack (%s).', this.hostname()); + return; + } -/** - * Handle `version` packet. - * @method - * @private - * @param {VersionPacket} packet - */ + this.ack = true; + this.logger.debug('Received verack (%s).', this.hostname()); + } -Peer.prototype.handleVersion = async function handleVersion(packet) { - if (this.version !== -1) - throw new Error('Peer sent a duplicate version.'); + /** + * Handle `ping` packet. + * @method + * @private + * @param {PingPacket} packet + */ - this.version = packet.version; - this.services = packet.services; - this.height = packet.height; - this.agent = packet.agent; - this.noRelay = packet.noRelay; - this.local = packet.remote; + async handlePing(packet) { + if (!packet.nonce) + return; - if (!this.network.selfConnect) { - if (this.options.hasNonce(packet.nonce)) - throw new Error('We connected to ourself. Oops.'); + this.send(new packets.PongPacket(packet.nonce)); } - if (this.version < common.MIN_VERSION) - throw new Error('Peer does not support required protocol version.'); + /** + * Handle `pong` packet. + * @method + * @private + * @param {PongPacket} packet + */ - if (this.outbound) { - if (!(this.services & services.NETWORK)) - throw new Error('Peer does not support network services.'); + async handlePong(packet) { + const nonce = packet.nonce; + const now = Date.now(); - if (this.options.headers) { - if (this.version < common.HEADERS_VERSION) - throw new Error('Peer does not support getheaders.'); + if (!this.challenge) { + this.logger.debug('Peer sent an unsolicited pong (%s).', this.hostname()); + return; } - if (this.options.spv) { - if (!(this.services & services.BLOOM)) - throw new Error('Peer does not support BIP37.'); - - if (this.version < common.BLOOM_VERSION) - throw new Error('Peer does not support BIP37.'); + if (!nonce.equals(this.challenge)) { + if (nonce.equals(common.ZERO_NONCE)) { + this.logger.debug('Peer sent a zero nonce (%s).', this.hostname()); + this.challenge = null; + return; + } + this.logger.debug('Peer sent the wrong nonce (%s).', this.hostname()); + return; } - if (this.options.hasWitness()) { - if (!(this.services & services.WITNESS)) - throw new Error('Peer does not support segregated witness.'); + if (now >= this.lastPing) { + this.lastPong = now; + if (this.minPing === -1) + this.minPing = now - this.lastPing; + this.minPing = Math.min(this.minPing, now - this.lastPing); + } else { + this.logger.debug('Timing mismatch (what?) (%s).', this.hostname()); } - if (this.options.compact) { - if (!this.hasCompactSupport()) { - this.logger.debug( - 'Peer does not support compact blocks (%s).', - this.hostname()); - } - } + this.challenge = null; } - this.send(new packets.VerackPacket()); -}; + /** + * Handle `sendheaders` packet. + * @method + * @private + * @param {SendHeadersPacket} packet + */ -/** - * Handle `verack` packet. - * @method - * @private - * @param {VerackPacket} packet - */ + async handleSendHeaders(packet) { + if (this.preferHeaders) { + this.logger.debug( + 'Peer sent a duplicate sendheaders (%s).', + this.hostname()); + return; + } -Peer.prototype.handleVerack = async function handleVerack(packet) { - if (this.ack) { - this.logger.debug('Peer sent duplicate ack (%s).', this.hostname()); - return; + this.preferHeaders = true; } - this.ack = true; - this.logger.debug('Received verack (%s).', this.hostname()); -}; - -/** - * Handle `ping` packet. - * @method - * @private - * @param {PingPacket} packet - */ + /** + * Handle `filterload` packet. + * @method + * @private + * @param {FilterLoadPacket} packet + */ -Peer.prototype.handlePing = async function handlePing(packet) { - if (!packet.nonce) - return; - - this.send(new packets.PongPacket(packet.nonce)); -}; + async handleFilterLoad(packet) { + if (!packet.isWithinConstraints()) { + this.increaseBan(100); + return; + } -/** - * Handle `pong` packet. - * @method - * @private - * @param {PongPacket} packet - */ + this.spvFilter = packet.filter; + this.noRelay = false; + } -Peer.prototype.handlePong = async function handlePong(packet) { - const nonce = packet.nonce; - const now = util.ms(); + /** + * Handle `filteradd` packet. + * @method + * @private + * @param {FilterAddPacket} packet + */ - if (!this.challenge) { - this.logger.debug('Peer sent an unsolicited pong (%s).', this.hostname()); - return; - } + async handleFilterAdd(packet) { + const data = packet.data; - if (!nonce.equals(this.challenge)) { - if (nonce.equals(encoding.ZERO_U64)) { - this.logger.debug('Peer sent a zero nonce (%s).', this.hostname()); - this.challenge = null; + if (data.length > consensus.MAX_SCRIPT_PUSH) { + this.increaseBan(100); return; } - this.logger.debug('Peer sent the wrong nonce (%s).', this.hostname()); - return; - } - - if (now >= this.lastPing) { - this.lastPong = now; - if (this.minPing === -1) - this.minPing = now - this.lastPing; - this.minPing = Math.min(this.minPing, now - this.lastPing); - } else { - this.logger.debug('Timing mismatch (what?) (%s).', this.hostname()); - } - this.challenge = null; -}; + if (this.spvFilter) + this.spvFilter.add(data); -/** - * Handle `sendheaders` packet. - * @method - * @private - * @param {SendHeadersPacket} packet - */ - -Peer.prototype.handleSendHeaders = async function handleSendHeaders(packet) { - if (this.preferHeaders) { - this.logger.debug( - 'Peer sent a duplicate sendheaders (%s).', - this.hostname()); - return; + this.noRelay = false; } - this.preferHeaders = true; -}; + /** + * Handle `filterclear` packet. + * @method + * @private + * @param {FilterClearPacket} packet + */ -/** - * Handle `filterload` packet. - * @method - * @private - * @param {FilterLoadPacket} packet - */ + async handleFilterClear(packet) { + if (this.spvFilter) + this.spvFilter.reset(); -Peer.prototype.handleFilterLoad = async function handleFilterLoad(packet) { - if (!packet.isWithinConstraints()) { - this.increaseBan(100); - return; + this.noRelay = false; } - this.spvFilter = packet.filter; - this.noRelay = false; -}; + /** + * Handle `feefilter` packet. + * @method + * @private + * @param {FeeFilterPacket} packet + */ -/** - * Handle `filteradd` packet. - * @method - * @private - * @param {FilterAddPacket} packet - */ + async handleFeeFilter(packet) { + const rate = packet.rate; -Peer.prototype.handleFilterAdd = async function handleFilterAdd(packet) { - const data = packet.data; + if (rate < 0 || rate > consensus.MAX_MONEY) { + this.increaseBan(100); + return; + } - if (data.length > consensus.MAX_SCRIPT_PUSH) { - this.increaseBan(100); - return; + this.feeRate = rate; } - if (this.spvFilter) - this.spvFilter.add(data); + /** + * Handle `sendcmpct` packet. + * @method + * @private + * @param {SendCmpctPacket} + */ - this.noRelay = false; -}; + async handleSendCmpct(packet) { + if (this.compactMode !== -1) { + this.logger.debug( + 'Peer sent a duplicate sendcmpct (%s).', + this.hostname()); + return; + } -/** - * Handle `filterclear` packet. - * @method - * @private - * @param {FilterClearPacket} packet - */ + if (packet.version > 2) { + // Ignore + this.logger.info( + 'Peer request compact blocks version %d (%s).', + packet.version, this.hostname()); + return; + } -Peer.prototype.handleFilterClear = async function handleFilterClear(packet) { - if (this.spvFilter) - this.spvFilter.reset(); + if (packet.mode > 1) { + this.logger.info( + 'Peer request compact blocks mode %d (%s).', + packet.mode, this.hostname()); + return; + } - this.noRelay = false; -}; + this.logger.info( + 'Peer initialized compact blocks (mode=%d, version=%d) (%s).', + packet.mode, packet.version, this.hostname()); -/** - * Handle `feefilter` packet. - * @method - * @private - * @param {FeeFilterPacket} packet - */ + this.compactMode = packet.mode; + this.compactWitness = packet.version === 2; + } -Peer.prototype.handleFeeFilter = async function handleFeeFilter(packet) { - const rate = packet.rate; + /** + * Send `getheaders` to peer. Note that unlike + * `getblocks`, `getheaders` can have a null locator. + * @param {Hash[]?} locator - Chain locator. + * @param {Hash?} stop - Hash to stop at. + */ - if (rate < 0 || rate > consensus.MAX_MONEY) { - this.increaseBan(100); - return; - } + sendGetHeaders(locator, stop) { + const packet = new packets.GetHeadersPacket(locator, stop); - this.feeRate = rate; -}; + let hash = null; + if (packet.locator.length > 0) + hash = packet.locator[0]; -/** - * Handle `sendcmpct` packet. - * @method - * @private - * @param {SendCmpctPacket} - */ + let end = null; + if (stop) + end = stop; -Peer.prototype.handleSendCmpct = async function handleSendCmpct(packet) { - if (this.compactMode !== -1) { this.logger.debug( - 'Peer sent a duplicate sendcmpct (%s).', + 'Requesting headers packet from peer with getheaders (%s).', this.hostname()); - return; - } - if (packet.version > 2) { - // Ignore - this.logger.info( - 'Peer request compact blocks version %d (%s).', - packet.version, this.hostname()); - return; - } + this.logger.debug( + 'Sending getheaders (hash=%h, stop=%h).', + hash, end); - if (packet.mode > 1) { - this.logger.info( - 'Peer request compact blocks mode %d (%s).', - packet.mode, this.hostname()); - return; + this.send(packet); } - this.logger.info( - 'Peer initialized compact blocks (mode=%d, version=%d) (%s).', - packet.mode, packet.version, this.hostname()); + /** + * Send `getblocks` to peer. + * @param {Hash[]} locator - Chain locator. + * @param {Hash?} stop - Hash to stop at. + */ - this.compactMode = packet.mode; - this.compactWitness = packet.version === 2; -}; + sendGetBlocks(locator, stop) { + const packet = new packets.GetBlocksPacket(locator, stop); -/** - * Handle `encinit` packet. - * @method - * @private - * @param {EncinitPacket} packet - */ + let hash = null; + if (packet.locator.length > 0) + hash = packet.locator[0]; -Peer.prototype.handleEncinit = async function handleEncinit(packet) { - if (!this.bip151) - return; + let end = null; + if (stop) + end = stop; - this.bip151.encinit(packet.publicKey, packet.cipher); + this.logger.debug( + 'Requesting inv packet from peer with getblocks (%s).', + this.hostname()); - this.send(this.bip151.toEncack()); -}; + this.logger.debug( + 'Sending getblocks (hash=%h, stop=%h).', + hash, end); -/** - * Handle `encack` packet. - * @method - * @private - * @param {EncackPacket} packet - */ + this.send(packet); + } -Peer.prototype.handleEncack = async function handleEncack(packet) { - if (!this.bip151) - return; + /** + * Send `mempool` to peer. + */ - this.bip151.encack(packet.publicKey); -}; + sendMempool() { + if (!this.handshake) + return; -/** - * Handle `authchallenge` packet. - * @method - * @private - * @param {AuthChallengePacket} packet - */ + if (!(this.services & services.BLOOM)) { + this.logger.debug( + 'Cannot request mempool for non-bloom peer (%s).', + this.hostname()); + return; + } -Peer.prototype.handleAuthChallenge = async function handleAuthChallenge(packet) { - if (!this.bip150) - return; + this.logger.debug( + 'Requesting inv packet from peer with mempool (%s).', + this.hostname()); - const sig = this.bip150.challenge(packet.hash); + this.send(new packets.MempoolPacket()); + } - this.send(new packets.AuthReplyPacket(sig)); -}; + /** + * Send `reject` to peer. + * @param {Number} code + * @param {String} reason + * @param {String} msg + * @param {Hash} hash + */ -/** - * Handle `authreply` packet. - * @method - * @private - * @param {AuthReplyPacket} packet - */ + sendReject(code, reason, msg, hash) { + const reject = packets.RejectPacket.fromReason(code, reason, msg, hash); -Peer.prototype.handleAuthReply = async function handleAuthReply(packet) { - if (!this.bip150) - return; + if (msg) { + this.logger.debug('Rejecting %s %h (%s): code=%s reason=%s.', + msg, hash, this.hostname(), code, reason); + } else { + this.logger.debug('Rejecting packet from %s: code=%s reason=%s.', + this.hostname(), code, reason); + } - const hash = this.bip150.reply(packet.signature); + this.logger.debug( + 'Sending reject packet to peer (%s).', + this.hostname()); - if (hash) - this.send(new packets.AuthProposePacket(hash)); -}; + this.send(reject); + } -/** - * Handle `authpropose` packet. - * @method - * @private - * @param {AuthProposePacket} packet - */ + /** + * Send a `sendcmpct` packet. + * @param {Number} mode + */ -Peer.prototype.handleAuthPropose = async function handleAuthPropose(packet) { - if (!this.bip150) - return; + sendCompact(mode) { + if (this.services & common.services.WITNESS) { + if (this.version >= common.COMPACT_WITNESS_VERSION) { + this.logger.info( + 'Initializing witness compact blocks (%s).', + this.hostname()); + this.send(new packets.SendCmpctPacket(mode, 2)); + return; + } + } - const hash = this.bip150.propose(packet.hash); + if (this.version >= common.COMPACT_VERSION) { + this.logger.info( + 'Initializing normal compact blocks (%s).', + this.hostname()); - this.send(new packets.AuthChallengePacket(hash)); -}; + this.send(new packets.SendCmpctPacket(mode, 1)); + } + } -/** - * Send `getheaders` to peer. Note that unlike - * `getblocks`, `getheaders` can have a null locator. - * @param {Hash[]?} locator - Chain locator. - * @param {Hash?} stop - Hash to stop at. - */ + /** + * Increase banscore on peer. + * @param {Number} score + * @returns {Boolean} + */ -Peer.prototype.sendGetHeaders = function sendGetHeaders(locator, stop) { - const packet = new packets.GetHeadersPacket(locator, stop); + increaseBan(score) { + this.banScore += score; - let hash = null; - if (packet.locator.length > 0) - hash = util.revHex(packet.locator[0]); + if (this.banScore >= this.options.banScore) { + this.logger.debug('Ban threshold exceeded (%s).', this.hostname()); + this.ban(); + return true; + } - let end = null; - if (stop) - end = util.revHex(stop); + return false; + } - this.logger.debug( - 'Requesting headers packet from peer with getheaders (%s).', - this.hostname()); + /** + * Ban peer. + */ - this.logger.debug( - 'Sending getheaders (hash=%s, stop=%s).', - hash, end); + ban() { + this.emit('ban'); + } - this.send(packet); -}; + /** + * Send a `reject` packet to peer. + * @param {String} msg + * @param {VerifyError} err + * @returns {Boolean} + */ -/** - * Send `getblocks` to peer. - * @param {Hash[]} locator - Chain locator. - * @param {Hash?} stop - Hash to stop at. - */ + reject(msg, err) { + this.sendReject(err.code, err.reason, msg, err.hash); + return this.increaseBan(err.score); + } -Peer.prototype.sendGetBlocks = function sendGetBlocks(locator, stop) { - const packet = new packets.GetBlocksPacket(locator, stop); + /** + * Test whether required services are available. + * @param {Number} services + * @returns {Boolean} + */ - let hash = null; - if (packet.locator.length > 0) - hash = util.revHex(packet.locator[0]); + hasServices(services) { + return (this.services & services) === services; + } - let end = null; - if (stop) - end = util.revHex(stop); + /** + * Test whether the WITNESS service bit is set. + * @returns {Boolean} + */ - this.logger.debug( - 'Requesting inv packet from peer with getblocks (%s).', - this.hostname()); + hasWitness() { + return (this.services & services.WITNESS) !== 0; + } - this.logger.debug( - 'Sending getblocks (hash=%s, stop=%s).', - hash, end); + /** + * Test whether the peer supports compact blocks. + * @returns {Boolean} + */ - this.send(packet); -}; + hasCompactSupport() { + if (this.version < common.COMPACT_VERSION) + return false; -/** - * Send `mempool` to peer. - */ + if (!this.options.hasWitness()) + return true; -Peer.prototype.sendMempool = function sendMempool() { - if (!this.handshake) - return; + if (!(this.services & services.WITNESS)) + return false; - if (!(this.services & services.BLOOM)) { - this.logger.debug( - 'Cannot request mempool for non-bloom peer (%s).', - this.hostname()); - return; + return this.version >= common.COMPACT_WITNESS_VERSION; } - this.logger.debug( - 'Requesting inv packet from peer with mempool (%s).', - this.hostname()); + /** + * Test whether the peer sent us a + * compatible compact block handshake. + * @returns {Boolean} + */ - this.send(new packets.MempoolPacket()); -}; + hasCompact() { + if (this.compactMode === -1) + return false; -/** - * Send `reject` to peer. - * @param {Number} code - * @param {String} reason - * @param {String} msg - * @param {Hash} hash - */ + if (!this.options.hasWitness()) + return true; -Peer.prototype.sendReject = function sendReject(code, reason, msg, hash) { - const reject = packets.RejectPacket.fromReason(code, reason, msg, hash); + if (!this.compactWitness) + return false; - if (msg) { - this.logger.debug('Rejecting %s %s (%s): code=%s reason=%s.', - msg, util.revHex(hash), this.hostname(), code, reason); - } else { - this.logger.debug('Rejecting packet from %s: code=%s reason=%s.', - this.hostname(), code, reason); + return true; } - this.logger.debug( - 'Sending reject packet to peer (%s).', - this.hostname()); + /** + * Inspect the peer. + * @returns {String} + */ - this.send(reject); -}; + [inspectSymbol]() { + return ''; + } +} /** - * Send a `sendcmpct` packet. - * @param {Number} mode + * Max output bytes buffered before + * invoking stall behavior for peer. + * @const {Number} + * @default */ -Peer.prototype.sendCompact = function sendCompact(mode) { - if (this.services & common.services.WITNESS) { - if (this.version >= common.COMPACT_WITNESS_VERSION) { - this.logger.info( - 'Initializing witness compact blocks (%s).', - this.hostname()); - this.send(new packets.SendCmpctPacket(mode, 2)); - return; - } - } - - if (this.version >= common.COMPACT_VERSION) { - this.logger.info( - 'Initializing normal compact blocks (%s).', - this.hostname()); - - this.send(new packets.SendCmpctPacket(mode, 1)); - } -}; +Peer.DRAIN_MAX = 10 << 20; /** - * Increase banscore on peer. - * @param {Number} score - * @returns {Boolean} + * Interval to check for drainage + * and required responses from peer. + * @const {Number} + * @default */ -Peer.prototype.increaseBan = function increaseBan(score) { - this.banScore += score; - - if (this.banScore >= this.options.banScore) { - this.logger.debug('Ban threshold exceeded (%s).', this.hostname()); - this.ban(); - return true; - } - - return false; -}; +Peer.STALL_INTERVAL = 5000; /** - * Ban peer. + * Interval for pinging peers. + * @const {Number} + * @default */ -Peer.prototype.ban = function ban() { - this.emit('ban'); -}; +Peer.PING_INTERVAL = 30000; /** - * Send a `reject` packet to peer. - * @param {String} msg - * @param {VerifyError} err - * @returns {Boolean} + * Interval to flush invs. + * Higher means more invs (usually + * txs) will be accumulated before + * flushing. + * @const {Number} + * @default */ -Peer.prototype.reject = function reject(msg, err) { - this.sendReject(err.code, err.reason, msg, err.hash); - return this.increaseBan(err.score); -}; +Peer.INV_INTERVAL = 5000; /** - * Test whether required services are available. - * @param {Number} services - * @returns {Boolean} + * Required time for peers to + * respond to messages (i.e. + * getblocks/getdata). + * @const {Number} + * @default */ -Peer.prototype.hasServices = function hasServices(services) { - return (this.services & services) === services; -}; +Peer.RESPONSE_TIMEOUT = 30000; /** - * Test whether the WITNESS service bit is set. - * @returns {Boolean} + * Required time for loader to + * respond with block/merkleblock. + * @const {Number} + * @default */ -Peer.prototype.hasWitness = function hasWitness() { - return (this.services & services.WITNESS) !== 0; -}; +Peer.BLOCK_TIMEOUT = 120000; /** - * Test whether the peer supports compact blocks. - * @returns {Boolean} + * Required time for loader to + * respond with a tx. + * @const {Number} + * @default */ -Peer.prototype.hasCompactSupport = function hasCompactSupport() { - if (this.version < common.COMPACT_VERSION) - return false; - - if (!this.options.hasWitness()) - return true; +Peer.TX_TIMEOUT = 120000; - if (!(this.services & services.WITNESS)) - return false; +/** + * Generic timeout interval. + * @const {Number} + * @default + */ - return this.version >= common.COMPACT_WITNESS_VERSION; -}; +Peer.TIMEOUT_INTERVAL = 20 * 60000; /** - * Test whether the peer sent us a - * compatible compact block handshake. - * @returns {Boolean} + * Peer Options + * @alias module:net.PeerOptions */ -Peer.prototype.hasCompact = function hasCompact() { - if (this.compactMode === -1) - return false; +class PeerOptions { + /** + * Create peer options. + * @constructor + */ + + constructor(options) { + this.network = Network.primary; + this.logger = Logger.global; + + this.createSocket = tcp.createSocket; + this.version = common.PROTOCOL_VERSION; + this.services = common.LOCAL_SERVICES; + this.agent = common.USER_AGENT; + this.noRelay = false; + this.spv = false; + this.compact = false; + this.headers = false; + this.banScore = common.BAN_SCORE; + + this.getHeight = PeerOptions.getHeight; + this.isFull = PeerOptions.isFull; + this.hasWitness = PeerOptions.hasWitness; + this.createNonce = PeerOptions.createNonce; + this.hasNonce = PeerOptions.hasNonce; + this.getRate = PeerOptions.getRate; + + if (options) + this.fromOptions(options); + } + + /** + * Inject properties from object. + * @private + * @param {Object} options + * @returns {PeerOptions} + */ + + fromOptions(options) { + assert(options, 'Options are required.'); + + if (options.network != null) + this.network = Network.get(options.network); + + if (options.logger != null) { + assert(typeof options.logger === 'object'); + this.logger = options.logger; + } - if (!this.options.hasWitness()) - return true; + if (options.createSocket != null) { + assert(typeof options.createSocket === 'function'); + this.createSocket = options.createSocket; + } - if (!this.compactWitness) - return false; + if (options.version != null) { + assert(typeof options.version === 'number'); + this.version = options.version; + } - return true; -}; + if (options.services != null) { + assert(typeof options.services === 'number'); + this.services = options.services; + } -/** - * Inspect the peer. - * @returns {String} - */ + if (options.agent != null) { + assert(typeof options.agent === 'string'); + this.agent = options.agent; + } -Peer.prototype.inspect = function inspect() { - return ''; -}; + if (options.noRelay != null) { + assert(typeof options.noRelay === 'boolean'); + this.noRelay = options.noRelay; + } -/** - * PeerOptions - * @alias module:net.PeerOptions - * @constructor - */ + if (options.spv != null) { + assert(typeof options.spv === 'boolean'); + this.spv = options.spv; + } -function PeerOptions(options) { - if (!(this instanceof PeerOptions)) - return new PeerOptions(options); - - this.network = Network.primary; - this.logger = Logger.global; - - this.createSocket = tcp.createSocket; - this.version = common.PROTOCOL_VERSION; - this.services = common.LOCAL_SERVICES; - this.agent = common.USER_AGENT; - this.noRelay = false; - this.spv = false; - this.compact = false; - this.headers = false; - this.banScore = common.BAN_SCORE; - - this.getHeight = PeerOptions.getHeight; - this.isFull = PeerOptions.isFull; - this.hasWitness = PeerOptions.hasWitness; - this.createNonce = PeerOptions.createNonce; - this.hasNonce = PeerOptions.hasNonce; - this.getRate = PeerOptions.getRate; - - if (options) - this.fromOptions(options); -} + if (options.compact != null) { + assert(typeof options.compact === 'boolean'); + this.compact = options.compact; + } -/** - * Inject properties from object. - * @private - * @param {Object} options - * @returns {PeerOptions} - */ + if (options.headers != null) { + assert(typeof options.headers === 'boolean'); + this.headers = options.headers; + } -PeerOptions.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Options are required.'); + if (options.banScore != null) { + assert(typeof options.banScore === 'number'); + this.banScore = options.banScore; + } - if (options.network != null) - this.network = Network.get(options.network); + if (options.getHeight != null) { + assert(typeof options.getHeight === 'function'); + this.getHeight = options.getHeight; + } - if (options.logger != null) { - assert(typeof options.logger === 'object'); - this.logger = options.logger; - } + if (options.isFull != null) { + assert(typeof options.isFull === 'function'); + this.isFull = options.isFull; + } - if (options.createSocket != null) { - assert(typeof options.createSocket === 'function'); - this.createSocket = options.createSocket; - } + if (options.hasWitness != null) { + assert(typeof options.hasWitness === 'function'); + this.hasWitness = options.hasWitness; + } - if (options.version != null) { - assert(typeof options.version === 'number'); - this.version = options.version; - } + if (options.createNonce != null) { + assert(typeof options.createNonce === 'function'); + this.createNonce = options.createNonce; + } - if (options.services != null) { - assert(typeof options.services === 'number'); - this.services = options.services; - } + if (options.hasNonce != null) { + assert(typeof options.hasNonce === 'function'); + this.hasNonce = options.hasNonce; + } - if (options.agent != null) { - assert(typeof options.agent === 'string'); - this.agent = options.agent; - } + if (options.getRate != null) { + assert(typeof options.getRate === 'function'); + this.getRate = options.getRate; + } - if (options.noRelay != null) { - assert(typeof options.noRelay === 'boolean'); - this.noRelay = options.noRelay; + return this; } - if (options.spv != null) { - assert(typeof options.spv === 'boolean'); - this.spv = options.spv; - } + /** + * Instantiate options from object. + * @param {Object} options + * @returns {PeerOptions} + */ - if (options.compact != null) { - assert(typeof options.compact === 'boolean'); - this.compact = options.compact; + static fromOptions(options) { + return new this().fromOptions(options); } - if (options.headers != null) { - assert(typeof options.headers === 'boolean'); - this.headers = options.headers; - } + /** + * Get the chain height. + * @private + * @returns {Number} + */ - if (options.banScore != null) { - assert(typeof options.banScore === 'number'); - this.banScore = options.banScore; + static getHeight() { + return 0; } - if (options.getHeight != null) { - assert(typeof options.getHeight === 'function'); - this.getHeight = options.getHeight; - } + /** + * Test whether the chain is synced. + * @private + * @returns {Boolean} + */ - if (options.isFull != null) { - assert(typeof options.isFull === 'function'); - this.isFull = options.isFull; + static isFull() { + return false; } - if (options.hasWitness != null) { - assert(typeof options.hasWitness === 'function'); - this.hasWitness = options.hasWitness; - } + /** + * Whether segwit is enabled. + * @private + * @returns {Boolean} + */ - if (options.createNonce != null) { - assert(typeof options.createNonce === 'function'); - this.createNonce = options.createNonce; + static hasWitness() { + return true; } - if (options.hasNonce != null) { - assert(typeof options.hasNonce === 'function'); - this.hasNonce = options.hasNonce; - } + /** + * Create a version packet nonce. + * @private + * @param {String} hostname + * @returns {Buffer} + */ - if (options.getRate != null) { - assert(typeof options.getRate === 'function'); - this.getRate = options.getRate; + static createNonce(hostname) { + return common.nonce(); } - return this; -}; - -/** - * Instantiate options from object. - * @param {Object} options - * @returns {PeerOptions} - */ - -PeerOptions.fromOptions = function fromOptions(options) { - return new PeerOptions().fromOptions(options); -}; - -/** - * Get the chain height. - * @private - * @returns {Number} - */ + /** + * Test whether version nonce is ours. + * @private + * @param {Buffer} nonce + * @returns {Boolean} + */ -PeerOptions.getHeight = function getHeight() { - return 0; -}; + static hasNonce(nonce) { + return false; + } -/** - * Test whether the chain is synced. - * @private - * @returns {Boolean} - */ + /** + * Get fee rate for txid. + * @private + * @param {Hash} hash + * @returns {Rate} + */ -PeerOptions.isFull = function isFull() { - return false; -}; + static getRate(hash) { + return -1; + } +} /** - * Whether segwit is enabled. - * @private - * @returns {Boolean} + * Request Entry + * @ignore */ -PeerOptions.hasWitness = function hasWitness() { - return true; -}; - -/** - * Create a version packet nonce. - * @private - * @param {String} hostname - * @returns {Buffer} - */ +class RequestEntry { + /** + * Create a request entry. + * @constructor + */ -PeerOptions.createNonce = function createNonce(hostname) { - return util.nonce(); -}; + constructor() { + this.timeout = 0; + this.jobs = []; + } -/** - * Test whether version nonce is ours. - * @private - * @param {Buffer} nonce - * @returns {Boolean} - */ + addJob(resolve, reject) { + this.jobs.push({ resolve, reject }); + } -PeerOptions.hasNonce = function hasNonce(nonce) { - return false; -}; + setTimeout(timeout) { + this.timeout = Date.now() + timeout; + } -/** - * Get fee rate for txid. - * @private - * @param {Hash} hash - * @returns {Rate} - */ + reject(err) { + for (const job of this.jobs) + job.reject(err); -PeerOptions.getRate = function getRate(hash) { - return -1; -}; + this.jobs.length = 0; + } -/** - * RequestEntry - * @constructor - * @ignore - */ + resolve(result) { + for (const job of this.jobs) + job.resolve(result); -function RequestEntry() { - this.timeout = 0; - this.jobs = []; + this.jobs.length = 0; + } } -RequestEntry.prototype.addJob = function addJob(resolve, reject) { - this.jobs.push(co.job(resolve, reject)); -}; - -RequestEntry.prototype.setTimeout = function setTimeout(timeout) { - this.timeout = util.ms() + timeout; -}; - -RequestEntry.prototype.reject = function reject(err) { - for (const job of this.jobs) - job.reject(err); - - this.jobs.length = 0; -}; - -RequestEntry.prototype.resolve = function resolve(result) { - for (const job of this.jobs) - job.resolve(result); - - this.jobs.length = 0; -}; - /* * Expose */ diff --git a/lib/net/pool.js b/lib/net/pool.js index 7732f506c..834fe8aa8 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -7,4391 +7,4296 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const EventEmitter = require('events'); -const AsyncObject = require('../utils/asyncobject'); +const {Lock} = require('bmutex'); +const IP = require('binet'); +const dns = require('bdns'); +const tcp = require('btcp'); +const UPNP = require('bupnp'); +const socks = require('bsocks'); +const List = require('blst'); +const {BloomFilter, RollingFilter} = require('bfilter'); +const {BufferMap, BufferSet} = require('buffer-map'); const util = require('../utils/util'); -const IP = require('../utils/ip'); -const co = require('../utils/co'); const common = require('./common'); const chainCommon = require('../blockchain/common'); const Address = require('../primitives/address'); -const BIP150 = require('./bip150'); -const BIP151 = require('./bip151'); const BIP152 = require('./bip152'); -const Bloom = require('../utils/bloom'); -const RollingFilter = require('../utils/rollingfilter'); -const secp256k1 = require('../crypto/secp256k1'); -const Lock = require('../utils/lock'); const Network = require('../protocol/network'); const Peer = require('./peer'); -const external = require('./external'); -const List = require('../utils/list'); -const tcp = require('./tcp'); -const dns = require('./dns'); const HostList = require('./hostlist'); -const UPNP = require('./upnp'); const InvItem = require('../primitives/invitem'); const packets = require('./packets'); const services = common.services; const invTypes = InvItem.types; const packetTypes = packets.types; const scores = HostList.scores; +const {inspectSymbol} = require('../utils'); /** + * Pool * A pool of peers for handling all network activity. * @alias module:net.Pool - * @constructor - * @param {Object} options - * @param {Chain} options.chain - * @param {Mempool?} options.mempool - * @param {Number?} [options.maxOutbound=8] - Maximum number of peers. - * @param {Boolean?} options.spv - Do an SPV sync. - * @param {Boolean?} options.noRelay - Whether to ask - * for relayed transactions. - * @param {Number?} [options.feeRate] - Fee filter rate. - * @param {Number?} [options.invTimeout=60000] - Timeout for broadcasted - * objects. - * @param {Boolean?} options.listen - Whether to spin up a server socket - * and listen for peers. - * @param {Boolean?} options.selfish - A selfish pool. Will not serve blocks, - * headers, hashes, utxos, or transactions to peers. - * @param {Boolean?} options.broadcast - Whether to automatically broadcast - * transactions accepted to our mempool. - * @param {String[]} options.seeds - * @param {Function?} options.createSocket - Custom function to create a socket. - * Must accept (port, host) and return a node-like socket. - * @param {Function?} options.createServer - Custom function to create a server. - * Must return a node-like server. - * @emits Pool#block - * @emits Pool#tx - * @emits Pool#peer - * @emits Pool#open - * @emits Pool#close - * @emits Pool#error - * @emits Pool#reject - */ - -function Pool(options) { - if (!(this instanceof Pool)) - return new Pool(options); - - AsyncObject.call(this); - - this.options = new PoolOptions(options); - - this.network = this.options.network; - this.logger = this.options.logger.context('net'); - this.chain = this.options.chain; - this.mempool = this.options.mempool; - this.server = this.options.createServer(); - this.nonces = this.options.nonces; - - this.locker = new Lock(true); - this.connected = false; - this.disconnecting = false; - this.syncing = false; - this.spvFilter = null; - this.txFilter = null; - this.blockMap = new Set(); - this.txMap = new Set(); - this.compactBlocks = new Set(); - this.invMap = new Map(); - this.pendingFilter = null; - this.pendingRefill = null; - - this.checkpoints = false; - this.headerChain = new List(); - this.headerNext = null; - this.headerTip = null; - - this.peers = new PeerList(); - this.authdb = new BIP150.AuthDB(this.options); - this.hosts = new HostList(this.options); - this.id = 0; + * @extends EventEmitter + */ + +class Pool extends EventEmitter { + /** + * Create a pool. + * @constructor + * @param {Object} options + */ + + constructor(options) { + super(); + + this.opened = false; + this.options = new PoolOptions(options); + + this.network = this.options.network; + this.logger = this.options.logger.context('net'); + this.chain = this.options.chain; + this.mempool = this.options.mempool; + this.server = this.options.createServer(); + this.nonces = this.options.nonces; + + this.locker = new Lock(true, BufferMap); + this.connected = false; + this.disconnecting = false; + this.syncing = false; + this.discovering = false; + this.spvFilter = null; + this.txFilter = null; + this.blockMap = new BufferSet(); + this.txMap = new BufferSet(); + this.compactBlocks = new BufferSet(); + this.invMap = new BufferMap(); + this.pendingFilter = null; + this.pendingRefill = null; - if (this.options.spv) - this.spvFilter = Bloom.fromRate(20000, 0.001, Bloom.flags.ALL); + this.checkpoints = false; + this.headerChain = new List(); + this.headerNext = null; + this.headerTip = null; - if (!this.options.mempool) - this.txFilter = new RollingFilter(50000, 0.000001); + this.peers = new PeerList(); + this.hosts = new HostList(this.options); + this.id = 0; - this._init(); -}; + if (this.options.spv) { + this.spvFilter = BloomFilter.fromRate( + 20000, 0.001, BloomFilter.flags.ALL); + } -Object.setPrototypeOf(Pool.prototype, AsyncObject.prototype); + if (!this.options.mempool) + this.txFilter = new RollingFilter(50000, 0.000001); -/** - * Discovery interval for UPNP and DNS seeds. - * @const {Number} - * @default - */ + this.init(); + } -Pool.DISCOVERY_INTERVAL = 120000; + /** + * Initialize the pool. + * @private + */ -/** - * Initialize the pool. - * @private - */ + init() { + this.server.on('error', (err) => { + this.emit('error', err); + }); -Pool.prototype._init = function _init() { - this.server.on('error', (err) => { - this.emit('error', err); - }); + this.server.on('connection', (socket) => { + this.handleSocket(socket); + this.emit('connection', socket); + }); - this.server.on('connection', (socket) => { - this.handleSocket(socket); - this.emit('connection', socket); - }); + this.server.on('listening', () => { + const data = this.server.address(); + this.logger.info( + 'Pool server listening on %s (port=%d).', + data.address, data.port); + this.emit('listening', data); + }); - this.server.on('listening', () => { - const data = this.server.address(); - this.logger.info( - 'Pool server listening on %s (port=%d).', - data.address, data.port); - this.emit('listening', data); - }); + this.chain.on('block', (block, entry) => { + this.emit('block', block, entry); + }); - this.chain.on('block', (block, entry) => { - this.emit('block', block, entry); - }); + this.chain.on('reset', () => { + if (this.checkpoints) + this.resetChain(); + this.forceSync(); + }); - this.chain.on('reset', () => { - if (this.checkpoints) - this.resetChain(); - this.forceSync(); - }); - - this.chain.on('full', () => { - this.sync(); - this.emit('full'); - this.logger.info('Chain is fully synced (height=%d).', this.chain.height); - }); - - this.chain.on('bad orphan', (err, id) => { - this.handleBadOrphan('block', err, id); - }); - - if (this.mempool) { - this.mempool.on('tx', (tx) => { - this.emit('tx', tx); + this.chain.on('full', () => { + this.sync(); + this.emit('full'); + this.logger.info('Chain is fully synced (height=%d).', this.chain.height); }); - this.mempool.on('bad orphan', (err, id) => { - this.handleBadOrphan('tx', err, id); + this.chain.on('bad orphan', (err, id) => { + this.handleBadOrphan('block', err, id); }); - } - if (!this.options.selfish && !this.options.spv) { if (this.mempool) { this.mempool.on('tx', (tx) => { - this.announceTX(tx); + this.emit('tx', tx); + }); + + this.mempool.on('bad orphan', (err, id) => { + this.handleBadOrphan('tx', err, id); }); } - // Normally we would also broadcast - // competing chains, but we want to - // avoid getting banned if an evil - // miner sends us an invalid competing - // chain that we can't connect and - // verify yet. - this.chain.on('block', (block) => { - if (!this.chain.synced) - return; - this.announceBlock(block); - }); + if (!this.options.selfish && !this.options.spv) { + if (this.mempool) { + this.mempool.on('tx', (tx) => { + this.announceTX(tx); + }); + } + + // Normally we would also broadcast + // competing chains, but we want to + // avoid getting banned if an evil + // miner sends us an invalid competing + // chain that we can't connect and + // verify yet. + this.chain.on('block', (block) => { + if (!this.chain.synced) + return; + this.announceBlock(block); + }); + } } -}; -/** - * Open the pool, wait for the chain to load. - * @method - * @alias Pool#open - * @returns {Promise} - */ + /** + * Open the pool, wait for the chain to load. + * @returns {Promise} + */ -Pool.prototype._open = async function _open() { - if (this.mempool) - await this.mempool.open(); - else - await this.chain.open(); + async open() { + assert(!this.opened, 'Pool is already open.'); + this.opened = true; - this.logger.info('Pool loaded (maxpeers=%d).', this.options.maxOutbound); + this.logger.info('Pool loaded (maxpeers=%d).', this.options.maxOutbound); - if (this.options.bip150) { - const key = secp256k1.publicKeyCreate(this.options.identityKey, true); - this.logger.info('Identity public key: %s.', key.toString('hex')); - this.logger.info('Identity address: %s.', BIP150.address(key)); + this.resetChain(); } - this.resetChain(); -}; + /** + * Close and destroy the pool. + * @method + * @alias Pool#close + * @returns {Promise} + */ -/** - * Reset header chain. - */ - -Pool.prototype.resetChain = function resetChain() { - if (!this.options.checkpoints) - return; + async close() { + assert(this.opened, 'Pool is not open.'); + this.opened = false; + return this.disconnect(); + } - this.checkpoints = false; - this.headerTip = null; - this.headerChain.reset(); - this.headerNext = null; + /** + * Reset header chain. + */ - const tip = this.chain.tip; + resetChain() { + if (!this.options.checkpoints) + return; - if (tip.height < this.network.lastCheckpoint) { - this.checkpoints = true; - this.headerTip = this.getNextTip(tip.height); - this.headerChain.push(new HeaderEntry(tip.hash, tip.height)); - this.logger.info( - 'Initialized header chain to height %d (checkpoint=%s).', - tip.height, util.revHex(this.headerTip.hash)); - } -}; + this.checkpoints = false; + this.headerTip = null; + this.headerChain.reset(); + this.headerNext = null; -/** - * Close and destroy the pool. - * @method - * @alias Pool#close - * @returns {Promise} - */ + const tip = this.chain.tip; -Pool.prototype._close = async function _close() { - await this.disconnect(); -}; + if (tip.height < this.network.lastCheckpoint) { + this.checkpoints = true; + this.headerTip = this.getNextTip(tip.height); + this.headerChain.push(new HeaderEntry(tip.hash, tip.height)); + this.logger.info( + 'Initialized header chain to height %d (checkpoint=%h).', + tip.height, this.headerTip.hash); + } + } -/** - * Connect to the network. - * @method - * @returns {Promise} - */ + /** + * Connect to the network. + * @method + * @returns {Promise} + */ -Pool.prototype.connect = async function connect() { - const unlock = await this.locker.lock(); - try { - return await this._connect(); - } finally { - unlock(); + async connect() { + const unlock = await this.locker.lock(); + try { + return await this._connect(); + } finally { + unlock(); + } } -}; -/** - * Connect to the network (no lock). - * @method - * @returns {Promise} - */ + /** + * Connect to the network (no lock). + * @method + * @returns {Promise} + */ -Pool.prototype._connect = async function _connect() { - assert(this.loaded, 'Pool is not loaded.'); + async _connect() { + assert(this.opened, 'Pool is not opened.'); - if (this.connected) - return; + if (this.connected) + return; - await this.hosts.open(); - await this.authdb.open(); + await this.hosts.open(); - await this.discoverGateway(); - await this.discoverExternal(); - await this.discoverSeeds(); + await this.discoverGateway(); + await this.discoverExternal(); + await this.discoverSeeds(); - this.fillOutbound(); + this.fillOutbound(); - await this.listen(); + await this.listen(); - this.startTimer(); + this.startTimer(); - this.connected = true; -}; + this.connected = true; + } -/** - * Disconnect from the network. - * @method - * @returns {Promise} - */ + /** + * Disconnect from the network. + * @method + * @returns {Promise} + */ -Pool.prototype.disconnect = async function disconnect() { - const unlock = await this.locker.lock(); - try { - return await this._disconnect(); - } finally { - unlock(); + async disconnect() { + const unlock = await this.locker.lock(); + try { + return await this._disconnect(); + } finally { + unlock(); + } } -}; -/** - * Disconnect from the network. - * @method - * @returns {Promise} - */ + /** + * Disconnect from the network. + * @method + * @returns {Promise} + */ -Pool.prototype._disconnect = async function _disconnect() { - assert(this.loaded, 'Pool is not loaded.'); + async _disconnect() { + for (const item of this.invMap.values()) + item.resolve(); - if (!this.connected) - return; + if (!this.connected) + return; - this.disconnecting = true; + this.disconnecting = true; - for (const item of this.invMap.values()) - item.resolve(); + this.peers.destroy(); - this.peers.destroy(); + this.blockMap.clear(); + this.txMap.clear(); - this.blockMap.clear(); - this.txMap.clear(); + if (this.pendingFilter != null) { + clearTimeout(this.pendingFilter); + this.pendingFilter = null; + } - if (this.pendingFilter != null) { - clearTimeout(this.pendingFilter); - this.pendingFilter = null; - } + if (this.pendingRefill != null) { + clearTimeout(this.pendingRefill); + this.pendingRefill = null; + } - if (this.pendingRefill != null) { - clearTimeout(this.pendingRefill); - this.pendingRefill = null; - } + this.checkpoints = false; + this.headerTip = null; + this.headerChain.reset(); + this.headerNext = null; - this.checkpoints = false; - this.headerTip = null; - this.headerChain.reset(); - this.headerNext = null; + this.stopTimer(); - this.stopTimer(); + await this.hosts.close(); - await this.authdb.close(); - await this.hosts.close(); + await this.unlisten(); - await this.unlisten(); + this.disconnecting = false; + this.syncing = false; + this.connected = false; + } - this.disconnecting = false; - this.syncing = false; - this.connected = false; -}; + /** + * Start listening on a server socket. + * @method + * @private + * @returns {Promise} + */ -/** - * Start listening on a server socket. - * @method - * @private - * @returns {Promise} - */ + async listen() { + assert(this.server); + assert(!this.connected, 'Already listening.'); -Pool.prototype.listen = async function listen() { - assert(this.server); - assert(!this.connected, 'Already listening.'); + if (!this.options.listen) + return; - if (!this.options.listen) - return; + this.server.maxConnections = this.options.maxInbound; - this.server.maxConnections = this.options.maxInbound; + await this.server.listen(this.options.port, this.options.host); + } - await this.server.listen(this.options.port, this.options.host); -}; + /** + * Stop listening on server socket. + * @method + * @private + * @returns {Promise} + */ -/** - * Stop listening on server socket. - * @method - * @private - * @returns {Promise} - */ + async unlisten() { + assert(this.server); + assert(this.connected, 'Not listening.'); -Pool.prototype.unlisten = async function unlisten() { - assert(this.server); - assert(this.connected, 'Not listening.'); + if (!this.options.listen) + return; - if (!this.options.listen) - return; + await this.server.close(); + } - await this.server.close(); -}; + /** + * Start discovery timer. + * @private + */ -/** - * Start discovery timer. - * @private - */ + startTimer() { + assert(this.timer == null, 'Timer already started.'); + this.timer = setInterval(() => this.discover(), Pool.DISCOVERY_INTERVAL); + } -Pool.prototype.startTimer = function startTimer() { - assert(this.timer == null, 'Timer already started.'); - this.timer = co.setInterval(this.discover, Pool.DISCOVERY_INTERVAL, this); -}; + /** + * Stop discovery timer. + * @private + */ -/** - * Stop discovery timer. - * @private - */ + stopTimer() { + assert(this.timer != null, 'Timer already stopped.'); + clearInterval(this.timer); + this.timer = null; + } -Pool.prototype.stopTimer = function stopTimer() { - assert(this.timer != null, 'Timer already stopped.'); - co.clearInterval(this.timer); - this.timer = null; -}; + /** + * Rediscover seeds and internet gateway. + * Attempt to add port mapping once again. + * @returns {Promise} + */ -/** - * Rediscover seeds and internet gateway. - * Attempt to add port mapping once again. - * @returns {Promise} - */ + async discover() { + if (this.discovering) + return; -Pool.prototype.discover = async function discover() { - await this.discoverGateway(); - await this.discoverSeeds(true); -}; + try { + this.discovering = true; + await this.discoverGateway(); + await this.discoverSeeds(true); + } finally { + this.discovering = false; + } + } -/** - * Attempt to add port mapping (i.e. - * remote:8333->local:8333) via UPNP. - * @returns {Promise} - */ + /** + * Attempt to add port mapping (i.e. + * remote:8333->local:8333) via UPNP. + * @returns {Promise} + */ -Pool.prototype.discoverGateway = async function discoverGateway() { - const src = this.options.publicPort; - const dest = this.options.port; + async discoverGateway() { + const src = this.options.publicPort; + const dest = this.options.port; - // Pointless if we're not listening. - if (!this.options.listen) - return false; + // Pointless if we're not listening. + if (!this.options.listen) + return false; - // UPNP is always optional, since - // it's likely to not work anyway. - if (!this.options.upnp) - return false; + // UPNP is always optional, since + // it's likely to not work anyway. + if (!this.options.upnp) + return false; - let wan; - try { - this.logger.debug('Discovering internet gateway (upnp).'); - wan = await UPNP.discover(); - } catch (e) { - this.logger.debug('Could not discover internet gateway (upnp).'); - this.logger.debug(e); - return false; - } + let wan; + try { + this.logger.debug('Discovering internet gateway (upnp).'); + wan = await UPNP.discover(); + } catch (e) { + this.logger.debug('Could not discover internet gateway (upnp).'); + this.logger.debug(e); + return false; + } - let host; - try { - host = await wan.getExternalIP(); - } catch (e) { - this.logger.debug('Could not find external IP (upnp).'); - this.logger.debug(e); - return false; - } + let host; + try { + host = await wan.getExternalIP(); + } catch (e) { + this.logger.debug('Could not find external IP (upnp).'); + this.logger.debug(e); + return false; + } - if (this.hosts.addLocal(host, src, scores.UPNP)) - this.logger.info('External IP found (upnp): %s.', host); + if (this.hosts.addLocal(host, src, scores.UPNP)) + this.logger.info('External IP found (upnp): %s.', host); - this.logger.debug( - 'Adding port mapping %d->%d.', - src, dest); + this.logger.debug( + 'Adding port mapping %d->%d.', + src, dest); + + try { + await wan.addPortMapping(host, src, dest); + } catch (e) { + this.logger.debug('Could not add port mapping (upnp).'); + this.logger.debug(e); + return false; + } - try { - await wan.addPortMapping(host, src, dest); - } catch (e) { - this.logger.debug('Could not add port mapping (upnp).'); - this.logger.debug(e); - return false; + return true; } - return true; -}; + /** + * Attempt to resolve DNS seeds if necessary. + * @param {Boolean} checkPeers + * @returns {Promise} + */ -/** - * Attempt to resolve DNS seeds if necessary. - * @param {Boolean} checkPeers - * @returns {Promise} - */ + async discoverSeeds(checkPeers) { + if (!this.options.discover) + return; -Pool.prototype.discoverSeeds = async function discoverSeeds(checkPeers) { - if (this.hosts.dnsSeeds.length === 0) - return; + if (this.hosts.dnsSeeds.length === 0) + return; - const max = Math.min(2, this.options.maxOutbound); - const size = this.hosts.size(); + const max = Math.min(2, this.options.maxOutbound); + const size = this.hosts.size(); - let total = 0; - for (let peer = this.peers.head(); peer; peer = peer.next) { - if (!peer.outbound) - continue; + let total = 0; + for (let peer = this.peers.head(); peer; peer = peer.next) { + if (!peer.outbound) + continue; - if (peer.connected) { - if (++total > max) - break; + if (peer.connected) { + if (++total > max) + break; + } } - } - if (size === 0 || (checkPeers && total < max)) { - this.logger.warning('Could not find enough peers.'); - this.logger.warning('Hitting DNS seeds...'); + if (size === 0 || (checkPeers && total < max)) { + this.logger.warning('Could not find enough peers.'); + this.logger.warning('Hitting DNS seeds...'); - await this.hosts.discoverSeeds(); + await this.hosts.discoverSeeds(); - this.logger.info( - 'Resolved %d hosts from DNS seeds.', - this.hosts.size() - size); + this.logger.info( + 'Resolved %d hosts from DNS seeds.', + this.hosts.size() - size); - this.refill(); + this.refill(); + } } -}; -/** - * Attempt to discover external IP via HTTP. - * @returns {Promise} - */ + /** + * Attempt to discover external IP via DNS. + * @returns {Promise} + */ -Pool.prototype.discoverExternal = async function discoverExternal() { - const port = this.options.publicPort; + async discoverExternal() { + const port = this.options.publicPort; - // Pointless if we're not listening. - if (!this.options.listen) - return; + // Pointless if we're not listening. + if (!this.options.listen) + return; - // Never hit an HTTP server if - // we're using an outbound proxy. - if (this.options.proxy) - return; + // Never hit a DNS server if + // we're using an outbound proxy. + if (this.options.proxy) + return; - // Try not to hit this if we can avoid it. - if (this.hosts.local.size > 0) - return; + // Try not to hit this if we can avoid it. + if (this.hosts.local.size > 0) + return; - let host4; - try { - host4 = await external.getIPv4(); - } catch (e) { - this.logger.debug('Could not find external IPv4 (http).'); - this.logger.debug(e); - } + let host4 = null; - if (host4 && this.hosts.addLocal(host4, port, scores.HTTP)) - this.logger.info('External IPv4 found (http): %s.', host4); + try { + host4 = await dns.getIPv4(2000); + } catch (e) { + this.logger.debug('Could not find external IPv4 (dns).'); + this.logger.debug(e); + } - let host6; - try { - host6 = await external.getIPv6(); - } catch (e) { - this.logger.debug('Could not find external IPv6 (http).'); - this.logger.debug(e); - } + if (host4 && this.hosts.addLocal(host4, port, scores.DNS)) + this.logger.info('External IPv4 found (dns): %s.', host4); - if (host6 && this.hosts.addLocal(host6, port, scores.HTTP)) - this.logger.info('External IPv6 found (http): %s.', host6); -}; + let host6 = null; -/** - * Handle incoming connection. - * @private - * @param {net.Socket} socket - */ + try { + host6 = await dns.getIPv6(2000); + } catch (e) { + this.logger.debug('Could not find external IPv6 (dns).'); + this.logger.debug(e); + } -Pool.prototype.handleSocket = function handleSocket(socket) { - if (!socket.remoteAddress) { - this.logger.debug('Ignoring disconnected peer.'); - socket.destroy(); - return; + if (host6 && this.hosts.addLocal(host6, port, scores.DNS)) + this.logger.info('External IPv6 found (dns): %s.', host6); } - const ip = IP.normalize(socket.remoteAddress); + /** + * Handle incoming connection. + * @private + * @param {net.Socket} socket + */ - if (this.peers.inbound >= this.options.maxInbound) { - this.logger.debug('Ignoring peer: too many inbound (%s).', ip); - socket.destroy(); - return; - } + handleSocket(socket) { + if (!socket.remoteAddress) { + this.logger.debug('Ignoring disconnected peer.'); + socket.destroy(); + return; + } - if (this.hosts.isBanned(ip)) { - this.logger.debug('Ignoring banned peer (%s).', ip); - socket.destroy(); - return; - } + const ip = IP.normalize(socket.remoteAddress); - const host = IP.toHostname(ip, socket.remotePort); + if (this.peers.inbound >= this.options.maxInbound) { + this.logger.debug('Ignoring peer: too many inbound (%s).', ip); + socket.destroy(); + return; + } - assert(!this.peers.map[host], 'Port collision.'); + if (this.hosts.isBanned(ip)) { + this.logger.debug('Ignoring banned peer (%s).', ip); + socket.destroy(); + return; + } - this.addInbound(socket); -}; + const host = IP.toHostname(ip, socket.remotePort); -/** - * Add a loader peer. Necessary for - * a sync to even begin. - * @private - */ + assert(!this.peers.map.has(host), 'Port collision.'); -Pool.prototype.addLoader = function addLoader() { - if (!this.loaded) - return; + this.addInbound(socket); + } - assert(!this.peers.load); + /** + * Add a loader peer. Necessary for + * a sync to even begin. + * @private + */ - for (let peer = this.peers.head(); peer; peer = peer.next) { - if (!peer.outbound) - continue; + addLoader() { + if (!this.opened) + return; - this.logger.info( - 'Repurposing peer for loader (%s).', - peer.hostname()); + assert(!this.peers.load); - this.setLoader(peer); + for (let peer = this.peers.head(); peer; peer = peer.next) { + if (!peer.outbound) + continue; - return; - } + this.logger.info( + 'Repurposing peer for loader (%s).', + peer.hostname()); - const addr = this.getHost(); + this.setLoader(peer); - if (!addr) - return; + return; + } - const peer = this.createOutbound(addr); + const addr = this.getHost(); - this.logger.info('Adding loader peer (%s).', peer.hostname()); + if (!addr) + return; - this.peers.add(peer); + const peer = this.createOutbound(addr); - this.setLoader(peer); -}; + this.logger.info('Adding loader peer (%s).', peer.hostname()); -/** - * Add a loader peer. Necessary for - * a sync to even begin. - * @private - */ + this.peers.add(peer); -Pool.prototype.setLoader = function setLoader(peer) { - if (!this.loaded) - return; + this.setLoader(peer); + } - assert(peer.outbound); - assert(!this.peers.load); - assert(!peer.loader); + /** + * Add a loader peer. Necessary for + * a sync to even begin. + * @private + */ - peer.loader = true; - this.peers.load = peer; + setLoader(peer) { + if (!this.opened) + return; - this.sendSync(peer); + assert(peer.outbound); + assert(!this.peers.load); + assert(!peer.loader); - this.emit('loader', peer); -}; + peer.loader = true; + this.peers.load = peer; -/** - * Start the blockchain sync. - */ + this.sendSync(peer); + + this.emit('loader', peer); + } -Pool.prototype.startSync = function startSync() { - if (!this.loaded) - return; + /** + * Start the blockchain sync. + */ - assert(this.connected, 'Pool is not connected!'); + startSync() { + if (!this.opened || !this.connected) + return; - this.syncing = true; - this.resync(false); -}; + this.syncing = true; + this.resync(false); + } -/** - * Force sending of a sync to each peer. - */ + /** + * Force sending of a sync to each peer. + */ -Pool.prototype.forceSync = function forceSync() { - if (!this.loaded) - return; + forceSync() { + if (!this.opened || !this.connected) + return; - assert(this.connected, 'Pool is not connected!'); + this.resync(true); + } - this.resync(true); -}; + /** + * Send a sync to each peer. + */ -/** - * Send a sync to each peer. - */ + sync(force) { + this.resync(false); + } -Pool.prototype.sync = function sync(force) { - this.resync(false); -}; + /** + * Stop the sync. + * @private + */ -/** - * Stop the sync. - * @private - */ + stopSync() { + if (!this.syncing) + return; -Pool.prototype.stopSync = function stopSync() { - if (!this.syncing) - return; + this.syncing = false; - this.syncing = false; + for (let peer = this.peers.head(); peer; peer = peer.next) { + if (!peer.outbound) + continue; - for (let peer = this.peers.head(); peer; peer = peer.next) { - if (!peer.outbound) - continue; + if (!peer.syncing) + continue; - if (!peer.syncing) - continue; + peer.syncing = false; + peer.merkleBlock = null; + peer.merkleTime = -1; + peer.merkleMatches = 0; + peer.merkleMap = null; + peer.blockTime = -1; + peer.blockMap.clear(); + peer.compactBlocks.clear(); + } - peer.syncing = false; - peer.merkleBlock = null; - peer.merkleTime = -1; - peer.merkleMatches = 0; - peer.merkleMap = null; - peer.blockTime = -1; - peer.blockMap.clear(); - peer.compactBlocks.clear(); + this.blockMap.clear(); + this.compactBlocks.clear(); } - this.blockMap.clear(); - this.compactBlocks.clear(); -}; - -/** - * Send a sync to each peer. - * @private - * @param {Boolean?} force - * @returns {Promise} - */ + /** + * Send a sync to each peer. + * @private + * @param {Boolean?} force + * @returns {Promise} + */ -Pool.prototype.resync = async function resync(force) { - if (!this.syncing) - return; + async resync(force) { + if (!this.syncing) + return; - let locator; - try { - locator = await this.chain.getLocator(); - } catch (e) { - this.emit('error', e); - return; - } + let locator; + try { + locator = await this.chain.getLocator(); + } catch (e) { + this.emit('error', e); + return; + } - for (let peer = this.peers.head(); peer; peer = peer.next) { - if (!peer.outbound) - continue; + for (let peer = this.peers.head(); peer; peer = peer.next) { + if (!peer.outbound) + continue; - if (!force && peer.syncing) - continue; + if (!force && peer.syncing) + continue; - this.sendLocator(locator, peer); + this.sendLocator(locator, peer); + } } -}; -/** - * Test whether a peer is sync-worthy. - * @param {Peer} peer - * @returns {Boolean} - */ - -Pool.prototype.isSyncable = function isSyncable(peer) { - if (!this.syncing) - return false; + /** + * Test whether a peer is sync-worthy. + * @param {Peer} peer + * @returns {Boolean} + */ - if (peer.destroyed) - return false; + isSyncable(peer) { + if (!this.syncing) + return false; - if (!peer.handshake) - return false; + if (peer.destroyed) + return false; - if (!(peer.services & services.NETWORK)) - return false; + if (!peer.handshake) + return false; - if (this.options.hasWitness() && !peer.hasWitness()) - return false; + if (!(peer.services & services.NETWORK)) + return false; - if (!peer.loader) { - if (!this.chain.synced) + if (this.options.hasWitness() && !peer.hasWitness()) return false; + + if (!peer.loader) { + if (!this.chain.synced) + return false; + } + + return true; } - return true; -}; + /** + * Start syncing from peer. + * @method + * @param {Peer} peer + * @returns {Promise} + */ -/** - * Start syncing from peer. - * @method - * @param {Peer} peer - * @returns {Promise} - */ + async sendSync(peer) { + if (peer.syncing) + return false; -Pool.prototype.sendSync = async function sendSync(peer) { - if (peer.syncing) - return false; + if (!this.isSyncable(peer)) + return false; - if (!this.isSyncable(peer)) - return false; + peer.syncing = true; + peer.blockTime = Date.now(); - peer.syncing = true; - peer.blockTime = util.ms(); + let locator; + try { + locator = await this.chain.getLocator(); + } catch (e) { + peer.syncing = false; + peer.blockTime = -1; + this.emit('error', e); + return false; + } - let locator; - try { - locator = await this.chain.getLocator(); - } catch (e) { - peer.syncing = false; - peer.blockTime = -1; - this.emit('error', e); - return false; + return this.sendLocator(locator, peer); } - return this.sendLocator(locator, peer); -}; + /** + * Send a chain locator and start syncing from peer. + * @method + * @param {Hash[]} locator + * @param {Peer} peer + * @returns {Boolean} + */ -/** - * Send a chain locator and start syncing from peer. - * @method - * @param {Hash[]} locator - * @param {Peer} peer - * @returns {Boolean} - */ + sendLocator(locator, peer) { + if (!this.isSyncable(peer)) + return false; -Pool.prototype.sendLocator = function sendLocator(locator, peer) { - if (!this.isSyncable(peer)) - return false; + // Ask for the mempool if we're synced. + if (this.network.requestMempool) { + if (peer.loader && this.chain.synced) + peer.sendMempool(); + } - // Ask for the mempool if we're synced. - if (this.network.requestMempool) { - if (peer.loader && this.chain.synced) - peer.sendMempool(); - } + peer.syncing = true; + peer.blockTime = Date.now(); + + if (this.checkpoints) { + peer.sendGetHeaders(locator, this.headerTip.hash); + return true; + } - peer.syncing = true; - peer.blockTime = util.ms(); + peer.sendGetBlocks(locator); - if (this.checkpoints) { - peer.sendGetHeaders(locator, this.headerTip.hash); return true; } - peer.sendGetBlocks(locator); + /** + * Send `mempool` to all peers. + */ - return true; -}; + sendMempool() { + for (let peer = this.peers.head(); peer; peer = peer.next) + peer.sendMempool(); + } -/** - * Send `mempool` to all peers. - */ + /** + * Send `getaddr` to all peers. + */ -Pool.prototype.sendMempool = function sendMempool() { - for (let peer = this.peers.head(); peer; peer = peer.next) - peer.sendMempool(); -}; + sendGetAddr() { + for (let peer = this.peers.head(); peer; peer = peer.next) + peer.sendGetAddr(); + } -/** - * Send `getaddr` to all peers. - */ + /** + * Request current header chain blocks. + * @private + * @param {Peer} peer + */ -Pool.prototype.sendGetAddr = function sendGetAddr() { - for (let peer = this.peers.head(); peer; peer = peer.next) - peer.sendGetAddr(); -}; + resolveHeaders(peer) { + const items = []; -/** - * Request current header chain blocks. - * @private - * @param {Peer} peer - */ - -Pool.prototype.resolveHeaders = function resolveHeaders(peer) { - const items = []; + for (let node = this.headerNext; node; node = node.next) { + this.headerNext = node.next; - for (let node = this.headerNext; node; node = node.next) { - this.headerNext = node.next; + items.push(node.hash); - items.push(node.hash); + if (items.length === common.MAX_INV) + break; + } - if (items.length === 50000) - break; + this.getBlock(peer, items); } - this.getBlock(peer, items); -}; + /** + * Update all peer heights by their best hash. + * @param {Hash} hash + * @param {Number} height + */ -/** - * Update all peer heights by their best hash. - * @param {Hash} hash - * @param {Number} height - */ + resolveHeight(hash, height) { + let total = 0; -Pool.prototype.resolveHeight = function resolveHeight(hash, height) { - let total = 0; - - for (let peer = this.peers.head(); peer; peer = peer.next) { - if (peer.bestHash !== hash) - continue; + for (let peer = this.peers.head(); peer; peer = peer.next) { + if (!peer.bestHash || !peer.bestHash.equals(hash)) + continue; - if (peer.bestHeight !== height) { - peer.bestHeight = height; - total++; + if (peer.bestHeight !== height) { + peer.bestHeight = height; + total += 1; + } } + + if (total > 0) + this.logger.debug('Resolved height for %d peers.', total); } - if (total > 0) - this.logger.debug('Resolved height for %d peers.', total); -}; + /** + * Find the next checkpoint. + * @private + * @param {Number} height + * @returns {Object} + */ -/** - * Find the next checkpoint. - * @private - * @param {Number} height - * @returns {Object} - */ + getNextTip(height) { + for (const next of this.network.checkpoints) { + if (next.height > height) + return new HeaderEntry(next.hash, next.height); + } -Pool.prototype.getNextTip = function getNextTip(height) { - for (const next of this.network.checkpoints) { - if (next.height > height) - return new HeaderEntry(next.hash, next.height); + throw new Error('Next checkpoint not found.'); } - throw new Error('Next checkpoint not found.'); -}; - -/** - * Announce broadcast list to peer. - * @param {Peer} peer - */ + /** + * Announce broadcast list to peer. + * @param {Peer} peer + */ -Pool.prototype.announceList = function announceList(peer) { - const blocks = []; - const txs = []; + announceList(peer) { + const blocks = []; + const txs = []; - for (const item of this.invMap.values()) { - switch (item.type) { - case invTypes.BLOCK: - blocks.push(item.msg); - break; - case invTypes.TX: - txs.push(item.msg); - break; - default: - assert(false, 'Bad item type.'); - break; + for (const item of this.invMap.values()) { + switch (item.type) { + case invTypes.BLOCK: + blocks.push(item.msg); + break; + case invTypes.TX: + txs.push(item.msg); + break; + default: + assert(false, 'Bad item type.'); + break; + } } - } - if (blocks.length > 0) - peer.announceBlock(blocks); + if (blocks.length > 0) + peer.announceBlock(blocks); - if (txs.length > 0) - peer.announceTX(txs); -}; + if (txs.length > 0) + peer.announceTX(txs); + } -/** - * Get a block/tx from the broadcast map. - * @private - * @param {Peer} peer - * @param {InvItem} item - * @returns {Promise} - */ + /** + * Get a block/tx from the broadcast map. + * @private + * @param {Peer} peer + * @param {InvItem} item + * @returns {Promise} + */ -Pool.prototype.getBroadcasted = function getBroadcasted(peer, item) { - const type = item.isTX() ? invTypes.TX : invTypes.BLOCK; - const entry = this.invMap.get(item.hash); + getBroadcasted(peer, item) { + const type = item.isTX() ? invTypes.TX : invTypes.BLOCK; + const entry = this.invMap.get(item.hash); - if (!entry) - return null; + if (!entry) + return null; + + if (type !== entry.type) { + this.logger.debug( + 'Peer requested item with the wrong type (%s).', + peer.hostname()); + return null; + } - if (type !== entry.type) { this.logger.debug( - 'Peer requested item with the wrong type (%s).', + 'Peer requested %s %h as a %s packet (%s).', + item.isTX() ? 'tx' : 'block', + item.hash, + item.hasWitness() ? 'witness' : 'normal', peer.hostname()); - return null; - } - this.logger.debug( - 'Peer requested %s %s as a %s packet (%s).', - item.isTX() ? 'tx' : 'block', - item.rhash(), - item.hasWitness() ? 'witness' : 'normal', - peer.hostname()); + entry.handleAck(peer); - entry.handleAck(peer); + return entry.msg; + } - return entry.msg; -}; + /** + * Get a block/tx either from the broadcast map, mempool, or blockchain. + * @method + * @private + * @param {Peer} peer + * @param {InvItem} item + * @returns {Promise} + */ -/** - * Get a block/tx either from the broadcast map, mempool, or blockchain. - * @method - * @private - * @param {Peer} peer - * @param {InvItem} item - * @returns {Promise} - */ + async getItem(peer, item) { + const entry = this.getBroadcasted(peer, item); -Pool.prototype.getItem = async function getItem(peer, item) { - const entry = this.getBroadcasted(peer, item); + if (entry) + return entry; - if (entry) - return entry; + if (this.options.selfish) + return null; - if (this.options.selfish) - return null; + if (item.isTX()) { + if (!this.mempool) + return null; + return this.mempool.getTX(item.hash); + } - if (item.isTX()) { - if (!this.mempool) + if (this.chain.options.spv) + return null; + + if (this.chain.options.prune) return null; - return this.mempool.getTX(item.hash); + + return this.chain.getBlock(item.hash); } - if (this.chain.options.spv) - return null; + /** + * Send a block from the broadcast list or chain. + * @method + * @private + * @param {Peer} peer + * @param {InvItem} item + * @returns {Boolean} + */ - if (this.chain.options.prune) - return null; + async sendBlock(peer, item, witness) { + const broadcasted = this.getBroadcasted(peer, item); - return await this.chain.getBlock(item.hash); -}; + // Check for a broadcasted item first. + if (broadcasted) { + peer.send(new packets.BlockPacket(broadcasted, witness)); + return true; + } -/** - * Send a block from the broadcast list or chain. - * @method - * @private - * @param {Peer} peer - * @param {InvItem} item - * @returns {Boolean} - */ + if (this.options.selfish + || this.chain.options.spv + || this.chain.options.prune) { + return false; + } -Pool.prototype.sendBlock = async function sendBlock(peer, item, witness) { - const broadcasted = this.getBroadcasted(peer, item); + // If we have the same serialization, we + // can write the raw binary to the socket. + if (witness || !this.options.hasWitness()) { + const block = await this.chain.getRawBlock(item.hash); - // Check for a broadcasted item first. - if (broadcasted) { - peer.send(new packets.BlockPacket(broadcasted, witness)); - return true; - } + if (block) { + peer.sendRaw('block', block); + return true; + } - if (this.options.selfish - || this.chain.options.spv - || this.chain.options.prune) { - return false; - } + return false; + } - // If we have the same serialization, we - // can write the raw binary to the socket. - if (witness || !this.options.hasWitness()) { - const block = await this.chain.getRawBlock(item.hash); + const block = await this.chain.getBlock(item.hash); if (block) { - peer.sendRaw('block', block); + peer.send(new packets.BlockPacket(block, witness)); return true; } return false; } - const block = await this.chain.getBlock(item.hash); + /** + * Create an outbound peer with no special purpose. + * @private + * @param {NetAddress} addr + * @returns {Peer} + */ - if (block) { - peer.send(new packets.BlockPacket(block, witness)); - return true; - } + createOutbound(addr) { + const peer = Peer.fromOutbound(this.options, addr); - return false; -}; + this.hosts.markAttempt(addr.hostname); -/** - * Create an outbound peer with no special purpose. - * @private - * @param {NetAddress} addr - * @returns {Peer} - */ + this.bindPeer(peer); -Pool.prototype.createOutbound = function createOutbound(addr) { - const cipher = BIP151.ciphers.CHACHAPOLY; - const identity = this.options.identityKey; - const peer = Peer.fromOutbound(this.options, addr); + this.logger.debug('Connecting to %s.', peer.hostname()); - this.hosts.markAttempt(addr.hostname); + peer.tryOpen(); - if (this.options.bip151) - peer.setCipher(cipher); + return peer; + } - if (this.options.bip150) - peer.setAuth(this.authdb, identity); + /** + * Accept an inbound socket. + * @private + * @param {net.Socket} socket + * @returns {Peer} + */ - this.bindPeer(peer); + createInbound(socket) { + const peer = Peer.fromInbound(this.options, socket); - this.logger.debug('Connecting to %s.', peer.hostname()); + this.bindPeer(peer); - peer.tryOpen(); + peer.tryOpen(); - return peer; -}; + return peer; + } -/** - * Accept an inbound socket. - * @private - * @param {net.Socket} socket - * @returns {Peer} - */ + /** + * Allocate new peer id. + * @returns {Number} + */ -Pool.prototype.createInbound = function createInbound(socket) { - const cipher = BIP151.ciphers.CHACHAPOLY; - const identity = this.options.identityKey; - const peer = Peer.fromInbound(this.options, socket); + uid() { + const MAX = Number.MAX_SAFE_INTEGER; - if (this.options.bip151) - peer.setCipher(cipher); + if (this.id >= MAX - this.peers.size() - 1) + this.id = 0; - if (this.options.bip150) - peer.setAuth(this.authdb, identity); + // Once we overflow, there's a chance + // of collisions. Unlikely to happen + // unless we have tried to connect 9 + // quadrillion times, but still + // account for it. + do { + this.id += 1; + } while (this.peers.find(this.id)); - this.bindPeer(peer); + return this.id; + } - peer.tryOpen(); + /** + * Bind to peer events. + * @private + * @param {Peer} peer + */ - return peer; -}; + bindPeer(peer) { + peer.id = this.uid(); -/** - * Allocate new peer id. - * @returns {Number} - */ + peer.onPacket = (packet) => { + return this.handlePacket(peer, packet); + }; -Pool.prototype.uid = function uid() { - const MAX = Number.MAX_SAFE_INTEGER; + peer.on('error', (err) => { + this.logger.debug(err); + }); - if (this.id >= MAX - this.peers.size() - 1) - this.id = 0; + peer.once('connect', () => { + this.handleConnect(peer); + }); - // Once we overflow, there's a chance - // of collisions. Unlikely to happen - // unless we have tried to connect 9 - // quadrillion times, but still - // account for it. - do { - this.id += 1; - } while (this.peers.find(this.id)); + peer.once('open', () => { + this.handleOpen(peer); + }); - return this.id; -}; + peer.once('close', (connected) => { + this.handleClose(peer, connected); + }); -/** - * Bind to peer events. - * @private - * @param {Peer} peer - */ + peer.once('ban', () => { + this.handleBan(peer); + }); + } -Pool.prototype.bindPeer = function bindPeer(peer) { - peer.id = this.uid(); + /** + * Handle peer packet event. + * @method + * @private + * @param {Peer} peer + * @param {Packet} packet + * @returns {Promise} + */ - peer.onPacket = (packet) => { - return this.handlePacket(peer, packet); - }; + async handlePacket(peer, packet) { + switch (packet.type) { + case packetTypes.VERSION: + await this.handleVersion(peer, packet); + break; + case packetTypes.VERACK: + await this.handleVerack(peer, packet); + break; + case packetTypes.PING: + await this.handlePing(peer, packet); + break; + case packetTypes.PONG: + await this.handlePong(peer, packet); + break; + case packetTypes.GETADDR: + await this.handleGetAddr(peer, packet); + break; + case packetTypes.ADDR: + await this.handleAddr(peer, packet); + break; + case packetTypes.INV: + await this.handleInv(peer, packet); + break; + case packetTypes.GETDATA: + await this.handleGetData(peer, packet); + break; + case packetTypes.NOTFOUND: + await this.handleNotFound(peer, packet); + break; + case packetTypes.GETBLOCKS: + await this.handleGetBlocks(peer, packet); + break; + case packetTypes.GETHEADERS: + await this.handleGetHeaders(peer, packet); + break; + case packetTypes.HEADERS: + await this.handleHeaders(peer, packet); + break; + case packetTypes.SENDHEADERS: + await this.handleSendHeaders(peer, packet); + break; + case packetTypes.BLOCK: + await this.handleBlock(peer, packet); + break; + case packetTypes.TX: + await this.handleTX(peer, packet); + break; + case packetTypes.REJECT: + await this.handleReject(peer, packet); + break; + case packetTypes.MEMPOOL: + await this.handleMempool(peer, packet); + break; + case packetTypes.FILTERLOAD: + await this.handleFilterLoad(peer, packet); + break; + case packetTypes.FILTERADD: + await this.handleFilterAdd(peer, packet); + break; + case packetTypes.FILTERCLEAR: + await this.handleFilterClear(peer, packet); + break; + case packetTypes.MERKLEBLOCK: + await this.handleMerkleBlock(peer, packet); + break; + case packetTypes.FEEFILTER: + await this.handleFeeFilter(peer, packet); + break; + case packetTypes.SENDCMPCT: + await this.handleSendCmpct(peer, packet); + break; + case packetTypes.CMPCTBLOCK: + await this.handleCmpctBlock(peer, packet); + break; + case packetTypes.GETBLOCKTXN: + await this.handleGetBlockTxn(peer, packet); + break; + case packetTypes.BLOCKTXN: + await this.handleBlockTxn(peer, packet); + break; + case packetTypes.UNKNOWN: + await this.handleUnknown(peer, packet); + break; + default: + assert(false, 'Bad packet type.'); + break; + } - peer.on('error', (err) => { - this.logger.debug(err); - }); + this.emit('packet', packet, peer); + } - peer.once('connect', () => { - this.handleConnect(peer); - }); + /** + * Handle peer connect event. + * @method + * @private + * @param {Peer} peer + */ - peer.once('open', () => { - this.handleOpen(peer); - }); + async handleConnect(peer) { + this.logger.info('Connected to %s.', peer.hostname()); - peer.once('close', (connected) => { - this.handleClose(peer, connected); - }); + if (peer.outbound) + this.hosts.markSuccess(peer.hostname()); - peer.once('ban', () => { - this.handleBan(peer); - }); -}; + this.emit('peer connect', peer); + } -/** - * Handle peer packet event. - * @method - * @private - * @param {Peer} peer - * @param {Packet} packet - * @returns {Promise} - */ + /** + * Handle peer open event. + * @method + * @private + * @param {Peer} peer + */ -Pool.prototype.handlePacket = async function handlePacket(peer, packet) { - switch (packet.type) { - case packetTypes.VERSION: - await this.handleVersion(peer, packet); - break; - case packetTypes.VERACK: - await this.handleVerack(peer, packet); - break; - case packetTypes.PING: - await this.handlePing(peer, packet); - break; - case packetTypes.PONG: - await this.handlePong(peer, packet); - break; - case packetTypes.GETADDR: - await this.handleGetAddr(peer, packet); - break; - case packetTypes.ADDR: - await this.handleAddr(peer, packet); - break; - case packetTypes.INV: - await this.handleInv(peer, packet); - break; - case packetTypes.GETDATA: - await this.handleGetData(peer, packet); - break; - case packetTypes.NOTFOUND: - await this.handleNotFound(peer, packet); - break; - case packetTypes.GETBLOCKS: - await this.handleGetBlocks(peer, packet); - break; - case packetTypes.GETHEADERS: - await this.handleGetHeaders(peer, packet); - break; - case packetTypes.HEADERS: - await this.handleHeaders(peer, packet); - break; - case packetTypes.SENDHEADERS: - await this.handleSendHeaders(peer, packet); - break; - case packetTypes.BLOCK: - await this.handleBlock(peer, packet); - break; - case packetTypes.TX: - await this.handleTX(peer, packet); - break; - case packetTypes.REJECT: - await this.handleReject(peer, packet); - break; - case packetTypes.MEMPOOL: - await this.handleMempool(peer, packet); - break; - case packetTypes.FILTERLOAD: - await this.handleFilterLoad(peer, packet); - break; - case packetTypes.FILTERADD: - await this.handleFilterAdd(peer, packet); - break; - case packetTypes.FILTERCLEAR: - await this.handleFilterClear(peer, packet); - break; - case packetTypes.MERKLEBLOCK: - await this.handleMerkleBlock(peer, packet); - break; - case packetTypes.FEEFILTER: - await this.handleFeeFilter(peer, packet); - break; - case packetTypes.SENDCMPCT: - await this.handleSendCmpct(peer, packet); - break; - case packetTypes.CMPCTBLOCK: - await this.handleCmpctBlock(peer, packet); - break; - case packetTypes.GETBLOCKTXN: - await this.handleGetBlockTxn(peer, packet); - break; - case packetTypes.BLOCKTXN: - await this.handleBlockTxn(peer, packet); - break; - case packetTypes.ENCINIT: - await this.handleEncinit(peer, packet); - break; - case packetTypes.ENCACK: - await this.handleEncack(peer, packet); - break; - case packetTypes.AUTHCHALLENGE: - await this.handleAuthChallenge(peer, packet); - break; - case packetTypes.AUTHREPLY: - await this.handleAuthReply(peer, packet); - break; - case packetTypes.AUTHPROPOSE: - await this.handleAuthPropose(peer, packet); - break; - case packetTypes.UNKNOWN: - await this.handleUnknown(peer, packet); - break; - default: - assert(false, 'Bad packet type.'); - break; - } - - this.emit('packet', packet, peer); -}; + async handleOpen(peer) { + // Advertise our address. + if (!this.options.selfish && this.options.listen) { + const addr = this.hosts.getLocal(peer.address); + if (addr) + peer.send(new packets.AddrPacket([addr])); + } -/** - * Handle peer connect event. - * @method - * @private - * @param {Peer} peer - */ + // We want compact blocks! + if (this.options.compact) + peer.sendCompact(this.options.blockMode); -Pool.prototype.handleConnect = async function handleConnect(peer) { - this.logger.info('Connected to %s.', peer.hostname()); + // Find some more peers. + if (!this.hosts.isFull()) + peer.sendGetAddr(); - if (peer.outbound) - this.hosts.markSuccess(peer.hostname()); + // Relay our spv filter if we have one. + if (this.spvFilter) + peer.sendFilterLoad(this.spvFilter); - this.emit('peer connect', peer); -}; + // Announce our currently broadcasted items. + this.announceList(peer); -/** - * Handle peer open event. - * @method - * @private - * @param {Peer} peer - */ + // Set a fee rate filter. + if (this.options.feeRate !== -1) + peer.sendFeeRate(this.options.feeRate); -Pool.prototype.handleOpen = async function handleOpen(peer) { - // Advertise our address. - if (!this.options.selfish && this.options.listen) { - const addr = this.hosts.getLocal(peer.address); - if (addr) - peer.send(new packets.AddrPacket([addr])); - } + // Start syncing the chain. + if (peer.outbound) + this.sendSync(peer); - // We want compact blocks! - if (this.options.compact) - peer.sendCompact(this.options.blockMode); + if (peer.outbound) { + this.hosts.markAck(peer.hostname(), peer.services); - // Find some more peers. - if (!this.hosts.isFull()) - peer.sendGetAddr(); + // If we don't have an ack'd + // loader yet consider it dead. + if (!peer.loader) { + if (this.peers.load && !this.peers.load.handshake) { + assert(this.peers.load.loader); + this.peers.load.loader = false; + this.peers.load = null; + } + } - // Relay our spv filter if we have one. - if (this.spvFilter) - peer.sendFilterLoad(this.spvFilter); + // If we do not have a loader, + // use this peer. + if (!this.peers.load) + this.setLoader(peer); + } - // Announce our currently broadcasted items. - this.announceList(peer); + this.emit('peer open', peer); + } - // Set a fee rate filter. - if (this.options.feeRate !== -1) - peer.sendFeeRate(this.options.feeRate); + /** + * Handle peer close event. + * @method + * @private + * @param {Peer} peer + * @param {Boolean} connected + */ - // Start syncing the chain. - if (peer.outbound) - this.sendSync(peer); + async handleClose(peer, connected) { + const outbound = peer.outbound; + const loader = peer.loader; + const size = peer.blockMap.size; - if (peer.outbound) { - this.hosts.markAck(peer.hostname(), peer.services); + this.removePeer(peer); - // If we don't have an ack'd - // loader yet consider it dead. - if (!peer.loader) { - if (this.peers.load && !this.peers.load.handshake) { - assert(this.peers.load.loader); - this.peers.load.loader = false; - this.peers.load = null; - } + if (loader) { + this.logger.info('Removed loader peer (%s).', peer.hostname()); + if (this.checkpoints) + this.resetChain(); } - // If we do not have a loader, - // use this peer. - if (!this.peers.load) - this.setLoader(peer); - } + this.nonces.remove(peer.hostname()); - this.emit('peer open', peer); -}; + this.emit('peer close', peer, connected); -/** - * Handle peer close event. - * @method - * @private - * @param {Peer} peer - * @param {Boolean} connected - */ + if (!this.opened) + return; -Pool.prototype.handleClose = async function handleClose(peer, connected) { - const outbound = peer.outbound; - const loader = peer.loader; - const size = peer.blockMap.size; + if (this.disconnecting) + return; - this.removePeer(peer); + if (this.chain.synced && size > 0) { + this.logger.warning('Peer disconnected with requested blocks.'); + this.logger.warning('Resending sync...'); + this.forceSync(); + } - if (loader) { - this.logger.info('Removed loader peer (%s).', peer.hostname()); - if (this.checkpoints) - this.resetChain(); + if (!outbound) + return; + + this.refill(); } - this.nonces.remove(peer.hostname()); + /** + * Handle ban event. + * @method + * @private + * @param {Peer} peer + */ - this.emit('peer close', peer, connected); + async handleBan(peer) { + this.ban(peer.address); + this.emit('ban', peer); + } - if (!this.loaded) - return; + /** + * Handle peer version event. + * @method + * @private + * @param {Peer} peer + * @param {VersionPacket} packet + */ - if (this.disconnecting) - return; + async handleVersion(peer, packet) { + this.logger.info( + 'Received version (%s): version=%d height=%d services=%s agent=%s', + peer.hostname(), + packet.version, + packet.height, + packet.services.toString(2), + packet.agent); + + this.network.time.add(peer.hostname(), packet.time); + this.nonces.remove(peer.hostname()); + + if (!peer.outbound && packet.remote.isRoutable()) + this.hosts.markLocal(packet.remote); + } + + /** + * Handle `verack` packet. + * @method + * @private + * @param {Peer} peer + * @param {VerackPacket} packet + */ + + async handleVerack(peer, packet) { + ; + } + + /** + * Handle `ping` packet. + * @method + * @private + * @param {Peer} peer + * @param {PingPacket} packet + */ + + async handlePing(peer, packet) { + ; + } + + /** + * Handle `pong` packet. + * @method + * @private + * @param {Peer} peer + * @param {PongPacket} packet + */ + + async handlePong(peer, packet) { + ; + } + + /** + * Handle `getaddr` packet. + * @method + * @private + * @param {Peer} peer + * @param {GetAddrPacket} packet + */ + + async handleGetAddr(peer, packet) { + if (this.options.selfish) + return; - if (this.chain.synced && size > 0) { - this.logger.warning('Peer disconnected with requested blocks.'); - this.logger.warning('Resending sync...'); - this.forceSync(); - } + if (peer.sentAddr) { + this.logger.debug( + 'Ignoring repeated getaddr (%s).', + peer.hostname()); + return; + } - if (!outbound) - return; + peer.sentAddr = true; - this.refill(); -}; + const addrs = this.hosts.toArray(); + const items = []; -/** - * Handle ban event. - * @method - * @private - * @param {Peer} peer - */ + for (const addr of addrs) { + if (!peer.addrFilter.added(addr.hostname, 'ascii')) + continue; -Pool.prototype.handleBan = async function handleBan(peer) { - this.ban(peer.address); - this.emit('ban', peer); -}; + items.push(addr); -/** - * Handle peer version event. - * @method - * @private - * @param {Peer} peer - * @param {VersionPacket} packet - */ + if (items.length === 1000) + break; + } -Pool.prototype.handleVersion = async function handleVersion(peer, packet) { - this.logger.info( - 'Received version (%s): version=%d height=%d services=%s agent=%s', - peer.hostname(), - packet.version, - packet.height, - packet.services.toString(2), - packet.agent); + if (items.length === 0) + return; - this.network.time.add(peer.hostname(), packet.time); - this.nonces.remove(peer.hostname()); + this.logger.debug( + 'Sending %d addrs to peer (%s)', + items.length, + peer.hostname()); - if (!peer.outbound && packet.remote.isRoutable()) - this.hosts.markLocal(packet.remote); -}; + peer.send(new packets.AddrPacket(items)); + } -/** - * Handle `verack` packet. - * @method - * @private - * @param {Peer} peer - * @param {VerackPacket} packet - */ + /** + * Handle peer addr event. + * @method + * @private + * @param {Peer} peer + * @param {AddrPacket} packet + */ -Pool.prototype.handleVerack = async function handleVerack(peer, packet) { - ; -}; + async handleAddr(peer, packet) { + const addrs = packet.items; + const now = this.network.now(); + const services = this.options.getRequiredServices(); -/** - * Handle `ping` packet. - * @method - * @private - * @param {Peer} peer - * @param {PingPacket} packet - */ + for (const addr of addrs) { + peer.addrFilter.add(addr.hostname, 'ascii'); -Pool.prototype.handlePing = async function handlePing(peer, packet) { - ; -}; + if (!addr.isRoutable()) + continue; -/** - * Handle `pong` packet. - * @method - * @private - * @param {Peer} peer - * @param {PongPacket} packet - */ + if (!addr.hasServices(services)) + continue; -Pool.prototype.handlePong = async function handlePong(peer, packet) { - ; -}; + if (addr.time <= 100000000 || addr.time > now + 10 * 60) + addr.time = now - 5 * 24 * 60 * 60; -/** - * Handle `getaddr` packet. - * @method - * @private - * @param {Peer} peer - * @param {GetAddrPacket} packet - */ + if (addr.port === 0) + continue; -Pool.prototype.handleGetAddr = async function handleGetAddr(peer, packet) { - if (this.options.selfish) - return; + this.hosts.add(addr, peer.address); + } - if (peer.sentAddr) { - this.logger.debug( - 'Ignoring repeated getaddr (%s).', + this.logger.info( + 'Received %d addrs (hosts=%d, peers=%d) (%s).', + addrs.length, + this.hosts.size(), + this.peers.size(), peer.hostname()); - return; - } - peer.sentAddr = true; + this.fillOutbound(); + } - const addrs = this.hosts.toArray(); - const items = []; + /** + * Handle `inv` packet. + * @method + * @private + * @param {Peer} peer + * @param {InvPacket} packet + */ + + async handleInv(peer, packet) { + const unlock = await this.locker.lock(); + try { + return await this._handleInv(peer, packet); + } finally { + unlock(); + } + } - for (const addr of addrs) { - if (!peer.addrFilter.added(addr.hostname, 'ascii')) - continue; + /** + * Handle `inv` packet (without a lock). + * @method + * @private + * @param {Peer} peer + * @param {InvPacket} packet + */ - items.push(addr); + async _handleInv(peer, packet) { + const items = packet.items; - if (items.length === 1000) - break; - } + if (items.length > common.MAX_INV) { + peer.increaseBan(100); + return; + } - if (items.length === 0) - return; + const blocks = []; + const txs = []; - this.logger.debug( - 'Sending %d addrs to peer (%s)', - items.length, - peer.hostname()); + let unknown = -1; - peer.send(new packets.AddrPacket(items)); -}; + for (const item of items) { + switch (item.type) { + case invTypes.BLOCK: + blocks.push(item.hash); + break; + case invTypes.TX: + txs.push(item.hash); + break; + default: + unknown = item.type; + continue; + } + peer.invFilter.add(item.hash); + } -/** - * Handle peer addr event. - * @method - * @private - * @param {Peer} peer - * @param {AddrPacket} packet - */ + this.logger.spam( + 'Received inv packet with %d items: blocks=%d txs=%d (%s).', + items.length, blocks.length, txs.length, peer.hostname()); -Pool.prototype.handleAddr = async function handleAddr(peer, packet) { - const addrs = packet.items; - const now = this.network.now(); - const services = this.options.getRequiredServices(); + if (unknown !== -1) { + this.logger.warning( + 'Peer sent an unknown inv type: %d (%s).', + unknown, peer.hostname()); + } - for (const addr of addrs) { - peer.addrFilter.add(addr.hostname, 'ascii'); + if (blocks.length > 0) + await this.handleBlockInv(peer, blocks); - if (!addr.isRoutable()) - continue; + if (txs.length > 0) + await this.handleTXInv(peer, txs); + } - if (!addr.hasServices(services)) - continue; + /** + * Handle `inv` packet from peer (containing only BLOCK types). + * @method + * @private + * @param {Peer} peer + * @param {Hash[]} hashes + * @returns {Promise} + */ - if (addr.time <= 100000000 || addr.time > now + 10 * 60) - addr.time = now - 5 * 24 * 60 * 60; + async handleBlockInv(peer, hashes) { + assert(hashes.length > 0); - if (addr.port === 0) - continue; + if (!this.syncing) + return; - this.hosts.add(addr, peer.address); - } + // Always keep track of the peer's best hash. + if (!peer.loader || this.chain.synced) { + const hash = hashes[hashes.length - 1]; + peer.bestHash = hash; + } - this.logger.info( - 'Received %d addrs (hosts=%d, peers=%d) (%s).', - addrs.length, - this.hosts.size(), - this.peers.size(), - peer.hostname()); + // Ignore for now if we're still syncing + if (!this.chain.synced && !peer.loader) + return; - this.fillOutbound(); -}; + if (this.options.hasWitness() && !peer.hasWitness()) + return; -/** - * Handle `inv` packet. - * @method - * @private - * @param {Peer} peer - * @param {InvPacket} packet - */ + // Request headers instead. + if (this.checkpoints) + return; -Pool.prototype.handleInv = async function handleInv(peer, packet) { - const unlock = await this.locker.lock(); - try { - return await this._handleInv(peer, packet); - } finally { - unlock(); - } -}; + this.logger.debug( + 'Received %d block hashes from peer (%s).', + hashes.length, + peer.hostname()); -/** - * Handle `inv` packet (without a lock). - * @method - * @private - * @param {Peer} peer - * @param {InvPacket} packet - */ + const items = []; -Pool.prototype._handleInv = async function _handleInv(peer, packet) { - const items = packet.items; + let exists = null; - if (items.length > 50000) { - peer.increaseBan(100); - return; - } + for (let i = 0; i < hashes.length; i++) { + const hash = hashes[i]; - const blocks = []; - const txs = []; - let unknown = -1; + // Resolve orphan chain. + if (this.chain.hasOrphan(hash)) { + this.logger.debug('Received known orphan hash (%s).', peer.hostname()); + await this.resolveOrphan(peer, hash); + continue; + } - for (const item of items) { - switch (item.type) { - case invTypes.BLOCK: - blocks.push(item.hash); - break; - case invTypes.TX: - txs.push(item.hash); - break; - default: - unknown = item.type; + // Request the block if we don't have it. + if (!await this.hasBlock(hash)) { + items.push(hash); continue; + } + + exists = hash; + + // Normally we request the hashContinue. + // In the odd case where we already have + // it, we can do one of two things: either + // force re-downloading of the block to + // continue the sync, or do a getblocks + // from the last hash (this will reset + // the hashContinue on the remote node). + if (i === hashes.length - 1) { + this.logger.debug('Received existing hash (%s).', peer.hostname()); + await this.getBlocks(peer, hash); + } } - peer.invFilter.add(item.hash, 'hex'); - } - this.logger.spam( - 'Received inv packet with %d items: blocks=%d txs=%d (%s).', - items.length, blocks.length, txs.length, peer.hostname()); + // Attempt to update the peer's best height + // with the last existing hash we know of. + if (exists && this.chain.synced) { + const height = await this.chain.getHeight(exists); + if (height !== -1) + peer.bestHeight = height; + } - if (unknown !== -1) { - this.logger.warning( - 'Peer sent an unknown inv type: %d (%s).', - unknown, peer.hostname()); + this.getBlock(peer, items); } - if (blocks.length > 0) - await this.handleBlockInv(peer, blocks); - - if (txs.length > 0) - await this.handleTXInv(peer, txs); -}; - -/** - * Handle `inv` packet from peer (containing only BLOCK types). - * @method - * @private - * @param {Peer} peer - * @param {Hash[]} hashes - * @returns {Promise} - */ + /** + * Handle peer inv packet (txs). + * @method + * @private + * @param {Peer} peer + * @param {Hash[]} hashes + */ -Pool.prototype.handleBlockInv = async function handleBlockInv(peer, hashes) { - assert(hashes.length > 0); + async handleTXInv(peer, hashes) { + assert(hashes.length > 0); - if (!this.syncing) - return; + if (this.syncing && !this.chain.synced) + return; - // Always keep track of the peer's best hash. - if (!peer.loader || this.chain.synced) { - const hash = hashes[hashes.length - 1]; - peer.bestHash = hash; + this.ensureTX(peer, hashes); } - // Ignore for now if we're still syncing - if (!this.chain.synced && !peer.loader) - return; + /** + * Handle `getdata` packet. + * @method + * @private + * @param {Peer} peer + * @param {GetDataPacket} packet + */ - if (this.options.hasWitness() && !peer.hasWitness()) - return; + async handleGetData(peer, packet) { + const items = packet.items; - // Request headers instead. - if (this.checkpoints) - return; + if (items.length > common.MAX_INV) { + this.logger.warning( + 'Peer sent inv with >50k items (%s).', + peer.hostname()); + peer.increaseBan(100); + peer.destroy(); + return; + } - this.logger.debug( - 'Received %s block hashes from peer (%s).', - hashes.length, - peer.hostname()); + const notFound = []; - const items = []; + let txs = 0; + let blocks = 0; + let compact = 0; + let unknown = -1; - let exists = null; + for (const item of items) { + if (item.isTX()) { + const tx = await this.getItem(peer, item); - for (let i = 0; i < hashes.length; i++) { - const hash = hashes[i]; - - // Resolve orphan chain. - if (this.chain.hasOrphan(hash)) { - this.logger.debug('Received known orphan hash (%s).', peer.hostname()); - await this.resolveOrphan(peer, hash); - continue; - } - - // Request the block if we don't have it. - if (!await this.hasBlock(hash)) { - items.push(hash); - continue; - } - - exists = hash; - - // Normally we request the hashContinue. - // In the odd case where we already have - // it, we can do one of two things: either - // force re-downloading of the block to - // continue the sync, or do a getblocks - // from the last hash (this will reset - // the hashContinue on the remote node). - if (i === hashes.length - 1) { - this.logger.debug('Received existing hash (%s).', peer.hostname()); - await this.getBlocks(peer, hash); - } - } - - // Attempt to update the peer's best height - // with the last existing hash we know of. - if (exists && this.chain.synced) { - const height = await this.chain.getHeight(exists); - if (height !== -1) - peer.bestHeight = height; - } - - this.getBlock(peer, items); -}; - -/** - * Handle peer inv packet (txs). - * @method - * @private - * @param {Peer} peer - * @param {Hash[]} hashes - */ - -Pool.prototype.handleTXInv = async function handleTXInv(peer, hashes) { - assert(hashes.length > 0); - - if (this.syncing && !this.chain.synced) - return; - - this.ensureTX(peer, hashes); -}; - -/** - * Handle `getdata` packet. - * @method - * @private - * @param {Peer} peer - * @param {GetDataPacket} packet - */ - -Pool.prototype.handleGetData = async function handleGetData(peer, packet) { - const items = packet.items; + if (!tx) { + notFound.push(item); + continue; + } - if (items.length > 50000) { - this.logger.warning('Peer sent inv with >50k items (%s).', peer.hostname()); - peer.increaseBan(100); - peer.destroy(); - return; - } + // Coinbases are an insta-ban from any node. + // This should technically never happen, but + // it's worth keeping here just in case. A + // 24-hour ban from any node is rough. + if (tx.isCoinbase()) { + notFound.push(item); + this.logger.warning('Failsafe: tried to relay a coinbase.'); + continue; + } - const notFound = []; - let txs = 0; - let blocks = 0; - let compact = 0; - let unknown = -1; + peer.send(new packets.TXPacket(tx, item.hasWitness())); - for (const item of items) { - if (item.isTX()) { - const tx = await this.getItem(peer, item); + txs += 1; - if (!tx) { - notFound.push(item); continue; } - // Coinbases are an insta-ban from any node. - // This should technically never happen, but - // it's worth keeping here just in case. A - // 24-hour ban from any node is rough. - if (tx.isCoinbase()) { - notFound.push(item); - this.logger.warning('Failsafe: tried to relay a coinbase.'); - continue; - } + switch (item.type) { + case invTypes.BLOCK: + case invTypes.WITNESS_BLOCK: { + const result = await this.sendBlock(peer, item, item.hasWitness()); + if (!result) { + notFound.push(item); + continue; + } + blocks += 1; + break; + } + case invTypes.FILTERED_BLOCK: + case invTypes.WITNESS_FILTERED_BLOCK: { + if (!this.options.bip37) { + this.logger.debug( + 'Peer requested a merkleblock without bip37 enabled (%s).', + peer.hostname()); + peer.destroy(); + return; + } - peer.send(new packets.TXPacket(tx, item.hasWitness())); + if (!peer.spvFilter) { + notFound.push(item); + continue; + } - txs++; + const block = await this.getItem(peer, item); - continue; - } + if (!block) { + notFound.push(item); + continue; + } - switch (item.type) { - case invTypes.BLOCK: - case invTypes.WITNESS_BLOCK: { - const result = await this.sendBlock(peer, item, item.hasWitness()); - if (!result) { - notFound.push(item); - continue; - } - blocks++; - break; - } - case invTypes.FILTERED_BLOCK: - case invTypes.WITNESS_FILTERED_BLOCK: { - if (!this.options.bip37) { - this.logger.debug( - 'Peer requested a merkleblock without bip37 enabled (%s).', - peer.hostname()); - peer.destroy(); - return; - } + const merkle = block.toMerkle(peer.spvFilter); - if (!peer.spvFilter) { - notFound.push(item); - continue; - } + peer.send(new packets.MerkleBlockPacket(merkle)); - const block = await this.getItem(peer, item); + for (const tx of merkle.txs) { + peer.send(new packets.TXPacket(tx, item.hasWitness())); + txs += 1; + } - if (!block) { - notFound.push(item); - continue; + blocks += 1; + + break; } + case invTypes.CMPCT_BLOCK: { + const height = await this.chain.getHeight(item.hash); - const merkle = block.toMerkle(peer.spvFilter); + // Fallback to full block. + if (height < this.chain.tip.height - 10) { + const result = await this.sendBlock( + peer, item, peer.compactWitness); - peer.send(new packets.MerkleBlockPacket(merkle)); + if (!result) { + notFound.push(item); + continue; + } - for (const tx of merkle.txs) { - peer.send(new packets.TXPacket(tx, item.hasWitness())); - txs++; - } + blocks += 1; - blocks++; + break; + } - break; - } - case invTypes.CMPCT_BLOCK: { - const height = await this.chain.getHeight(item.hash); + const block = await this.getItem(peer, item); - // Fallback to full block. - if (height < this.chain.tip.height - 10) { - const result = await this.sendBlock(peer, item, peer.compactWitness); - if (!result) { + if (!block) { notFound.push(item); continue; } - blocks++; - break; - } - const block = await this.getItem(peer, item); + peer.sendCompactBlock(block); + + blocks += 1; + compact += 1; - if (!block) { + break; + } + default: { + unknown = item.type; notFound.push(item); continue; } - - peer.sendCompactBlock(block); - - blocks++; - compact++; - - break; } - default: { - unknown = item.type; - notFound.push(item); - continue; + + if (peer.hashContinue && item.hash.equals(peer.hashContinue)) { + peer.sendInv([new InvItem(invTypes.BLOCK, this.chain.tip.hash)]); + peer.hashContinue = null; } - } - if (item.hash === peer.hashContinue) { - peer.sendInv([new InvItem(invTypes.BLOCK, this.chain.tip.hash)]); - peer.hashContinue = null; + // Wait for the peer to read + // before we pull more data + // out of the database. + await peer.drain(); } - // Wait for the peer to read - // before we pull more data - // out of the database. - await peer.drain(); - } + if (notFound.length > 0) + peer.send(new packets.NotFoundPacket(notFound)); - if (notFound.length > 0) - peer.send(new packets.NotFoundPacket(notFound)); - - if (txs > 0) { - this.logger.debug( - 'Served %d txs with getdata (notfound=%d) (%s).', - txs, notFound.length, peer.hostname()); - } + if (txs > 0) { + this.logger.debug( + 'Served %d txs with getdata (notfound=%d) (%s).', + txs, notFound.length, peer.hostname()); + } - if (blocks > 0) { - this.logger.debug( - 'Served %d blocks with getdata (notfound=%d, cmpct=%d) (%s).', - blocks, notFound.length, compact, peer.hostname()); - } + if (blocks > 0) { + this.logger.debug( + 'Served %d blocks with getdata (notfound=%d, cmpct=%d) (%s).', + blocks, notFound.length, compact, peer.hostname()); + } - if (unknown !== -1) { - this.logger.warning( - 'Peer sent an unknown getdata type: %s (%d).', - unknown, peer.hostname()); + if (unknown !== -1) { + this.logger.warning( + 'Peer sent an unknown getdata type: %d (%s).', + unknown, peer.hostname()); + } } -}; -/** - * Handle peer notfound packet. - * @method - * @private - * @param {Peer} peer - * @param {NotFoundPacket} packet - */ + /** + * Handle peer notfound packet. + * @method + * @private + * @param {Peer} peer + * @param {NotFoundPacket} packet + */ -Pool.prototype.handleNotFound = async function handleNotFound(peer, packet) { - const items = packet.items; + async handleNotFound(peer, packet) { + const items = packet.items; - for (const item of items) { - if (!this.resolveItem(peer, item)) { - this.logger.warning( - 'Peer sent notfound for unrequested item: %s (%s).', - item.hash, peer.hostname()); - peer.destroy(); - return; + for (const item of items) { + if (!this.resolveItem(peer, item)) { + this.logger.warning( + 'Peer sent notfound for unrequested item: %h (%s).', + item.hash, peer.hostname()); + peer.destroy(); + return; + } } } -}; -/** - * Handle `getblocks` packet. - * @method - * @private - * @param {Peer} peer - * @param {GetBlocksPacket} packet - */ + /** + * Handle `getblocks` packet. + * @method + * @private + * @param {Peer} peer + * @param {GetBlocksPacket} packet + */ -Pool.prototype.handleGetBlocks = async function handleGetBlocks(peer, packet) { - if (!this.chain.synced) - return; + async handleGetBlocks(peer, packet) { + if (!this.chain.synced) + return; - if (this.options.selfish) - return; + if (this.options.selfish) + return; - if (this.chain.options.spv) - return; + if (this.chain.options.spv) + return; + + if (this.chain.options.prune) + return; - if (this.chain.options.prune) - return; + let hash = await this.chain.findLocator(packet.locator); - let hash = await this.chain.findLocator(packet.locator); + if (hash) + hash = await this.chain.getNextHash(hash); - if (hash) - hash = await this.chain.getNextHash(hash); + const blocks = []; - const blocks = []; + while (hash) { + if (packet.stop && hash.equals(packet.stop)) + break; - while (hash) { - blocks.push(new InvItem(invTypes.BLOCK, hash)); + blocks.push(new InvItem(invTypes.BLOCK, hash)); - if (hash === packet.stop) - break; + if (blocks.length === 500) { + peer.hashContinue = hash; + break; + } - if (blocks.length === 500) { - peer.hashContinue = hash; - break; + hash = await this.chain.getNextHash(hash); } - hash = await this.chain.getNextHash(hash); + peer.sendInv(blocks); } - peer.sendInv(blocks); -}; + /** + * Handle `getheaders` packet. + * @method + * @private + * @param {Peer} peer + * @param {GetHeadersPacket} packet + */ -/** - * Handle `getheaders` packet. - * @method - * @private - * @param {Peer} peer - * @param {GetHeadersPacket} packet - */ + async handleGetHeaders(peer, packet) { + if (!this.chain.synced) + return; -Pool.prototype.handleGetHeaders = async function handleGetHeaders(peer, packet) { - if (!this.chain.synced) - return; + if (this.options.selfish) + return; - if (this.options.selfish) - return; + if (this.chain.options.spv) + return; - if (this.chain.options.spv) - return; + if (this.chain.options.prune) + return; - if (this.chain.options.prune) - return; + let hash; + if (packet.locator.length > 0) { + hash = await this.chain.findLocator(packet.locator); + if (hash) + hash = await this.chain.getNextHash(hash); + } else { + hash = packet.stop; + } - let hash; - if (packet.locator.length > 0) { - hash = await this.chain.findLocator(packet.locator); + let entry; if (hash) - hash = await this.chain.getNextHash(hash); - } else { - hash = packet.stop; - } + entry = await this.chain.getEntry(hash); - let entry; - if (hash) - entry = await this.chain.getEntry(hash); + const headers = []; - const headers = []; + while (entry) { + headers.push(entry.toHeaders()); - while (entry) { - headers.push(entry.toHeaders()); + if (packet.stop && entry.hash.equals(packet.stop)) + break; - if (entry.hash === packet.stop) - break; + if (headers.length === 2000) + break; - if (headers.length === 2000) - break; + entry = await this.chain.getNext(entry); + } - entry = await this.chain.getNext(entry); + peer.sendHeaders(headers); } - peer.sendHeaders(headers); -}; + /** + * Handle `headers` packet from a given peer. + * @method + * @private + * @param {Peer} peer + * @param {HeadersPacket} packet + * @returns {Promise} + */ -/** - * Handle `headers` packet from a given peer. - * @method - * @private - * @param {Peer} peer - * @param {HeadersPacket} packet - * @returns {Promise} - */ - -Pool.prototype.handleHeaders = async function handleHeaders(peer, packet) { - const unlock = await this.locker.lock(); - try { - return await this._handleHeaders(peer, packet); - } finally { - unlock(); + async handleHeaders(peer, packet) { + const unlock = await this.locker.lock(); + try { + return await this._handleHeaders(peer, packet); + } finally { + unlock(); + } } -}; - -/** - * Handle `headers` packet from - * a given peer without a lock. - * @method - * @private - * @param {Peer} peer - * @param {HeadersPacket} packet - * @returns {Promise} - */ -Pool.prototype._handleHeaders = async function _handleHeaders(peer, packet) { - const headers = packet.items; + /** + * Handle `headers` packet from + * a given peer without a lock. + * @method + * @private + * @param {Peer} peer + * @param {HeadersPacket} packet + * @returns {Promise} + */ - if (!this.checkpoints) - return; + async _handleHeaders(peer, packet) { + const headers = packet.items; - if (!this.syncing) - return; - - if (!peer.loader) - return; - - if (headers.length === 0) - return; - - if (headers.length > 2000) { - peer.increaseBan(100); - return; - } + if (!this.checkpoints) + return; - assert(this.headerChain.size > 0); + if (!this.syncing) + return; - let checkpoint = false; - let node = null; + if (!peer.loader) + return; - for (const header of headers) { - const last = this.headerChain.tail; - const hash = header.hash('hex'); - const height = last.height + 1; + if (headers.length === 0) + return; - if (!header.verify()) { - this.logger.warning( - 'Peer sent an invalid header (%s).', - peer.hostname()); + if (headers.length > 2000) { peer.increaseBan(100); - peer.destroy(); return; } - if (header.prevBlock !== last.hash) { - this.logger.warning( - 'Peer sent a bad header chain (%s).', - peer.hostname()); - peer.destroy(); - return; - } + assert(this.headerChain.size > 0); + + let checkpoint = false; + let node = null; - node = new HeaderEntry(hash, height); + for (const header of headers) { + const last = this.headerChain.tail; + const hash = header.hash(); + const height = last.height + 1; - if (node.height === this.headerTip.height) { - if (node.hash !== this.headerTip.hash) { + if (!header.verify()) { this.logger.warning( - 'Peer sent an invalid checkpoint (%s).', + 'Peer sent an invalid header (%s).', peer.hostname()); + peer.increaseBan(100); peer.destroy(); return; } - checkpoint = true; - } - - if (!this.headerNext) - this.headerNext = node; - this.headerChain.push(node); - } - - this.logger.debug( - 'Received %s headers from peer (%s).', - headers.length, - peer.hostname()); + if (!header.prevBlock.equals(last.hash)) { + this.logger.warning( + 'Peer sent a bad header chain (%s).', + peer.hostname()); + peer.destroy(); + return; + } - // If we received a valid header - // chain, consider this a "block". - peer.blockTime = util.ms(); + node = new HeaderEntry(hash, height); - // Request the blocks we just added. - if (checkpoint) { - this.headerChain.shift(); - this.resolveHeaders(peer); - return; - } + if (node.height === this.headerTip.height) { + if (!node.hash.equals(this.headerTip.hash)) { + this.logger.warning( + 'Peer sent an invalid checkpoint (%s).', + peer.hostname()); + peer.destroy(); + return; + } + checkpoint = true; + } - // Request more headers. - peer.sendGetHeaders([node.hash], this.headerTip.hash); -}; + if (!this.headerNext) + this.headerNext = node; -/** - * Handle `sendheaders` packet. - * @method - * @private - * @param {Peer} peer - * @param {SendHeadersPacket} packet - * @returns {Promise} - */ + this.headerChain.push(node); + } -Pool.prototype.handleSendHeaders = async function handleSendHeaders(peer, packet) { - ; -}; + this.logger.debug( + 'Received %d headers from peer (%s).', + headers.length, + peer.hostname()); -/** - * Handle `block` packet. Attempt to add to chain. - * @method - * @private - * @param {Peer} peer - * @param {BlockPacket} packet - * @returns {Promise} - */ + // If we received a valid header + // chain, consider this a "block". + peer.blockTime = Date.now(); -Pool.prototype.handleBlock = async function handleBlock(peer, packet) { - const flags = chainCommon.flags.DEFAULT_FLAGS; + // Request the blocks we just added. + if (checkpoint) { + this.headerChain.shift(); + this.resolveHeaders(peer); + return; + } - if (this.options.spv) { - this.logger.warning( - 'Peer sent unsolicited block (%s).', - peer.hostname()); - return; + // Request more headers. + peer.sendGetHeaders([node.hash], this.headerTip.hash); } - await this.addBlock(peer, packet.block, flags); -}; + /** + * Handle `sendheaders` packet. + * @method + * @private + * @param {Peer} peer + * @param {SendHeadersPacket} packet + * @returns {Promise} + */ -/** - * Attempt to add block to chain. - * @method - * @private - * @param {Peer} peer - * @param {Block} block - * @returns {Promise} - */ - -Pool.prototype.addBlock = async function addBlock(peer, block, flags) { - const hash = block.hash('hex'); - const unlock = await this.locker.lock(hash); - try { - return await this._addBlock(peer, block, flags); - } finally { - unlock(); + async handleSendHeaders(peer, packet) { + ; } -}; -/** - * Attempt to add block to chain (without a lock). - * @method - * @private - * @param {Peer} peer - * @param {Block} block - * @returns {Promise} - */ + /** + * Handle `block` packet. Attempt to add to chain. + * @method + * @private + * @param {Peer} peer + * @param {BlockPacket} packet + * @returns {Promise} + */ -Pool.prototype._addBlock = async function _addBlock(peer, block, flags) { - if (!this.syncing) - return; + async handleBlock(peer, packet) { + const flags = chainCommon.flags.DEFAULT_FLAGS; - const hash = block.hash('hex'); + if (this.options.spv) { + this.logger.warning( + 'Peer sent unsolicited block (%s).', + peer.hostname()); + return; + } - if (!this.resolveBlock(peer, hash)) { - this.logger.warning( - 'Received unrequested block: %s (%s).', - block.rhash(), peer.hostname()); - peer.destroy(); - return; + await this.addBlock(peer, packet.block, flags); + } + + /** + * Attempt to add block to chain. + * @method + * @private + * @param {Peer} peer + * @param {Block} block + * @returns {Promise} + */ + + async addBlock(peer, block, flags) { + const hash = block.hash(); + const unlock = await this.locker.lock(hash); + try { + return await this._addBlock(peer, block, flags); + } finally { + unlock(); + } } - peer.blockTime = util.ms(); + /** + * Attempt to add block to chain (without a lock). + * @method + * @private + * @param {Peer} peer + * @param {Block} block + * @returns {Promise} + */ - let entry; - try { - entry = await this.chain.add(block, flags, peer.id); - } catch (err) { - if (err.type === 'VerifyError') { - peer.reject('block', err); - this.logger.warning(err); + async _addBlock(peer, block, flags) { + if (!this.syncing) return; - } - throw err; - } - // Block was orphaned. - if (!entry) { - if (this.checkpoints) { + const hash = block.hash(); + + if (!this.resolveBlock(peer, hash)) { this.logger.warning( - 'Peer sent orphan block with getheaders (%s).', - peer.hostname()); + 'Received unrequested block: %h (%s).', + block.hash(), peer.hostname()); + peer.destroy(); return; } - // During a getblocks sync, peers send - // their best tip frequently. We can grab - // the height commitment from the coinbase. - const height = block.getCoinbaseHeight(); + peer.blockTime = Date.now(); - if (height !== -1) { - peer.bestHash = hash; - peer.bestHeight = height; - this.resolveHeight(hash, height); + let entry; + try { + entry = await this.chain.add(block, flags, peer.id); + } catch (err) { + if (err.type === 'VerifyError') { + peer.reject('block', err); + this.logger.warning(err); + return; + } + throw err; } - this.logger.debug('Peer sent an orphan block. Resolving.'); - - await this.resolveOrphan(peer, hash); + // Block was orphaned. + if (!entry) { + if (this.checkpoints) { + this.logger.warning( + 'Peer sent orphan block with getheaders (%s).', + peer.hostname()); + return; + } - return; - } + // During a getblocks sync, peers send + // their best tip frequently. We can grab + // the height commitment from the coinbase. + const height = block.getCoinbaseHeight(); - if (this.chain.synced) { - peer.bestHash = entry.hash; - peer.bestHeight = entry.height; - this.resolveHeight(entry.hash, entry.height); - } + if (height !== -1) { + peer.bestHash = hash; + peer.bestHeight = height; + this.resolveHeight(hash, height); + } - this.logStatus(block); + this.logger.debug('Peer sent an orphan block. Resolving.'); - await this.resolveChain(peer, hash); -}; + await this.resolveOrphan(peer, hash); -/** - * Resolve header chain. - * @method - * @private - * @param {Peer} peer - * @param {Hash} hash - * @returns {Promise} - */ + return; + } -Pool.prototype.resolveChain = async function resolveChain(peer, hash) { - if (!this.checkpoints) - return; + if (this.chain.synced) { + peer.bestHash = entry.hash; + peer.bestHeight = entry.height; + this.resolveHeight(entry.hash, entry.height); + } - if (!peer.loader) - return; + this.logStatus(block); - if (peer.destroyed) - throw new Error('Peer was destroyed (header chain resolution).'); + await this.resolveChain(peer, hash); + } - const node = this.headerChain.head; + /** + * Resolve header chain. + * @method + * @private + * @param {Peer} peer + * @param {Hash} hash + * @returns {Promise} + */ - assert(node); + async resolveChain(peer, hash) { + if (!this.checkpoints) + return; - if (hash !== node.hash) { - this.logger.warning( - 'Header hash mismatch %s != %s (%s).', - util.revHex(hash), - util.revHex(node.hash), - peer.hostname()); + if (!peer.loader) + return; - peer.destroy(); + if (peer.destroyed) + throw new Error('Peer was destroyed (header chain resolution).'); - return; - } + const node = this.headerChain.head; - if (node.height < this.network.lastCheckpoint) { - if (node.height === this.headerTip.height) { - this.logger.info( - 'Received checkpoint %s (%d).', - util.revHex(node.hash), node.height); + assert(node); - this.headerTip = this.getNextTip(node.height); + if (!hash.equals(node.hash)) { + this.logger.warning( + 'Header hash mismatch %h != %h (%s).', + hash, + node.hash, + peer.hostname()); - peer.sendGetHeaders([hash], this.headerTip.hash); + peer.destroy(); return; } - this.headerChain.shift(); - this.resolveHeaders(peer); + if (node.height < this.network.lastCheckpoint) { + if (node.height === this.headerTip.height) { + this.logger.info( + 'Received checkpoint %h (%d).', + node.hash, node.height); - return; - } + this.headerTip = this.getNextTip(node.height); - this.logger.info( - 'Switching to getblocks (%s).', - peer.hostname()); + peer.sendGetHeaders([hash], this.headerTip.hash); - await this.switchSync(peer, hash); -}; + return; + } -/** - * Switch to getblocks. - * @method - * @private - * @param {Peer} peer - * @param {Hash} hash - * @returns {Promise} - */ + this.headerChain.shift(); + this.resolveHeaders(peer); + + return; + } -Pool.prototype.switchSync = async function switchSync(peer, hash) { - assert(this.checkpoints); + this.logger.info( + 'Switching to getblocks (%s).', + peer.hostname()); - this.checkpoints = false; - this.headerTip = null; - this.headerChain.reset(); - this.headerNext = null; + await this.switchSync(peer, hash); + } - await this.getBlocks(peer, hash); -}; + /** + * Switch to getblocks. + * @method + * @private + * @param {Peer} peer + * @param {Hash} hash + * @returns {Promise} + */ -/** - * Handle bad orphan. - * @method - * @private - * @param {String} msg - * @param {VerifyError} err - * @param {Number} id - */ + async switchSync(peer, hash) { + assert(this.checkpoints); -Pool.prototype.handleBadOrphan = function handleBadOrphan(msg, err, id) { - const peer = this.peers.find(id); + this.checkpoints = false; + this.headerTip = null; + this.headerChain.reset(); + this.headerNext = null; - if (!peer) { - this.logger.warning( - 'Could not find offending peer for orphan: %s (%d).', - util.revHex(err.hash), id); - return; + await this.getBlocks(peer, hash); } - this.logger.debug( - 'Punishing peer for sending a bad orphan (%s).', - peer.hostname()); + /** + * Handle bad orphan. + * @method + * @private + * @param {String} msg + * @param {VerifyError} err + * @param {Number} id + */ - // Punish the original peer who sent this. - peer.reject(msg, err); -}; + handleBadOrphan(msg, err, id) { + const peer = this.peers.find(id); -/** - * Log sync status. - * @private - * @param {Block} block - */ + if (!peer) { + this.logger.warning( + 'Could not find offending peer for orphan: %h (%d).', + err.hash, id); + return; + } -Pool.prototype.logStatus = function logStatus(block) { - if (this.chain.height % 20 === 0) { - this.logger.debug('Status:' - + ' time=%s height=%d progress=%s' - + ' orphans=%d active=%d' - + ' target=%s peers=%d', - util.date(block.time), - this.chain.height, - (this.chain.getProgress() * 100).toFixed(2) + '%', - this.chain.orphanMap.size, - this.blockMap.size, - block.bits, - this.peers.size()); - } + this.logger.debug( + 'Punishing peer for sending a bad orphan (%s).', + peer.hostname()); - if (this.chain.height % 2000 === 0) { - this.logger.info( - 'Received 2000 more blocks (height=%d, hash=%s).', - this.chain.height, - block.rhash()); - } -}; + // Punish the original peer who sent this. + peer.reject(msg, err); + } + + /** + * Log sync status. + * @private + * @param {Block} block + */ + + logStatus(block) { + if (this.chain.height % 20 === 0) { + this.logger.debug('Status:' + + ' time=%s height=%d progress=%s' + + ' orphans=%d active=%d' + + ' target=%s peers=%d', + util.date(block.time), + this.chain.height, + (this.chain.getProgress() * 100).toFixed(2) + '%', + this.chain.orphanMap.size, + this.blockMap.size, + block.bits, + this.peers.size()); + } -/** - * Handle a transaction. Attempt to add to mempool. - * @method - * @private - * @param {Peer} peer - * @param {TXPacket} packet - * @returns {Promise} - */ + if (this.chain.height % 2000 === 0) { + this.logger.info( + 'Received 2000 more blocks (height=%d, hash=%h).', + this.chain.height, + block.hash()); + } + } -Pool.prototype.handleTX = async function handleTX(peer, packet) { - const hash = packet.tx.hash('hex'); - const unlock = await this.locker.lock(hash); - try { - return await this._handleTX(peer, packet); - } finally { - unlock(); + /** + * Handle a transaction. Attempt to add to mempool. + * @method + * @private + * @param {Peer} peer + * @param {TXPacket} packet + * @returns {Promise} + */ + + async handleTX(peer, packet) { + const hash = packet.tx.hash(); + const unlock = await this.locker.lock(hash); + try { + return await this._handleTX(peer, packet); + } finally { + unlock(); + } } -}; -/** - * Handle a transaction. Attempt to add to mempool (without a lock). - * @method - * @private - * @param {Peer} peer - * @param {TXPacket} packet - * @returns {Promise} - */ + /** + * Handle a transaction. Attempt to add to mempool (without a lock). + * @method + * @private + * @param {Peer} peer + * @param {TXPacket} packet + * @returns {Promise} + */ -Pool.prototype._handleTX = async function _handleTX(peer, packet) { - const tx = packet.tx; - const hash = tx.hash('hex'); - const flags = chainCommon.flags.VERIFY_NONE; - const block = peer.merkleBlock; + async _handleTX(peer, packet) { + const tx = packet.tx; + const hash = tx.hash(); + const flags = chainCommon.flags.VERIFY_NONE; + const block = peer.merkleBlock; - if (block) { - assert(peer.merkleMatches > 0); - assert(peer.merkleMap); + if (block) { + assert(peer.merkleMatches > 0); + assert(peer.merkleMap); + + if (block.hasTX(hash)) { + if (peer.merkleMap.has(hash)) { + this.logger.warning( + 'Peer sent duplicate merkle tx: %h (%s).', + tx.hash(), peer.hostname()); + peer.increaseBan(100); + return; + } - if (block.hasTX(hash)) { - if (peer.merkleMap.has(hash)) { - this.logger.warning( - 'Peer sent duplicate merkle tx: %s (%s).', - tx.txid(), peer.hostname()); - peer.increaseBan(100); - return; - } + peer.merkleMap.add(hash); - peer.merkleMap.add(hash); + block.txs.push(tx); - block.txs.push(tx); + if (--peer.merkleMatches === 0) { + peer.merkleBlock = null; + peer.merkleTime = -1; + peer.merkleMatches = 0; + peer.merkleMap = null; + await this._addBlock(peer, block, flags); + } - if (--peer.merkleMatches === 0) { - peer.merkleBlock = null; - peer.merkleTime = -1; - peer.merkleMatches = 0; - peer.merkleMap = null; - await this._addBlock(peer, block, flags); + return; } + } + if (!this.resolveTX(peer, hash)) { + this.logger.warning( + 'Peer sent unrequested tx: %h (%s).', + tx.hash(), peer.hostname()); + peer.destroy(); return; } - } - if (!this.resolveTX(peer, hash)) { - this.logger.warning( - 'Peer sent unrequested tx: %s (%s).', - tx.txid(), peer.hostname()); - peer.destroy(); - return; - } + if (!this.mempool) { + this.emit('tx', tx); + return; + } - if (!this.mempool) { - this.emit('tx', tx); - return; - } + let missing; + try { + missing = await this.mempool.addTX(tx, peer.id); + } catch (err) { + if (err.type === 'VerifyError') { + peer.reject('tx', err); + this.logger.info(err); + return; + } + throw err; + } - let missing; - try { - missing = await this.mempool.addTX(tx, peer.id); - } catch (err) { - if (err.type === 'VerifyError') { - peer.reject('tx', err); - this.logger.info(err); - return; + if (missing && missing.length > 0) { + this.logger.debug( + 'Requesting %d missing transactions (%s).', + missing.length, peer.hostname()); + + this.ensureTX(peer, missing); } - throw err; } - if (missing && missing.length > 0) { - this.logger.debug( - 'Requesting %d missing transactions (%s).', - missing.length, peer.hostname()); + /** + * Handle peer reject event. + * @method + * @private + * @param {Peer} peer + * @param {RejectPacket} packet + */ - this.ensureTX(peer, missing); - } -}; + async handleReject(peer, packet) { + this.logger.warning( + 'Received reject (%s): msg=%s code=%s reason=%s hash=%h.', + peer.hostname(), + packet.message, + packet.getCode(), + packet.reason, + packet.hash); + + if (!packet.hash) + return; -/** - * Handle peer reject event. - * @method - * @private - * @param {Peer} peer - * @param {RejectPacket} packet - */ + const entry = this.invMap.get(packet.hash); -Pool.prototype.handleReject = async function handleReject(peer, packet) { - this.logger.warning( - 'Received reject (%s): msg=%s code=%s reason=%s hash=%s.', - peer.hostname(), - packet.message, - packet.getCode(), - packet.reason, - packet.rhash()); + if (!entry) + return; - if (!packet.hash) - return; + entry.handleReject(peer); + } - const entry = this.invMap.get(packet.hash); + /** + * Handle `mempool` packet. + * @method + * @private + * @param {Peer} peer + * @param {MempoolPacket} packet + */ - if (!entry) - return; + async handleMempool(peer, packet) { + if (!this.mempool) + return; - entry.handleReject(peer); -}; + if (!this.chain.synced) + return; -/** - * Handle `mempool` packet. - * @method - * @private - * @param {Peer} peer - * @param {MempoolPacket} packet - */ + if (this.options.selfish) + return; -Pool.prototype.handleMempool = async function handleMempool(peer, packet) { - if (!this.mempool) - return; + if (!this.options.bip37) { + this.logger.debug( + 'Peer requested mempool without bip37 enabled (%s).', + peer.hostname()); + peer.destroy(); + return; + } - if (!this.chain.synced) - return; + const items = []; - if (this.options.selfish) - return; + for (const hash of this.mempool.map.keys()) + items.push(new InvItem(invTypes.TX, hash)); - if (!this.options.bip37) { this.logger.debug( - 'Peer requested mempool without bip37 enabled (%s).', + 'Sending mempool snapshot (%s).', peer.hostname()); - peer.destroy(); - return; - } - const items = []; + peer.queueInv(items); + } - for (const hash of this.mempool.map.keys()) - items.push(new InvItem(invTypes.TX, hash)); + /** + * Handle `filterload` packet. + * @method + * @private + * @param {Peer} peer + * @param {FilterLoadPacket} packet + */ - this.logger.debug( - 'Sending mempool snapshot (%s).', - peer.hostname()); + async handleFilterLoad(peer, packet) { + ; + } - peer.queueInv(items); -}; + /** + * Handle `filteradd` packet. + * @method + * @private + * @param {Peer} peer + * @param {FilterAddPacket} packet + */ -/** - * Handle `filterload` packet. - * @method - * @private - * @param {Peer} peer - * @param {FilterLoadPacket} packet - */ + async handleFilterAdd(peer, packet) { + ; + } -Pool.prototype.handleFilterLoad = async function handleFilterLoad(peer, packet) { - ; -}; + /** + * Handle `filterclear` packet. + * @method + * @private + * @param {Peer} peer + * @param {FilterClearPacket} packet + */ -/** - * Handle `filteradd` packet. - * @method - * @private - * @param {Peer} peer - * @param {FilterAddPacket} packet - */ + async handleFilterClear(peer, packet) { + ; + } -Pool.prototype.handleFilterAdd = async function handleFilterAdd(peer, packet) { - ; -}; + /** + * Handle `merkleblock` packet. + * @method + * @private + * @param {Peer} peer + * @param {MerkleBlockPacket} block + */ -/** - * Handle `filterclear` packet. - * @method - * @private - * @param {Peer} peer - * @param {FilterClearPacket} packet - */ + async handleMerkleBlock(peer, packet) { + const hash = packet.block.hash(); + const unlock = await this.locker.lock(hash); + try { + return await this._handleMerkleBlock(peer, packet); + } finally { + unlock(); + } + } -Pool.prototype.handleFilterClear = async function handleFilterClear(peer, packet) { - ; -}; + /** + * Handle `merkleblock` packet (without a lock). + * @method + * @private + * @param {Peer} peer + * @param {MerkleBlockPacket} block + */ -/** - * Handle `merkleblock` packet. - * @method - * @private - * @param {Peer} peer - * @param {MerkleBlockPacket} block - */ + async _handleMerkleBlock(peer, packet) { + if (!this.syncing) + return; -Pool.prototype.handleMerkleBlock = async function handleMerkleBlock(peer, packet) { - const hash = packet.block.hash('hex'); - const unlock = await this.locker.lock(hash); - try { - return await this._handleMerkleBlock(peer, packet); - } finally { - unlock(); - } -}; + // Potential DoS. + if (!this.options.spv) { + this.logger.warning( + 'Peer sent unsolicited merkleblock (%s).', + peer.hostname()); + peer.increaseBan(100); + return; + } -/** - * Handle `merkleblock` packet (without a lock). - * @method - * @private - * @param {Peer} peer - * @param {MerkleBlockPacket} block - */ + const block = packet.block; + const hash = block.hash(); -Pool.prototype._handleMerkleBlock = async function _handleMerkleBlock(peer, packet) { - if (!this.syncing) - return; + if (!peer.blockMap.has(hash)) { + this.logger.warning( + 'Peer sent an unrequested merkleblock (%s).', + peer.hostname()); + peer.destroy(); + return; + } - // Potential DoS. - if (!this.options.spv) { - this.logger.warning( - 'Peer sent unsolicited merkleblock (%s).', - peer.hostname()); - peer.increaseBan(100); - return; - } + if (peer.merkleBlock) { + this.logger.warning( + 'Peer sent a merkleblock prematurely (%s).', + peer.hostname()); + peer.increaseBan(100); + return; + } - const block = packet.block; - const hash = block.hash('hex'); + if (!block.verify()) { + this.logger.warning( + 'Peer sent an invalid merkleblock (%s).', + peer.hostname()); + peer.increaseBan(100); + return; + } - if (!peer.blockMap.has(hash)) { - this.logger.warning( - 'Peer sent an unrequested merkleblock (%s).', - peer.hostname()); - peer.destroy(); - return; - } + const tree = block.getTree(); - if (peer.merkleBlock) { - this.logger.warning( - 'Peer sent a merkleblock prematurely (%s).', - peer.hostname()); - peer.increaseBan(100); - return; - } + if (tree.matches.length === 0) { + const flags = chainCommon.flags.VERIFY_NONE; + await this._addBlock(peer, block, flags); + return; + } - if (!block.verify()) { - this.logger.warning( - 'Peer sent an invalid merkleblock (%s).', - peer.hostname()); - peer.increaseBan(100); - return; + peer.merkleBlock = block; + peer.merkleTime = Date.now(); + peer.merkleMatches = tree.matches.length; + peer.merkleMap = new BufferSet(); } - const tree = block.getTree(); + /** + * Handle `sendcmpct` packet. + * @method + * @private + * @param {Peer} peer + * @param {FeeFilterPacket} packet + */ - if (tree.matches.length === 0) { - const flags = chainCommon.flags.VERIFY_NONE; - await this._addBlock(peer, block, flags); - return; + async handleFeeFilter(peer, packet) { + ; } - peer.merkleBlock = block; - peer.merkleTime = util.ms(); - peer.merkleMatches = tree.matches.length; - peer.merkleMap = new Set(); -}; - -/** - * Handle `sendcmpct` packet. - * @method - * @private - * @param {Peer} peer - * @param {FeeFilterPacket} packet - */ + /** + * Handle `sendcmpct` packet. + * @method + * @private + * @param {Peer} peer + * @param {SendCmpctPacket} packet + */ -Pool.prototype.handleFeeFilter = async function handleFeeFilter(peer, packet) { - ; -}; + async handleSendCmpct(peer, packet) { + ; + } -/** - * Handle `sendcmpct` packet. - * @method - * @private - * @param {Peer} peer - * @param {SendCmpctPacket} packet - */ + /** + * Handle `cmpctblock` packet. + * @method + * @private + * @param {Peer} peer + * @param {CompactBlockPacket} packet + */ -Pool.prototype.handleSendCmpct = async function handleSendCmpct(peer, packet) { - ; -}; + async handleCmpctBlock(peer, packet) { + const block = packet.block; + const hash = block.hash(); + const witness = peer.compactWitness; -/** - * Handle `cmpctblock` packet. - * @method - * @private - * @param {Peer} peer - * @param {CompactBlockPacket} packet - */ + if (!this.syncing) + return; -Pool.prototype.handleCmpctBlock = async function handleCmpctBlock(peer, packet) { - const block = packet.block; - const hash = block.hash('hex'); - const witness = peer.compactWitness; + if (!this.options.compact) { + this.logger.info( + 'Peer sent unsolicited cmpctblock (%s).', + peer.hostname()); + this.destroy(); + return; + } - if (!this.syncing) - return; + if (!peer.hasCompactSupport() || !peer.hasCompact()) { + this.logger.info( + 'Peer sent unsolicited cmpctblock (%s).', + peer.hostname()); + this.destroy(); + return; + } - if (!this.options.compact) { - this.logger.info( - 'Peer sent unsolicited cmpctblock (%s).', - peer.hostname()); - this.destroy(); - return; - } + if (peer.compactBlocks.has(hash)) { + this.logger.debug( + 'Peer sent us a duplicate compact block (%s).', + peer.hostname()); + return; + } - if (!peer.hasCompactSupport() || !peer.hasCompact()) { - this.logger.info( - 'Peer sent unsolicited cmpctblock (%s).', - peer.hostname()); - this.destroy(); - return; - } + if (this.compactBlocks.has(hash)) { + this.logger.debug( + 'Already waiting for compact block %h (%s).', + hash, peer.hostname()); + return; + } - if (peer.compactBlocks.has(hash)) { - this.logger.debug( - 'Peer sent us a duplicate compact block (%s).', - peer.hostname()); - return; - } + if (!peer.blockMap.has(hash)) { + if (this.options.blockMode !== 1) { + this.logger.warning( + 'Peer sent us an unrequested compact block (%s).', + peer.hostname()); + peer.destroy(); + return; + } + peer.blockMap.set(hash, Date.now()); + assert(!this.blockMap.has(hash)); + this.blockMap.add(hash); + } - if (this.compactBlocks.has(hash)) { - this.logger.debug( - 'Already waiting for compact block %s (%s).', - hash, peer.hostname()); - return; - } + if (!this.mempool) { + this.logger.warning('Requesting compact blocks without a mempool!'); + return; + } - if (!peer.blockMap.has(hash)) { - if (this.options.blockMode !== 1) { - this.logger.warning( - 'Peer sent us an unrequested compact block (%s).', + if (!block.verify()) { + this.logger.debug( + 'Peer sent an invalid compact block (%s).', peer.hostname()); - peer.destroy(); + peer.increaseBan(100); return; } - peer.blockMap.set(hash, util.ms()); - assert(!this.blockMap.has(hash)); - this.blockMap.add(hash); - } - - if (!this.mempool) { - this.logger.warning('Requesting compact blocks without a mempool!'); - return; - } - - if (!block.verify()) { - this.logger.debug( - 'Peer sent an invalid compact block (%s).', - peer.hostname()); - peer.increaseBan(100); - return; - } - let result; - try { - result = block.init(); - } catch (e) { - this.logger.debug( - 'Peer sent an invalid compact block (%s).', - peer.hostname()); - peer.increaseBan(100); - return; - } + let result; + try { + result = block.init(); + } catch (e) { + this.logger.debug( + 'Peer sent an invalid compact block (%s).', + peer.hostname()); + peer.increaseBan(100); + return; + } - if (!result) { - this.logger.warning( - 'Siphash collision for %s. Requesting full block (%s).', - block.rhash(), peer.hostname()); - peer.getFullBlock(hash); - peer.increaseBan(10); - return; - } + if (!result) { + this.logger.warning( + 'Siphash collision for %h. Requesting full block (%s).', + block.hash(), peer.hostname()); + peer.getFullBlock(hash); + peer.increaseBan(10); + return; + } - const full = block.fillMempool(witness, this.mempool); + const full = block.fillMempool(witness, this.mempool); - if (full) { - this.logger.debug( - 'Received full compact block %s (%s).', - block.rhash(), peer.hostname()); - const flags = chainCommon.flags.VERIFY_BODY; - await this.addBlock(peer, block.toBlock(), flags); - return; - } + if (full) { + this.logger.debug( + 'Received full compact block %h (%s).', + block.hash(), peer.hostname()); + const flags = chainCommon.flags.VERIFY_BODY; + await this.addBlock(peer, block.toBlock(), flags); + return; + } - if (this.options.blockMode === 1) { if (peer.compactBlocks.size >= 15) { this.logger.warning('Compact block DoS attempt (%s).', peer.hostname()); peer.destroy(); return; } - } - block.now = util.ms(); + block.now = Date.now(); - assert(!peer.compactBlocks.has(hash)); - peer.compactBlocks.set(hash, block); + assert(!peer.compactBlocks.has(hash)); + peer.compactBlocks.set(hash, block); - this.compactBlocks.add(hash); + this.compactBlocks.add(hash); - this.logger.debug( - 'Received non-full compact block %s tx=%d/%d (%s).', - block.rhash(), block.count, block.totalTX, peer.hostname()); + this.logger.debug( + 'Received non-full compact block %h tx=%d/%d (%s).', + block.hash(), block.count, block.totalTX, peer.hostname()); - peer.send(new packets.GetBlockTxnPacket(block.toRequest())); -}; + peer.send(new packets.GetBlockTxnPacket(block.toRequest())); + } -/** - * Handle `getblocktxn` packet. - * @method - * @private - * @param {Peer} peer - * @param {GetBlockTxnPacket} packet - */ + /** + * Handle `getblocktxn` packet. + * @method + * @private + * @param {Peer} peer + * @param {GetBlockTxnPacket} packet + */ -Pool.prototype.handleGetBlockTxn = async function handleGetBlockTxn(peer, packet) { - const req = packet.request; + async handleGetBlockTxn(peer, packet) { + const req = packet.request; - if (this.chain.options.spv) - return; + if (this.chain.options.spv) + return; - if (this.chain.options.prune) - return; + if (this.chain.options.prune) + return; - if (this.options.selfish) - return; + if (this.options.selfish) + return; - const item = new InvItem(invTypes.BLOCK, req.hash); + const item = new InvItem(invTypes.BLOCK, req.hash); - const block = await this.getItem(peer, item); + const block = await this.getItem(peer, item); - if (!block) { - this.logger.debug( - 'Peer sent getblocktxn for non-existent block (%s).', - peer.hostname()); - peer.increaseBan(100); - return; - } + if (!block) { + this.logger.debug( + 'Peer sent getblocktxn for non-existent block (%s).', + peer.hostname()); + peer.increaseBan(100); + return; + } - const height = await this.chain.getHeight(req.hash); + const height = await this.chain.getHeight(req.hash); + + if (height < this.chain.tip.height - 15) { + this.logger.debug( + 'Peer sent a getblocktxn for a block > 15 deep (%s)', + peer.hostname()); + return; + } - if (height < this.chain.tip.height - 15) { this.logger.debug( - 'Peer sent a getblocktxn for a block > 15 deep (%s)', + 'Sending blocktxn for %h to peer (%s).', + block.hash(), peer.hostname()); - return; + + const res = BIP152.TXResponse.fromBlock(block, req); + + peer.send(new packets.BlockTxnPacket(res, peer.compactWitness)); } - this.logger.debug( - 'Sending blocktxn for %s to peer (%s).', - block.rhash(), - peer.hostname()); + /** + * Handle `blocktxn` packet. + * @method + * @private + * @param {Peer} peer + * @param {BlockTxnPacket} packet + */ - const res = BIP152.TXResponse.fromBlock(block, req); + async handleBlockTxn(peer, packet) { + const res = packet.response; + const block = peer.compactBlocks.get(res.hash); + const flags = chainCommon.flags.VERIFY_BODY; - peer.send(new packets.BlockTxnPacket(res, peer.compactWitness)); -}; + if (!block) { + this.logger.debug( + 'Peer sent unsolicited blocktxn (%s).', + peer.hostname()); + return; + } -/** - * Handle `blocktxn` packet. - * @method - * @private - * @param {Peer} peer - * @param {BlockTxnPacket} packet - */ + peer.compactBlocks.delete(res.hash); + + assert(this.compactBlocks.has(res.hash)); + this.compactBlocks.delete(res.hash); -Pool.prototype.handleBlockTxn = async function handleBlockTxn(peer, packet) { - const res = packet.response; - const block = peer.compactBlocks.get(res.hash); - const flags = chainCommon.flags.VERIFY_BODY; + if (!block.fillMissing(res)) { + this.logger.warning( + 'Peer sent non-full blocktxn for %h. Requesting full block (%s).', + block.hash(), + peer.hostname()); + peer.getFullBlock(res.hash); + peer.increaseBan(10); + return; + } - if (!block) { this.logger.debug( - 'Peer sent unsolicited blocktxn (%s).', - peer.hostname()); - return; - } + 'Filled compact block %h (%s).', + block.hash(), peer.hostname()); - peer.compactBlocks.delete(res.hash); + await this.addBlock(peer, block.toBlock(), flags); + } - assert(this.compactBlocks.has(res.hash)); - this.compactBlocks.delete(res.hash); + /** + * Handle `unknown` packet. + * @method + * @private + * @param {Peer} peer + * @param {UnknownPacket} packet + */ - if (!block.fillMissing(res)) { + async handleUnknown(peer, packet) { this.logger.warning( - 'Peer sent non-full blocktxn for %s. Requesting full block (%s).', - block.rhash(), - peer.hostname()); - peer.getFullBlock(res.hash); - peer.increaseBan(10); - return; + 'Unknown packet: %s (%s).', + packet.cmd, peer.hostname()); } - this.logger.debug( - 'Filled compact block %s (%s).', - block.rhash(), peer.hostname()); - - await this.addBlock(peer, block.toBlock(), flags); -}; + /** + * Create an inbound peer from an existing socket. + * @private + * @param {net.Socket} socket + */ -/** - * Handle `encinit` packet. - * @method - * @private - * @param {Peer} peer - * @param {EncinitPacket} packet - */ + addInbound(socket) { + if (!this.opened) { + socket.destroy(); + return; + } -Pool.prototype.handleEncinit = async function handleEncinit(peer, packet) { - ; -}; + const peer = this.createInbound(socket); -/** - * Handle `encack` packet. - * @method - * @private - * @param {Peer} peer - * @param {EncackPacket} packet - */ + this.logger.info('Added inbound peer (%s).', peer.hostname()); -Pool.prototype.handleEncack = async function handleEncack(peer, packet) { - ; -}; + this.peers.add(peer); + } -/** - * Handle `authchallenge` packet. - * @method - * @private - * @param {Peer} peer - * @param {AuthChallengePacket} packet - */ + /** + * Allocate a host from the host list. + * @returns {NetAddress} + */ -Pool.prototype.handleAuthChallenge = async function handleAuthChallenge(peer, packet) { - ; -}; + getHost() { + for (const addr of this.hosts.nodes) { + if (this.peers.has(addr.hostname)) + continue; -/** - * Handle `authreply` packet. - * @method - * @private - * @param {Peer} peer - * @param {AuthReplyPacket} packet - */ + return addr; + } -Pool.prototype.handleAuthReply = async function handleAuthReply(peer, packet) { - ; -}; + const services = this.options.getRequiredServices(); + const now = this.network.now(); -/** - * Handle `authpropose` packet. - * @method - * @private - * @param {Peer} peer - * @param {AuthProposePacket} packet - */ + for (let i = 0; i < 100; i++) { + const entry = this.hosts.getHost(); -Pool.prototype.handleAuthPropose = async function handleAuthPropose(peer, packet) { - ; -}; + if (!entry) + break; -/** - * Handle `unknown` packet. - * @method - * @private - * @param {Peer} peer - * @param {UnknownPacket} packet - */ + const addr = entry.addr; -Pool.prototype.handleUnknown = async function handleUnknown(peer, packet) { - this.logger.warning( - 'Unknown packet: %s (%s).', - packet.cmd, peer.hostname()); -}; + if (this.peers.has(addr.hostname)) + continue; -/** - * Create an inbound peer from an existing socket. - * @private - * @param {net.Socket} socket - */ + if (!addr.isValid()) + continue; -Pool.prototype.addInbound = function addInbound(socket) { - if (!this.loaded) { - socket.destroy(); - return; - } + if (!addr.hasServices(services)) + continue; - const peer = this.createInbound(socket); + if (!this.options.onion && addr.isOnion()) + continue; - this.logger.info('Added inbound peer (%s).', peer.hostname()); + if (i < 30 && now - entry.lastAttempt < 600) + continue; - this.peers.add(peer); -}; + if (i < 50 && addr.port !== this.network.port) + continue; -/** - * Allocate a host from the host list. - * @returns {NetAddress} - */ + if (i < 95 && this.hosts.isBanned(addr.host)) + continue; -Pool.prototype.getHost = function getHost() { - for (const addr of this.hosts.nodes) { - if (this.peers.has(addr.hostname)) - continue; + return entry.addr; + } - return addr; + return null; } - const services = this.options.getRequiredServices(); - const now = this.network.now(); - - for (let i = 0; i < 100; i++) { - const entry = this.hosts.getHost(); + /** + * Create an outbound non-loader peer. These primarily + * exist for transaction relaying. + * @private + */ - if (!entry) - break; - - const addr = entry.addr; - - if (this.peers.has(addr.hostname)) - continue; + addOutbound() { + if (!this.opened) + return; - if (!addr.isValid()) - continue; + if (this.peers.outbound >= this.options.maxOutbound) + return; - if (!addr.hasServices(services)) - continue; + // Hang back if we don't + // have a loader peer yet. + if (!this.peers.load) + return; - if (!this.options.onion && addr.isOnion()) - continue; + const addr = this.getHost(); - if (i < 30 && now - entry.lastAttempt < 600) - continue; + if (!addr) + return; - if (i < 50 && addr.port !== this.network.port) - continue; + const peer = this.createOutbound(addr); - if (i < 95 && this.hosts.isBanned(addr.host)) - continue; + this.peers.add(peer); - return entry.addr; + this.emit('peer', peer); } - return null; -}; - -/** - * Create an outbound non-loader peer. These primarily - * exist for transaction relaying. - * @private - */ - -Pool.prototype.addOutbound = function addOutbound() { - if (!this.loaded) - return; - - if (this.peers.outbound >= this.options.maxOutbound) - return; + /** + * Attempt to refill the pool with peers (no lock). + * @private + */ - // Hang back if we don't - // have a loader peer yet. - if (!this.peers.load) - return; + fillOutbound() { + const need = this.options.maxOutbound - this.peers.outbound; - const addr = this.getHost(); - - if (!addr) - return; + if (!this.peers.load) + this.addLoader(); - const peer = this.createOutbound(addr); + if (need <= 0) + return; - this.peers.add(peer); + this.logger.debug('Refilling peers (%d/%d).', + this.peers.outbound, + this.options.maxOutbound); - this.emit('peer', peer); -}; + for (let i = 0; i < need; i++) + this.addOutbound(); + } -/** - * Attempt to refill the pool with peers (no lock). - * @private - */ + /** + * Attempt to refill the pool with peers (no lock). + * @private + */ -Pool.prototype.fillOutbound = function fillOutbound() { - const need = this.options.maxOutbound - this.peers.outbound; + refill() { + if (this.pendingRefill != null) + return; - if (!this.peers.load) - this.addLoader(); + this.pendingRefill = setTimeout(() => { + this.pendingRefill = null; + this.fillOutbound(); + }, 3000); + } - if (need <= 0) - return; + /** + * Remove a peer from any list. Drop all load requests. + * @private + * @param {Peer} peer + */ - this.logger.debug('Refilling peers (%d/%d).', - this.peers.outbound, - this.options.maxOutbound); + removePeer(peer) { + this.peers.remove(peer); - for (let i = 0; i < need; i++) - this.addOutbound(); -}; + for (const hash of peer.blockMap.keys()) + this.resolveBlock(peer, hash); -/** - * Attempt to refill the pool with peers (no lock). - * @private - */ + for (const hash of peer.txMap.keys()) + this.resolveTX(peer, hash); -Pool.prototype.refill = function refill() { - if (this.pendingRefill != null) - return; + for (const hash of peer.compactBlocks.keys()) { + assert(this.compactBlocks.has(hash)); + this.compactBlocks.delete(hash); + } - this.pendingRefill = setTimeout(() => { - this.pendingRefill = null; - this.fillOutbound(); - }, 3000); -}; + peer.compactBlocks.clear(); + } -/** - * Remove a peer from any list. Drop all load requests. - * @private - * @param {Peer} peer - */ + /** + * Ban peer. + * @param {NetAddress} addr + */ -Pool.prototype.removePeer = function removePeer(peer) { - this.peers.remove(peer); + ban(addr) { + const peer = this.peers.get(addr.hostname); - for (const hash of peer.blockMap.keys()) - this.resolveBlock(peer, hash); + this.logger.debug('Banning peer (%s).', addr.hostname); - for (const hash of peer.txMap.keys()) - this.resolveTX(peer, hash); + this.hosts.ban(addr.host); + this.hosts.remove(addr.hostname); - for (const hash of peer.compactBlocks.keys()) { - assert(this.compactBlocks.has(hash)); - this.compactBlocks.delete(hash); + if (peer) + peer.destroy(); } - peer.compactBlocks.clear(); -}; + /** + * Unban peer. + * @param {NetAddress} addr + */ -/** - * Ban peer. - * @param {NetAddress} addr - */ + unban(addr) { + this.hosts.unban(addr.host); + } -Pool.prototype.ban = function ban(addr) { - const peer = this.peers.get(addr.hostname); + /** + * Set the spv filter. + * @param {BloomFilter} filter + * @param {String?} enc + */ - this.logger.debug('Banning peer (%s).', addr.hostname); + setFilter(filter) { + if (!this.options.spv) + return; - this.hosts.ban(addr.host); - this.hosts.remove(addr.hostname); + this.spvFilter = filter; + this.queueFilterLoad(); + } - if (peer) - peer.destroy(); -}; + /** + * Watch a an address hash (filterload, SPV-only). + * @param {Buffer|Hash} data + * @param {String?} enc + */ -/** - * Unban peer. - * @param {NetAddress} addr - */ + watch(data, enc) { + if (!this.options.spv) + return; -Pool.prototype.unban = function unban(addr) { - this.hosts.unban(addr.host); -}; + this.spvFilter.add(data, enc); + this.queueFilterLoad(); + } -/** - * Set the spv filter. - * @param {Bloom} filter - * @param {String?} enc - */ + /** + * Reset the spv filter (filterload, SPV-only). + */ -Pool.prototype.setFilter = function setFilter(filter) { - if (!this.options.spv) - return; + unwatch() { + if (!this.options.spv) + return; - this.spvFilter = filter; - this.queueFilterLoad(); -}; + this.spvFilter.reset(); + this.queueFilterLoad(); + } -/** - * Watch a an address hash (filterload, SPV-only). - * @param {Buffer|Hash} data - * @param {String?} enc - */ + /** + * Queue a resend of the bloom filter. + */ -Pool.prototype.watch = function watch(data, enc) { - if (!this.options.spv) - return; + queueFilterLoad() { + if (!this.options.spv) + return; - this.spvFilter.add(data, enc); - this.queueFilterLoad(); -}; + if (this.pendingFilter != null) + return; -/** - * Reset the spv filter (filterload, SPV-only). - */ + this.pendingFilter = setTimeout(() => { + this.pendingFilter = null; + this.sendFilterLoad(); + }, 100); + } -Pool.prototype.unwatch = function unwatch() { - if (!this.options.spv) - return; + /** + * Resend the bloom filter to peers. + */ - this.spvFilter.reset(); - this.queueFilterLoad(); -}; + sendFilterLoad() { + if (!this.options.spv) + return; -/** - * Queue a resend of the bloom filter. - */ + assert(this.spvFilter); -Pool.prototype.queueFilterLoad = function queueFilterLoad() { - if (!this.options.spv) - return; + for (let peer = this.peers.head(); peer; peer = peer.next) + peer.sendFilterLoad(this.spvFilter); + } - if (this.pendingFilter != null) - return; + /** + * Add an address to the bloom filter (SPV-only). + * @param {Address|AddressString} address + */ - this.pendingFilter = setTimeout(() => { - this.pendingFilter = null; - this.sendFilterLoad(); - }, 100); -}; + watchAddress(address) { + if (typeof address === 'string') + address = Address.fromString(address, this.network); -/** - * Resend the bloom filter to peers. - */ + const hash = Address.getHash(address); + this.watch(hash); + } -Pool.prototype.sendFilterLoad = function sendFilterLoad() { - if (!this.options.spv) - return; + /** + * Add an outpoint to the bloom filter (SPV-only). + * @param {Outpoint} outpoint + */ - assert(this.spvFilter); + watchOutpoint(outpoint) { + this.watch(outpoint.toRaw()); + } - for (let peer = this.peers.head(); peer; peer = peer.next) - peer.sendFilterLoad(this.spvFilter); -}; + /** + * Send `getblocks` to peer after building + * locator and resolving orphan root. + * @method + * @param {Peer} peer + * @param {Hash} orphan - Orphan hash to resolve. + * @returns {Promise} + */ -/** - * Add an address to the bloom filter (SPV-only). - * @param {Address|Base58Address} address - */ + async resolveOrphan(peer, orphan) { + const locator = await this.chain.getLocator(); + const root = this.chain.getOrphanRoot(orphan); -Pool.prototype.watchAddress = function watchAddress(address) { - const hash = Address.getHash(address); - this.watch(hash); -}; + assert(root); -/** - * Add an outpoint to the bloom filter (SPV-only). - * @param {Outpoint} outpoint - */ + peer.sendGetBlocks(locator, root); + } -Pool.prototype.watchOutpoint = function watchOutpoint(outpoint) { - this.watch(outpoint.toRaw()); -}; + /** + * Send `getheaders` to peer after building locator. + * @method + * @param {Peer} peer + * @param {Hash} tip - Tip to build chain locator from. + * @param {Hash?} stop + * @returns {Promise} + */ -/** - * Send `getblocks` to peer after building - * locator and resolving orphan root. - * @method - * @param {Peer} peer - * @param {Hash} orphan - Orphan hash to resolve. - * @returns {Promise} - */ + async getHeaders(peer, tip, stop) { + const locator = await this.chain.getLocator(tip); + peer.sendGetHeaders(locator, stop); + } -Pool.prototype.resolveOrphan = async function resolveOrphan(peer, orphan) { - const locator = await this.chain.getLocator(); - const root = this.chain.getOrphanRoot(orphan); + /** + * Send `getblocks` to peer after building locator. + * @method + * @param {Peer} peer + * @param {Hash} tip - Tip hash to build chain locator from. + * @param {Hash?} stop + * @returns {Promise} + */ - assert(root); + async getBlocks(peer, tip, stop) { + const locator = await this.chain.getLocator(tip); + peer.sendGetBlocks(locator, stop); + } - peer.sendGetBlocks(locator, root); -}; + /** + * Queue a `getdata` request to be sent. + * @param {Peer} peer + * @param {Hash[]} hashes + */ -/** - * Send `getheaders` to peer after building locator. - * @method - * @param {Peer} peer - * @param {Hash} tip - Tip to build chain locator from. - * @param {Hash?} stop - * @returns {Promise} - */ + getBlock(peer, hashes) { + if (!this.opened) + return; -Pool.prototype.getHeaders = async function getHeaders(peer, tip, stop) { - const locator = await this.chain.getLocator(tip); - peer.sendGetHeaders(locator, stop); -}; + if (!peer.handshake) + throw new Error('Peer handshake not complete (getdata).'); -/** - * Send `getblocks` to peer after building locator. - * @method - * @param {Peer} peer - * @param {Hash} tip - Tip hash to build chain locator from. - * @param {Hash?} stop - * @returns {Promise} - */ + if (peer.destroyed) + throw new Error('Peer is destroyed (getdata).'); -Pool.prototype.getBlocks = async function getBlocks(peer, tip, stop) { - const locator = await this.chain.getLocator(tip); - peer.sendGetBlocks(locator, stop); -}; + let now = Date.now(); -/** - * Queue a `getdata` request to be sent. - * @param {Peer} peer - * @param {Hash[]} hashes - */ + const items = []; -Pool.prototype.getBlock = function getBlock(peer, hashes) { - if (!this.loaded) - return; + for (const hash of hashes) { + if (this.blockMap.has(hash)) + continue; - if (!peer.handshake) - throw new Error('Peer handshake not complete (getdata).'); + this.blockMap.add(hash); + peer.blockMap.set(hash, now); - if (peer.destroyed) - throw new Error('Peer is destroyed (getdata).'); + if (this.chain.synced) + now += 100; - let now = util.ms(); - const items = []; + items.push(hash); + } - for (const hash of hashes) { - if (this.blockMap.has(hash)) - continue; + if (items.length === 0) + return; - this.blockMap.add(hash); - peer.blockMap.set(hash, now); + if (peer.blockMap.size >= common.MAX_BLOCK_REQUEST) { + this.logger.warning( + 'Peer advertised too many blocks (%s).', + peer.hostname()); + peer.destroy(); + return; + } - if (this.chain.synced) - now += 100; + this.logger.debug( + 'Requesting %d/%d blocks from peer with getdata (%s).', + items.length, + this.blockMap.size, + peer.hostname()); - items.push(hash); + peer.getBlock(items); } - if (items.length === 0) - return; + /** + * Queue a `getdata` request to be sent. + * @param {Peer} peer + * @param {Hash[]} hashes + */ - this.logger.debug( - 'Requesting %d/%d blocks from peer with getdata (%s).', - items.length, - this.blockMap.size, - peer.hostname()); - - peer.getBlock(items); -}; - -/** - * Queue a `getdata` request to be sent. - * @param {Peer} peer - * @param {Hash[]} hashes - */ - -Pool.prototype.getTX = function getTX(peer, hashes) { - if (!this.loaded) - return; + getTX(peer, hashes) { + if (!this.opened) + return; - if (!peer.handshake) - throw new Error('Peer handshake not complete (getdata).'); + if (!peer.handshake) + throw new Error('Peer handshake not complete (getdata).'); - if (peer.destroyed) - throw new Error('Peer is destroyed (getdata).'); + if (peer.destroyed) + throw new Error('Peer is destroyed (getdata).'); - let now = util.ms(); + let now = Date.now(); - const items = []; + const items = []; - for (const hash of hashes) { - if (this.txMap.has(hash)) - continue; + for (const hash of hashes) { + if (this.txMap.has(hash)) + continue; - this.txMap.add(hash); - peer.txMap.set(hash, now); + this.txMap.add(hash); + peer.txMap.set(hash, now); - now += 50; + now += 50; - items.push(hash); - } + items.push(hash); + } - if (items.length === 0) - return; + if (items.length === 0) + return; - this.logger.debug( - 'Requesting %d/%d txs from peer with getdata (%s).', - items.length, - this.txMap.size, - peer.hostname()); + if (peer.txMap.size >= common.MAX_TX_REQUEST) { + this.logger.warning( + 'Peer advertised too many txs (%s).', + peer.hostname()); + peer.destroy(); + return; + } - peer.getTX(items); -}; + this.logger.debug( + 'Requesting %d/%d txs from peer with getdata (%s).', + items.length, + this.txMap.size, + peer.hostname()); -/** - * Test whether the chain has or has seen an item. - * @method - * @param {Hash} hash - * @returns {Promise} - Returns Boolean. - */ + peer.getTX(items); + } -Pool.prototype.hasBlock = async function hasBlock(hash) { - // Check the lock. - if (this.locker.has(hash)) - return true; + /** + * Test whether the chain has or has seen an item. + * @method + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ - // Check the chain. - if (await this.chain.has(hash)) - return true; + async hasBlock(hash) { + // Check the lock. + if (this.locker.has(hash)) + return true; - return false; -}; + // Check the chain. + if (await this.chain.has(hash)) + return true; -/** - * Test whether the mempool has or has seen an item. - * @param {Hash} hash - * @returns {Boolean} - */ + return false; + } -Pool.prototype.hasTX = function hasTX(hash) { - // Check the lock queue. - if (this.locker.has(hash)) - return true; + /** + * Test whether the mempool has or has seen an item. + * @param {Hash} hash + * @returns {Boolean} + */ - if (!this.mempool) { - // Check the TX filter if - // we don't have a mempool. - if (!this.txFilter.added(hash, 'hex')) - return true; - } else { - // Check the mempool. - if (this.mempool.has(hash)) + hasTX(hash) { + // Check the lock queue. + if (this.locker.has(hash)) return true; - // If we recently rejected this item. Ignore. - if (this.mempool.hasReject(hash)) { - this.logger.spam('Saw known reject of %s.', util.revHex(hash)); - return true; + if (!this.mempool) { + // Check the TX filter if + // we don't have a mempool. + if (!this.txFilter.added(hash)) + return true; + } else { + // Check the mempool. + if (this.mempool.has(hash)) + return true; + + // If we recently rejected this item. Ignore. + if (this.mempool.hasReject(hash)) { + this.logger.spam('Saw known reject of %h.', hash); + return true; + } } + + return false; } - return false; -}; + /** + * Queue a `getdata` request to be sent. + * Check tx existence before requesting. + * @param {Peer} peer + * @param {Hash[]} hashes + */ -/** - * Queue a `getdata` request to be sent. - * Check tx existence before requesting. - * @param {Peer} peer - * @param {Hash[]} hashes - */ + ensureTX(peer, hashes) { + const items = []; -Pool.prototype.ensureTX = function ensureTX(peer, hashes) { - const items = []; + for (const hash of hashes) { + if (this.hasTX(hash)) + continue; - for (const hash of hashes) { - if (this.hasTX(hash)) - continue; + items.push(hash); + } - items.push(hash); + this.getTX(peer, items); } - this.getTX(peer, items); -}; + /** + * Fulfill a requested tx. + * @param {Peer} peer + * @param {Hash} hash + * @returns {Boolean} + */ -/** - * Fulfill a requested tx. - * @param {Peer} peer - * @param {Hash} hash - * @returns {Boolean} - */ + resolveTX(peer, hash) { + if (!peer.txMap.has(hash)) + return false; -Pool.prototype.resolveTX = function resolveTX(peer, hash) { - if (!peer.txMap.has(hash)) - return false; + peer.txMap.delete(hash); + + assert(this.txMap.has(hash)); + this.txMap.delete(hash); + + return true; + } - peer.txMap.delete(hash); + /** + * Fulfill a requested block. + * @param {Peer} peer + * @param {Hash} hash + * @returns {Boolean} + */ - assert(this.txMap.has(hash)); - this.txMap.delete(hash); + resolveBlock(peer, hash) { + if (!peer.blockMap.has(hash)) + return false; - return true; -}; + peer.blockMap.delete(hash); -/** - * Fulfill a requested block. - * @param {Peer} peer - * @param {Hash} hash - * @returns {Boolean} - */ + assert(this.blockMap.has(hash)); + this.blockMap.delete(hash); -Pool.prototype.resolveBlock = function resolveBlock(peer, hash) { - if (!peer.blockMap.has(hash)) - return false; + return true; + } - peer.blockMap.delete(hash); + /** + * Fulfill a requested item. + * @param {Peer} peer + * @param {InvItem} item + * @returns {Boolean} + */ - assert(this.blockMap.has(hash)); - this.blockMap.delete(hash); + resolveItem(peer, item) { + if (item.isBlock()) + return this.resolveBlock(peer, item.hash); - return true; -}; + if (item.isTX()) + return this.resolveTX(peer, item.hash); -/** - * Fulfill a requested item. - * @param {Peer} peer - * @param {InvItem} item - * @returns {Boolean} - */ + return false; + } -Pool.prototype.resolveItem = function resolveItem(peer, item) { - if (item.isBlock()) - return this.resolveBlock(peer, item.hash); + /** + * Broadcast a transaction or block. + * @param {TX|Block} msg + * @returns {Promise} + */ - if (item.isTX()) - return this.resolveTX(peer, item.hash); + broadcast(msg) { + const hash = msg.hash(); - return false; -}; + let item = this.invMap.get(hash); -/** - * Broadcast a transaction or block. - * @param {TX|Block} msg - * @returns {Promise} - */ + if (item) { + item.refresh(); + item.announce(); + } else { + item = new BroadcastItem(this, msg); + item.start(); + item.announce(); + } -Pool.prototype.broadcast = function broadcast(msg) { - const hash = msg.hash('hex'); + return new Promise((resolve, reject) => { + item.addJob(resolve, reject); + }); + } - let item = this.invMap.get(hash); + /** + * Announce a block to all peers. + * @param {Block} tx + */ - if (item) { - item.refresh(); - item.announce(); - } else { - item = new BroadcastItem(this, msg); - item.start(); - item.announce(); + announceBlock(msg) { + for (let peer = this.peers.head(); peer; peer = peer.next) + peer.announceBlock(msg); } - return new Promise((resolve, reject) => { - item.addJob(resolve, reject); - }); -}; - -/** - * Announce a block to all peers. - * @param {Block} tx - */ + /** + * Announce a transaction to all peers. + * @param {TX} tx + */ -Pool.prototype.announceBlock = function announceBlock(msg) { - for (let peer = this.peers.head(); peer; peer = peer.next) - peer.announceBlock(msg); -}; + announceTX(msg) { + for (let peer = this.peers.head(); peer; peer = peer.next) + peer.announceTX(msg); + } +} /** - * Announce a transaction to all peers. - * @param {TX} tx + * Discovery interval for UPNP and DNS seeds. + * @const {Number} + * @default */ -Pool.prototype.announceTX = function announceTX(msg) { - for (let peer = this.peers.head(); peer; peer = peer.next) - peer.announceTX(msg); -}; +Pool.DISCOVERY_INTERVAL = 120000; /** - * PoolOptions + * Pool Options * @alias module:net.PoolOptions - * @constructor */ -function PoolOptions(options) { - if (!(this instanceof PoolOptions)) - return new PoolOptions(options); - - this.network = Network.primary; - this.logger = null; - this.chain = null; - this.mempool = null; - - this.nonces = new NonceList(); - - this.prefix = null; - this.checkpoints = true; - this.spv = false; - this.bip37 = false; - this.listen = false; - this.compact = true; - this.noRelay = false; - this.host = '0.0.0.0'; - this.port = this.network.port; - this.publicHost = '0.0.0.0'; - this.publicPort = this.network.port; - this.maxOutbound = 8; - this.maxInbound = 8; - this.createSocket = this._createSocket.bind(this); - this.createServer = tcp.createServer; - this.resolve = this._resolve.bind(this); - this.proxy = null; - this.onion = false; - this.upnp = false; - this.selfish = false; - this.version = common.PROTOCOL_VERSION; - this.agent = common.USER_AGENT; - this.bip151 = false; - this.bip150 = false; - this.authPeers = []; - this.knownPeers = {}; - this.identityKey = secp256k1.generatePrivateKey(); - this.banScore = common.BAN_SCORE; - this.banTime = common.BAN_TIME; - this.feeRate = -1; - this.seeds = this.network.seeds; - this.nodes = []; - this.invTimeout = 60000; - this.blockMode = 0; - this.services = common.LOCAL_SERVICES; - this.requiredServices = common.REQUIRED_SERVICES; - this.persistent = false; - - this.fromOptions(options); -} - -/** - * Inject properties from object. - * @private - * @param {Object} options - * @returns {PoolOptions} - */ +class PoolOptions { + /** + * Create pool options. + * @constructor + */ -PoolOptions.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Pool requires options.'); - assert(options.chain && typeof options.chain === 'object', - 'Pool options require a blockchain.'); + constructor(options) { + this.network = Network.primary; + this.logger = null; + this.chain = null; + this.mempool = null; - this.chain = options.chain; - this.network = options.chain.network; - this.logger = options.chain.logger; + this.nonces = new NonceList(); - this.port = this.network.port; - this.seeds = this.network.seeds; - this.port = this.network.port; - this.publicPort = this.network.port; + this.prefix = null; + this.checkpoints = true; + this.spv = false; + this.bip37 = false; + this.listen = false; + this.compact = true; + this.noRelay = false; + this.host = '0.0.0.0'; + this.port = this.network.port; + this.publicHost = '0.0.0.0'; + this.publicPort = this.network.port; + this.maxOutbound = 8; + this.maxInbound = 8; + this.createSocket = this._createSocket.bind(this); + this.createServer = tcp.createServer; + this.resolve = this._resolve.bind(this); + this.proxy = null; + this.onion = false; + this.upnp = false; + this.selfish = false; + this.version = common.PROTOCOL_VERSION; + this.agent = common.USER_AGENT; + this.banScore = common.BAN_SCORE; + this.banTime = common.BAN_TIME; + this.feeRate = -1; + this.seeds = this.network.seeds; + this.nodes = []; + this.invTimeout = 60000; + this.blockMode = 0; + this.services = common.LOCAL_SERVICES; + this.requiredServices = common.REQUIRED_SERVICES; + this.memory = true; + this.discover = true; + + this.fromOptions(options); + } + + /** + * Inject properties from object. + * @private + * @param {Object} options + * @returns {PoolOptions} + */ + + fromOptions(options) { + assert(options, 'Pool requires options.'); + assert(options.chain && typeof options.chain === 'object', + 'Pool options require a blockchain.'); + + this.chain = options.chain; + this.network = options.chain.network; + this.logger = options.chain.logger; + + this.port = this.network.port; + this.seeds = this.network.seeds; + this.port = this.network.port; + this.publicPort = this.network.port; + + if (options.logger != null) { + assert(typeof options.logger === 'object'); + this.logger = options.logger; + } - if (options.logger != null) { - assert(typeof options.logger === 'object'); - this.logger = options.logger; - } + if (options.mempool != null) { + assert(typeof options.mempool === 'object'); + this.mempool = options.mempool; + } - if (options.mempool != null) { - assert(typeof options.mempool === 'object'); - this.mempool = options.mempool; - } + if (options.prefix != null) { + assert(typeof options.prefix === 'string'); + this.prefix = options.prefix; + } - if (options.prefix != null) { - assert(typeof options.prefix === 'string'); - this.prefix = options.prefix; - } + if (options.checkpoints != null) { + assert(typeof options.checkpoints === 'boolean'); + assert(options.checkpoints === this.chain.options.checkpoints); + this.checkpoints = options.checkpoints; + } else { + this.checkpoints = this.chain.options.checkpoints; + } - if (options.checkpoints != null) { - assert(typeof options.checkpoints === 'boolean'); - assert(options.checkpoints === this.chain.options.checkpoints); - this.checkpoints = options.checkpoints; - } else { - this.checkpoints = this.chain.options.checkpoints; - } + if (options.spv != null) { + assert(typeof options.spv === 'boolean'); + assert(options.spv === this.chain.options.spv); + this.spv = options.spv; + } else { + this.spv = this.chain.options.spv; + } - if (options.spv != null) { - assert(typeof options.spv === 'boolean'); - assert(options.spv === this.chain.options.spv); - this.spv = options.spv; - } else { - this.spv = this.chain.options.spv; - } + if (options.bip37 != null) { + assert(typeof options.bip37 === 'boolean'); + this.bip37 = options.bip37; + } - if (options.bip37 != null) { - assert(typeof options.bip37 === 'boolean'); - this.bip37 = options.bip37; - } + if (options.listen != null) { + assert(typeof options.listen === 'boolean'); + this.listen = options.listen; + } - if (options.listen != null) { - assert(typeof options.listen === 'boolean'); - this.listen = options.listen; - } + if (options.compact != null) { + assert(typeof options.compact === 'boolean'); + this.compact = options.compact; + } - if (options.compact != null) { - assert(typeof options.compact === 'boolean'); - this.compact = options.compact; - } + if (options.noRelay != null) { + assert(typeof options.noRelay === 'boolean'); + this.noRelay = options.noRelay; + } - if (options.noRelay != null) { - assert(typeof options.noRelay === 'boolean'); - this.noRelay = options.noRelay; - } + if (options.host != null) { + assert(typeof options.host === 'string'); + const raw = IP.toBuffer(options.host); + this.host = IP.toString(raw); + if (IP.isRoutable(raw)) + this.publicHost = this.host; + } - if (options.host != null) { - assert(typeof options.host === 'string'); - const raw = IP.toBuffer(options.host); - this.host = IP.toString(raw); - if (IP.isRoutable(raw)) - this.publicHost = this.host; - } + if (options.port != null) { + assert((options.port & 0xffff) === options.port); + this.port = options.port; + this.publicPort = options.port; + } - if (options.port != null) { - assert(util.isU16(options.port)); - this.port = options.port; - this.publicPort = options.port; - } + if (options.publicHost != null) { + assert(typeof options.publicHost === 'string'); + this.publicHost = IP.normalize(options.publicHost); + } - if (options.publicHost != null) { - assert(typeof options.publicHost === 'string'); - this.publicHost = IP.normalize(options.publicHost); - } + if (options.publicPort != null) { + assert((options.publicPort & 0xffff) === options.publicPort); + this.publicPort = options.publicPort; + } - if (options.publicPort != null) { - assert(util.isU16(options.publicPort)); - this.publicPort = options.publicPort; - } + if (options.maxOutbound != null) { + assert(typeof options.maxOutbound === 'number'); + assert(options.maxOutbound > 0); + this.maxOutbound = options.maxOutbound; + } - if (options.maxOutbound != null) { - assert(typeof options.maxOutbound === 'number'); - assert(options.maxOutbound > 0); - this.maxOutbound = options.maxOutbound; - } + if (options.maxInbound != null) { + assert(typeof options.maxInbound === 'number'); + this.maxInbound = options.maxInbound; + } - if (options.maxInbound != null) { - assert(typeof options.maxInbound === 'number'); - this.maxInbound = options.maxInbound; - } + if (options.createSocket) { + assert(typeof options.createSocket === 'function'); + this.createSocket = options.createSocket; + } - if (options.createSocket) { - assert(typeof options.createSocket === 'function'); - this.createSocket = options.createSocket; - } + if (options.createServer) { + assert(typeof options.createServer === 'function'); + this.createServer = options.createServer; + } - if (options.createServer) { - assert(typeof options.createServer === 'function'); - this.createServer = options.createServer; - } + if (options.resolve) { + assert(typeof options.resolve === 'function'); + this.resolve = options.resolve; + } - if (options.resolve) { - assert(typeof options.resolve === 'function'); - this.resolve = options.resolve; - } + if (options.proxy) { + assert(typeof options.proxy === 'string'); + this.proxy = options.proxy; + } - if (options.proxy) { - assert(typeof options.proxy === 'string'); - this.proxy = options.proxy; - } + if (options.onion != null) { + assert(typeof options.onion === 'boolean'); + this.onion = options.onion; + } - if (options.onion != null) { - assert(typeof options.onion === 'boolean'); - this.onion = options.onion; - } + if (options.upnp != null) { + assert(typeof options.upnp === 'boolean'); + this.upnp = options.upnp; + } - if (options.upnp != null) { - assert(typeof options.upnp === 'boolean'); - this.upnp = options.upnp; - } + if (options.selfish) { + assert(typeof options.selfish === 'boolean'); + this.selfish = options.selfish; + } - if (options.selfish) { - assert(typeof options.selfish === 'boolean'); - this.selfish = options.selfish; - } + if (options.version) { + assert(typeof options.version === 'number'); + this.version = options.version; + } - if (options.version) { - assert(typeof options.version === 'number'); - this.version = options.version; - } + if (options.agent) { + assert(typeof options.agent === 'string'); + assert(options.agent.length <= 255); + this.agent = options.agent; + } - if (options.agent) { - assert(typeof options.agent === 'string'); - assert(options.agent.length <= 255); - this.agent = options.agent; - } + if (options.banScore != null) { + assert(typeof this.options.banScore === 'number'); + this.banScore = this.options.banScore; + } - if (options.bip151 != null) { - assert(typeof options.bip151 === 'boolean'); - this.bip151 = options.bip151; - } + if (options.banTime != null) { + assert(typeof this.options.banTime === 'number'); + this.banTime = this.options.banTime; + } - if (options.bip150 != null) { - assert(typeof options.bip150 === 'boolean'); - assert(!options.bip150 || this.bip151, - 'Cannot enable bip150 without bip151.'); + if (options.feeRate != null) { + assert(typeof this.options.feeRate === 'number'); + this.feeRate = this.options.feeRate; + } - if (options.knownPeers) { - assert(typeof options.knownPeers === 'object'); - assert(!Array.isArray(options.knownPeers)); - this.knownPeers = options.knownPeers; + if (options.seeds) { + assert(Array.isArray(options.seeds)); + this.seeds = options.seeds; } - if (options.authPeers) { - assert(Array.isArray(options.authPeers)); - this.authPeers = options.authPeers; + if (options.nodes) { + assert(Array.isArray(options.nodes)); + this.nodes = options.nodes; } - if (options.identityKey) { - assert(Buffer.isBuffer(options.identityKey), - 'Identity key must be a buffer.'); - assert(secp256k1.privateKeyVerify(options.identityKey), - 'Invalid identity key.'); - this.identityKey = options.identityKey; + if (options.only != null) { + assert(Array.isArray(options.only)); + if (options.only.length > 0) { + this.nodes = options.only; + this.maxOutbound = options.only.length; + this.discover = false; + } } - } - if (options.banScore != null) { - assert(typeof this.options.banScore === 'number'); - this.banScore = this.options.banScore; - } + if (options.discover != null) { + assert(typeof options.discover === 'boolean'); + this.discover = options.discover; + } - if (options.banTime != null) { - assert(typeof this.options.banTime === 'number'); - this.banTime = this.options.banTime; - } + if (options.invTimeout != null) { + assert(typeof options.invTimeout === 'number'); + this.invTimeout = options.invTimeout; + } - if (options.feeRate != null) { - assert(typeof this.options.feeRate === 'number'); - this.feeRate = this.options.feeRate; - } + if (options.blockMode != null) { + assert(typeof options.blockMode === 'number'); + this.blockMode = options.blockMode; + } - if (options.seeds) { - assert(Array.isArray(options.seeds)); - this.seeds = options.seeds; - } + if (options.memory != null) { + assert(typeof options.memory === 'boolean'); + this.memory = options.memory; + } - if (options.nodes) { - assert(Array.isArray(options.nodes)); - this.nodes = options.nodes; - } + if (this.spv) { + this.requiredServices |= common.services.BLOOM; + this.services &= ~common.services.NETWORK; + this.noRelay = true; + this.checkpoints = true; + this.compact = false; + this.bip37 = false; + this.listen = false; + } - if (options.only != null) { - assert(Array.isArray(options.only)); - if (options.only.length > 0) { - this.nodes = options.only; - this.maxOutbound = options.only.length; + if (this.selfish) { + this.services &= ~common.services.NETWORK; + this.bip37 = false; } - } - if (options.invTimeout != null) { - assert(typeof options.invTimeout === 'number'); - this.invTimeout = options.invTimeout; - } + if (this.bip37) + this.services |= common.services.BLOOM; - if (options.blockMode != null) { - assert(typeof options.blockMode === 'number'); - this.blockMode = options.blockMode; - } + if (this.proxy) + this.listen = false; - if (options.persistent != null) { - assert(typeof options.persistent === 'boolean'); - this.persistent = options.persistent; - } + if (options.services != null) { + assert((options.services >>> 0) === options.services); + this.services = options.services; + } - if (this.spv) { - this.requiredServices |= common.services.BLOOM; - this.services &= ~common.services.NETWORK; - this.noRelay = true; - this.checkpoints = true; - this.compact = false; - this.bip37 = false; - this.listen = false; - } + if (options.requiredServices != null) { + assert((options.requiredServices >>> 0) === options.requiredServices); + this.requiredServices = options.requiredServices; + } - if (this.selfish) { - this.services &= ~common.services.NETWORK; - this.bip37 = false; + return this; } - if (this.bip37) - this.services |= common.services.BLOOM; - - if (this.proxy) - this.listen = false; - - if (options.services != null) { - assert(util.isU32(options.services)); - this.services = options.services; - } + /** + * Instantiate options from object. + * @param {Object} options + * @returns {PoolOptions} + */ - if (options.requiredServices != null) { - assert(util.isU32(options.requiredServices)); - this.requiredServices = options.requiredServices; + static fromOptions(options) { + return new PoolOptions().fromOptions(options); } - return this; -}; - -/** - * Instantiate options from object. - * @param {Object} options - * @returns {PoolOptions} - */ + /** + * Get the chain height. + * @private + * @returns {Number} + */ -PoolOptions.fromOptions = function fromOptions(options) { - return new PoolOptions().fromOptions(options); -}; - -/** - * Get the chain height. - * @private - * @returns {Number} - */ + getHeight() { + return this.chain.height; + } -PoolOptions.prototype.getHeight = function getHeight() { - return this.chain.height; -}; + /** + * Test whether the chain is synced. + * @private + * @returns {Boolean} + */ -/** - * Test whether the chain is synced. - * @private - * @returns {Boolean} - */ + isFull() { + return this.chain.synced; + } -PoolOptions.prototype.isFull = function isFull() { - return this.chain.synced; -}; + /** + * Get required services for outbound peers. + * @private + * @returns {Number} + */ -/** - * Get required services for outbound peers. - * @private - * @returns {Number} - */ + getRequiredServices() { + let services = this.requiredServices; + if (this.hasWitness()) + services |= common.services.WITNESS; + return services; + } -PoolOptions.prototype.getRequiredServices = function getRequiredServices() { - let services = this.requiredServices; - if (this.hasWitness()) - services |= common.services.WITNESS; - return services; -}; + /** + * Whether segwit is enabled. + * @private + * @returns {Boolean} + */ -/** - * Whether segwit is enabled. - * @private - * @returns {Boolean} - */ + hasWitness() { + return this.chain.state.hasWitness(); + } -PoolOptions.prototype.hasWitness = function hasWitness() { - return this.chain.state.hasWitness(); -}; + /** + * Create a version packet nonce. + * @private + * @param {String} hostname + * @returns {Buffer} + */ -/** - * Create a version packet nonce. - * @private - * @param {String} hostname - * @returns {Buffer} - */ + createNonce(hostname) { + return this.nonces.alloc(hostname); + } -PoolOptions.prototype.createNonce = function createNonce(hostname) { - return this.nonces.alloc(hostname); -}; + /** + * Test whether version nonce is ours. + * @private + * @param {Buffer} nonce + * @returns {Boolean} + */ -/** - * Test whether version nonce is ours. - * @private - * @param {Buffer} nonce - * @returns {Boolean} - */ + hasNonce(nonce) { + return this.nonces.has(nonce); + } -PoolOptions.prototype.hasNonce = function hasNonce(nonce) { - return this.nonces.has(nonce); -}; + /** + * Get fee rate for txid. + * @private + * @param {Hash} hash + * @returns {Rate} + */ -/** - * Get fee rate for txid. - * @private - * @param {Hash} hash - * @returns {Rate} - */ + getRate(hash) { + if (!this.mempool) + return -1; -PoolOptions.prototype.getRate = function getRate(hash) { - if (!this.mempool) - return -1; + const entry = this.mempool.getEntry(hash); - const entry = this.mempool.getEntry(hash); + if (!entry) + return -1; - if (!entry) - return -1; + return entry.getRate(); + } - return entry.getRate(); -}; + /** + * Default createSocket call. + * @private + * @param {Number} port + * @param {String} host + * @returns {net.Socket} + */ -/** - * Default createSocket call. - * @private - * @param {Number} port - * @param {String} host - * @returns {net.Socket} - */ + _createSocket(port, host) { + if (this.proxy) + return socks.connect(this.proxy, port, host); -PoolOptions.prototype._createSocket = function _createSocket(port, host) { - return tcp.createSocket(port, host, this.proxy); -}; + return tcp.createSocket(port, host); + } -/** - * Default resolve call. - * @private - * @param {String} name - * @returns {String[]} - */ + /** + * Default resolve call. + * @private + * @param {String} name + * @returns {String[]} + */ -PoolOptions.prototype._resolve = function _resolve(name) { - if (this.onion) - return dns.lookup(name, this.proxy); + _resolve(name) { + if (this.onion) + return socks.resolve(this.proxy, name); - return dns.lookup(name); -}; + return dns.lookup(name); + } +} /** * Peer List * @alias module:net.PeerList - * @constructor - * @param {Object} options */ -function PeerList() { - this.map = new Map(); - this.ids = new Map(); - this.list = new List(); - this.load = null; - this.inbound = 0; - this.outbound = 0; -} +class PeerList { + /** + * Create peer list. + * @constructor + * @param {Object} options + */ -/** - * Get the list head. - * @returns {Peer} - */ + constructor() { + this.map = new Map(); + this.ids = new Map(); + this.list = new List(); + this.load = null; + this.inbound = 0; + this.outbound = 0; + } -PeerList.prototype.head = function head() { - return this.list.head; -}; + /** + * Get the list head. + * @returns {Peer} + */ -/** - * Get the list tail. - * @returns {Peer} - */ + head() { + return this.list.head; + } -PeerList.prototype.tail = function tail() { - return this.list.tail; -}; + /** + * Get the list tail. + * @returns {Peer} + */ -/** - * Get list size. - * @returns {Number} - */ + tail() { + return this.list.tail; + } -PeerList.prototype.size = function size() { - return this.list.size; -}; + /** + * Get list size. + * @returns {Number} + */ -/** - * Add peer to list. - * @param {Peer} peer - */ + size() { + return this.list.size; + } -PeerList.prototype.add = function add(peer) { - assert(this.list.push(peer)); + /** + * Add peer to list. + * @param {Peer} peer + */ - assert(!this.map.has(peer.hostname())); - this.map.set(peer.hostname(), peer); + add(peer) { + assert(this.list.push(peer)); - assert(!this.ids.has(peer.id)); - this.ids.set(peer.id, peer); + assert(!this.map.has(peer.hostname())); + this.map.set(peer.hostname(), peer); - if (peer.outbound) - this.outbound++; - else - this.inbound++; -}; + assert(!this.ids.has(peer.id)); + this.ids.set(peer.id, peer); -/** - * Remove peer from list. - * @param {Peer} peer - */ + if (peer.outbound) + this.outbound += 1; + else + this.inbound += 1; + } -PeerList.prototype.remove = function remove(peer) { - assert(this.list.remove(peer)); + /** + * Remove peer from list. + * @param {Peer} peer + */ - assert(this.ids.has(peer.id)); - this.ids.delete(peer.id); + remove(peer) { + assert(this.list.remove(peer)); - assert(this.map.has(peer.hostname())); - this.map.delete(peer.hostname()); + assert(this.ids.has(peer.id)); + this.ids.delete(peer.id); - if (peer === this.load) { - assert(peer.loader); - peer.loader = false; - this.load = null; - } + assert(this.map.has(peer.hostname())); + this.map.delete(peer.hostname()); - if (peer.outbound) - this.outbound--; - else - this.inbound--; -}; + if (peer === this.load) { + assert(peer.loader); + peer.loader = false; + this.load = null; + } -/** - * Get peer by hostname. - * @param {String} hostname - * @returns {Peer} - */ + if (peer.outbound) + this.outbound -= 1; + else + this.inbound -= 1; + } -PeerList.prototype.get = function get(hostname) { - return this.map.get(hostname); -}; + /** + * Get peer by hostname. + * @param {String} hostname + * @returns {Peer} + */ -/** - * Test whether a peer exists. - * @param {String} hostname - * @returns {Boolean} - */ + get(hostname) { + return this.map.get(hostname); + } -PeerList.prototype.has = function has(hostname) { - return this.map.has(hostname); -}; + /** + * Test whether a peer exists. + * @param {String} hostname + * @returns {Boolean} + */ -/** - * Get peer by ID. - * @param {Number} id - * @returns {Peer} - */ + has(hostname) { + return this.map.has(hostname); + } -PeerList.prototype.find = function find(id) { - return this.ids.get(id); -}; + /** + * Get peer by ID. + * @param {Number} id + * @returns {Peer} + */ -/** - * Destroy peer list (kills peers). - */ + find(id) { + return this.ids.get(id); + } -PeerList.prototype.destroy = function destroy() { - let next; + /** + * Destroy peer list (kills peers). + */ - for (let peer = this.list.head; peer; peer = next) { - next = peer.next; - peer.destroy(); + destroy() { + let next; + + for (let peer = this.list.head; peer; peer = next) { + next = peer.next; + peer.destroy(); + } } -}; +} /** + * Broadcast Item * Represents an item that is broadcasted via an inv/getdata cycle. * @alias module:net.BroadcastItem - * @constructor + * @extends EventEmitter * @private - * @param {Pool} pool - * @param {TX|Block} msg * @emits BroadcastItem#ack * @emits BroadcastItem#reject * @emits BroadcastItem#timeout */ -function BroadcastItem(pool, msg) { - if (!(this instanceof BroadcastItem)) - return new BroadcastItem(pool, msg); +class BroadcastItem extends EventEmitter { + /** + * Create broadcast item. + * @constructor + * @param {Pool} pool + * @param {TX|Block} msg + */ - assert(!msg.mutable, 'Cannot broadcast mutable item.'); + constructor(pool, msg) { + super(); - const item = msg.toInv(); + assert(!msg.mutable, 'Cannot broadcast mutable item.'); - this.pool = pool; - this.hash = item.hash; - this.type = item.type; - this.msg = msg; - this.jobs = []; -} + const item = msg.toInv(); + + this.pool = pool; + this.hash = item.hash; + this.type = item.type; + this.msg = msg; + this.jobs = []; + } -Object.setPrototypeOf(BroadcastItem.prototype, EventEmitter.prototype); + /** + * Add a job to be executed on ack, timeout, or reject. + */ -/** - * Add a job to be executed on ack, timeout, or reject. - * @returns {Promise} - */ + addJob(resolve, reject) { + this.jobs.push({ resolve, reject }); + } -BroadcastItem.prototype.addJob = function addJob(resolve, reject) { - this.jobs.push(co.job(resolve, reject)); -}; + /** + * Start the broadcast. + */ -/** - * Start the broadcast. - */ + start() { + assert(!this.timeout, 'Already started.'); + assert(!this.pool.invMap.has(this.hash), 'Already started.'); -BroadcastItem.prototype.start = function start() { - assert(!this.timeout, 'Already started.'); - assert(!this.pool.invMap.has(this.hash), 'Already started.'); + this.pool.invMap.set(this.hash, this); - this.pool.invMap.set(this.hash, this); + this.refresh(); - this.refresh(); + return this; + } - return this; -}; + /** + * Refresh the timeout on the broadcast. + */ -/** - * Refresh the timeout on the broadcast. - */ + refresh() { + if (this.timeout != null) { + clearTimeout(this.timeout); + this.timeout = null; + } -BroadcastItem.prototype.refresh = function refresh() { - if (this.timeout != null) { - clearTimeout(this.timeout); - this.timeout = null; + this.timeout = setTimeout(() => { + this.emit('timeout'); + this.reject(new Error('Timed out.')); + }, this.pool.options.invTimeout); } - this.timeout = setTimeout(() => { - this.emit('timeout'); - this.reject(new Error('Timed out.')); - }, this.pool.options.invTimeout); -}; + /** + * Announce the item. + */ -/** - * Announce the item. - */ - -BroadcastItem.prototype.announce = function announce() { - switch (this.type) { - case invTypes.TX: - this.pool.announceTX(this.msg); - break; - case invTypes.BLOCK: - this.pool.announceBlock(this.msg); - break; - default: - assert(false, 'Bad type.'); - break; + announce() { + switch (this.type) { + case invTypes.TX: + this.pool.announceTX(this.msg); + break; + case invTypes.BLOCK: + this.pool.announceBlock(this.msg); + break; + default: + assert(false, 'Bad type.'); + break; + } } -}; -/** - * Finish the broadcast. - */ + /** + * Finish the broadcast. + */ -BroadcastItem.prototype.cleanup = function cleanup() { - assert(this.timeout != null, 'Already finished.'); - assert(this.pool.invMap.has(this.hash), 'Already finished.'); + cleanup() { + assert(this.timeout != null, 'Already finished.'); + assert(this.pool.invMap.has(this.hash), 'Already finished.'); - clearTimeout(this.timeout); - this.timeout = null; + clearTimeout(this.timeout); + this.timeout = null; - this.pool.invMap.delete(this.hash); -}; + this.pool.invMap.delete(this.hash); + } -/** - * Finish the broadcast, return with an error. - * @param {Error} err - */ + /** + * Finish the broadcast, return with an error. + * @param {Error} err + */ -BroadcastItem.prototype.reject = function reject(err) { - this.cleanup(); + reject(err) { + this.cleanup(); - for (const job of this.jobs) - job.reject(err); + for (const job of this.jobs) + job.reject(err); - this.jobs.length = 0; -}; + this.jobs.length = 0; + } -/** - * Finish the broadcast successfully. - */ + /** + * Finish the broadcast successfully. + */ -BroadcastItem.prototype.resolve = function resolve() { - this.cleanup(); + resolve() { + this.cleanup(); - for (const job of this.jobs) - job.resolve(false); + for (const job of this.jobs) + job.resolve(false); - this.jobs.length = 0; -}; + this.jobs.length = 0; + } -/** - * Handle an ack from a peer. - * @param {Peer} peer - */ + /** + * Handle an ack from a peer. + * @param {Peer} peer + */ -BroadcastItem.prototype.handleAck = function handleAck(peer) { - setTimeout(() => { - this.emit('ack', peer); + handleAck(peer) { + setTimeout(() => { + this.emit('ack', peer); - for (const job of this.jobs) - job.resolve(true); + for (const job of this.jobs) + job.resolve(true); - this.jobs.length = 0; - }, 1000); -}; + this.jobs.length = 0; + }, 1000); + } -/** - * Handle a reject from a peer. - * @param {Peer} peer - */ + /** + * Handle a reject from a peer. + * @param {Peer} peer + */ -BroadcastItem.prototype.handleReject = function handleReject(peer) { - this.emit('reject', peer); + handleReject(peer) { + this.emit('reject', peer); - for (const job of this.jobs) - job.resolve(false); + for (const job of this.jobs) + job.resolve(false); - this.jobs.length = 0; -}; + this.jobs.length = 0; + } -/** - * Inspect the broadcast item. - * @returns {String} - */ + /** + * Inspect the broadcast item. + * @returns {String} + */ -BroadcastItem.prototype.inspect = function inspect() { - const type = this.type === invTypes.TX ? 'tx' : 'block'; - const hash = util.revHex(this.hash); - return ``; -}; + [inspectSymbol]() { + const type = this.type === invTypes.TX ? 'tx' : 'block'; + const hash = util.revHex(this.hash); + return ``; + } +} /** - * NonceList - * @constructor + * Nonce List * @ignore */ -function NonceList() { - this.map = new Map(); - this.hosts = new Map(); -} +class NonceList { + /** + * Create nonce list. + * @constructor + */ -NonceList.prototype.alloc = function alloc(hostname) { - for (;;) { - const nonce = util.nonce(); - const key = nonce.toString('hex'); + constructor() { + this.map = new BufferMap(); + this.hosts = new Map(); + } + + alloc(hostname) { + for (;;) { + const nonce = common.nonce(); - if (this.map.has(key)) - continue; + if (this.map.has(nonce)) + continue; - this.map.set(key, hostname); + this.map.set(nonce, hostname); - assert(!this.hosts.has(hostname)); - this.hosts.set(hostname, key); + assert(!this.hosts.has(hostname)); + this.hosts.set(hostname, nonce); - return nonce; + return nonce; + } } -}; -NonceList.prototype.has = function has(nonce) { - const key = nonce.toString('hex'); - return this.map.has(key); -}; + has(nonce) { + return this.map.has(nonce); + } -NonceList.prototype.remove = function remove(hostname) { - const key = this.hosts.get(hostname); + remove(hostname) { + const key = this.hosts.get(hostname); - if (!key) - return false; + if (!key) + return false; - this.hosts.delete(hostname); + this.hosts.delete(hostname); - assert(this.map.has(key)); - this.map.delete(key); + assert(this.map.has(key)); + this.map.delete(key); - return true; -}; + return true; + } +} /** - * HeaderEntry - * @constructor + * Header Entry * @ignore */ -function HeaderEntry(hash, height) { - this.hash = hash; - this.height = height; - this.prev = null; - this.next = null; +class HeaderEntry { + /** + * Create header entry. + * @constructor + */ + + constructor(hash, height) { + this.hash = hash; + this.height = height; + this.prev = null; + this.next = null; + } } /* diff --git a/lib/net/proxysocket.js b/lib/net/proxysocket.js deleted file mode 100644 index 80f91641f..000000000 --- a/lib/net/proxysocket.js +++ /dev/null @@ -1,213 +0,0 @@ -/*! - * proxysocket.js - wsproxy socket for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const EventEmitter = require('events'); -const IOClient = require('socket.io-client'); -const util = require('../utils/util'); -const digest = require('../crypto/digest'); -const BufferWriter = require('../utils/writer'); - -function ProxySocket(uri) { - if (!(this instanceof ProxySocket)) - return new ProxySocket(uri); - - EventEmitter.call(this); - - this.info = null; - - this.socket = new IOClient(uri, { reconnection: false }); - 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._init(); -} - -Object.setPrototypeOf(ProxySocket.prototype, EventEmitter.prototype); - -ProxySocket.prototype._init = function _init() { - this.socket.on('info', (info) => { - if (this.closed) - return; - - this.info = info; - - if (info.pow) { - this.snonce = Buffer.from(info.snonce, 'hex'); - this.target = Buffer.from(info.target, 'hex'); - } - - this.emit('info', info); - }); - - this.socket.on('error', (err) => { - console.error(err); - }); - - this.socket.on('tcp connect', (addr, port) => { - if (this.closed) - return; - this.remoteAddress = addr; - this.remotePort = port; - this.emit('connect'); - }); - - this.socket.on('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.on('tcp close', (data) => { - if (this.closed) - return; - this.closed = true; - this.emit('close'); - }); - - this.socket.on('tcp error', (e) => { - const err = new Error(e.message); - err.code = e.code; - this.emit('error', err); - }); - - this.socket.on('tcp timeout', () => { - this.emit('timeout'); - }); - - this.socket.on('disconnect', () => { - if (this.closed) - return; - this.closed = true; - this.emit('close'); - }); -}; - -ProxySocket.prototype.connect = function connect(port, host) { - this.remoteAddress = host; - this.remotePort = port; - - if (this.closed) { - this.sendBuffer.length = 0; - return; - } - - if (!this.info) { - this.once('info', connect.bind(this, port, host)); - return; - } - - let nonce = 0; - - if (this.info.pow) { - const bw = new BufferWriter(); - - bw.writeU32(nonce); - bw.writeBytes(this.snonce); - bw.writeU32(port); - bw.writeString(host, 'ascii'); - - const pow = bw.render(); - - util.log( - 'Solving proof of work to create socket (%d, %s) -- please wait.', - port, host); - - do { - nonce++; - assert(nonce <= 0xffffffff, 'Could not create socket.'); - pow.writeUInt32LE(nonce, 0, true); - } while (digest.hash256(pow).compare(this.target) > 0); - - util.log('Solved proof of work: %d', nonce); - } - - this.socket.emit('tcp connect', port, host, nonce); - - for (const chunk of this.sendBuffer) - this.write(chunk); - - this.sendBuffer.length = 0; -}; - -ProxySocket.prototype.setKeepAlive = function setKeepAlive(enable, delay) { - this.socket.emit('tcp keep alive', enable, delay); -}; - -ProxySocket.prototype.setNoDelay = function setNoDelay(enable) { - this.socket.emit('tcp no delay', enable); -}; - -ProxySocket.prototype.setTimeout = function setTimeout(timeout, callback) { - this.socket.emit('tcp set timeout', timeout); - if (callback) - this.on('timeout', callback); -}; - -ProxySocket.prototype.write = function write(data, callback) { - if (!this.info) { - this.sendBuffer.push(data); - - if (callback) - callback(); - - return true; - } - - this.bytesWritten += data.length; - - this.socket.emit('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); - } -}; - -ProxySocket.prototype.destroy = function destroy() { - if (this.closed) - return; - this.closed = true; - this.socket.disconnect(); -}; - -ProxySocket.connect = function connect(uri, port, host) { - const socket = new ProxySocket(uri); - socket.connect(port, host); - return socket; -}; - -module.exports = ProxySocket; diff --git a/lib/net/seeds/main.js b/lib/net/seeds/main.js index 00fd1d1e1..0ede51634 100644 --- a/lib/net/seeds/main.js +++ b/lib/net/seeds/main.js @@ -1,5 +1,5 @@ 'use strict'; module.exports = [ - + '193.70.122.58' ]; diff --git a/lib/net/seeds/testnet.js b/lib/net/seeds/testnet.js index 9f7024021..0ede51634 100644 --- a/lib/net/seeds/testnet.js +++ b/lib/net/seeds/testnet.js @@ -1,5 +1,5 @@ 'use strict'; module.exports = [ - '198.100.147.52' + '193.70.122.58' ]; diff --git a/lib/net/socks-browser.js b/lib/net/socks-browser.js deleted file mode 100644 index 212272704..000000000 --- a/lib/net/socks-browser.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -exports.unsupported = true; diff --git a/lib/net/socks.js b/lib/net/socks.js deleted file mode 100644 index e0282fa5e..000000000 --- a/lib/net/socks.js +++ /dev/null @@ -1,775 +0,0 @@ -/*! - * socks.js - socks proxy for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module net/socks - */ - -const assert = require('assert'); -const EventEmitter = require('events'); -const net = require('net'); -const util = require('../utils/util'); -const IP = require('../utils/ip'); -const StaticWriter = require('../utils/staticwriter'); -const BufferReader = require('../utils/reader'); - -/** - * SOCKS state machine - * @constructor - */ - -function SOCKS() { - if (!(this instanceof SOCKS)) - return new SOCKS(); - - EventEmitter.call(this); - - this.socket = new net.Socket(); - this.state = SOCKS.states.INIT; - this.target = SOCKS.states.INIT; - this.destHost = '0.0.0.0'; - this.destPort = 0; - this.username = ''; - this.password = ''; - this.name = 'localhost'; - this.destroyed = false; - this.timeout = null; - this.proxied = false; -} - -Object.setPrototypeOf(SOCKS.prototype, EventEmitter.prototype); - -SOCKS.states = { - INIT: 0, - CONNECT: 1, - HANDSHAKE: 2, - AUTH: 3, - PROXY: 4, - PROXY_DONE: 5, - RESOLVE: 6, - RESOLVE_DONE: 7 -}; - -SOCKS.statesByVal = util.reverse(SOCKS.states); - -SOCKS.errors = [ - '', - 'General failure', - 'Connection not allowed', - 'Network is unreachable', - 'Host is unreachable', - 'Connection refused', - 'TTL expired', - 'Command not supported', - 'Address type not supported', - 'Unknown proxy error' -]; - -SOCKS.prototype.error = function error(err) { - if (this.destroyed) - return; - - if (err instanceof Error) { - this.emit('error', err); - this.destroy(); - return; - } - - const msg = util.fmt.apply(util, arguments); - this.emit('error', new Error(msg)); - this.destroy(); -}; - -SOCKS.prototype.getError = function getError(code) { - if (code >= SOCKS.errors.length) - return SOCKS.errors[9]; - - return SOCKS.errors[code]; -}; - -SOCKS.prototype.destroy = function destroy() { - if (this.destroyed) - return; - - this.destroyed = true; - this.socket.destroy(); - - this.stopTimeout(); - - if (this.state === this.target) - return; - - this.emit('close'); -}; - -SOCKS.prototype.startTimeout = function startTimeout() { - this.timeout = setTimeout(() => { - const state = SOCKS.statesByVal[this.state]; - this.timeout = null; - this.error('SOCKS request timed out (state=%s).', state); - }, 8000); -}; - -SOCKS.prototype.stopTimeout = function stopTimeout() { - if (this.timeout != null) { - clearTimeout(this.timeout); - this.timeout = null; - } -}; - -SOCKS.prototype.connect = function connect(port, host) { - assert(typeof port === 'number'); - assert(typeof host === 'string'); - - this.state = SOCKS.states.CONNECT; - this.socket.connect(port, host); - - this.socket.on('connect', () => { - if (this.proxied) - return; - this.handleConnect(); - }); - - this.socket.on('data', (data) => { - if (this.proxied) - return; - this.handleData(data); - }); - - this.socket.on('error', (err) => { - if (this.proxied) - return; - this.handleError(err); - }); - - this.socket.on('close', () => { - if (this.proxied) - return; - this.handleClose(); - }); -}; - -SOCKS.prototype.open = function open(options) { - assert(this.state === SOCKS.states.INIT); - - assert(options); - - if (options.username != null) { - assert(typeof options.username === 'string'); - this.username = options.username; - assert(typeof options.password === 'string', - 'Username must have a password.'); - } - - if (options.password != null) { - assert(typeof options.password === 'string'); - this.password = options.password; - } - - this.startTimeout(); - this.connect(options.port, options.host); -}; - -SOCKS.prototype.proxy = function proxy(options) { - assert(options); - assert(typeof options.destHost === 'string'); - assert(typeof options.destPort === 'number'); - - this.destHost = options.destHost; - this.destPort = options.destPort; - this.target = SOCKS.states.PROXY_DONE; - - this.open(options); -}; - -SOCKS.prototype.resolve = function resolve(options) { - assert(options); - assert(typeof options.name === 'string'); - - this.name = options.name; - this.target = SOCKS.states.RESOLVE_DONE; - - this.open(options); -}; - -SOCKS.prototype.handleConnect = function handleConnect() { - assert(this.state === SOCKS.states.CONNECT); - this.sendHandshake(); -}; - -SOCKS.prototype.handleError = function handleError(err) { - this.error(err); -}; - -SOCKS.prototype.handleClose = function handleClose() { - if (this.state !== this.target) { - const state = SOCKS.statesByVal[this.state]; - this.error('SOCKS request destroyed (state=%s).', state); - return; - } - - this.destroy(); -}; - -SOCKS.prototype.handleData = function handleData(data) { - switch (this.state) { - case SOCKS.states.INIT: - this.error('Data before SOCKS connection.'); - break; - case SOCKS.states.CONNECT: - this.error('Data before SOCKS handshake.'); - break; - case SOCKS.states.HANDSHAKE: - this.handleHandshake(data); - break; - case SOCKS.states.AUTH: - this.handleAuth(data); - break; - case SOCKS.states.PROXY: - this.handleProxy(data); - break; - case SOCKS.states.RESOLVE: - this.handleResolve(data); - break; - case SOCKS.states.PROXY_DONE: - case SOCKS.states.RESOLVE_DONE: - break; - default: - assert(false, 'Bad state.'); - break; - } -}; - -SOCKS.prototype.sendHandshake = function sendHandshake() { - let packet; - - if (this.username) { - packet = Buffer.allocUnsafe(4); - packet[0] = 0x05; - packet[1] = 0x02; - packet[2] = 0x00; - packet[3] = 0x02; - } else { - packet = Buffer.allocUnsafe(3); - packet[0] = 0x05; - packet[1] = 0x01; - packet[2] = 0x00; - } - - this.state = SOCKS.states.HANDSHAKE; - this.socket.write(packet); -}; - -SOCKS.prototype.handleHandshake = function handleHandshake(data) { - if (data.length !== 2) { - this.error('Bad SOCKS handshake response (size).'); - return; - } - - if (data[0] !== 0x05) { - this.error('Bad SOCKS version for handshake.'); - return; - } - - this.emit('handshake'); - - switch (data[1]) { - case 0xff: - this.error('No acceptable SOCKS auth methods.'); - break; - case 0x02: - this.sendAuth(); - break; - case 0x00: - this.state = SOCKS.states.AUTH; - this.auth(); - break; - default: - this.error('SOCKS handshake error: %d.', data[1]); - break; - } -}; - -SOCKS.prototype.sendAuth = function sendAuth() { - const user = this.username; - const pass = this.password; - - if (!user) { - this.error('No username passed for SOCKS auth.'); - return; - } - - if (!pass) { - this.error('No password passed for SOCKS auth.'); - return; - } - - const ulen = Buffer.byteLength(user, 'ascii'); - const plen = Buffer.byteLength(pass, 'ascii'); - const size = 3 + ulen + plen; - - const bw = new StaticWriter(size); - - bw.writeU8(0x01); - bw.writeU8(ulen); - bw.writeString(user, 'ascii'); - bw.writeU8(plen); - bw.writeString(pass, 'ascii'); - - const packet = bw.render(); - - this.state = SOCKS.states.AUTH; - this.socket.write(packet); -}; - -SOCKS.prototype.handleAuth = function handleAuth(data) { - if (data.length !== 2) { - this.error('Bad packet size for SOCKS auth.'); - return; - } - - if (data[0] !== 0x01) { - this.error('Bad SOCKS auth version number.'); - return; - } - - if (data[1] !== 0x00) { - this.error('SOCKS auth failure: %d.', data[0]); - return; - } - - this.auth(); -}; - -SOCKS.prototype.auth = function auth() { - this.emit('auth'); - - switch (this.target) { - case SOCKS.states.PROXY_DONE: - this.sendProxy(); - break; - case SOCKS.states.RESOLVE_DONE: - this.sendResolve(); - break; - default: - this.error('Bad target state.'); - break; - } -}; - -SOCKS.prototype.sendProxy = function sendProxy() { - const host = this.destHost; - const port = this.destPort; - let ip, len, type, name; - - switch (IP.getStringType(host)) { - case IP.types.IPV4: - ip = IP.toBuffer(host); - type = 0x01; - name = ip.slice(12, 16); - len = 4; - break; - case IP.types.IPV6: - ip = IP.toBuffer(host); - type = 0x04; - name = ip; - len = 16; - break; - default: - type = 0x03; - name = Buffer.from(host, 'ascii'); - len = 1 + name.length; - break; - } - - const bw = new StaticWriter(6 + len); - - bw.writeU8(0x05); - bw.writeU8(0x01); - bw.writeU8(0x00); - bw.writeU8(type); - - if (type === 0x03) - bw.writeU8(name.length); - - bw.writeBytes(name); - bw.writeU16BE(port); - - const packet = bw.render(); - - this.state = SOCKS.states.PROXY; - this.socket.write(packet); -}; - -SOCKS.prototype.handleProxy = function handleProxy(data) { - if (data.length < 6) { - this.error('Bad packet size for SOCKS connect.'); - return; - } - - if (data[0] !== 0x05) { - this.error('Bad SOCKS version for connect.'); - return; - } - - if (data[1] !== 0x00) { - const msg = this.getError(data[1]); - this.error('SOCKS connect error: %s.', msg); - return; - } - - if (data[2] !== 0x00) { - this.error('SOCKS connect failed (padding).'); - return; - } - - let addr; - try { - addr = parseAddr(data, 3); - } catch (e) { - this.error(e); - return; - } - - this.state = SOCKS.states.PROXY_DONE; - this.stopTimeout(); - this.proxied = true; - - this.emit('proxy address', addr); - this.emit('proxy', this.socket); -}; - -SOCKS.prototype.sendResolve = function sendResolve() { - const name = this.name; - const len = Buffer.byteLength(name, 'utf8'); - - const bw = new StaticWriter(7 + len); - - bw.writeU8(0x05); - bw.writeU8(0xf0); - bw.writeU8(0x00); - bw.writeU8(0x03); - bw.writeU8(len); - bw.writeString(name, 'utf8'); - bw.writeU16BE(0); - - const packet = bw.render(); - - this.state = SOCKS.states.RESOLVE; - this.socket.write(packet); -}; - -SOCKS.prototype.handleResolve = function handleResolve(data) { - if (data.length < 6) { - this.error('Bad packet size for tor resolve.'); - return; - } - - if (data[0] !== 0x05) { - this.error('Bad SOCKS version for tor resolve.'); - return; - } - - if (data[1] !== 0x00) { - const msg = this.getError(data[1]); - this.error('Tor resolve error: %s (%s).', msg, this.name); - return; - } - - if (data[2] !== 0x00) { - this.error('Tor resolve failed (padding).'); - return; - } - - let addr; - try { - addr = parseAddr(data, 3); - } catch (e) { - this.error(e); - return; - } - - if (addr.type === 0x03) { - this.error('Bad address type for tor resolve.'); - return; - } - - this.state = SOCKS.states.RESOLVE_DONE; - this.destroy(); - - this.emit('resolve', [addr.host]); -}; - -SOCKS.resolve = function resolve(options) { - const socks = new SOCKS(); - return new Promise((resolve, reject) => { - socks.resolve(options); - socks.on('resolve', resolve); - socks.on('error', reject); - }); -}; - -SOCKS.proxy = function proxy(options) { - const socks = new SOCKS(); - return new Promise((resolve, reject) => { - socks.proxy(options); - socks.on('proxy', resolve); - socks.on('error', reject); - }); -}; - -/** - * Proxy Socket - * @constructor - * @param {String} host - * @param {Number} port - * @param {String?} user - * @param {String?} pass - */ - -function Proxy(host, port, user, pass) { - if (!(this instanceof Proxy)) - return new Proxy(host, port, user, pass); - - EventEmitter.call(this); - - assert(typeof host === 'string'); - assert(typeof port === 'number'); - - this.socket = null; - this.host = host; - this.port = port; - this.username = user || null; - this.password = pass || null; - this.bytesWritten = 0; - this.bytesRead = 0; - this.remoteAddress = null; - this.remotePort = 0; - this.ops = []; -} - -Object.setPrototypeOf(Proxy.prototype, EventEmitter.prototype); - -Proxy.prototype.connect = async function connect(port, host) { - assert(!this.socket, 'Already connected.'); - - const options = { - host: this.host, - port: this.port, - username: this.username, - password: this.password, - destHost: host, - destPort: port - }; - - let socket; - try { - socket = await SOCKS.proxy(options); - } catch (e) { - this.emit('error', e); - return; - } - - this.remoteAddress = host; - this.remotePort = port; - this.socket = socket; - - this.socket.on('error', (err) => { - this.emit('error', err); - }); - - this.socket.on('close', () => { - this.emit('close'); - }); - - this.socket.on('data', (data) => { - this.bytesRead += data.length; - this.emit('data', data); - }); - - this.socket.on('drain', () => { - this.emit('drain'); - }); - - this.socket.on('timeout', () => { - this.emit('timeout'); - }); - - for (const op of this.ops) - op.call(this); - - this.ops.length = 0; - - this.emit('connect'); -}; - -Proxy.prototype.setKeepAlive = function setKeepAlive(enable, delay) { - if (!this.socket) { - this.ops.push(() => { - this.socket.setKeepAlive(enable, delay); - }); - return; - } - this.socket.setKeepAlive(enable, delay); -}; - -Proxy.prototype.setNoDelay = function setNoDelay(enable) { - if (!this.socket) { - this.ops.push(() => { - this.socket.setNoDelay(enable); - }); - return; - } - this.socket.setNoDelay(enable); -}; - -Proxy.prototype.setTimeout = function setTimeout(timeout, callback) { - if (!this.socket) { - this.ops.push(() => { - this.socket.setTimeout(timeout, callback); - }); - return; - } - this.socket.setTimeout(timeout, callback); -}; - -Proxy.prototype.write = function write(data, callback) { - assert(this.socket, 'Not connected.'); - this.bytesWritten += data.length; - return this.socket.write(data, callback); -}; - -Proxy.prototype.end = function end() { - assert(this.socket, 'Not connected.'); - return this.socket.end(); -}; - -Proxy.prototype.pause = function pause() { - assert(this.socket, 'Not connected.'); - return this.socket.pause(); -}; - -Proxy.prototype.resume = function resume() { - assert(this.socket, 'Not connected.'); - return this.socket.resume(); -}; - -Proxy.prototype.destroy = function destroy() { - if (!this.socket) - return; - this.socket.destroy(); -}; - -/* - * Helpers - */ - -function parseProxy(host) { - const index = host.indexOf('@'); - - if (index === -1) { - const addr = IP.fromHostname(host, 1080); - return { - host: addr.host, - port: addr.port - }; - } - - const left = host.substring(0, index); - const right = host.substring(index + 1); - - const parts = left.split(':'); - assert(parts.length > 1, 'Bad username and password.'); - - const addr = IP.fromHostname(right, 1080); - - return { - host: addr.host, - port: addr.port, - username: parts[0], - password: parts[1] - }; -} - -function parseAddr(data, offset) { - const br = new BufferReader(data); - - if (br.left() < offset + 2) - throw new Error('Bad SOCKS address length.'); - - br.seek(offset); - - const type = br.readU8(); - let host, port; - - switch (type) { - case 0x01: { - if (br.left() < 6) - throw new Error('Bad SOCKS ipv4 length.'); - - host = IP.toString(br.readBytes(4)); - port = br.readU16BE(); - break; - } - case 0x03: { - const len = br.readU8(); - - if (br.left() < len + 2) - throw new Error('Bad SOCKS domain length.'); - - host = br.readString(len, 'utf8'); - port = br.readU16BE(); - break; - } - case 0x04: { - if (br.left() < 18) - throw new Error('Bad SOCKS ipv6 length.'); - - host = IP.toString(br.readBytes(16)); - port = br.readU16BE(); - break; - } - default: { - throw new Error(`Unknown SOCKS address type: ${type}.`); - } - } - - return { - type: type, - host: host, - port: port - }; -} - -/* - * Expose - */ - -exports.connect = function connect(proxy, destPort, destHost) { - const addr = parseProxy(proxy); - const host = addr.host; - const port = addr.port; - const user = addr.username; - const pass = addr.password; - - const socket = new Proxy(host, port, user, pass); - socket.connect(destPort, destHost); - - return socket; -}; - -exports.resolve = function resolve(proxy, name) { - const addr = parseProxy(proxy); - return SOCKS.resolve({ - host: addr.host, - port: addr.port, - username: addr.username, - password: addr.password, - name: name - }); -}; diff --git a/lib/net/tcp-browser.js b/lib/net/tcp-browser.js deleted file mode 100644 index 4bdd7644d..000000000 --- a/lib/net/tcp-browser.js +++ /dev/null @@ -1,39 +0,0 @@ -/*! - * tcp.js - tcp backend for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const ProxySocket = require('./proxysocket'); -const EventEmitter = require('events'); -const tcp = exports; - -tcp.createSocket = function createSocket(port, host, proxy) { - return ProxySocket.connect(proxy, port, host); -}; - -tcp.createServer = function createServer() { - const server = new EventEmitter(); - - server.listen = async function listen(port, host) { - server.emit('listening'); - return; - }; - - server.close = async function close() { - return; - }; - - server.address = function address() { - return { - address: '127.0.0.1', - port: 0 - }; - }; - - server.maxConnections = undefined; - - return server; -}; diff --git a/lib/net/tcp.js b/lib/net/tcp.js deleted file mode 100644 index 2866e5539..000000000 --- a/lib/net/tcp.js +++ /dev/null @@ -1,101 +0,0 @@ -/*! - * tcp.js - tcp backend for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -/* eslint prefer-arrow-callback: "off" */ - -'use strict'; - -const EventEmitter = require('events'); -const net = require('net'); -const socks = require('./socks'); - -/** - * @exports net/tcp - */ - -const tcp = exports; - -/** - * Create a TCP socket and connect. - * @param {Number} port - * @param {String} host - * @param {String?} proxy - * @returns {Object} - */ - -tcp.createSocket = function createSocket(port, host, proxy) { - if (proxy) - return socks.connect(proxy, port, host); - return net.connect(port, host); -}; - -/** - * Create a TCP server. - * @returns {Object} - */ - -tcp.createServer = function createServer() { - const server = new net.Server(); - const ee = new EventEmitter(); - - ee.listen = function listen(port, host) { - return new Promise((resolve, reject) => { - server.once('error', reject); - server.listen(port, host, () => { - server.removeListener('error', reject); - resolve(); - }); - }); - }; - - ee.close = function close() { - return new Promise((resolve, reject) => { - server.close(wrap(resolve, reject)); - }); - }; - - ee.address = function address() { - return server.address(); - }; - - Object.defineProperty(ee, 'maxConnections', { - get() { - return server.maxConnections; - }, - set(value) { - server.maxConnections = value; - return server.maxConnections; - } - }); - - server.on('listening', () => { - ee.emit('listening'); - }); - - server.on('connection', (socket) => { - ee.emit('connection', socket); - }); - - server.on('error', (err) => { - ee.emit('error', err); - }); - - return ee; -}; - -/* - * Helpers - */ - -function wrap(resolve, reject) { - return function(err, result) { - if (err) { - reject(err); - return; - } - resolve(result); - }; -} diff --git a/lib/net/upnp-browser.js b/lib/net/upnp-browser.js deleted file mode 100644 index 05ecb876f..000000000 --- a/lib/net/upnp-browser.js +++ /dev/null @@ -1,41 +0,0 @@ -/*! - * upnp-browser.js - upnp for bcoin - * Copyright (c) 2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * UPNP - * @constructor - * @ignore - * @param {String?} host - Multicast IP. - * @param {Number?} port - Multicast port. - * @param {String?} gateway - Gateway name. - */ - -function UPNP(host, port, gateway) { - throw new Error('UPNP not supported.'); -} - -/** - * Discover gateway and resolve service. - * @param {String?} host - Multicast IP. - * @param {Number?} port - Multicast port. - * @param {String?} gateway - Gateway type. - * @param {String[]?} targets - Target service types. - * @returns {Promise} Service. - */ - -UPNP.discover = function discover(host, port, gateway, targets) { - return new Promise((resolve, reject) => { - reject(new Error('UPNP not supported.')); - }); -}; - -/* - * Expose - */ - -module.exports = UPNP; diff --git a/lib/net/upnp.js b/lib/net/upnp.js deleted file mode 100644 index 85ec40dea..000000000 --- a/lib/net/upnp.js +++ /dev/null @@ -1,724 +0,0 @@ -/*! - * upnp.js - upnp for bcoin - * Copyright (c) 2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const dgram = require('dgram'); -const url = require('url'); -const request = require('../http/request'); -const co = require('../utils/co'); -const Lock = require('../utils/lock'); -const IP = require('../utils/ip'); - -/** - * UPNP - * @alias module:net.UPNP - * @constructor - * @param {String?} host - Multicast IP. - * @param {Number?} port - Multicast port. - * @param {String?} gateway - Gateway name. - */ - -function UPNP(host, port, gateway) { - if (!(this instanceof UPNP)) - return new UPNP(host, port, gateway); - - this.host = host || '239.255.255.250'; - this.port = port || 1900; - this.gateway = gateway || UPNP.INTERNET_GATEWAY; - this.locker = new Lock(); - this.timeout = null; - this.job = null; -} - -/** - * Default internet gateway string. - * @const {String} - * @default - */ - -UPNP.INTERNET_GATEWAY = 'urn:schemas-upnp-org:device:InternetGatewayDevice:1'; - -/** - * Default service types. - * @const {String[]} - * @default - */ - -UPNP.WAN_SERVICES = [ - 'urn:schemas-upnp-org:service:WANIPConnection:1', - 'urn:schemas-upnp-org:service:WANPPPConnection:1' -]; - -/** - * Timeout before killing request. - * @const {Number} - * @default - */ - -UPNP.RESPONSE_TIMEOUT = 1000; - -/** - * Clean up current job. - * @private - * @returns {Job} - */ - -UPNP.prototype.cleanupJob = function cleanupJob() { - const job = this.job; - - assert(this.socket); - assert(this.job); - - this.job = null; - - this.socket.close(); - this.socket = null; - - this.stopTimeout(); - - return job; -}; - -/** - * Reject current job. - * @private - * @param {Error} err - */ - -UPNP.prototype.rejectJob = function rejectJob(err) { - const job = this.cleanupJob(); - job.reject(err); -}; - -/** - * Resolve current job. - * @private - * @param {Object} result - */ - -UPNP.prototype.resolveJob = function resolveJob(result) { - const job = this.cleanupJob(); - job.resolve(result); -}; - -/** - * Start gateway timeout. - * @private - */ - -UPNP.prototype.startTimeout = function startTimeout() { - this.stopTimeout(); - this.timeout = setTimeout(() => { - this.timeout = null; - this.rejectJob(new Error('Request timed out.')); - }, UPNP.RESPONSE_TIMEOUT); -}; - -/** - * Stop gateway timeout. - * @private - */ - -UPNP.prototype.stopTimeout = function stopTimeout() { - if (this.timeout != null) { - clearTimeout(this.timeout); - this.timeout = null; - } -}; - -/** - * Discover gateway. - * @returns {Promise} Location string. - */ - -UPNP.prototype.discover = async function discover() { - const unlock = await this.locker.lock(); - try { - return await this._discover(); - } finally { - unlock(); - } -}; - -/** - * Discover gateway (without a lock). - * @private - * @returns {Promise} Location string. - */ - -UPNP.prototype._discover = async function _discover() { - const socket = dgram.createSocket('udp4'); - - socket.on('error', (err) => { - this.rejectJob(err); - }); - - socket.on('message', (data, rinfo) => { - const msg = data.toString('utf8'); - this.handleMsg(msg); - }); - - this.socket = socket; - this.startTimeout(); - - const msg = '' - + 'M-SEARCH * HTTP/1.1\r\n' - + `HOST: ${this.host}:${this.port}\r\n` - + 'MAN: ssdp:discover\r\n' - + 'MX: 10\r\n' - + 'ST: ssdp:all\r\n'; - - socket.send(msg, this.port, this.host); - - return await new Promise((resolve, reject) => { - this.job = co.job(resolve, reject); - }); -}; - -/** - * Handle incoming UDP message. - * @private - * @param {String} msg - * @returns {Promise} - */ - -UPNP.prototype.handleMsg = async function handleMsg(msg) { - if (!this.socket) - return; - - let headers; - try { - headers = UPNP.parseHeader(msg); - } catch (e) { - return; - } - - if (!headers.location) - return; - - if (headers.st !== this.gateway) - return; - - this.resolveJob(headers.location); -}; - -/** - * Resolve service parameters from location. - * @param {String} location - * @param {String[]} targets - Target services. - * @returns {Promise} - */ - -UPNP.prototype.resolve = async function resolve(location, targets) { - const host = parseHost(location); - - if (!targets) - targets = UPNP.WAN_SERVICES; - - const res = await request({ - method: 'GET', - uri: location, - timeout: UPNP.RESPONSE_TIMEOUT, - expect: 'xml' - }); - - const xml = XMLElement.fromRaw(res.body); - - const services = parseServices(xml); - assert(services.length > 0, 'No services found.'); - - const service = extractServices(services, targets); - assert(service, 'No service found.'); - assert(service.serviceId, 'No service ID found.'); - assert(service.serviceId.length > 0, 'No service ID found.'); - assert(service.controlURL, 'No control URL found.'); - assert(service.controlURL.length > 0, 'No control URL found.'); - - service.controlURL = prependHost(host, service.controlURL); - - if (service.eventSubURL) - service.eventSubURL = prependHost(host, service.eventSubURL); - - if (service.SCPDURL) - service.SCPDURL = prependHost(host, service.SCPDURL); - - return service; -}; - -/** - * Parse UPNP datagram. - * @private - * @param {String} str - * @returns {Object} - */ - -UPNP.parseHeader = function parseHeader(str) { - const lines = str.split(/\r?\n/); - const headers = Object.create(null); - - for (let line of lines) { - line = line.trim(); - - if (line.length === 0) - continue; - - const index = line.indexOf(':'); - - if (index === -1) { - const left = line.toLowerCase(); - headers[left] = ''; - continue; - } - - let left = line.substring(0, index); - let right = line.substring(index + 1); - - left = left.trim(); - right = right.trim(); - - left = left.toLowerCase(); - - headers[left] = right; - } - - return headers; -}; - -/** - * Discover gateway and resolve service. - * @param {String?} host - Multicast IP. - * @param {Number?} port - Multicast port. - * @param {String?} gateway - Gateway type. - * @param {String[]?} targets - Target service types. - * @returns {Promise} Service. - */ - -UPNP.discover = async function discover(host, port, gateway, targets) { - const upnp = new UPNP(host, port, gateway); - const location = await upnp.discover(); - const service = await upnp.resolve(location, targets); - return new UPNPService(service); -}; - -/** - * Gateway Service - * @constructor - * @ignore - * @param {Object} options - Service parameters. - */ - -function UPNPService(options) { - if (!(this instanceof UPNPService)) - return new UPNPService(options); - - this.serviceType = options.serviceType; - this.serviceId = options.serviceId; - this.controlURL = options.controlURL; - this.eventSubURL = options.eventSubURL; - this.SCPDURL = options.SCPDURL; -} - -/** - * Compile SOAP request. - * @private - * @param {String} action - * @param {String[]} args - * @returns {String} - */ - -UPNPService.prototype.createRequest = function createRequest(action, args) { - const type = JSON.stringify(this.serviceType); - - let params = ''; - - for (const [key, value] of args) { - params += `<${key}>`; - if (value != null) - params += value; - params += ``; - } - - return '' - + '' - + '' - + '' - + `` - + `${params}` - + `` - + '' - + ''; -}; - -/** - * Send SOAP request and parse XML response. - * @private - * @param {String} action - * @param {String[]} args - * @returns {XMLElement} - */ - -UPNPService.prototype.soapRequest = async function soapRequest(action, args) { - const type = this.serviceType; - const req = this.createRequest(action, args); - - const res = await request({ - method: 'POST', - uri: this.controlURL, - timeout: UPNP.RESPONSE_TIMEOUT, - expect: 'xml', - headers: { - 'Content-Type': 'text/xml; charset="utf-8"', - 'Content-Length': Buffer.byteLength(req, 'utf8').toString(10), - 'Connection': 'close', - 'SOAPAction': JSON.stringify(`${type}#${action}`) - }, - body: req - }); - - const xml = XMLElement.fromRaw(res.body); - const err = findError(xml); - - if (err) - throw err; - - return xml; -}; - -/** - * Attempt to get external IP from service (wan). - * @returns {Promise} - */ - -UPNPService.prototype.getExternalIP = async function getExternalIP() { - const action = 'GetExternalIPAddress'; - const xml = await this.soapRequest(action, []); - const ip = findIP(xml); - - if (!ip) - throw new Error('Could not find external IP.'); - - return ip; -}; - -/** - * Attempt to add port mapping to local IP. - * @param {String} remote - Remote IP. - * @param {Number} src - Remote port. - * @param {Number} dest - Local port. - * @returns {Promise} - */ - -UPNPService.prototype.addPortMapping = async function addPortMapping(remote, src, dest) { - const action = 'AddPortMapping'; - const local = IP.getPrivate(); - - if (local.length === 0) - throw new Error('Cannot determine local IP.'); - - const xml = await this.soapRequest(action, [ - ['NewRemoteHost', remote], - ['NewExternalPort', src], - ['NewProtocol', 'TCP'], - ['NewInternalClient', local[0]], - ['NewInternalPort', dest], - ['NewEnabled', 'True'], - ['NewPortMappingDescription', 'upnp:bcoin'], - ['NewLeaseDuration', 0] - ]); - - const child = xml.find('AddPortMappingResponse'); - - if (!child) - throw new Error('Port mapping failed.'); - - return child.text; -}; - -/** - * Attempt to remove port mapping from local IP. - * @param {String} remote - Remote IP. - * @param {Number} port - Remote port. - * @returns {Promise} - */ - -UPNPService.prototype.removePortMapping = async function removePortMapping(remote, port) { - const action = 'DeletePortMapping'; - - const xml = await this.soapRequest(action, [ - ['NewRemoteHost', remote], - ['NewExternalPort', port], - ['NewProtocol', 'TCP'] - ]); - - const child = xml.find('DeletePortMappingResponse'); - - if (!child) - throw new Error('Port unmapping failed.'); - - return child.text; -}; - -/** - * XML Element - * @constructor - * @ignore - */ - -function XMLElement(name) { - this.name = name; - this.type = name.replace(/^[^:]:/, ''); - this.children = []; - this.text = ''; -} - -/** - * Insantiate element from raw XML. - * @param {String} xml - * @returns {XMLElement} - */ - -XMLElement.fromRaw = function fromRaw(xml) { - const sentinel = new XMLElement(''); - const stack = [sentinel]; - - let current = sentinel; - let decl = false; - - while (xml.length > 0) { - let m; - - m = /^<\?xml[^<>]*\?>/i.exec(xml); - if (m) { - xml = xml.substring(m[0].length); - assert(current === sentinel, 'XML declaration inside element.'); - assert(!decl, 'XML declaration seen twice.'); - decl = true; - continue; - } - - m = /^<([\w:]+)[^<>]*?(\/?)>/i.exec(xml); - if (m) { - xml = xml.substring(m[0].length); - - const name = m[1]; - const trailing = m[2] === '/'; - const element = new XMLElement(name); - - if (trailing) { - current.add(element); - continue; - } - - stack.push(element); - current.add(element); - current = element; - - continue; - } - - m = /^<\/([\w:]+)[^<>]*>/i.exec(xml); - if (m) { - xml = xml.substring(m[0].length); - - const name = m[1]; - - assert(stack.length !== 1, 'No start tag.'); - - const element = stack.pop(); - - assert(element.name === name, 'Tag mismatch.'); - current = stack[stack.length - 1]; - - if (current === sentinel) - break; - - continue; - } - - m = /^([^<]+)/i.exec(xml); - if (m) { - xml = xml.substring(m[0].length); - const text = m[1]; - current.text = text.trim(); - continue; - } - - throw new Error('XML parse error.'); - } - - assert(sentinel.children.length > 0, 'No root element.'); - - return sentinel.children[0]; -}; - -/** - * Push element onto children. - * @param {XMLElement} child - * @returns {Number} - */ - -XMLElement.prototype.add = function add(child) { - return this.children.push(child); -}; - -/** - * Collect all descendants with matching name. - * @param {String} name - * @returns {XMLElement[]} - */ - -XMLElement.prototype.collect = function collect(name) { - return this._collect(name, []); -}; - -/** - * Collect all descendants with matching name. - * @private - * @param {String} name - * @param {XMLElement[]} result - * @returns {XMLElement[]} - */ - -XMLElement.prototype._collect = function _collect(name, result) { - for (const child of this.children) { - if (child.type === name) { - result.push(child); - continue; - } - - child._collect(name, result); - } - - return result; -}; - -/** - * Find child element with matching name. - * @param {String} name - * @returns {XMLElement|null} - */ - -XMLElement.prototype.find = function find(name) { - for (const child of this.children) { - if (child.type === name) - return child; - - const desc = child.find(name); - - if (desc) - return desc; - } - - return null; -}; - -/* - * XML Helpers - */ - -function parseServices(el) { - const children = el.collect('service'); - const services = []; - - for (const child of children) - services.push(parseService(child)); - - return services; -} - -function parseService(el) { - const service = Object.create(null); - - for (const child of el.children) { - if (child.children.length > 0) - continue; - - service[child.type] = child.text; - } - - return service; -} - -function findService(services, name) { - for (const service of services) { - if (service.serviceType === name) - return service; - } - - return null; -} - -function extractServices(services, targets) { - for (const name of targets) { - const service = findService(services, name); - if (service) - return service; - } - - return null; -} - -function findIP(el) { - const child = el.find('NewExternalIPAddress'); - - if (!child) - return null; - - return IP.normalize(child.text); -} - -function findError(el) { - const child = el.find('UPnPError'); - - if (!child) - return null; - - let code = -1; - const ccode = child.find('errorCode'); - - if (ccode && /^\d+$/.test(ccode.text)) - code = parseInt(ccode.text, 10); - - let desc = 'Unknown'; - const cdesc = child.find('errorDescription'); - - if (cdesc) - desc = cdesc.text; - - return new Error(`UPnPError: ${desc} (${code}).`); -} - -/* - * Helpers - */ - -function parseHost(uri) { - const {protocol, host} = url.parse(uri); - - assert(protocol === 'http:' || protocol === 'https:', - 'Bad URL for location.'); - - return `${protocol}//${host}`; -} - -function prependHost(host, uri) { - if (uri.indexOf('://') === -1) { - if (uri[0] !== '/') - uri = '/' + uri; - uri = host + uri; - } - return uri; -} - -/* - * Expose - */ - -module.exports = UPNP; diff --git a/lib/node/config.js b/lib/node/config.js deleted file mode 100644 index 549fffb9c..000000000 --- a/lib/node/config.js +++ /dev/null @@ -1,1123 +0,0 @@ -/*! - * config.js - configuration parsing for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const Path = require('path'); -const os = require('os'); -const fs = require('../utils/fs'); -const util = require('../utils/util'); -const HOME = os.homedir ? os.homedir() : '/'; - -/** - * Config Parser - * @alias module:node.Config - * @constructor - * @param {String} module - Module name (e.g. `bcoin`). - */ - -function Config(module) { - if (!(this instanceof Config)) - return new Config(module); - - assert(typeof module === 'string'); - assert(module.length > 0); - - this.module = module; - this.network = 'main'; - this.prefix = Path.join(HOME, `.${module}`); - - this.options = Object.create(null); - this.data = Object.create(null); - this.env = Object.create(null); - this.args = Object.create(null); - this.argv = []; - this.pass = []; - this.query = Object.create(null); - this.hash = Object.create(null); -} - -/** - * Option name aliases. - * @const {Object} - */ - -Config.alias = { - 'seed': 'seeds', - 'node': 'nodes', - 'n': 'network' -}; - -/** - * Inject options. - * @param {Object} options - */ - -Config.prototype.inject = function inject(options) { - for (const key of Object.keys(options)) { - const value = options[key]; - - switch (key) { - case 'hash': - case 'query': - case 'env': - case 'argv': - case 'config': - continue; - } - - this.set(key, value); - } -}; - -/** - * Load options from hash, query, env, or args. - * @param {Object} options - */ - -Config.prototype.load = function load(options) { - if (options.hash) - this.parseHash(options.hash); - - if (options.query) - this.parseQuery(options.query); - - if (options.env) - this.parseEnv(options.env); - - if (options.argv) - this.parseArg(options.argv); - - this.network = this.getNetwork(); - this.prefix = this.getPrefix(); -}; - -/** - * Open a config file. - * @param {String} file - e.g. `bcoin.conf`. - * @throws on IO error - */ - -Config.prototype.open = function open(file) { - if (fs.unsupported) - return; - - const path = this.getFile(file); - - let text; - try { - text = fs.readFileSync(path, 'utf8'); - } catch (e) { - if (e.code === 'ENOENT') - return; - throw e; - } - - this.parseConfig(text); - - this.network = this.getNetwork(); - this.prefix = this.getPrefix(); -}; - -/** - * Set default option. - * @param {String} key - * @param {Object} value - */ - -Config.prototype.set = function set(key, value) { - assert(typeof key === 'string', 'Key must be a string.'); - - if (value == null) - return; - - key = key.replace(/-/g, ''); - key = key.toLowerCase(); - - this.options[key] = value; -}; - -/** - * Test whether a config option is present. - * @param {String} key - * @returns {Boolean} - */ - -Config.prototype.has = function has(key) { - if (typeof key === 'number') { - assert(key >= 0, 'Index must be positive.'); - if (key >= this.argv.length) - return false; - return true; - } - - assert(typeof key === 'string', 'Key must be a string.'); - - key = key.replace(/-/g, ''); - key = key.toLowerCase(); - - if (this.hash[key] != null) - return true; - - if (this.query[key] != null) - return true; - - if (this.args[key] != null) - return true; - - if (this.env[key] != null) - return true; - - if (this.data[key] != null) - return true; - - if (this.options[key] != null) - return true; - - return false; -}; - -/** - * Get a config option. - * @param {String} key - * @param {Object?} fallback - * @returns {Object|null} - */ - -Config.prototype.get = function get(key, fallback) { - if (fallback === undefined) - fallback = null; - - if (Array.isArray(key)) { - const keys = key; - for (const key of keys) { - const value = this.get(key); - if (value !== null) - return value; - } - return fallback; - } - - if (typeof key === 'number') { - assert(key >= 0, 'Index must be positive.'); - - if (key >= this.argv.length) - return fallback; - - if (this.argv[key] != null) - return this.argv[key]; - - return fallback; - } - - assert(typeof key === 'string', 'Key must be a string.'); - - key = key.replace(/-/g, ''); - key = key.toLowerCase(); - - if (this.hash[key] != null) - return this.hash[key]; - - if (this.query[key] != null) - return this.query[key]; - - if (this.args[key] != null) - return this.args[key]; - - if (this.env[key] != null) - return this.env[key]; - - if (this.data[key] != null) - return this.data[key]; - - if (this.options[key] != null) - return this.options[key]; - - return fallback; -}; - -/** - * Get a value's type. - * @param {String} key - * @returns {String} - */ - -Config.prototype.typeOf = function typeOf(key) { - const value = this.get(key); - - if (value === null) - return 'null'; - - return typeof value; -}; - -/** - * Get a config option (as a string). - * @param {String} key - * @param {Object?} fallback - * @returns {String|null} - */ - -Config.prototype.str = function str(key, fallback) { - const value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'string') - throw new Error(`${fmt(key)} must be a string.`); - - return value; -}; - -/** - * Get a config option (as an integer). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Config.prototype.int = function int(key, fallback) { - let value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'string') { - if (typeof value !== 'number') - throw new Error(`${fmt(key)} must be an int.`); - - if (!Number.isSafeInteger(value)) - throw new Error(`${fmt(key)} must be an int.`); - - return value; - } - - if (!/^\-?\d+$/.test(value)) - throw new Error(`${fmt(key)} must be an int.`); - - value = parseInt(value, 10); - - if (!Number.isSafeInteger(value)) - throw new Error(`${fmt(key)} must be an int.`); - - return value; -}; - -/** - * Get a config option (as a unsigned integer). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Config.prototype.uint = function uint(key, fallback) { - const value = this.int(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (value < 0) - throw new Error(`${fmt(key)} must be a uint.`); - - return value; -}; - -/** - * Get a config option (as a float). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Config.prototype.float = function float(key, fallback) { - let value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'string') { - if (typeof value !== 'number') - throw new Error(`${fmt(key)} must be a float.`); - - if (!isFinite(value)) - throw new Error(`${fmt(key)} must be a float.`); - - return value; - } - - if (!/^\-?\d*(?:\.\d*)?$/.test(value)) - throw new Error(`${fmt(key)} must be a float.`); - - if (!/\d/.test(value)) - throw new Error(`${fmt(key)} must be a float.`); - - value = parseFloat(value); - - if (!isFinite(value)) - throw new Error(`${fmt(key)} must be a float.`); - - return value; -}; - -/** - * Get a config option (as a positive float). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Config.prototype.ufloat = function ufloat(key, fallback) { - const value = this.float(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (value < 0) - throw new Error(`${fmt(key)} must be a positive float.`); - - return value; -}; - -/** - * Get a value (as a fixed number). - * @param {String} key - * @param {Number?} exp - * @param {Object?} fallback - * @returns {Number|null} - */ - -Config.prototype.fixed = function fixed(key, exp, fallback) { - const value = this.float(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - try { - return util.fromFloat(value, exp || 0); - } catch (e) { - throw new Error(`${fmt(key)} must be a fixed number.`); - } -}; - -/** - * Get a value (as a positive fixed number). - * @param {String} key - * @param {Number?} exp - * @param {Object?} fallback - * @returns {Number|null} - */ - -Config.prototype.ufixed = function ufixed(key, exp, fallback) { - const value = this.fixed(key, exp); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (value < 0) - throw new Error(`${fmt(key)} must be a positive fixed number.`); - - return value; -}; - -/** - * Get a config option (as a boolean). - * @param {String} key - * @param {Object?} fallback - * @returns {Boolean|null} - */ - -Config.prototype.bool = function bool(key, fallback) { - const value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - // Bitcoin Core compat. - if (typeof value === 'number') { - if (value === 1) - return true; - - if (value === 0) - return false; - } - - if (typeof value !== 'string') { - if (typeof value !== 'boolean') - throw new Error(`${fmt(key)} must be a boolean.`); - return value; - } - - if (value === 'true' || value === '1') - return true; - - if (value === 'false' || value === '0') - return false; - - throw new Error(`${fmt(key)} must be a boolean.`); -}; - -/** - * Get a config option (as a buffer). - * @param {String} key - * @param {Object?} fallback - * @returns {Buffer|null} - */ - -Config.prototype.buf = function buf(key, fallback, enc) { - const value = this.get(key); - - if (!enc) - enc = 'hex'; - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'string') { - if (!Buffer.isBuffer(value)) - throw new Error(`${fmt(key)} must be a buffer.`); - return value; - } - - const data = Buffer.from(value, enc); - - if (data.length !== Buffer.byteLength(value, enc)) - throw new Error(`${fmt(key)} must be a ${enc} string.`); - - return data; -}; - -/** - * Get a config option (as an array of strings). - * @param {String} key - * @param {Object?} fallback - * @returns {String[]|null} - */ - -Config.prototype.array = function array(key, fallback) { - const value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'string') { - if (!Array.isArray(value)) - throw new Error(`${fmt(key)} must be an array.`); - return value; - } - - const parts = value.trim().split(/\s*,\s*/); - const result = []; - - for (const part of parts) { - if (part.length === 0) - continue; - - result.push(part); - } - - return result; -}; - -/** - * Get a config option (as an object). - * @param {String} key - * @param {Object?} fallback - * @returns {Object|null} - */ - -Config.prototype.obj = function obj(key, fallback) { - const value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'object') - throw new Error(`${fmt(key)} must be an object.`); - - return value; -}; - -/** - * Get a config option (as a function). - * @param {String} key - * @param {Object?} fallback - * @returns {Function|null} - */ - -Config.prototype.func = function func(key, fallback) { - const value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'function') - throw new Error(`${fmt(key)} must be a function.`); - - return value; -}; - -/** - * Get a config option (as a string). - * @param {String} key - * @param {Object?} fallback - * @returns {String|null} - */ - -Config.prototype.path = function path(key, fallback) { - let value = this.str(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - switch (value[0]) { - case '~': // home dir - value = Path.join(HOME, value.substring(1)); - break; - case '@': // prefix - value = Path.join(this.prefix, value.substring(1)); - break; - default: // cwd - break; - } - - return Path.normalize(value); -}; - -/** - * Get a config option (in MB). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Config.prototype.mb = function mb(key, fallback) { - const value = this.uint(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - return value * 1024 * 1024; -}; - -/** - * Grab network type from config data. - * @private - * @returns {String} - */ - -Config.prototype.getNetwork = function getNetwork() { - let network = this.str('network'); - - if (!network) - network = 'main'; - - assert(isAlpha(network), 'Bad network.'); - - return network; -}; - -/** - * Grab prefix from config data. - * @private - * @returns {String} - */ - -Config.prototype.getPrefix = function getPrefix() { - let prefix = this.str('prefix'); - - if (prefix) { - if (prefix[0] === '~') - prefix = Path.join(HOME, prefix.substring(1)); - return prefix; - } - - prefix = Path.join(HOME, `.${this.module}`); - - const network = this.str('network'); - - if (network) { - assert(isAlpha(network), 'Bad network.'); - if (network !== 'main') - prefix = Path.join(prefix, network); - } - - return Path.normalize(prefix); -}; - -/** - * Grab config filename from config data. - * @private - * @param {String} file - * @returns {String} - */ - -Config.prototype.getFile = function getFile(file) { - const name = this.str('config'); - - if (name) - return name; - - return Path.join(this.prefix, file); -}; - -/** - * Ensure prefix. - * @returns {Promise} - */ - -Config.prototype.ensure = function ensure() { - if (fs.unsupported) - return Promise.resolve(); - - return fs.mkdirp(this.prefix); -}; - -/** - * Create a file path using `prefix`. - * @param {String} file - * @returns {String} - */ - -Config.prototype.location = function location(file) { - return Path.join(this.prefix, file); -}; - -/** - * Parse config text. - * @private - * @param {String} text - */ - -Config.prototype.parseConfig = function parseConfig(text) { - assert(typeof text === 'string', 'Config must be text.'); - - if (text.charCodeAt(0) === 0xfeff) - text = text.substring(1); - - text = text.replace(/\r\n/g, '\n'); - text = text.replace(/\r/g, '\n'); - text = text.replace(/\\\n/g, ''); - - let colons = true; - let seen = false; - let num = 0; - - for (const chunk of text.split('\n')) { - const line = chunk.trim(); - - num += 1; - - if (line.length === 0) - continue; - - if (line[0] === '#') - continue; - - const equal = line.indexOf('='); - const colon = line.indexOf(':'); - - let index = -1; - - if (colon !== -1 && (colon < equal || equal === -1)) { - if (seen && !colons) - throw new Error(`Expected '=' on line ${num}: "${line}".`); - - index = colon; - seen = true; - colons = true; - } else if (equal !== -1) { - if (seen && colons) - throw new Error(`Expected ':' on line ${num}: "${line}".`); - - index = equal; - seen = true; - colons = false; - } else { - const symbol = colons ? ':' : '='; - throw new Error(`Expected '${symbol}' on line ${num}: "${line}".`); - } - - let key = line.substring(0, index).trim(); - - key = key.replace(/\-/g, ''); - - if (!isLowerKey(key)) - throw new Error(`Invalid option on line ${num}: ${key}.`); - - const value = line.substring(index + 1).trim(); - - if (value.length === 0) - continue; - - const alias = Config.alias[key]; - - if (alias) - key = alias; - - this.data[key] = value; - } -}; - -/** - * Parse arguments. - * @private - * @param {Array?} argv - */ - -Config.prototype.parseArg = function parseArg(argv) { - if (!argv || typeof argv !== 'object') - argv = process.argv; - - assert(Array.isArray(argv)); - - let last = null; - let pass = false; - - for (let i = 2; i < argv.length; i++) { - const arg = argv[i]; - - assert(typeof arg === 'string'); - - if (arg === '--') { - pass = true; - continue; - } - - if (pass) { - this.pass.push(arg); - continue; - } - - if (arg.length === 0) { - last = null; - continue; - } - - if (arg.indexOf('--') === 0) { - const index = arg.indexOf('='); - - let key = null; - let value = null; - let empty = false; - - if (index !== -1) { - // e.g. --opt=val - key = arg.substring(2, index); - value = arg.substring(index + 1); - last = null; - empty = false; - } else { - // e.g. --opt - key = arg.substring(2); - value = 'true'; - last = null; - empty = true; - } - - key = key.replace(/\-/g, ''); - - if (!isLowerKey(key)) - throw new Error(`Invalid argument: --${key}.`); - - if (value.length === 0) - continue; - - // Do not allow one-letter aliases. - if (key.length > 1) { - const alias = Config.alias[key]; - if (alias) - key = alias; - } - - this.args[key] = value; - - if (empty) - last = key; - - continue; - } - - if (arg[0] === '-') { - // e.g. -abc - last = null; - - for (let j = 1; j < arg.length; j++) { - let key = arg[j]; - - if ((key < 'a' || key > 'z') - && (key < 'A' || key > 'Z') - && (key < '0' || key > '9') - && key !== '?') { - throw new Error(`Invalid argument: -${key}.`); - } - - const alias = Config.alias[key]; - - if (alias) - key = alias; - - this.args[key] = 'true'; - - last = key; - } - - continue; - } - - // e.g. foo - const value = arg; - - if (value.length === 0) { - last = null; - continue; - } - - if (last) { - this.args[last] = value; - last = null; - } else { - this.argv.push(value); - } - } -}; - -/** - * Parse environment variables. - * @private - * @param {Object?} env - * @returns {Object} - */ - -Config.prototype.parseEnv = function parseEnv(env) { - let prefix = this.module; - - prefix = prefix.toUpperCase(); - prefix = prefix.replace(/-/g, '_'); - prefix += '_'; - - if (!env || typeof env !== 'object') - env = process.env; - - assert(env && typeof env === 'object'); - - for (let key of Object.keys(env)) { - const value = env[key]; - - assert(typeof value === 'string'); - - if (!util.startsWith(key, prefix)) - continue; - - key = key.substring(prefix.length); - key = key.replace(/_/g, ''); - - if (!isUpperKey(key)) - continue; - - if (value.length === 0) - continue; - - key = key.toLowerCase(); - - // Do not allow one-letter aliases. - if (key.length > 1) { - const alias = Config.alias[key]; - if (alias) - key = alias; - } - - this.env[key] = value; - } -}; - -/** - * Parse uri querystring variables. - * @private - * @param {String} query - */ - -Config.prototype.parseQuery = function parseQuery(query) { - if (typeof query !== 'string') { - if (!global.location) - return {}; - - query = global.location.search; - - if (typeof query !== 'string') - return {}; - } - - return this.parseForm(query, this.query); -}; - -/** - * Parse uri hash variables. - * @private - * @param {String} hash - */ - -Config.prototype.parseHash = function parseHash(hash) { - if (typeof hash !== 'string') { - if (!global.location) - return {}; - - hash = global.location.hash; - - if (typeof hash !== 'string') - return {}; - } - - return this.parseForm(hash, this.hash); -}; - -/** - * Parse form-urlencoded variables. - * @private - * @param {String} query - */ - -Config.prototype.parseForm = function parseForm(query, map) { - assert(typeof query === 'string'); - - if (query.length === 0) - return; - - let ch = '?'; - - if (map === this.hash) - ch = '#'; - - if (query[0] === ch) - query = query.substring(1); - - for (const pair of query.split('&')) { - const index = pair.indexOf('='); - - let key, value; - if (index !== -1) { - key = pair.substring(0, index); - value = pair.substring(index + 1); - } else { - key = pair; - value = 'true'; - } - - key = unescape(key); - key = key.replace(/\-/g, ''); - - if (!isLowerKey(key)) - continue; - - value = unescape(value); - - if (value.length === 0) - continue; - - const alias = Config.alias[key]; - - if (alias) - key = alias; - - map[key] = value; - } -}; - -/* - * Helpers - */ - -function fmt(key) { - if (Array.isArray(key)) - key = key[0]; - - if (typeof key === 'number') - return `Argument #${key}`; - - return key; -} - -function unescape(str) { - try { - str = decodeURIComponent(str); - str = str.replace(/\+/g, ' '); - } catch (e) { - ; - } - str = str.replace(/\0/g, ''); - return str; -} - -function isAlpha(str) { - return /^[a-z0-9]+$/.test(str); -} - -function isKey(key) { - return /^[a-zA-Z0-9]+$/.test(key); -} - -function isLowerKey(key) { - if (!isKey(key)) - return false; - - return !/[A-Z]/.test(key); -} - -function isUpperKey(key) { - if (!isKey(key)) - return false; - - return !/[a-z]/.test(key); -} - -/* - * Expose - */ - -module.exports = Config; diff --git a/lib/node/fullnode.js b/lib/node/fullnode.js index 0cad4e8da..2a078c89e 100644 --- a/lib/node/fullnode.js +++ b/lib/node/fullnode.js @@ -7,138 +7,141 @@ 'use strict'; +const assert = require('bsert'); const Chain = require('../blockchain/chain'); const Fees = require('../mempool/fees'); const Mempool = require('../mempool/mempool'); const Pool = require('../net/pool'); const Miner = require('../mining/miner'); -const HTTPServer = require('../http/server'); -const RPC = require('../http/rpc'); const Node = require('./node'); +const HTTP = require('./http'); +const RPC = require('./rpc'); +const blockstore = require('../blockstore'); +const TXIndexer = require('../indexer/txindexer'); +const AddrIndexer = require('../indexer/addrindexer'); /** + * Full Node * Respresents a fullnode complete with a * chain, mempool, miner, etc. * @alias module:node.FullNode * @extends Node - * @constructor - * @param {Object?} options - * @property {Chain} chain - * @property {PolicyEstimator} fees - * @property {Mempool} mempool - * @property {Pool} pool - * @property {Miner} miner - * @property {HTTPServer} http - * @emits FullNode#block - * @emits FullNode#tx - * @emits FullNode#connect - * @emits FullNode#disconnect - * @emits FullNode#reset - * @emits FullNode#error */ -function FullNode(options) { - if (!(this instanceof FullNode)) - return new FullNode(options); - - Node.call(this, options); - - // SPV flag. - this.spv = false; - - // Instantiate blockchain. - this.chain = new Chain({ - network: this.network, - logger: this.logger, - workers: this.workers, - db: this.config.str('db'), - prefix: this.config.prefix, - maxFiles: this.config.uint('max-files'), - cacheSize: this.config.mb('cache-size'), - forceFlags: this.config.bool('force-flags'), - bip91: this.config.bool('bip91'), - bip148: this.config.bool('bip148'), - prune: this.config.bool('prune'), - checkpoints: this.config.bool('checkpoints'), - coinCache: this.config.mb('coin-cache'), - entryCache: this.config.uint('entry-cache'), - indexTX: this.config.bool('index-tx'), - indexAddress: this.config.bool('index-address') - }); - - // Fee estimation. - this.fees = new Fees(this.logger); - this.fees.init(); - - // Mempool needs access to the chain. - this.mempool = new Mempool({ - network: this.network, - logger: this.logger, - workers: this.workers, - chain: this.chain, - fees: this.fees, - db: this.config.str('db'), - prefix: this.config.prefix, - persistent: this.config.bool('persistent-mempool'), - maxSize: this.config.mb('mempool-size'), - limitFree: this.config.bool('limit-free'), - limitFreeRelay: this.config.uint('limit-free-relay'), - requireStandard: this.config.bool('require-standard'), - rejectAbsurdFees: this.config.bool('reject-absurd-fees'), - replaceByFee: this.config.bool('replace-by-fee'), - indexAddress: this.config.bool('index-address') - }); - - // Pool needs access to the chain and mempool. - this.pool = new Pool({ - network: this.network, - logger: this.logger, - chain: this.chain, - mempool: this.mempool, - prefix: this.config.prefix, - selfish: this.config.bool('selfish'), - compact: this.config.bool('compact'), - bip37: this.config.bool('bip37'), - bip151: this.config.bool('bip151'), - bip150: this.config.bool('bip150'), - identityKey: this.config.buf('identity-key'), - maxOutbound: this.config.uint('max-outbound'), - maxInbound: this.config.uint('max-inbound'), - proxy: this.config.str('proxy'), - onion: this.config.bool('onion'), - upnp: this.config.bool('upnp'), - seeds: this.config.array('seeds'), - nodes: this.config.array('nodes'), - only: this.config.array('only'), - publicHost: this.config.str('public-host'), - publicPort: this.config.uint('public-port'), - host: this.config.str('host'), - port: this.config.uint('port'), - listen: this.config.bool('listen'), - persistent: this.config.bool('persistent') - }); - - // Miner needs access to the chain and mempool. - this.miner = new Miner({ - network: this.network, - logger: this.logger, - workers: this.workers, - chain: this.chain, - mempool: this.mempool, - address: this.config.array('coinbase-address'), - coinbaseFlags: this.config.str('coinbase-flags'), - preverify: this.config.bool('preverify'), - maxWeight: this.config.uint('max-weight'), - reservedWeight: this.config.uint('reserved-weight'), - reservedSigops: this.config.uint('reserved-sigops') - }); - - // RPC needs access to the node. - this.rpc = new RPC(this); - - // HTTP needs access to the node. - if (!HTTPServer.unsupported) { - this.http = new HTTPServer({ +class FullNode extends Node { + /** + * Create a full node. + * @constructor + * @param {Object?} options + */ + + constructor(options) { + super('fcoin', 'fcoin.conf', 'debug.log', options); + + this.opened = false; + + // SPV flag. + this.spv = false; + + // Instantiate block storage. + this.blocks = blockstore.create({ + network: this.network, + logger: this.logger, + prefix: this.config.prefix, + cacheSize: this.config.mb('block-cache-size'), + memory: this.memory + }); + + // Chain needs access to blocks. + this.chain = new Chain({ + network: this.network, + logger: this.logger, + blocks: this.blocks, + workers: this.workers, + memory: this.config.bool('memory'), + prefix: this.config.prefix, + maxFiles: this.config.uint('max-files'), + cacheSize: this.config.mb('cache-size'), + forceFlags: this.config.bool('force-flags'), + bip91: this.config.bool('bip91'), + bip148: this.config.bool('bip148'), + prune: this.config.bool('prune'), + checkpoints: this.config.bool('checkpoints'), + entryCache: this.config.uint('entry-cache'), + indexTX: this.config.bool('index-tx'), + indexAddress: this.config.bool('index-address') + }); + + // Fee estimation. + this.fees = new Fees(this.logger); + this.fees.init(); + + // Mempool needs access to the chain. + this.mempool = new Mempool({ + network: this.network, + logger: this.logger, + workers: this.workers, + chain: this.chain, + fees: this.fees, + memory: this.memory, + prefix: this.config.prefix, + persistent: this.config.bool('persistent-mempool'), + maxSize: this.config.mb('mempool-size'), + limitFree: this.config.bool('limit-free'), + limitFreeRelay: this.config.uint('limit-free-relay'), + requireStandard: this.config.bool('require-standard'), + rejectAbsurdFees: this.config.bool('reject-absurd-fees'), + replaceByFee: this.config.bool('replace-by-fee'), + indexAddress: this.config.bool('index-address') + }); + + // Pool needs access to the chain and mempool. + this.pool = new Pool({ + network: this.network, + logger: this.logger, + chain: this.chain, + mempool: this.mempool, + prefix: this.config.prefix, + selfish: this.config.bool('selfish'), + compact: this.config.bool('compact'), + bip37: this.config.bool('bip37'), + maxOutbound: this.config.uint('max-outbound'), + maxInbound: this.config.uint('max-inbound'), + createSocket: this.config.func('create-socket'), + proxy: this.config.str('proxy'), + onion: this.config.bool('onion'), + upnp: this.config.bool('upnp'), + seeds: this.config.array('seeds'), + nodes: this.config.array('nodes'), + only: this.config.array('only'), + publicHost: this.config.str('public-host'), + publicPort: this.config.uint('public-port'), + host: this.config.str('host'), + port: this.config.uint('port'), + listen: this.config.bool('listen'), + memory: this.memory + }); + + // Miner needs access to the chain and mempool. + this.miner = new Miner({ + network: this.network, + logger: this.logger, + workers: this.workers, + chain: this.chain, + mempool: this.mempool, + address: this.config.array('coinbase-address'), + coinbaseFlags: this.config.str('coinbase-flags'), + preverify: this.config.bool('preverify'), + maxWeight: this.config.uint('max-weight'), + reservedWeight: this.config.uint('reserved-weight'), + reservedSigops: this.config.uint('reserved-sigops') + }); + + // RPC needs access to the node. + this.rpc = new RPC(this); + + // HTTP needs access to the node. + this.http = new HTTP({ network: this.network, logger: this.logger, node: this, @@ -149,367 +152,458 @@ function FullNode(options) { host: this.config.str('http-host'), port: this.config.uint('http-port'), apiKey: this.config.str('api-key'), - noAuth: this.config.bool('no-auth') + noAuth: this.config.bool('no-auth'), + cors: this.config.bool('cors'), + maxTxs: this.config.uint('max-txs') }); + + // Indexers + if (this.config.bool('index-tx')) { + this.txindex = new TXIndexer({ + network: this.network, + logger: this.logger, + blocks: this.blocks, + chain: this.chain, + prune: this.config.bool('prune'), + memory: this.memory, + prefix: this.config.str('index-prefix', this.config.prefix) + }); + } + + if (this.config.bool('index-address')) { + this.addrindex= new AddrIndexer({ + network: this.network, + logger: this.logger, + blocks: this.blocks, + chain: this.chain, + prune: this.config.bool('prune'), + memory: this.memory, + prefix: this.config.str('index-prefix', this.config.prefix), + maxTxs: this.config.uint('max-txs') + }); + } + + this.init(); } - this._init(); -} + /** + * Initialize the node. + * @private + */ -Object.setPrototypeOf(FullNode.prototype, Node.prototype); + init() { + // Bind to errors + this.chain.on('error', err => this.error(err)); + this.mempool.on('error', err => this.error(err)); + this.pool.on('error', err => this.error(err)); + this.miner.on('error', err => this.error(err)); -/** - * Initialize the node. - * @private - */ + if (this.txindex) + this.txindex.on('error', err => this.error(err)); -FullNode.prototype._init = function _init() { - // Bind to errors - this.chain.on('error', err => this.error(err)); - this.mempool.on('error', err => this.error(err)); - this.pool.on('error', err => this.error(err)); - this.miner.on('error', err => this.error(err)); + if (this.addrindex) + this.addrindex.on('error', err => this.error(err)); - if (this.http) - this.http.on('error', err => this.error(err)); + if (this.http) + this.http.on('error', err => this.error(err)); - this.mempool.on('tx', (tx) => { - this.miner.cpu.notifyEntry(); - this.emit('tx', tx); - }); + this.mempool.on('tx', (tx) => { + this.miner.cpu.notifyEntry(); + this.emit('tx', tx); + }); - this.chain.hook('connect', async (entry, block) => { - try { - await this.mempool._addBlock(entry, block.txs); - } catch (e) { - this.error(e); - } - this.emit('block', block); - this.emit('connect', entry, block); - }); + this.chain.on('connect', async (entry, block) => { + try { + await this.mempool._addBlock(entry, block.txs); + } catch (e) { + this.error(e); + } + this.emit('block', block); + this.emit('connect', entry, block); + }); - this.chain.hook('disconnect', async (entry, block) => { - try { - await this.mempool._removeBlock(entry, block.txs); - } catch (e) { - this.error(e); - } - this.emit('disconnect', entry, block); - }); + this.chain.on('disconnect', async (entry, block) => { + try { + await this.mempool._removeBlock(entry, block.txs); + } catch (e) { + this.error(e); + } + this.emit('disconnect', entry, block); + }); - this.chain.hook('reorganize', async (tip, competitor) => { - try { - await this.mempool._handleReorg(); - } catch (e) { - this.error(e); - } - this.emit('reorganize', tip, competitor); - }); + this.chain.on('reorganize', async (tip, competitor) => { + try { + await this.mempool._handleReorg(); + } catch (e) { + this.error(e); + } + this.emit('reorganize', tip, competitor); + }); - this.chain.hook('reset', async (tip) => { - try { - await this.mempool._reset(); - } catch (e) { - this.error(e); - } - this.emit('reset', tip); - }); + this.chain.on('reset', async (tip) => { + try { + await this.mempool._reset(); + } catch (e) { + this.error(e); + } + this.emit('reset', tip); + }); - this.loadPlugins(); -}; + this.loadPlugins(); + } -/** - * Open the node and all its child objects, - * wait for the database to load. - * @alias FullNode#open - * @returns {Promise} - */ + /** + * Open the node and all its child objects, + * wait for the database to load. + * @alias FullNode#open + * @returns {Promise} + */ + + async open() { + assert(!this.opened, 'FullNode is already open.'); + this.opened = true; + + await this.handlePreopen(); + await this.blocks.open(); + await this.chain.open(); + await this.mempool.open(); + await this.miner.open(); + await this.pool.open(); -FullNode.prototype._open = async function _open() { - await this.chain.open(); - await this.mempool.open(); - await this.miner.open(); - await this.pool.open(); + if (this.txindex) + await this.txindex.open(); - await this.openPlugins(); + if (this.addrindex) + await this.addrindex.open(); + + await this.openPlugins(); - if (this.http) await this.http.open(); + await this.handleOpen(); - this.logger.info('Node is loaded.'); -}; + this.logger.info('Node is loaded.'); + } -/** - * Close the node, wait for the database to close. - * @alias FullNode#close - * @returns {Promise} - */ + /** + * Close the node, wait for the database to close. + * @alias FullNode#close + * @returns {Promise} + */ -FullNode.prototype._close = async function _close() { - if (this.http) + async close() { + assert(this.opened, 'FullNode is not open.'); + this.opened = false; + + await this.handlePreclose(); await this.http.close(); - await this.closePlugins(); + if (this.txindex) + await this.txindex.close(); - await this.pool.close(); - await this.miner.close(); - await this.mempool.close(); - await this.chain.close(); + if (this.addrindex) + await this.addrindex.close(); - this.logger.info('Node is closed.'); -}; + await this.closePlugins(); -/** - * Rescan for any missed transactions. - * @param {Number|Hash} start - Start block. - * @param {Bloom} filter - * @param {Function} iter - Iterator. - * @returns {Promise} - */ + await this.pool.close(); + await this.miner.close(); + await this.mempool.close(); + await this.chain.close(); + await this.blocks.close(); -FullNode.prototype.scan = function scan(start, filter, iter) { - return this.chain.scan(start, filter, iter); -}; + await this.handleClose(); + } -/** - * Broadcast a transaction (note that this will _not_ be verified - * by the mempool - use with care, lest you get banned from - * bitcoind nodes). - * @param {TX|Block} item - * @returns {Promise} - */ + /** + * Rescan for any missed transactions. + * @param {Number|Hash} start - Start block. + * @param {Bloom} filter + * @param {Function} iter - Iterator. + * @returns {Promise} + */ -FullNode.prototype.broadcast = async function broadcast(item) { - try { - await this.pool.broadcast(item); - } catch (e) { - this.emit('error', e); + scan(start, filter, iter) { + return this.chain.scan(start, filter, iter); } -}; -/** - * Add transaction to mempool, broadcast. - * @param {TX} tx - */ + /** + * Broadcast a transaction (note that this will _not_ be verified + * by the mempool - use with care, lest you get banned from + * bitcoind nodes). + * @param {TX|Block} item + * @returns {Promise} + */ -FullNode.prototype.sendTX = async function sendTX(tx) { - let missing; - - try { - missing = await this.mempool.addTX(tx); - } catch (err) { - // if (err.type === 'VerifyError' && err.score === 0) { - // this.error(err); - // this.logger.warning('Verification failed for tx: %s.', tx.txid()); - // this.logger.warning('Attempting to broadcast anyway...'); - // this.broadcast(tx); - // return; - // } - throw err; + async broadcast(item) { + try { + await this.pool.broadcast(item); + } catch (e) { + this.emit('error', e); + } } - if (missing) { - this.logger.warning('TX was orphaned in mempool: %s.', tx.txid()); - this.logger.warning('Attempting to broadcast anyway...'); - this.broadcast(tx); - return; - } + /** + * Add transaction to mempool, broadcast. + * @param {TX} tx + * @returns {Promise} + */ - // We need to announce by hand if - // we're running in selfish mode. - if (this.pool.options.selfish) - this.pool.broadcast(tx); -}; + async sendTX(tx) { + let missing; -/** - * Add transaction to mempool, broadcast. Silence errors. - * @param {TX} tx - * @returns {Promise} - */ + try { + missing = await this.mempool.addTX(tx); + } catch (err) { + // if (err.type === 'VerifyError' && err.score === 0) { + // this.error(err); + // this.logger.warning('Verification failed for tx: %h.', tx.hash()); + // this.logger.warning('Attempting to broadcast anyway...'); + // this.broadcast(tx); + // return; + // } + throw err; + } -FullNode.prototype.relay = async function relay(tx) { - try { - await this.sendTX(tx); - } catch (e) { - this.error(e); - } -}; + if (missing) { + this.logger.warning('TX was orphaned in mempool: %h.', tx.hash()); + this.logger.warning('Attempting to broadcast anyway...'); + this.broadcast(tx); + return; + } -/** - * Connect to the network. - * @returns {Promise} - */ + // We need to announce by hand if + // we're running in selfish mode. + if (this.pool.options.selfish) + this.broadcast(tx); + } -FullNode.prototype.connect = function connect() { - return this.pool.connect(); -}; + /** + * Add transaction to mempool, broadcast. Silence errors. + * @param {TX} tx + * @returns {Promise} + */ -/** - * Disconnect from the network. - * @returns {Promise} - */ + async relay(tx) { + try { + await this.sendTX(tx); + } catch (e) { + this.error(e); + } + } -FullNode.prototype.disconnect = function disconnect() { - return this.pool.disconnect(); -}; + /** + * Connect to the network. + * @returns {Promise} + */ -/** - * Start the blockchain sync. - */ + connect() { + return this.pool.connect(); + } -FullNode.prototype.startSync = function startSync() { - return this.pool.startSync(); -}; + /** + * Disconnect from the network. + * @returns {Promise} + */ -/** - * Stop syncing the blockchain. - */ + disconnect() { + return this.pool.disconnect(); + } -FullNode.prototype.stopSync = function stopSync() { - return this.pool.stopSync(); -}; + /** + * Start the blockchain sync. + */ -/** - * Retrieve a block from the chain database. - * @param {Hash} hash - * @returns {Promise} - Returns {@link Block}. - */ + startSync() { + if (this.txindex) + this.txindex.sync(); -FullNode.prototype.getBlock = function getBlock(hash) { - return this.chain.getBlock(hash); -}; + if (this.addrindex) + this.addrindex.sync(); -/** - * Retrieve a coin from the mempool or chain database. - * Takes into account spent coins in the mempool. - * @param {Hash} hash - * @param {Number} index - * @returns {Promise} - Returns {@link Coin}. - */ + return this.pool.startSync(); + } -FullNode.prototype.getCoin = async function getCoin(hash, index) { - const coin = this.mempool.getCoin(hash, index); + /** + * Stop syncing the blockchain. + */ - if (coin) - return coin; + stopSync() { + return this.pool.stopSync(); + } - if (this.mempool.isSpent(hash, index)) - return null; + /** + * Retrieve a block from the chain database. + * @param {Hash} hash + * @returns {Promise} - Returns {@link Block}. + */ - return await this.chain.getCoin(hash, index); -}; + getBlock(hash) { + return this.chain.getBlock(hash); + } -/** - * Get coins that pertain to an address from the mempool or chain database. - * Takes into account spent coins in the mempool. - * @param {Address} addrs - * @returns {Promise} - Returns {@link Coin}[]. - */ + /** + * Retrieve a coin from the mempool or chain database. + * Takes into account spent coins in the mempool. + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} - Returns {@link Coin}. + */ -FullNode.prototype.getCoinsByAddress = async function getCoinsByAddress(addrs) { - const mempool = this.mempool.getCoinsByAddress(addrs); - const chain = await this.chain.getCoinsByAddress(addrs); - const out = []; + async getCoin(hash, index) { + const coin = this.mempool.getCoin(hash, index); - for (const coin of chain) { - const spent = this.mempool.isSpent(coin.hash, coin.index); + if (coin) + return coin; - if (spent) - continue; + if (this.mempool.isSpent(hash, index)) + return null; - out.push(coin); + return this.chain.getCoin(hash, index); } - for (const coin of mempool) - out.push(coin); - - return out; -}; - -/** - * Retrieve transactions pertaining to an - * address from the mempool or chain database. - * @param {Address} addrs - * @returns {Promise} - Returns {@link TXMeta}[]. - */ + /** + * Retrieve transactions pertaining to an + * address from the mempool or chain database. + * @param {Address} addr + * @param {Object} options + * @param {Number} options.limit + * @param {Number} options.reverse + * @param {Buffer} options.after + * @returns {Promise} - Returns {@link TXMeta}[]. + */ + + async getMetaByAddress(addr, options = {}) { + if (!this.txindex || !this.addrindex) + return []; + + const {reverse, after} = options; + let {limit} = options; + + let metas = []; + + const confirmed = async () => { + const hashes = await this.addrindex.getHashesByAddress( + addr, {limit, reverse, after}); + + for (const hash of hashes) { + const mtx = await this.txindex.getMeta(hash); + assert(mtx); + metas.push(mtx); + } + }; + + const unconfirmed = () => { + const mempool = this.mempool.getMetaByAddress( + addr, {limit, reverse, after}); + + metas = metas.concat(mempool); + }; + + if (reverse) + unconfirmed(); + else + await confirmed(); + + if (metas.length > 0) + limit -= metas.length; + + if (limit <= 0) + return metas; + + if (reverse) + await confirmed(); + else + unconfirmed(); + + return metas; + } -FullNode.prototype.getMetaByAddress = async function getMetaByAddress(addrs) { - const mempool = this.mempool.getMetaByAddress(addrs); - const chain = await this.chain.getMetaByAddress(addrs); - return chain.concat(mempool); -}; + /** + * Retrieve a transaction from the mempool or chain database. + * @param {Hash} hash + * @returns {Promise} - Returns {@link TXMeta}. + */ -/** - * Retrieve a transaction from the mempool or chain database. - * @param {Hash} hash - * @returns {Promise} - Returns {@link TXMeta}. - */ + async getMeta(hash) { + const meta = this.mempool.getMeta(hash); -FullNode.prototype.getMeta = async function getMeta(hash) { - const meta = this.mempool.getMeta(hash); + if (meta) + return meta; - if (meta) - return meta; + if (this.txindex) + return this.txindex.getMeta(hash); - return await this.chain.getMeta(hash); -}; + return null; + } -/** - * Retrieve a spent coin viewpoint from mempool or chain database. - * @param {TXMeta} meta - * @returns {Promise} - Returns {@link CoinView}. - */ + /** + * Retrieve a spent coin viewpoint from mempool or chain database. + * @param {TXMeta} meta + * @returns {Promise} - Returns {@link CoinView}. + */ -FullNode.prototype.getMetaView = async function getMetaView(meta) { - if (meta.height === -1) - return this.mempool.getSpentView(meta.tx); - return this.chain.getSpentView(meta.tx); -}; + async getMetaView(meta) { + if (meta.height === -1) + return this.mempool.getSpentView(meta.tx); -/** - * Retrieve transactions pertaining to an - * address from the mempool or chain database. - * @param {Address} addrs - * @returns {Promise} - Returns {@link TX}[]. - */ + if (this.txindex) + return this.txindex.getSpentView(meta.tx); -FullNode.prototype.getTXByAddress = async function getTXByAddress(addrs) { - const mtxs = await this.getMetaByAddress(addrs); - const out = []; + return null; + } - for (const mtx of mtxs) - out.push(mtx.tx); + /** + * Retrieve transactions pertaining to an + * address from the mempool or chain database. + * @param {Address} addr + * @param {Object} options + * @param {Number} options.limit + * @param {Number} options.reverse + * @param {Buffer} options.after + * @returns {Promise} - Returns {@link TX}[]. + */ + + async getTXByAddress(addr, options = {}) { + const mtxs = await this.getMetaByAddress(addr, options); + const out = []; + + for (const mtx of mtxs) + out.push(mtx.tx); + + return out; + } - return out; -}; + /** + * Retrieve a transaction from the mempool or chain database. + * @param {Hash} hash + * @returns {Promise} - Returns {@link TX}. + */ -/** - * Retrieve a transaction from the mempool or chain database. - * @param {Hash} hash - * @returns {Promise} - Returns {@link TX}. - */ + async getTX(hash) { + const mtx = await this.getMeta(hash); -FullNode.prototype.getTX = async function getTX(hash) { - const mtx = await this.getMeta(hash); + if (!mtx) + return null; - if (!mtx) - return null; + return mtx.tx; + } - return mtx.tx; -}; + /** + * Test whether the mempool or chain contains a transaction. + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ -/** - * Test whether the mempool or chain contains a transaction. - * @param {Hash} hash - * @returns {Promise} - Returns Boolean. - */ + async hasTX(hash) { + if (this.mempool.hasEntry(hash)) + return true; -FullNode.prototype.hasTX = async function hasTX(hash) { - if (this.mempool.hasEntry(hash)) - return true; + if (this.txindex) + return this.txindex.hasTX(hash); - return await this.chain.hasTX(hash); -}; + return false; + } +} /* * Expose diff --git a/lib/node/http.js b/lib/node/http.js new file mode 100644 index 000000000..01de675fb --- /dev/null +++ b/lib/node/http.js @@ -0,0 +1,785 @@ +/*! + * server.js - http server for bcoin + * Copyright (c) 2014-2015, Fedor Indutny (MIT License) + * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const assert = require('bsert'); +const path = require('path'); +const {Server} = require('bweb'); +const Validator = require('bval'); +const {base58} = require('bstring'); +const {BloomFilter} = require('bfilter'); +const sha256 = require('bcrypto/lib/sha256'); +const random = require('bcrypto/lib/random'); +const {safeEqual} = require('bcrypto/lib/safe'); +const util = require('../utils/util'); +const Address = require('../primitives/address'); +const TX = require('../primitives/tx'); +const Outpoint = require('../primitives/outpoint'); +const Network = require('../protocol/network'); +const pkg = require('../pkg'); + +/** + * HTTP + * @alias module:http.Server + */ + +class HTTP extends Server { + /** + * Create an http server. + * @constructor + * @param {Object} options + */ + + constructor(options) { + super(new HTTPOptions(options)); + + this.network = this.options.network; + this.logger = this.options.logger.context('node-http'); + this.node = this.options.node; + + this.chain = this.node.chain; + this.mempool = this.node.mempool; + this.pool = this.node.pool; + this.fees = this.node.fees; + this.miner = this.node.miner; + this.rpc = this.node.rpc; + + this.init(); + } + + /** + * Initialize routes. + * @private + */ + + init() { + this.on('request', (req, res) => { + if (req.method === 'POST' && req.pathname === '/') + return; + + this.logger.debug('Request for method=%s path=%s (%s).', + req.method, req.pathname, req.socket.remoteAddress); + }); + + this.on('listening', (address) => { + this.logger.info('Node HTTP server listening on %s (port=%d).', + address.address, address.port); + }); + + this.initRouter(); + this.initSockets(); + } + + /** + * Initialize routes. + * @private + */ + + initRouter() { + if (this.options.cors) + this.use(this.cors()); + + if (!this.options.noAuth) { + this.use(this.basicAuth({ + hash: sha256.digest, + password: this.options.apiKey, + realm: 'node' + })); + } + + this.use(this.bodyParser({ + type: 'json' + })); + + this.use(this.jsonRPC()); + this.use(this.router()); + + this.error((err, req, res) => { + const code = err.statusCode || 500; + res.json(code, { + error: { + type: err.type, + code: err.code, + message: err.message + } + }); + }); + + this.get('/', async (req, res) => { + const totalTX = this.mempool ? this.mempool.map.size : 0; + const size = this.mempool ? this.mempool.getSize() : 0; + + let addr = this.pool.hosts.getLocal(); + + if (!addr) + addr = this.pool.hosts.address; + + res.json(200, { + version: pkg.version, + network: this.network.type, + chain: { + height: this.chain.height, + tip: this.chain.tip.rhash(), + progress: this.chain.getProgress() + }, + indexes: { + addr: { + enabled: Boolean(this.node.addrindex), + height: this.node.addrindex ? this.node.addrindex.height : 0 + }, + tx: { + enabled: Boolean(this.node.txindex), + height: this.node.txindex ? this.node.txindex.height : 0 + } + }, + pool: { + host: addr.host, + port: addr.port, + agent: this.pool.options.agent, + services: this.pool.options.services.toString(2), + outbound: this.pool.peers.outbound, + inbound: this.pool.peers.inbound + }, + mempool: { + tx: totalTX, + size: size + }, + time: { + uptime: this.node.uptime(), + system: util.now(), + adjusted: this.network.now(), + offset: this.network.time.offset + }, + memory: this.logger.memoryUsage() + }); + }); + + // UTXO by id + this.get('/coin/:hash/:index', async (req, res) => { + const valid = Validator.fromRequest(req); + const hash = valid.brhash('hash'); + const index = valid.u32('index'); + + enforce(hash, 'Hash is required.'); + enforce(index != null, 'Index is required.'); + enforce(!this.chain.options.spv, 'Cannot get coins in SPV mode.'); + + const coin = await this.node.getCoin(hash, index); + + if (!coin) { + res.json(404); + return; + } + + res.json(200, coin.getJSON(this.network)); + }); + + // TX by hash + this.get('/tx/:hash', async (req, res) => { + const valid = Validator.fromRequest(req); + const hash = valid.brhash('hash'); + + enforce(hash, 'Hash is required.'); + enforce(!this.chain.options.spv, 'Cannot get TX in SPV mode.'); + + const meta = await this.node.getMeta(hash); + + if (!meta) { + res.json(404); + return; + } + + const view = await this.node.getMetaView(meta); + + res.json(200, meta.getJSON(this.network, view, this.chain.height)); + }); + + // TX by address + this.get('/tx/address/:address', async (req, res) => { + const valid = Validator.fromRequest(req); + const address = valid.str('address'); + const limit = valid.uint('limit', this.options.maxTxs); + const reverse = valid.bool('reverse', false); + const after = valid.brhash('after', null); + + enforce(address, 'Address is required.'); + enforce(!this.chain.options.spv, 'Cannot get TX in SPV mode.'); + enforce(limit <= this.options.maxTxs, + `Limit above max of ${this.options.maxTxs}.`); + + const addr = Address.fromString(address, this.network); + + const metas = await this.node.getMetaByAddress( + addr, {limit, reverse, after}); + + const result = []; + + for (const meta of metas) { + const view = await this.node.getMetaView(meta); + result.push(meta.getJSON(this.network, view, this.chain.height)); + } + + res.json(200, result); + }); + + // Block by hash/height + this.get('/block/:block', async (req, res) => { + const valid = Validator.fromRequest(req); + const hash = valid.uintbrhash('block'); + + enforce(hash != null, 'Hash or height required.'); + enforce(!this.chain.options.spv, 'Cannot get block in SPV mode.'); + + const block = await this.chain.getBlock(hash); + + if (!block) { + res.json(404); + return; + } + + const view = await this.chain.getBlockView(block); + + if (!view) { + res.json(404); + return; + } + + const height = await this.chain.getHeight(hash); + const depth = this.chain.height - height + 1; + + res.json(200, block.getJSON(this.network, view, height, depth)); + }); + + // Mempool snapshot + this.get('/mempool', async (req, res) => { + enforce(this.mempool, 'No mempool available.'); + + const hashes = this.mempool.getSnapshot(); + const result = []; + + for (const hash of hashes) + result.push(util.revHex(hash)); + + res.json(200, result); + }); + + // Broadcast TX + this.post('/broadcast', async (req, res) => { + const valid = Validator.fromRequest(req); + const raw = valid.buf('tx'); + + enforce(raw, 'TX is required.'); + + const tx = TX.fromRaw(raw); + + await this.node.sendTX(tx); + + res.json(200, { success: true }); + }); + + // Estimate fee + this.get('/fee', async (req, res) => { + const valid = Validator.fromRequest(req); + const blocks = valid.u32('blocks'); + + if (!this.fees) { + res.json(200, { rate: this.network.feeRate }); + return; + } + + const fee = this.fees.estimateFee(blocks); + + res.json(200, { rate: fee }); + }); + + // Reset chain + this.post('/reset', async (req, res) => { + const valid = Validator.fromRequest(req); + const height = valid.u32('height'); + + enforce(height != null, 'Height is required.'); + enforce(height <= this.chain.height, + 'Height cannot be greater than chain tip.'); + + await this.chain.reset(height); + + res.json(200, { success: true }); + }); + } + + /** + * Handle new websocket. + * @private + * @param {WebSocket} socket + */ + + handleSocket(socket) { + socket.hook('auth', (...args) => { + if (socket.channel('auth')) + throw new Error('Already authed.'); + + if (!this.options.noAuth) { + const valid = new Validator(args); + const key = valid.str(0, ''); + + if (key.length > 255) + throw new Error('Invalid API key.'); + + const data = Buffer.from(key, 'ascii'); + const hash = sha256.digest(data); + + if (!safeEqual(hash, this.options.apiHash)) + throw new Error('Invalid API key.'); + } + + socket.join('auth'); + + this.logger.info('Successful auth from %s.', socket.host); + this.handleAuth(socket); + + return null; + }); + + socket.fire('version', { + version: pkg.version, + network: this.network.type + }); + } + + /** + * Handle new auth'd websocket. + * @private + * @param {WebSocket} socket + */ + + handleAuth(socket) { + socket.hook('watch chain', () => { + socket.join('chain'); + return null; + }); + + socket.hook('unwatch chain', () => { + socket.leave('chain'); + return null; + }); + + socket.hook('watch mempool', () => { + socket.join('mempool'); + return null; + }); + + socket.hook('unwatch mempool', () => { + socket.leave('mempool'); + return null; + }); + + socket.hook('set filter', (...args) => { + const valid = new Validator(args); + const data = valid.buf(0); + + if (!data) + throw new Error('Invalid parameter.'); + + socket.filter = BloomFilter.fromRaw(data); + + return null; + }); + + socket.hook('get tip', () => { + return this.chain.tip.toRaw(); + }); + + socket.hook('get entry', async (...args) => { + const valid = new Validator(args); + const block = valid.uintbrhash(0); + + if (block == null) + throw new Error('Invalid parameter.'); + + const entry = await this.chain.getEntry(block); + + if (!entry) + return null; + + if (!await this.chain.isMainChain(entry)) + return null; + + return entry.toRaw(); + }); + + socket.hook('get hashes', async (...args) => { + const valid = new Validator(args); + const start = valid.i32(0, -1); + const end = valid.i32(1, -1); + + return this.chain.getHashes(start, end); + }); + + socket.hook('add filter', (...args) => { + const valid = new Validator(args); + const chunks = valid.array(0); + + if (!chunks) + throw new Error('Invalid parameter.'); + + if (!socket.filter) + throw new Error('No filter set.'); + + const items = new Validator(chunks); + + for (let i = 0; i < chunks.length; i++) { + const data = items.buf(i); + + if (!data) + throw new Error('Bad data chunk.'); + + socket.filter.add(data); + + if (this.node.spv) + this.pool.watch(data); + } + + return null; + }); + + socket.hook('reset filter', () => { + socket.filter = null; + return null; + }); + + socket.hook('estimate fee', (...args) => { + const valid = new Validator(args); + const blocks = valid.u32(0); + + if (!this.fees) + return this.network.feeRate; + + return this.fees.estimateFee(blocks); + }); + + socket.hook('send', (...args) => { + const valid = new Validator(args); + const data = valid.buf(0); + + if (!data) + throw new Error('Invalid parameter.'); + + const tx = TX.fromRaw(data); + + this.node.relay(tx); + + return null; + }); + + socket.hook('rescan', (...args) => { + const valid = new Validator(args); + const start = valid.uintbrhash(0); + + if (start == null) + throw new Error('Invalid parameter.'); + + return this.scan(socket, start); + }); + } + + /** + * Bind to chain events. + * @private + */ + + initSockets() { + const pool = this.mempool || this.pool; + + this.chain.on('connect', (entry, block, view) => { + const sockets = this.channel('chain'); + + if (!sockets) + return; + + const raw = entry.toRaw(); + + this.to('chain', 'chain connect', raw); + + for (const socket of sockets) { + const txs = this.filterBlock(socket, block); + socket.fire('block connect', raw, txs); + } + }); + + this.chain.on('disconnect', (entry, block, view) => { + const sockets = this.channel('chain'); + + if (!sockets) + return; + + const raw = entry.toRaw(); + + this.to('chain', 'chain disconnect', raw); + this.to('chain', 'block disconnect', raw); + }); + + this.chain.on('reset', (tip) => { + const sockets = this.channel('chain'); + + if (!sockets) + return; + + this.to('chain', 'chain reset', tip.toRaw()); + }); + + pool.on('tx', (tx) => { + const sockets = this.channel('mempool'); + + if (!sockets) + return; + + const raw = tx.toRaw(); + + for (const socket of sockets) { + if (!this.filterTX(socket, tx)) + continue; + + socket.fire('tx', raw); + } + }); + } + + /** + * Filter block by socket. + * @private + * @param {WebSocket} socket + * @param {Block} block + * @returns {TX[]} + */ + + filterBlock(socket, block) { + if (!socket.filter) + return []; + + const txs = []; + + for (const tx of block.txs) { + if (this.filterTX(socket, tx)) + txs.push(tx.toRaw()); + } + + return txs; + } + + /** + * Filter transaction by socket. + * @private + * @param {WebSocket} socket + * @param {TX} tx + * @returns {Boolean} + */ + + filterTX(socket, tx) { + if (!socket.filter) + return false; + + let found = false; + + for (let i = 0; i < tx.outputs.length; i++) { + const output = tx.outputs[i]; + const hash = output.getHash(); + + if (!hash) + continue; + + if (socket.filter.test(hash)) { + const prevout = Outpoint.fromTX(tx, i); + socket.filter.add(prevout.toRaw()); + found = true; + } + } + + if (found) + return true; + + if (!tx.isCoinbase()) { + for (const {prevout} of tx.inputs) { + if (socket.filter.test(prevout.toRaw())) + return true; + } + } + + return false; + } + + /** + * Scan using a socket's filter. + * @private + * @param {WebSocket} socket + * @param {Hash} start + * @returns {Promise} + */ + + async scan(socket, start) { + if (!socket.filter) + return null; + + await this.node.scan(start, socket.filter, (entry, txs) => { + const block = entry.toRaw(); + const raw = []; + + for (const tx of txs) + raw.push(tx.toRaw()); + + return socket.call('block rescan', block, raw); + }); + return null; + } +} + +class HTTPOptions { + /** + * HTTPOptions + * @alias module:http.HTTPOptions + * @constructor + * @param {Object} options + */ + + constructor(options) { + this.network = Network.primary; + this.logger = null; + this.node = null; + this.apiKey = base58.encode(random.randomBytes(20)); + this.apiHash = sha256.digest(Buffer.from(this.apiKey, 'ascii')); + this.noAuth = false; + this.cors = false; + this.maxTxs = 100; + + this.prefix = null; + this.host = '127.0.0.1'; + this.port = 8080; + this.ssl = false; + this.keyFile = null; + this.certFile = null; + + this.fromOptions(options); + } + + /** + * Inject properties from object. + * @private + * @param {Object} options + * @returns {HTTPOptions} + */ + + fromOptions(options) { + assert(options); + assert(options.node && typeof options.node === 'object', + 'HTTP Server requires a Node.'); + + this.node = options.node; + this.network = options.node.network; + this.logger = options.node.logger; + + this.port = this.network.rpcPort; + + if (options.logger != null) { + assert(typeof options.logger === 'object'); + this.logger = options.logger; + } + + if (options.apiKey != null) { + assert(typeof options.apiKey === 'string', + 'API key must be a string.'); + assert(options.apiKey.length <= 255, + 'API key must be under 256 bytes.'); + this.apiKey = options.apiKey; + this.apiHash = sha256.digest(Buffer.from(this.apiKey, 'ascii')); + } + + if (options.noAuth != null) { + assert(typeof options.noAuth === 'boolean'); + this.noAuth = options.noAuth; + } + + if (options.cors != null) { + assert(typeof options.cors === 'boolean'); + this.cors = options.cors; + } + + if (options.prefix != null) { + assert(typeof options.prefix === 'string'); + this.prefix = options.prefix; + this.keyFile = path.join(this.prefix, 'key.pem'); + this.certFile = path.join(this.prefix, 'cert.pem'); + } + + if (options.host != null) { + assert(typeof options.host === 'string'); + this.host = options.host; + } + + if (options.port != null) { + assert((options.port & 0xffff) === options.port, + 'Port must be a number.'); + this.port = options.port; + } + + if (options.ssl != null) { + assert(typeof options.ssl === 'boolean'); + this.ssl = options.ssl; + } + + if (options.keyFile != null) { + assert(typeof options.keyFile === 'string'); + this.keyFile = options.keyFile; + } + + if (options.certFile != null) { + assert(typeof options.certFile === 'string'); + this.certFile = options.certFile; + } + + if (options.maxTxs != null) { + assert(Number.isSafeInteger(options.maxTxs)); + this.maxTxs = options.maxTxs; + } + + // Allow no-auth implicitly + // if we're listening locally. + if (!options.apiKey) { + if (this.host === '127.0.0.1' || this.host === '::1') + this.noAuth = true; + } + + return this; + } + + /** + * Instantiate http options from object. + * @param {Object} options + * @returns {HTTPOptions} + */ + + static fromOptions(options) { + return new HTTPOptions().fromOptions(options); + } +} + +/* + * Helpers + */ + +function enforce(value, msg) { + if (!value) { + const err = new Error(msg); + err.statusCode = 400; + throw err; + } +} + +/* + * Expose + */ + +module.exports = HTTP; diff --git a/lib/node/index.js b/lib/node/index.js index 5e156cb34..c78e3bb85 100644 --- a/lib/node/index.js +++ b/lib/node/index.js @@ -10,8 +10,8 @@ * @module node */ -exports.Config = require('./config'); exports.FullNode = require('./fullnode'); -exports.Logger = require('./logger'); +exports.HTTP = require('./http'); exports.Node = require('./node'); +exports.RPC = require('./rpc'); exports.SPVNode = require('./spvnode'); diff --git a/lib/node/logger.js b/lib/node/logger.js deleted file mode 100644 index 189d5fffb..000000000 --- a/lib/node/logger.js +++ /dev/null @@ -1,926 +0,0 @@ -/*! - * logger.js - basic logger for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const fs = require('../utils/fs'); -const util = require('../utils/util'); -const co = require('../utils/co'); -const Lock = require('../utils/lock'); - -/** - * Basic stdout and file logger. - * @alias module:node.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); - - this.level = Logger.levels.NONE; - this.colors = Logger.HAS_TTY; - this.console = true; - this.shrink = true; - this.closed = true; - this.closing = false; - this.filename = null; - this.stream = null; - this.contexts = Object.create(null); - this.locker = new Lock(); - - if (options) - this.set(options); -} - -/** - * Whether stdout is a tty FD. - * @const {Boolean} - */ - -Logger.HAS_TTY = Boolean(process.stdout && process.stdout.isTTY); - -/** - * Maximum file size. - * @const {Number} - * @default - */ - -Logger.MAX_FILE_SIZE = 20 << 20; - -/** - * Available log levels. - * @enum {Number} - */ - -Logger.levels = { - NONE: 0, - ERROR: 1, - WARNING: 2, - INFO: 3, - DEBUG: 4, - SPAM: 5 -}; - -/** - * Available log levels. - * @const {String[]} - * @default - */ - -Logger.levelsByVal = [ - 'none', - 'error', - 'warning', - 'info', - 'debug', - 'spam' -]; - -/** - * Available log levels. - * @const {String[]} - * @default - */ - -Logger.prefixByVal = [ - 'N', - 'E', - 'W', - 'I', - 'D', - 'S' -]; - -/** - * Default CSI colors. - * @const {String[]} - * @default - */ - -Logger.styles = [ - '0', - '1;31', - '1;33', - '94', - '90', - '90' -]; - -/** - * Set logger options. - * @param {Object} options - */ - -Logger.prototype.set = function set(options) { - assert(options); - assert(this.closed); - - if (typeof options === 'string') { - this.setLevel(options); - return; - } - - if (options.level != null) { - assert(typeof options.level === 'string'); - this.setLevel(options.level); - } - - if (options.colors != null && Logger.HAS_TTY) { - assert(typeof options.colors === 'boolean'); - this.colors = options.colors; - } - - if (options.console != null) { - assert(typeof options.console === 'boolean'); - this.console = options.console; - } - - if (options.shrink != null) { - assert(typeof options.shrink === 'boolean'); - this.shrink = options.shrink; - } - - if (options.filename != null) { - assert(typeof options.filename === 'string', 'Bad file.'); - this.filename = options.filename; - } -}; - -/** - * Open the logger. - * @method - * @returns {Promise} - */ - -Logger.prototype.open = async function open() { - const unlock = await this.locker.lock(); - try { - return await this._open(); - } finally { - unlock(); - } -}; - -/** - * Open the logger (no lock). - * @method - * @returns {Promise} - */ - -Logger.prototype._open = async function _open() { - if (!this.filename) { - this.closed = false; - return; - } - - if (this.stream) { - this.closed = false; - return; - } - - if (fs.unsupported) { - this.closed = false; - return; - } - - if (this.shrink) - await this.truncate(); - - this.stream = await openStream(this.filename); - this.stream.once('error', this.handleError.bind(this)); - this.closed = false; -}; - -/** - * Destroy the write stream. - * @method - * @returns {Promise} - */ - -Logger.prototype.close = async function close() { - const unlock = await this.locker.lock(); - try { - return await this._close(); - } finally { - unlock(); - } -}; - -/** - * Destroy the write stream (no lock). - * @method - * @returns {Promise} - */ - -Logger.prototype._close = async function _close() { - if (this.timer != null) { - co.clearTimeout(this.timer); - this.timer = null; - } - - if (fs.unsupported) { - this.closed = true; - this.stream = null; - return; - } - - if (this.stream) { - try { - this.closing = true; - await closeStream(this.stream); - } finally { - this.closing = false; - } - this.stream = null; - } - - this.closed = true; -}; - -/** - * Truncate the log file to the last 20mb. - * @method - * @private - * @returns {Promise} - */ - -Logger.prototype.truncate = async function truncate() { - if (!this.filename) - return; - - if (fs.unsupported) - return; - - assert(!this.stream); - - let stat; - try { - stat = await fs.stat(this.filename); - } catch (e) { - if (e.code === 'ENOENT') - return; - throw e; - } - - const maxSize = Logger.MAX_FILE_SIZE; - - if (stat.size <= maxSize + (maxSize / 10)) - return; - - this.debug('Truncating log file to %d bytes.', maxSize); - - const fd = await fs.open(this.filename, 'r+'); - const data = Buffer.allocUnsafe(maxSize); - - await fs.read(fd, data, 0, maxSize, stat.size - maxSize); - await fs.ftruncate(fd, maxSize); - await fs.write(fd, data, 0, maxSize, 0); - await fs.close(fd); -}; - -/** - * Handle write stream error. - * @param {Error} err - */ - -Logger.prototype.handleError = function handleError(err) { - try { - this.stream.close(); - } catch (e) { - ; - } - - this.stream = null; - this.retry(); -}; - -/** - * Try to reopen the logger. - * @method - * @private - * @returns {Promise} - */ - -Logger.prototype.reopen = async function reopen() { - const unlock = await this.locker.lock(); - try { - return await this._reopen(); - } finally { - unlock(); - } -}; - -/** - * Try to reopen the logger (no lock). - * @method - * @private - * @returns {Promise} - */ - -Logger.prototype._reopen = async function _reopen() { - if (this.stream) - return; - - if (this.closed) - return; - - if (fs.unsupported) - return; - - try { - this.stream = await openStream(this.filename); - } catch (e) { - this.retry(); - return; - } - - this.stream.once('error', this.handleError.bind(this)); -}; - -/** - * Try to reopen the logger after a timeout. - * @method - * @private - * @returns {Promise} - */ - -Logger.prototype.retry = function retry() { - assert(this.timer == null); - this.timer = co.setTimeout(() => { - this.timer = null; - this.reopen(); - }, 10000, this); -}; - -/** - * Set the log file location. - * @param {String} filename - */ - -Logger.prototype.setFile = function setFile(filename) { - assert(typeof filename === 'string'); - assert(!this.stream, 'Log stream has already been created.'); - this.filename = filename; -}; - -/** - * Set or reset the log level. - * @param {String} level - */ - -Logger.prototype.setLevel = function setLevel(name) { - const level = Logger.levels[name.toUpperCase()]; - 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(...args) { - if (this.level < Logger.levels.ERROR) - return; - - const err = args[0]; - - if (err instanceof Error) { - this.logError(Logger.levels.ERROR, null, err); - return; - } - - this.log(Logger.levels.ERROR, null, args); -}; - -/** - * Output a log to the `warning` log level. - * @param {String|Object} obj - * @param {...Object} args - */ - -Logger.prototype.warning = function warning(...args) { - if (this.level < Logger.levels.WARNING) - return; - - const err = args[0]; - - if (err instanceof Error) { - this.logError(Logger.levels.WARNING, null, err); - return; - } - - this.log(Logger.levels.WARNING, null, args); -}; - -/** - * Output a log to the `info` log level. - * @param {String|Object} obj - * @param {...Object} args - */ - -Logger.prototype.info = function info(...args) { - if (this.level < Logger.levels.INFO) - return; - - const err = args[0]; - - if (err instanceof Error) { - this.logError(Logger.levels.INFO, null, err); - return; - } - - this.log(Logger.levels.INFO, null, args); -}; - -/** - * Output a log to the `debug` log level. - * @param {String|Object} obj - * @param {...Object} args - */ - -Logger.prototype.debug = function debug(...args) { - if (this.level < Logger.levels.DEBUG) - return; - - const err = args[0]; - - if (err instanceof Error) { - this.logError(Logger.levels.DEBUG, null, err); - return; - } - - this.log(Logger.levels.DEBUG, null, args); -}; - -/** - * Output a log to the `spam` log level. - * @param {String|Object} obj - * @param {...Object} args - */ - -Logger.prototype.spam = function spam(...args) { - if (this.level < Logger.levels.SPAM) - return; - - const err = args[0]; - - if (err instanceof Error) { - this.logError(Logger.levels.SPAM, null, err); - return; - } - - this.log(Logger.levels.SPAM, null, args); -}; - -/** - * Output a log to the desired log level. - * Note that this bypasses the level check. - * @param {String} level - * @param {String|null} module - * @param {Object[]} args - */ - -Logger.prototype.log = function log(level, module, args) { - if (this.closed) - return; - - if (this.level < level) - return; - - this.writeConsole(level, module, args); - this.writeStream(level, module, args); -}; - -/** - * Create logger context. - * @param {String} module - * @returns {LoggerContext} - */ - -Logger.prototype.context = function context(module) { - let ctx = this.contexts[module]; - - if (!ctx) { - ctx = new LoggerContext(this, module); - this.contexts[module] = ctx; - } - - return ctx; -}; - -/** - * Write log to the console. - * @param {String} level - * @param {String|null} module - * @param {Object[]} args - */ - -Logger.prototype.writeConsole = function writeConsole(level, module, args) { - const name = Logger.levelsByVal[level]; - - assert(name, 'Invalid log level.'); - - if (!this.console) - return false; - - if (!process.stdout) { - let msg = `[${name}] `; - - if (module) - msg += `(${module}) `; - - if (typeof args[0] === 'object') { - return level === Logger.levels.ERROR - ? console.error(msg, args[0]) - : console.log(msg, args[0]); - } - - msg += util.format(args, false); - - if (level === Logger.levels.ERROR) { - console.error(msg); - return true; - } - - console.log(msg); - - return true; - } - - let msg; - if (this.colors) { - const color = Logger.styles[level]; - assert(color); - - msg = `\x1b[${color}m[${name}]\x1b[m `; - } else { - msg = `[${name}] `; - } - - if (module) - msg += `(${module}) `; - - msg += util.format(args, this.colors); - msg += '\n'; - - return level === Logger.levels.ERROR - ? process.stderr.write(msg) - : process.stdout.write(msg); -}; - -/** - * Write a string to the output stream (usually a file). - * @param {String} level - * @param {String|null} module - * @param {Object[]} args - */ - -Logger.prototype.writeStream = function writeStream(level, module, args) { - const name = Logger.prefixByVal[level]; - - assert(name, 'Invalid log level.'); - - if (!this.stream) - return; - - if (this.closing) - return; - - let msg = `[${name}:${util.date()}] `; - - if (module) - msg += `(${module}) `; - - msg += util.format(args, false); - msg += '\n'; - - this.stream.write(msg); -}; - -/** - * Helper to parse an error into a nicer - * format. Call's `log` internally. - * @private - * @param {Number} level - * @param {String|null} module - * @param {Error} err - */ - -Logger.prototype.logError = function logError(level, module, err) { - if (this.closed) - return; - - if (fs.unsupported && this.console) { - if (level <= Logger.levels.WARNING) - console.error(err); - } - - let msg = String(err.message).replace(/^ *Error: */, ''); - - if (level !== Logger.levels.ERROR) - msg = `Error: ${msg}`; - - this.log(level, module, [msg]); - - if (level <= Logger.levels.WARNING) { - if (this.stream) - this.stream.write(err.stack + '\n'); - } -}; - -/** - * Log the current memory usage. - * @param {String|null} module - */ - -Logger.prototype.memory = function memory(module) { - const mem = util.memoryUsage(); - - this.log(Logger.levels.DEBUG, module, [ - 'Memory: rss=%dmb, js-heap=%d/%dmb native-heap=%dmb', - mem.total, - mem.jsHeap, - mem.jsHeapTotal, - mem.nativeHeap - ]); -}; - -/** - * Basic stdout and file logger. - * @constructor - * @ignore - * @param {Logger} logger - * @param {String} module - */ - -function LoggerContext(logger, module) { - if (!(this instanceof LoggerContext)) - return new LoggerContext(logger, module); - - assert(typeof module === 'string'); - - this.logger = logger; - this.module = module; -} - -/** - * Open the logger. - * @returns {Promise} - */ - -LoggerContext.prototype.open = function open() { - return this.logger.open(); -}; - -/** - * Destroy the write stream. - * @returns {Promise} - */ - -LoggerContext.prototype.close = function close() { - return this.logger.close(); -}; - -/** - * Set the log file location. - * @param {String} filename - */ - -LoggerContext.prototype.setFile = function setFile(filename) { - this.logger.setFile(filename); -}; - -/** - * Set or reset the log level. - * @param {String} level - */ - -LoggerContext.prototype.setLevel = function setLevel(name) { - this.logger.setLevel(name); -}; - -/** - * Output a log to the `error` log level. - * @param {String|Object|Error} err - * @param {...Object} args - */ - -LoggerContext.prototype.error = function error(...args) { - if (this.logger.level < Logger.levels.ERROR) - return; - - const err = args[0]; - - if (err instanceof Error) { - this.logError(Logger.levels.ERROR, err); - return; - } - - this.log(Logger.levels.ERROR, args); -}; - -/** - * Output a log to the `warning` log level. - * @param {String|Object} obj - * @param {...Object} args - */ - -LoggerContext.prototype.warning = function warning(...args) { - if (this.logger.level < Logger.levels.WARNING) - return; - - const err = args[0]; - - if (err instanceof Error) { - this.logError(Logger.levels.WARNING, err); - return; - } - - this.log(Logger.levels.WARNING, args); -}; - -/** - * Output a log to the `info` log level. - * @param {String|Object} obj - * @param {...Object} args - */ - -LoggerContext.prototype.info = function info(...args) { - if (this.logger.level < Logger.levels.INFO) - return; - - const err = args[0]; - - if (err instanceof Error) { - this.logError(Logger.levels.INFO, err); - return; - } - - this.log(Logger.levels.INFO, args); -}; - -/** - * Output a log to the `debug` log level. - * @param {String|Object} obj - * @param {...Object} args - */ - -LoggerContext.prototype.debug = function debug(...args) { - if (this.logger.level < Logger.levels.DEBUG) - return; - - const err = args[0]; - - if (err instanceof Error) { - this.logError(Logger.levels.DEBUG, err); - return; - } - - this.log(Logger.levels.DEBUG, args); -}; - -/** - * Output a log to the `spam` log level. - * @param {String|Object} obj - * @param {...Object} args - */ - -LoggerContext.prototype.spam = function spam(...args) { - if (this.logger.level < Logger.levels.SPAM) - return; - - const err = args[0]; - - if (err instanceof Error) { - this.logError(Logger.levels.SPAM, err); - return; - } - - this.log(Logger.levels.SPAM, args); -}; - -/** - * Output a log to the desired log level. - * Note that this bypasses the level check. - * @param {String} level - * @param {Object[]} args - */ - -LoggerContext.prototype.log = function log(level, args) { - this.logger.log(level, this.module, args); -}; - -/** - * Create logger context. - * @param {String} module - * @returns {LoggerContext} - */ - -LoggerContext.prototype.context = function context(module) { - return new LoggerContext(this.logger, module); -}; - -/** - * Helper to parse an error into a nicer - * format. Call's `log` internally. - * @private - * @param {Number} level - * @param {Error} err - */ - -LoggerContext.prototype.logError = function logError(level, err) { - this.logger.logError(level, this.module, err); -}; - -/** - * Log the current memory usage. - */ - -LoggerContext.prototype.memory = function memory() { - this.logger.memory(this.module); -}; - -/* - * Default - */ - -Logger.global = new Logger(); - -/* - * Helpers - */ - -function openStream(filename) { - return new Promise((resolve, reject) => { - const stream = fs.createWriteStream(filename, { flags: 'a' }); - - const cleanup = () => { - /* eslint-disable */ - stream.removeListener('error', onError); - stream.removeListener('open', onOpen); - /* eslint-enable */ - }; - - const onError = (err) => { - try { - stream.close(); - } catch (e) { - ; - } - cleanup(); - reject(err); - }; - - const onOpen = () => { - cleanup(); - resolve(stream); - }; - - stream.once('error', onError); - stream.once('open', onOpen); - }); -} - -function closeStream(stream) { - return new Promise((resolve, reject) => { - const cleanup = () => { - /* eslint-disable */ - stream.removeListener('error', onError); - stream.removeListener('close', onClose); - /* eslint-enable */ - }; - - const onError = (err) => { - cleanup(); - reject(err); - }; - - const onClose = () => { - cleanup(); - resolve(stream); - }; - - stream.removeAllListeners('error'); - stream.removeAllListeners('close'); - stream.once('error', onError); - stream.once('close', onClose); - - stream.close(); - }); -} - -/* - * Expose - */ - -module.exports = Logger; diff --git a/lib/node/node.js b/lib/node/node.js index a05e07990..f3d0b7ece 100644 --- a/lib/node/node.js +++ b/lib/node/node.js @@ -7,408 +7,407 @@ 'use strict'; -const assert = require('assert'); -const AsyncObject = require('../utils/asyncobject'); -const util = require('../utils/util'); +const assert = require('bsert'); +const EventEmitter = require('events'); +const fs = require('bfile'); +const Logger = require('blgr'); +const Config = require('bcfg'); const Network = require('../protocol/network'); -const Logger = require('./logger'); const WorkerPool = require('../workers/workerpool'); -const secp256k1 = require('../crypto/secp256k1'); -const native = require('../native'); -const Config = require('./config'); /** + * Node * Base class from which every other * Node-like object inherits. * @alias module:node.Node - * @constructor + * @extends EventEmitter * @abstract - * @param {Object} options */ -function Node(options) { - if (!(this instanceof Node)) - return new Node(options); - - AsyncObject.call(this); - - this.config = new Config('fcoin'); - this.config.inject(options); - this.config.load(options); - - if (options.config) - this.config.open('fcoin.conf'); - - this.network = Network.get(this.config.network); - this.startTime = -1; - this.bound = []; - this.plugins = Object.create(null); - this.stack = []; - - this.logger = null; - this.workers = null; - - this.spv = false; - this.chain = null; - this.fees = null; - this.mempool = null; - this.pool = null; - this.miner = null; - this.http = null; - - this.init(); -} - -Object.setPrototypeOf(Node.prototype, AsyncObject.prototype); - -/** - * Initialize options. - * @private - * @param {Object} options - */ +class Node extends EventEmitter { + /** + * Create a node. + * @constructor + * @param {Object} options + */ + + constructor(module, config, file, options) { + super(); + + this.config = new Config(module, { + suffix: 'network', + fallback: 'main', + alias: { 'n': 'network' } + }); + + this.config.inject(options); + this.config.load(options); + + if (options.file || options.config) + this.config.open(config); + + this.network = Network.get(this.config.getSuffix()); + this.memory = this.config.bool('memory', true); + this.startTime = -1; + this.bound = []; + this.plugins = Object.create(null); + this.stack = []; + + this.logger = null; + this.workers = null; + + this.spv = false; + this.blocks = null; + this.chain = null; + this.fees = null; + this.mempool = null; + this.pool = null; + this.miner = null; + this.http = null; + this.txindex = null; + this.addrindex = null; + + this._init(file); + } -Node.prototype.initOptions = function initOptions() { - let logger = new Logger(); - const config = this.config; + /** + * Initialize node. + * @private + * @param {Object} options + */ + + _init(file) { + const config = this.config; + + let logger = new Logger(); + + if (config.has('logger')) + logger = config.obj('logger'); + + logger.set({ + filename: !this.memory && config.bool('log-file') + ? config.location(file) + : null, + level: config.str('log-level'), + console: config.bool('log-console'), + shrink: config.bool('log-shrink') + }); + + this.logger = logger.context('node'); + + this.workers = new WorkerPool({ + enabled: config.bool('workers'), + size: config.uint('workers-size'), + timeout: config.uint('workers-timeout'), + file: config.str('worker-file') + }); + + this.on('error', () => {}); + + this.workers.on('spawn', (child) => { + this.logger.info('Spawning worker process: %d.', child.id); + }); + + this.workers.on('exit', (code, child) => { + this.logger.warning('Worker %d exited: %s.', child.id, code); + }); + + this.workers.on('log', (text, child) => { + this.logger.debug('Worker %d says:', child.id); + this.logger.debug(text); + }); + + this.workers.on('error', (err, child) => { + if (child) { + this.logger.error('Worker %d error: %s', child.id, err.message); + return; + } + this.emit('error', err); + }); + } - if (config.has('logger')) - logger = config.obj('logger'); + /** + * Ensure prefix directory. + * @returns {Promise} + */ - logger.set({ - filename: config.bool('log-file') - ? config.location('debug.log') - : null, - level: config.str('log-level'), - console: config.bool('log-console'), - shrink: config.bool('log-shrink') - }); + async ensure() { + if (fs.unsupported) + return undefined; - this.logger = logger.context('node'); + if (this.memory) + return undefined; - this.workers = new WorkerPool({ - enabled: config.bool('workers'), - size: config.uint('workers-size'), - timeout: config.uint('workers-timeout'), - file: config.str('worker-file') - }); -}; + if (this.blocks) + await this.blocks.ensure(); -/** - * Initialize node. - * @private - * @param {Object} options - */ + return fs.mkdirp(this.config.prefix); + } -Node.prototype.init = function init() { - this.initOptions(); + /** + * Create a file path using `prefix`. + * @param {String} file + * @returns {String} + */ - this.on('error', () => {}); + location(name) { + return this.config.location(name); + } - this.workers.on('spawn', (child) => { - this.logger.info('Spawning worker process: %d.', child.id); - }); + /** + * Open node. Bind all events. + * @private + */ + + async handlePreopen() { + await this.logger.open(); + await this.workers.open(); + + this._bind(this.network.time, 'offset', (offset) => { + this.logger.info( + 'Time offset: %d (%d minutes).', + offset, offset / 60 | 0); + }); + + this._bind(this.network.time, 'sample', (sample, total) => { + this.logger.debug( + 'Added time data: samples=%d, offset=%d (%d minutes).', + total, sample, sample / 60 | 0); + }); + + this._bind(this.network.time, 'mismatch', () => { + this.logger.warning('Adjusted time mismatch!'); + this.logger.warning('Please make sure your system clock is correct!'); + }); + } - this.workers.on('exit', (code, child) => { - this.logger.warning('Worker %d exited: %s.', child.id, code); - }); + /** + * Open node. + * @private + */ - this.workers.on('log', (text, child) => { - this.logger.debug('Worker %d says:', child.id); - this.logger.debug(text); - }); + async handleOpen() { + this.startTime = Date.now(); - this.workers.on('error', (err, child) => { - if (child) { - this.logger.error('Worker %d error: %s', child.id, err.message); - return; + if (!this.workers.enabled) { + this.logger.warning('Warning: worker pool is disabled.'); + this.logger.warning('Verification will be slow.'); } - this.emit('error', err); - }); - - this.hook('preopen', () => this.handlePreopen()); - this.hook('preclose', () => this.handlePreclose()); - this.hook('open', () => this.handleOpen()); - this.hook('close', () => this.handleClose()); -}; - -/** - * Ensure prefix directory. - * @returns {Promise} - */ - -Node.prototype.ensure = function ensure() { - return this.config.ensure(); -}; - -/** - * Create a file path using `prefix`. - * @param {String} file - * @returns {String} - */ - -Node.prototype.location = function location(name) { - return this.config.location(name); -}; - -/** - * Open node. Bind all events. - * @private - */ + } -Node.prototype.handlePreopen = async function handlePreopen() { - await this.logger.open(); - await this.workers.open(); + /** + * Open node. Bind all events. + * @private + */ - this.bind(this.network.time, 'offset', (offset) => { - this.logger.info('Time offset: %d (%d minutes).', offset, offset / 60 | 0); - }); + async handlePreclose() { + ; + } - this.bind(this.network.time, 'sample', (sample, total) => { - this.logger.debug( - 'Added time data: samples=%d, offset=%d (%d minutes).', - total, sample, sample / 60 | 0); - }); + /** + * Close node. Unbind all events. + * @private + */ - this.bind(this.network.time, 'mismatch', () => { - this.logger.warning('Adjusted time mismatch!'); - this.logger.warning('Please make sure your system clock is correct!'); - }); -}; + async handleClose() { + for (const [obj, event, listener] of this.bound) + obj.removeListener(event, listener); -/** - * Open node. - * @private - */ + this.bound.length = 0; + this.startTime = -1; -Node.prototype.handleOpen = async function handleOpen() { - this.startTime = util.now(); + this.logger.info('Node is closed.'); - if (!secp256k1.binding) { - this.logger.warning('Warning: secp256k1-node was not built.'); - this.logger.warning('Verification will be slow.'); + await this.workers.close(); + await this.logger.close(); } - if (!native.binding) { - this.logger.warning('Warning: bcoin-native was not built.'); - this.logger.warning('Hashing will be slow.'); + /** + * Bind to an event on `obj`, save listener for removal. + * @private + * @param {EventEmitter} obj + * @param {String} event + * @param {Function} listener + */ + + _bind(obj, event, listener) { + this.bound.push([obj, event, listener]); + obj.on(event, listener); } - if (!this.workers.enabled) { - this.logger.warning('Warning: worker pool is disabled.'); - this.logger.warning('Verification will be slow.'); + /** + * Emit and log an error. + * @private + * @param {Error} err + */ + + error(err) { + this.logger.error(err); + this.emit('error', err); } -}; -/** - * Open node. Bind all events. - * @private - */ + /** + * Get node uptime in seconds. + * @returns {Number} + */ -Node.prototype.handlePreclose = async function handlePreclose() { - ; -}; + uptime() { + if (this.startTime === -1) + return 0; -/** - * Close node. Unbind all events. - * @private - */ + return Math.floor((Date.now() - this.startTime) / 1000); + } -Node.prototype.handleClose = async function handleClose() { - for (const [obj, event, listener] of this.bound) - obj.removeListener(event, listener); + /** + * Attach a plugin. + * @param {Object} plugin + * @returns {Object} Plugin instance. + */ - this.bound.length = 0; - this.startTime = -1; + use(plugin) { + assert(plugin, 'Plugin must be an object.'); + assert(typeof plugin.init === 'function', '`init` must be a function.'); - await this.workers.close(); - await this.logger.close(); -}; + assert(!this.loaded, 'Cannot add plugin after node is loaded.'); -/** - * Bind to an event on `obj`, save listener for removal. - * @private - * @param {EventEmitter} obj - * @param {String} event - * @param {Function} listener - */ + const instance = plugin.init(this); -Node.prototype.bind = function bind(obj, event, listener) { - this.bound.push([obj, event, listener]); - obj.on(event, listener); -}; + assert(!instance.open || typeof instance.open === 'function', + '`open` must be a function.'); + assert(!instance.close || typeof instance.close === 'function', + '`close` must be a function.'); -/** - * Emit and log an error. - * @private - * @param {Error} err - */ + if (plugin.id) { + assert(typeof plugin.id === 'string', '`id` must be a string.'); -Node.prototype.error = function error(err) { - this.logger.error(err); - this.emit('error', err); -}; + // Reserved names + switch (plugin.id) { + case 'chain': + case 'fees': + case 'mempool': + case 'miner': + case 'pool': + case 'rpc': + case 'http': + assert(false, `${plugin.id} is already added.`); + break; + } -/** - * Get node uptime in seconds. - * @returns {Number} - */ + assert(!this.plugins[plugin.id], `${plugin.id} is already added.`); -Node.prototype.uptime = function uptime() { - if (this.startTime === -1) - return 0; + this.plugins[plugin.id] = instance; + } - return util.now() - this.startTime; -}; + this.stack.push(instance); -/** - * Attach a plugin. - * @param {Object} plugin - * @returns {Object} Plugin instance. - */ + if (typeof instance.on === 'function') + instance.on('error', err => this.error(err)); -Node.prototype.use = function use(plugin) { - assert(plugin, 'Plugin must be an object.'); - assert(typeof plugin.init === 'function', '`init` must be a function.'); + return instance; + } - assert(!this.loaded, 'Cannot add plugin after node is loaded.'); + /** + * Test whether a plugin is available. + * @param {String} name + * @returns {Boolean} + */ - const instance = plugin.init(this); + has(name) { + return this.plugins[name] != null; + } - assert(!instance.open || typeof instance.open === 'function', - '`open` must be a function.'); - assert(!instance.close || typeof instance.close === 'function', - '`close` must be a function.'); + /** + * Get a plugin. + * @param {String} name + * @returns {Object|null} + */ - if (plugin.id) { - assert(typeof plugin.id === 'string', '`id` must be a string.'); + get(name) { + assert(typeof name === 'string', 'Plugin name must be a string.'); - // Reserved names - switch (plugin.id) { + // Reserved names. + switch (name) { case 'chain': + assert(this.chain, 'chain is not loaded.'); + return this.chain; case 'fees': + assert(this.fees, 'fees is not loaded.'); + return this.fees; case 'mempool': + assert(this.mempool, 'mempool is not loaded.'); + return this.mempool; case 'miner': + assert(this.miner, 'miner is not loaded.'); + return this.miner; case 'pool': + assert(this.pool, 'pool is not loaded.'); + return this.pool; case 'rpc': + assert(this.rpc, 'rpc is not loaded.'); + return this.rpc; case 'http': - assert(false, `${plugin.id} is already added.`); - break; + assert(this.http, 'http is not loaded.'); + return this.http; } - assert(!this.plugins[plugin.id], `${plugin.id} is already added.`); - - this.plugins[plugin.id] = instance; + return this.plugins[name] || null; } - this.stack.push(instance); - - if (typeof instance.on === 'function') - instance.on('error', err => this.error(err)); - - return instance; -}; - -/** - * Test whether a plugin is available. - * @param {String} name - * @returns {Boolean} - */ - -Node.prototype.has = function has(name) { - return this.plugins[name] != null; -}; - -/** - * Get a plugin. - * @param {String} name - * @returns {Object|null} - */ - -Node.prototype.get = function get(name) { - assert(typeof name === 'string', 'Plugin name must be a string.'); - - // Reserved names. - switch (name) { - case 'chain': - assert(this.chain, 'chain is not loaded.'); - return this.chain; - case 'fees': - assert(this.fees, 'fees is not loaded.'); - return this.fees; - case 'mempool': - assert(this.mempool, 'mempool is not loaded.'); - return this.mempool; - case 'miner': - assert(this.miner, 'miner is not loaded.'); - return this.miner; - case 'pool': - assert(this.pool, 'pool is not loaded.'); - return this.pool; - case 'rpc': - assert(this.rpc, 'rpc is not loaded.'); - return this.rpc; - case 'http': - assert(this.http, 'http is not loaded.'); - return this.http; + /** + * Require a plugin. + * @param {String} name + * @returns {Object} + * @throws {Error} on onloaded plugin + */ + + require(name) { + const plugin = this.get(name); + assert(plugin, `${name} is not loaded.`); + return plugin; } - return this.plugins[name] || null; -}; - -/** - * Require a plugin. - * @param {String} name - * @returns {Object} - * @throws {Error} on onloaded plugin - */ - -Node.prototype.require = function require(name) { - const plugin = this.get(name); - assert(plugin, `${name} is not loaded.`); - return plugin; -}; - -/** - * Load plugins. - * @private - */ - -Node.prototype.loadPlugins = function loadPlugins() { - const plugins = this.config.array('plugins', []); - const loader = this.config.func('loader'); - - for (let plugin of plugins) { - if (typeof plugin === 'string') { - assert(loader, 'Must pass a loader function.'); - plugin = loader(plugin); + /** + * Load plugins. + * @private + */ + + loadPlugins() { + const plugins = this.config.array('plugins', []); + const loader = this.config.func('loader'); + + for (let plugin of plugins) { + if (typeof plugin === 'string') { + assert(loader, 'Must pass a loader function.'); + plugin = loader(plugin); + } + this.use(plugin); } - this.use(plugin); } -}; -/** - * Open plugins. - * @private - */ + /** + * Open plugins. + * @private + */ -Node.prototype.openPlugins = async function openPlugins() { - for (const plugin of this.stack) { - if (plugin.open) - await plugin.open(); + async openPlugins() { + for (const plugin of this.stack) { + if (plugin.open) + await plugin.open(); + } } -}; -/** - * Close plugins. - * @private - */ + /** + * Close plugins. + * @private + */ -Node.prototype.closePlugins = async function closePlugins() { - for (const plugin of this.stack) { - if (plugin.close) - await plugin.close(); + async closePlugins() { + for (const plugin of this.stack) { + if (plugin.close) + await plugin.close(); + } } -}; +} /* * Expose diff --git a/lib/node/rpc.js b/lib/node/rpc.js new file mode 100644 index 000000000..a46b2025d --- /dev/null +++ b/lib/node/rpc.js @@ -0,0 +1,2865 @@ +/*! + * rpc.js - bitcoind-compatible json rpc for bcoin. + * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const assert = require('bsert'); +const bweb = require('bweb'); +const {Lock} = require('bmutex'); +const IP = require('binet'); +const Validator = require('bval'); +const {BufferMap, BufferSet} = require('buffer-map'); +const hash160 = require('bcrypto/lib/hash160'); +const hash256 = require('bcrypto/lib/hash256'); +const {safeEqual} = require('bcrypto/lib/safe'); +const secp256k1 = require('bcrypto/lib/secp256k1'); +const util = require('../utils/util'); +const common = require('../blockchain/common'); +const Amount = require('../btc/amount'); +const NetAddress = require('../net/netaddress'); +const Script = require('../script/script'); +const Address = require('../primitives/address'); +const Block = require('../primitives/block'); +const Headers = require('../primitives/headers'); +const Input = require('../primitives/input'); +const KeyRing = require('../primitives/keyring'); +const MerkleBlock = require('../primitives/merkleblock'); +const MTX = require('../primitives/mtx'); +const Network = require('../protocol/network'); +const Outpoint = require('../primitives/outpoint'); +const Output = require('../primitives/output'); +const TX = require('../primitives/tx'); +const consensus = require('../protocol/consensus'); +const pkg = require('../pkg'); +const RPCBase = bweb.RPC; +const RPCError = bweb.RPCError; + +/* + * Constants + */ + +const errs = { + // Standard JSON-RPC 2.0 errors + INVALID_REQUEST: bweb.errors.INVALID_REQUEST, + METHOD_NOT_FOUND: bweb.errors.METHOD_NOT_FOUND, + INVALID_PARAMS: bweb.errors.INVALID_PARAMS, + INTERNAL_ERROR: bweb.errors.INTERNAL_ERROR, + PARSE_ERROR: bweb.errors.PARSE_ERROR, + + // General application defined errors + MISC_ERROR: -1, + FORBIDDEN_BY_SAFE_MODE: -2, + TYPE_ERROR: -3, + INVALID_ADDRESS_OR_KEY: -5, + OUT_OF_MEMORY: -7, + INVALID_PARAMETER: -8, + DATABASE_ERROR: -20, + DESERIALIZATION_ERROR: -22, + VERIFY_ERROR: -25, + VERIFY_REJECTED: -26, + VERIFY_ALREADY_IN_CHAIN: -27, + IN_WARMUP: -28, + + // P2P client errors + CLIENT_NOT_CONNECTED: -9, + CLIENT_IN_INITIAL_DOWNLOAD: -10, + CLIENT_NODE_ALREADY_ADDED: -23, + CLIENT_NODE_NOT_ADDED: -24, + CLIENT_NODE_NOT_CONNECTED: -29, + CLIENT_INVALID_IP_OR_SUBNET: -30, + CLIENT_P2P_DISABLED: -31 +}; + +const MAGIC_STRING = 'Bitcoin Signed Message:\n'; + +/** + * Bitcoin RPC + * @alias module:http.RPC + * @extends bweb.RPC + */ + +class RPC extends RPCBase { + /** + * Create RPC. + * @param {Node} node + */ + + constructor(node) { + super(); + + assert(node, 'RPC requires a Node.'); + + this.node = node; + this.network = node.network; + this.workers = node.workers; + this.chain = node.chain; + this.mempool = node.mempool; + this.pool = node.pool; + this.fees = node.fees; + this.miner = node.miner; + this.logger = node.logger.context('node-rpc'); + this.locker = new Lock(); + + this.mining = false; + this.procLimit = 0; + this.attempt = null; + this.lastActivity = 0; + this.boundChain = false; + this.nonce1 = 0; + this.nonce2 = 0; + this.merkleMap = new BufferMap(); + this.pollers = []; + + this.init(); + } + + getCode(err) { + switch (err.type) { + case 'RPCError': + return err.code; + case 'ValidationError': + return errs.TYPE_ERROR; + case 'EncodingError': + return errs.DESERIALIZATION_ERROR; + default: + return errs.INTERNAL_ERROR; + } + } + + handleCall(cmd, query) { + if (cmd.method !== 'getwork' + && cmd.method !== 'getblocktemplate' + && cmd.method !== 'getbestblockhash') { + this.logger.debug('Handling RPC call: %s.', cmd.method); + if (cmd.method !== 'submitblock' + && cmd.method !== 'getmemorypool') { + this.logger.debug(cmd.params); + } + } + + if (cmd.method === 'getwork') { + if (query.longpoll) + cmd.method = 'getworklp'; + } + } + + init() { + this.add('stop', this.stop); + this.add('help', this.help); + + this.add('getblockchaininfo', this.getBlockchainInfo); + this.add('getbestblockhash', this.getBestBlockHash); + this.add('getblockcount', this.getBlockCount); + this.add('getblock', this.getBlock); + this.add('getblockbyheight', this.getBlockByHeight); + this.add('getblockhash', this.getBlockHash); + this.add('getblockheader', this.getBlockHeader); + this.add('getchaintips', this.getChainTips); + this.add('getdifficulty', this.getDifficulty); + this.add('getmempoolancestors', this.getMempoolAncestors); + this.add('getmempooldescendants', this.getMempoolDescendants); + this.add('getmempoolentry', this.getMempoolEntry); + this.add('getmempoolinfo', this.getMempoolInfo); + this.add('getrawmempool', this.getRawMempool); + this.add('gettxout', this.getTXOut); + this.add('gettxoutsetinfo', this.getTXOutSetInfo); + this.add('pruneblockchain', this.pruneBlockchain); + this.add('verifychain', this.verifyChain); + + this.add('invalidateblock', this.invalidateBlock); + this.add('reconsiderblock', this.reconsiderBlock); + + this.add('getnetworkhashps', this.getNetworkHashPS); + this.add('getmininginfo', this.getMiningInfo); + this.add('prioritisetransaction', this.prioritiseTransaction); + this.add('getwork', this.getWork); + this.add('getworklp', this.getWorkLongpoll); + this.add('getblocktemplate', this.getBlockTemplate); + this.add('submitblock', this.submitBlock); + this.add('verifyblock', this.verifyBlock); + + this.add('setgenerate', this.setGenerate); + this.add('getgenerate', this.getGenerate); + this.add('generate', this.generate); + this.add('generatetoaddress', this.generateToAddress); + + this.add('estimatefee', this.estimateFee); + this.add('estimatepriority', this.estimatePriority); + this.add('estimatesmartfee', this.estimateSmartFee); + this.add('estimatesmartpriority', this.estimateSmartPriority); + + this.add('getinfo', this.getInfo); + this.add('validateaddress', this.validateAddress); + this.add('createmultisig', this.createMultisig); + this.add('createwitnessaddress', this.createWitnessAddress); + this.add('verifymessage', this.verifyMessage); + this.add('signmessagewithprivkey', this.signMessageWithPrivkey); + + this.add('setmocktime', this.setMockTime); + + this.add('getconnectioncount', this.getConnectionCount); + this.add('ping', this.ping); + this.add('getpeerinfo', this.getPeerInfo); + this.add('addnode', this.addNode); + this.add('disconnectnode', this.disconnectNode); + this.add('getaddednodeinfo', this.getAddedNodeInfo); + this.add('getnettotals', this.getNetTotals); + this.add('getnetworkinfo', this.getNetworkInfo); + this.add('setban', this.setBan); + this.add('listbanned', this.listBanned); + this.add('clearbanned', this.clearBanned); + + this.add('getrawtransaction', this.getRawTransaction); + this.add('createrawtransaction', this.createRawTransaction); + this.add('decoderawtransaction', this.decodeRawTransaction); + this.add('decodescript', this.decodeScript); + this.add('sendrawtransaction', this.sendRawTransaction); + this.add('signrawtransaction', this.signRawTransaction); + + this.add('gettxoutproof', this.getTXOutProof); + this.add('verifytxoutproof', this.verifyTXOutProof); + + this.add('getmemoryinfo', this.getMemoryInfo); + this.add('setloglevel', this.setLogLevel); + } + + /* + * Overall control/query calls + */ + + async getInfo(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getinfo'); + + return { + version: pkg.version, + protocolversion: this.pool.options.version, + walletversion: 0, + balance: 0, + blocks: this.chain.height, + timeoffset: this.network.time.offset, + connections: this.pool.peers.size(), + proxy: '', + difficulty: toDifficulty(this.chain.tip.bits), + testnet: this.network !== Network.main, + keypoololdest: 0, + keypoolsize: 0, + unlocked_until: 0, + paytxfee: Amount.btc(this.network.feeRate, true), + relayfee: Amount.btc(this.network.minRelay, true), + errors: '' + }; + } + + async help(args, _help) { + if (args.length === 0) + return `Select a command:\n${Object.keys(this.calls).join('\n')}`; + + const json = { + method: args[0], + params: [] + }; + + return await this.execute(json, true); + } + + async stop(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'stop'); + + this.node.close().catch((err) => { + setImmediate(() => { + throw err; + }); + }); + + return 'Stopping.'; + } + + /* + * P2P networking + */ + + async getNetworkInfo(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getnetworkinfo'); + + const hosts = this.pool.hosts; + const locals = []; + + for (const local of hosts.local.values()) { + locals.push({ + address: local.addr.host, + port: local.addr.port, + score: local.score + }); + } + + return { + version: pkg.version, + subversion: this.pool.options.agent, + protocolversion: this.pool.options.version, + localservices: hex32(this.pool.options.services), + localrelay: !this.pool.options.noRelay, + timeoffset: this.network.time.offset, + networkactive: this.pool.connected, + connections: this.pool.peers.size(), + networks: [], + relayfee: Amount.btc(this.network.minRelay, true), + incrementalfee: 0, + localaddresses: locals, + warnings: '' + }; + } + + async addNode(args, help) { + if (help || args.length !== 2) + throw new RPCError(errs.MISC_ERROR, 'addnode "node" "add|remove|onetry"'); + + const valid = new Validator(args); + const node = valid.str(0, ''); + const cmd = valid.str(1, ''); + + switch (cmd) { + case 'add': { + this.pool.hosts.addNode(node); + ; // fall through + } + case 'onetry': { + const addr = parseNetAddress(node, this.network); + + if (!this.pool.peers.get(addr.hostname)) { + const peer = this.pool.createOutbound(addr); + this.pool.peers.add(peer); + } + + break; + } + case 'remove': { + this.pool.hosts.removeNode(node); + break; + } + } + + return null; + } + + async disconnectNode(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'disconnectnode "node"'); + + const valid = new Validator(args); + const str = valid.str(0, ''); + + const addr = parseIP(str, this.network); + const peer = this.pool.peers.get(addr.hostname); + + if (peer) + peer.destroy(); + + return null; + } + + async getAddedNodeInfo(args, help) { + if (help || args.length > 1) + throw new RPCError(errs.MISC_ERROR, 'getaddednodeinfo ( "node" )'); + + const hosts = this.pool.hosts; + const valid = new Validator(args); + const addr = valid.str(0, ''); + + let target; + if (args.length === 1) + target = parseIP(addr, this.network); + + const result = []; + + for (const node of hosts.nodes) { + if (target) { + if (node.host !== target.host) + continue; + + if (node.port !== target.port) + continue; + } + + const peer = this.pool.peers.get(node.hostname); + + if (!peer || !peer.connected) { + result.push({ + addednode: node.hostname, + connected: false, + addresses: [] + }); + continue; + } + + result.push({ + addednode: node.hostname, + connected: peer.connected, + addresses: [ + { + address: peer.hostname(), + connected: peer.outbound + ? 'outbound' + : 'inbound' + } + ] + }); + } + + if (target && result.length === 0) { + throw new RPCError(errs.CLIENT_NODE_NOT_ADDED, + 'Node has not been added.'); + } + + return result; + } + + async getConnectionCount(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getconnectioncount'); + + return this.pool.peers.size(); + } + + async getNetTotals(args, help) { + let sent = 0; + let recv = 0; + + if (help || args.length > 0) + throw new RPCError(errs.MISC_ERROR, 'getnettotals'); + + for (let peer = this.pool.peers.head(); peer; peer = peer.next) { + sent += peer.socket.bytesWritten; + recv += peer.socket.bytesRead; + } + + return { + totalbytesrecv: recv, + totalbytessent: sent, + timemillis: Date.now() + }; + } + + async getPeerInfo(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getpeerinfo'); + + const peers = []; + + for (let peer = this.pool.peers.head(); peer; peer = peer.next) { + const offset = this.network.time.known.get(peer.hostname()) || 0; + const hashes = []; + + for (const hash in peer.blockMap.keys()) { + const str = util.revHex(hash); + hashes.push(str); + } + + peer.getName(); + + peers.push({ + id: peer.id, + addr: peer.hostname(), + addrlocal: !peer.local.isNull() + ? peer.local.hostname + : undefined, + name: peer.name || undefined, + services: hex32(peer.services), + relaytxes: !peer.noRelay, + lastsend: peer.lastSend / 1000 | 0, + lastrecv: peer.lastRecv / 1000 | 0, + bytessent: peer.socket.bytesWritten, + bytesrecv: peer.socket.bytesRead, + conntime: peer.time !== 0 ? (Date.now() - peer.time) / 1000 | 0 : 0, + timeoffset: offset, + pingtime: peer.lastPong !== -1 + ? (peer.lastPong - peer.lastPing) / 1000 + : -1, + minping: peer.minPing !== -1 ? peer.minPing / 1000 : -1, + version: peer.version, + subver: peer.agent, + inbound: !peer.outbound, + startingheight: peer.height, + besthash: peer.bestHash ? util.revHex(peer.bestHash) : null, + bestheight: peer.bestHeight, + banscore: peer.banScore, + inflight: hashes, + whitelisted: false + }); + } + + return peers; + } + + async ping(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'ping'); + + for (let peer = this.pool.peers.head(); peer; peer = peer.next) + peer.sendPing(); + + return null; + } + + async setBan(args, help) { + const valid = new Validator(args); + const str = valid.str(0, ''); + const action = valid.str(1, ''); + + if (help + || args.length < 2 + || (action !== 'add' && action !== 'remove')) { + throw new RPCError(errs.MISC_ERROR, + 'setban "ip(/netmask)" "add|remove" (bantime) (absolute)'); + } + + const addr = parseNetAddress(str, this.network); + + switch (action) { + case 'add': + this.pool.ban(addr); + break; + case 'remove': + this.pool.unban(addr); + break; + } + + return null; + } + + async listBanned(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'listbanned'); + + const banned = []; + + for (const [host, time] of this.pool.hosts.banned) { + banned.push({ + address: host, + banned_until: time + this.pool.options.banTime, + ban_created: time, + ban_reason: '' + }); + } + + return banned; + } + + async clearBanned(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'clearbanned'); + + this.pool.hosts.clearBanned(); + + return null; + } + + /* Block chain and UTXO */ + async getBlockchainInfo(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getblockchaininfo'); + + return { + chain: this.network.type !== 'testnet' + ? this.network.type + : 'test', + blocks: this.chain.height, + headers: this.chain.height, + bestblockhash: this.chain.tip.rhash(), + difficulty: toDifficulty(this.chain.tip.bits), + mediantime: await this.chain.getMedianTime(this.chain.tip), + verificationprogress: this.chain.getProgress(), + chainwork: this.chain.tip.chainwork.toString('hex', 64), + pruned: this.chain.options.prune, + softforks: this.getSoftforks(), + bip9_softforks: await this.getBIP9Softforks(), + pruneheight: this.chain.options.prune + ? Math.max(0, this.chain.height - this.network.block.keepBlocks) + : null + }; + } + + async getBestBlockHash(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getbestblockhash'); + + return this.chain.tip.rhash(); + } + + async getBlockCount(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getblockcount'); + + return this.chain.tip.height; + } + + async getBlock(args, help) { + if (help || args.length < 1 || args.length > 3) + throw new RPCError(errs.MISC_ERROR, 'getblock "hash" ( verbose )'); + + const valid = new Validator(args); + const hash = valid.brhash(0); + const verbose = valid.bool(1, true); + const details = valid.bool(2, false); + + if (!hash) + throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.'); + + const entry = await this.chain.getEntry(hash); + + if (!entry) + throw new RPCError(errs.MISC_ERROR, 'Block not found.'); + + const block = await this.chain.getBlock(entry.hash); + + if (!block) { + if (this.chain.options.spv) + throw new RPCError(errs.MISC_ERROR, 'Block not available (spv mode)'); + + if (this.chain.options.prune) { + throw new RPCError(errs.MISC_ERROR, + 'Block not available (pruned data)'); + } + + throw new RPCError(errs.MISC_ERROR, 'Can\'t read block from disk'); + } + + if (!verbose) + return block.toRaw().toString('hex'); + + return await this.blockToJSON(entry, block, details); + } + + async getBlockByHeight(args, help) { + if (help || args.length < 1 || args.length > 3) { + throw new RPCError(errs.MISC_ERROR, + 'getblockbyheight "height" ( verbose )'); + } + + const valid = new Validator(args); + const height = valid.u32(0, -1); + const verbose = valid.bool(1, true); + const details = valid.bool(2, false); + + if (height === -1) + throw new RPCError(errs.TYPE_ERROR, 'Invalid block height.'); + + const entry = await this.chain.getEntry(height); + + if (!entry) + throw new RPCError(errs.MISC_ERROR, 'Block not found.'); + + const block = await this.chain.getBlock(entry.hash); + + if (!block) { + if (this.chain.options.spv) + throw new RPCError(errs.MISC_ERROR, 'Block not available (spv mode)'); + + if (this.chain.options.prune) { + throw new RPCError(errs.MISC_ERROR, + 'Block not available (pruned data)'); + } + + throw new RPCError(errs.DATABASE_ERROR, 'Can\'t read block from disk'); + } + + if (!verbose) + return block.toRaw().toString('hex'); + + return await this.blockToJSON(entry, block, details); + } + + async getBlockHash(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'getblockhash index'); + + const valid = new Validator(args); + const height = valid.u32(0); + + if (height == null || height > this.chain.height) + throw new RPCError(errs.INVALID_PARAMETER, 'Block height out of range.'); + + const hash = await this.chain.getHash(height); + + if (!hash) + throw new RPCError(errs.MISC_ERROR, 'Not found.'); + + return util.revHex(hash); + } + + async getBlockHeader(args, help) { + if (help || args.length < 1 || args.length > 2) + throw new RPCError(errs.MISC_ERROR, 'getblockheader "hash" ( verbose )'); + + const valid = new Validator(args); + const hash = valid.brhash(0); + const verbose = valid.bool(1, true); + + if (!hash) + throw new RPCError(errs.MISC_ERROR, 'Invalid block hash.'); + + const entry = await this.chain.getEntry(hash); + + if (!entry) + throw new RPCError(errs.MISC_ERROR, 'Block not found.'); + + if (!verbose) + return entry.toRaw().toString('hex', 0, 80); + + return await this.headerToJSON(entry); + } + + async getChainTips(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getchaintips'); + + const tips = await this.chain.getTips(); + const result = []; + + for (const hash of tips) { + const entry = await this.chain.getEntry(hash); + + assert(entry); + + const fork = await this.findFork(entry); + const main = await this.chain.isMainChain(entry); + + result.push({ + height: entry.height, + hash: entry.rhash(), + branchlen: entry.height - fork.height, + status: main ? 'active' : 'valid-headers' + }); + } + + return result; + } + + async getDifficulty(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getdifficulty'); + + return toDifficulty(this.chain.tip.bits); + } + + async getMempoolInfo(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getmempoolinfo'); + + if (!this.mempool) + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); + + return { + size: this.mempool.map.size, + bytes: this.mempool.getSize(), + usage: this.mempool.getSize(), + maxmempool: this.mempool.options.maxSize, + mempoolminfee: Amount.btc(this.mempool.options.minRelay, true) + }; + } + + async getMempoolAncestors(args, help) { + if (help || args.length < 1 || args.length > 2) + throw new RPCError(errs.MISC_ERROR, 'getmempoolancestors txid (verbose)'); + + const valid = new Validator(args); + const hash = valid.brhash(0); + const verbose = valid.bool(1, false); + + if (!this.mempool) + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); + + if (!hash) + throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); + + const entry = this.mempool.getEntry(hash); + + if (!entry) + throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.'); + + const entries = this.mempool.getAncestors(entry); + const out = []; + + if (verbose) { + for (const entry of entries) + out.push(this.entryToJSON(entry)); + } else { + for (const entry of entries) + out.push(entry.txid()); + } + + return out; + } + + async getMempoolDescendants(args, help) { + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'getmempooldescendants txid (verbose)'); + } + + const valid = new Validator(args); + const hash = valid.brhash(0); + const verbose = valid.bool(1, false); + + if (!this.mempool) + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); + + if (!hash) + throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); + + const entry = this.mempool.getEntry(hash); + + if (!entry) + throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.'); + + const entries = this.mempool.getDescendants(entry); + const out = []; + + if (verbose) { + for (const entry of entries) + out.push(this.entryToJSON(entry)); + } else { + for (const entry of entries) + out.push(entry.txid()); + } + + return out; + } + + async getMempoolEntry(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'getmempoolentry txid'); + + const valid = new Validator(args); + const hash = valid.brhash(0); + + if (!this.mempool) + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); + + if (!hash) + throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); + + const entry = this.mempool.getEntry(hash); + + if (!entry) + throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.'); + + return this.entryToJSON(entry); + } + + async getRawMempool(args, help) { + if (help || args.length > 1) + throw new RPCError(errs.MISC_ERROR, 'getrawmempool ( verbose )'); + + const valid = new Validator(args); + const verbose = valid.bool(0, false); + + if (!this.mempool) + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); + + if (verbose) { + const out = {}; + + for (const entry of this.mempool.map.values()) + out[entry.txid()] = this.entryToJSON(entry); + + return out; + } + + const hashes = this.mempool.getSnapshot(); + + return hashes.map(util.revHex); + } + + async getTXOut(args, help) { + if (help || args.length < 2 || args.length > 3) { + throw new RPCError(errs.MISC_ERROR, + 'gettxout "txid" n ( includemempool )'); + } + + const valid = new Validator(args); + const hash = valid.brhash(0); + const index = valid.u32(1); + const mempool = valid.bool(2, true); + + if (this.chain.options.spv) + throw new RPCError(errs.MISC_ERROR, 'Cannot get coins in SPV mode.'); + + if (this.chain.options.prune) + throw new RPCError(errs.MISC_ERROR, 'Cannot get coins when pruned.'); + + if (!hash || index == null) + throw new RPCError(errs.TYPE_ERROR, 'Invalid outpoint.'); + + let coin; + if (mempool) { + if (!this.mempool) + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); + coin = this.mempool.getCoin(hash, index); + } + + if (!coin) + coin = await this.chain.getCoin(hash, index); + + if (!coin) + return null; + + return { + bestblock: this.chain.tip.rhash(), + confirmations: coin.getDepth(this.chain.height), + value: Amount.btc(coin.value, true), + scriptPubKey: this.scriptToJSON(coin.script, true), + version: coin.version, + coinbase: coin.coinbase + }; + } + + async getTXOutProof(args, help) { + if (help || (args.length !== 1 && args.length !== 2)) { + throw new RPCError(errs.MISC_ERROR, + 'gettxoutproof ["txid",...] ( blockhash )'); + } + + const valid = new Validator(args); + const txids = valid.array(0); + const hash = valid.brhash(1); + + if (this.chain.options.spv) + throw new RPCError(errs.MISC_ERROR, 'Cannot get coins in SPV mode.'); + + if (this.chain.options.prune) + throw new RPCError(errs.MISC_ERROR, 'Cannot get coins when pruned.'); + + if (!txids || txids.length === 0) + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid TXIDs.'); + + const items = new Validator(txids); + const set = new BufferSet(); + const hashes = []; + + let last = null; + + for (let i = 0; i < txids.length; i++) { + const hash = items.brhash(i); + + if (!hash) + throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); + + if (set.has(hash)) + throw new RPCError(errs.INVALID_PARAMETER, 'Duplicate txid.'); + + set.add(hash); + hashes.push(hash); + + last = hash; + } + + let block = null; + + if (hash) { + block = await this.chain.getBlock(hash); + } else if (await this.node.hasTX(last)) { + const tx = await this.node.getMeta(last); + if (tx) + block = await this.chain.getBlock(tx.block); + } else { + const coin = await this.chain.getCoin(last, 0); + if (coin) + block = await this.chain.getBlock(coin.height); + } + + if (!block) + throw new RPCError(errs.MISC_ERROR, 'Block not found.'); + + for (const hash of hashes) { + if (!block.hasTX(hash)) { + throw new RPCError(errs.VERIFY_ERROR, + 'Block does not contain all txids.'); + } + } + + block = MerkleBlock.fromHashes(block, hashes); + + return block.toRaw().toString('hex'); + } + + async verifyTXOutProof(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'verifytxoutproof "proof"'); + + const valid = new Validator(args); + const data = valid.buf(0); + + if (!data) + throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.'); + + const block = MerkleBlock.fromRaw(data); + + if (!block.verify()) + return []; + + const entry = await this.chain.getEntry(block.hash()); + + if (!entry) + throw new RPCError(errs.MISC_ERROR, 'Block not found in chain.'); + + const tree = block.getTree(); + const out = []; + + for (const hash of tree.matches) + out.push(util.revHex(hash)); + + return out; + } + + async getTXOutSetInfo(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'gettxoutsetinfo'); + + if (this.chain.options.spv) { + throw new RPCError(errs.MISC_ERROR, + 'Chainstate not available (SPV mode).'); + } + + return { + height: this.chain.height, + bestblock: this.chain.tip.rhash(), + transactions: this.chain.db.state.tx, + txouts: this.chain.db.state.coin, + bytes_serialized: 0, + hash_serialized: 0, + total_amount: Amount.btc(this.chain.db.state.value, true) + }; + } + + async pruneBlockchain(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'pruneblockchain'); + + if (this.chain.options.spv) + throw new RPCError(errs.MISC_ERROR, 'Cannot prune chain in SPV mode.'); + + if (this.chain.options.prune) + throw new RPCError(errs.MISC_ERROR, 'Chain is already pruned.'); + + if (this.chain.height < this.network.block.pruneAfterHeight) + throw new RPCError(errs.MISC_ERROR, 'Chain is too short for pruning.'); + + try { + await this.chain.prune(); + } catch (e) { + throw new RPCError(errs.DATABASE_ERROR, e.message); + } + } + + async verifyChain(args, help) { + if (help || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'verifychain ( checklevel numblocks )'); + } + + const valid = new Validator(args); + const level = valid.u32(0); + const blocks = valid.u32(1); + + if (level == null || blocks == null) + throw new RPCError(errs.TYPE_ERROR, 'Missing parameters.'); + + if (this.chain.options.spv) + throw new RPCError(errs.MISC_ERROR, 'Cannot verify chain in SPV mode.'); + + if (this.chain.options.prune) + throw new RPCError(errs.MISC_ERROR, 'Cannot verify chain when pruned.'); + + return null; + } + + /* + * Mining + */ + + async submitWork(data) { + const unlock = await this.locker.lock(); + try { + return await this._submitWork(data); + } finally { + unlock(); + } + } + + async _submitWork(data) { + const attempt = this.attempt; + + if (!attempt) + return false; + + if (data.length !== 128) + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid work size.'); + + const raw = data.slice(0, 80); + swap32(raw); + + const header = Headers.fromHead(raw); + + if (header.prevBlock !== attempt.prevBlock + || header.bits !== attempt.bits) { + return false; + } + + if (!header.verify()) + return false; + + const nonces = this.merkleMap.get(header.merkleRoot); + + if (!nonces) + return false; + + const [n1, n2] = nonces; + const nonce = header.nonce; + const time = header.time; + + const proof = attempt.getProof(n1, n2, time, nonce); + + if (!proof.verify(attempt.target)) + return false; + + const block = attempt.commit(proof); + + let entry; + try { + entry = await this.chain.add(block); + } catch (err) { + if (err.type === 'VerifyError') { + this.logger.warning('RPC block rejected: %h (%s).', + block.hash(), err.reason); + return false; + } + throw err; + } + + if (!entry) { + this.logger.warning('RPC block rejected: %h (bad-prevblk).', + block.hash()); + return false; + } + + return true; + } + + async createWork(data) { + const unlock = await this.locker.lock(); + try { + return await this._createWork(data); + } finally { + unlock(); + } + } + + async _createWork() { + const attempt = await this.updateWork(); + const n1 = this.nonce1; + const n2 = this.nonce2; + const time = attempt.time; + + const data = Buffer.allocUnsafe(128); + data.fill(0); + + const root = attempt.getRoot(n1, n2); + const head = attempt.getHeader(root, time, 0); + + head.copy(data, 0); + + data[80] = 0x80; + data.writeUInt32BE(80 * 8, data.length - 4, true); + + swap32(data); + + return { + data: data.toString('hex'), + target: attempt.target.toString('hex'), + height: attempt.height + }; + } + + async getWorkLongpoll(args, help) { + await this.longpoll(); + return await this.createWork(); + } + + async getWork(args, help) { + if (args.length > 1) + throw new RPCError(errs.MISC_ERROR, 'getwork ( "data" )'); + + if (args.length === 1) { + const valid = new Validator(args); + const data = valid.buf(0); + + if (!data) + throw new RPCError(errs.TYPE_ERROR, 'Invalid work data.'); + + return await this.submitWork(data); + } + + return await this.createWork(); + } + + async submitBlock(args, help) { + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'submitblock "hexdata" ( "jsonparametersobject" )'); + } + + const valid = new Validator(args); + const data = valid.buf(0); + + const block = Block.fromRaw(data); + + return await this.addBlock(block); + } + + async getBlockTemplate(args, help) { + if (help || args.length > 1) { + throw new RPCError(errs.MISC_ERROR, + 'getblocktemplate ( "jsonrequestobject" )'); + } + + const validator = new Validator(args); + const options = validator.obj(0, {}); + const valid = new Validator(options); + const mode = valid.str('mode', 'template'); + + if (mode !== 'template' && mode !== 'proposal') + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid mode.'); + + if (mode === 'proposal') { + const data = valid.buf('data'); + + if (!data) + throw new RPCError(errs.TYPE_ERROR, 'Missing data parameter.'); + + const block = Block.fromRaw(data); + + if (!block.prevBlock.equals(this.chain.tip.hash)) + return 'inconclusive-not-best-prevblk'; + + try { + await this.chain.verifyBlock(block); + } catch (e) { + if (e.type === 'VerifyError') + return e.reason; + throw e; + } + + return null; + } + + let maxVersion = valid.u32('maxversion', -1); + let rules = valid.array('rules'); + + if (rules) + maxVersion = -1; + + const capabilities = valid.array('capabilities'); + let coinbase = false; + + if (capabilities) { + let txnCap = false; + let valueCap = false; + + for (const capability of capabilities) { + if (typeof capability !== 'string') + throw new RPCError(errs.TYPE_ERROR, 'Invalid capability.'); + + switch (capability) { + case 'coinbasetxn': + txnCap = true; + break; + case 'coinbasevalue': + // Prefer value if they support it. + valueCap = true; + break; + } + } + + // BIP22 states that we can't have coinbasetxn + // _and_ coinbasevalue in the same template. + // The problem is, many clients _say_ they + // support coinbasetxn when they don't (ckpool). + // To make matters worse, some clients will + // parse an undefined `coinbasevalue` as zero. + // Because of all of this, coinbasetxn is + // disabled for now. + valueCap = true; + + if (txnCap && !valueCap) { + if (this.miner.addresses.length === 0) { + throw new RPCError(errs.MISC_ERROR, + 'No addresses available for coinbase.'); + } + coinbase = true; + } + } + + if (!this.network.selfConnect) { + if (this.pool.peers.size() === 0) { + throw new RPCError(errs.CLIENT_NOT_CONNECTED, + 'Bitcoin is not connected!'); + } + + if (!this.chain.synced) { + throw new RPCError(errs.CLIENT_IN_INITIAL_DOWNLOAD, + 'Bitcoin is downloading blocks...'); + } + } + + const lpid = valid.str('longpollid'); + + if (lpid) + await this.handleLongpoll(lpid); + + if (!rules) + rules = []; + + return await this.createTemplate(maxVersion, coinbase, rules); + } + + async createTemplate(maxVersion, coinbase, rules) { + const unlock = await this.locker.lock(); + try { + return await this._createTemplate(maxVersion, coinbase, rules); + } finally { + unlock(); + } + } + + async _createTemplate(maxVersion, coinbase, rules) { + const attempt = await this.getTemplate(); + const scale = attempt.witness ? 1 : consensus.WITNESS_SCALE_FACTOR; + + // Default mutable fields. + const mutable = ['time', 'transactions', 'prevblock']; + + // The miner doesn't support + // versionbits. Force them to + // encode our version. + if (maxVersion >= 2) + mutable.push('version/force'); + + // Allow the miner to change + // our provided coinbase. + // Note that these are implied + // without `coinbasetxn`. + if (coinbase) { + mutable.push('coinbase'); + mutable.push('coinbase/append'); + mutable.push('generation'); + } + + // Build an index of every transaction. + const index = new BufferMap(); + for (let i = 0; i < attempt.items.length; i++) { + const entry = attempt.items[i]; + index.set(entry.hash, i + 1); + } + + // Calculate dependencies for each transaction. + const txs = []; + for (let i = 0; i < attempt.items.length; i++) { + const entry = attempt.items[i]; + const tx = entry.tx; + const deps = []; + + for (let j = 0; j < tx.inputs.length; j++) { + const input = tx.inputs[j]; + const dep = index.get(input.prevout.hash); + + if (dep == null) + continue; + + if (deps.indexOf(dep) === -1) { + assert(dep < i + 1); + deps.push(dep); + } + } + + txs.push({ + data: tx.toRaw().toString('hex'), + txid: tx.txid(), + hash: tx.wtxid(), + depends: deps, + fee: entry.fee, + sigops: entry.sigops / scale | 0, + weight: tx.getWeight() + }); + } + + if (this.chain.options.bip91) { + rules.push('segwit'); + rules.push('segsignal'); + } + + if (this.chain.options.bip148) + rules.push('segwit'); + + // Calculate version based on given rules. + let version = attempt.version; + const vbavailable = {}; + const vbrules = []; + + for (const deploy of this.network.deploys) { + const state = await this.chain.getState(this.chain.tip, deploy); + let name = deploy.name; + + switch (state) { + case common.thresholdStates.DEFINED: + case common.thresholdStates.FAILED: + break; + case common.thresholdStates.LOCKED_IN: + version |= 1 << deploy.bit; + case common.thresholdStates.STARTED: + if (!deploy.force) { + if (rules.indexOf(name) === -1) + version &= ~(1 << deploy.bit); + if (deploy.required) + name = '!' + name; + } + vbavailable[name] = deploy.bit; + break; + case common.thresholdStates.ACTIVE: + if (!deploy.force && deploy.required) { + if (rules.indexOf(name) === -1) { + throw new RPCError(errs.INVALID_PARAMETER, + `Client must support ${name}.`); + } + name = '!' + name; + } + vbrules.push(name); + break; + default: + assert(false, 'Bad state.'); + break; + } + } + + version >>>= 0; + + const json = { + capabilities: ['proposal'], + mutable: mutable, + version: version, + rules: vbrules, + vbavailable: vbavailable, + vbrequired: 0, + height: attempt.height, + previousblockhash: util.revHex(attempt.prevBlock), + target: util.revHex(attempt.target), + bits: hex32(attempt.bits), + noncerange: '00000000ffffffff', + curtime: attempt.time, + mintime: attempt.mtp + 1, + maxtime: attempt.time + 7200, + expires: attempt.time + 7200, + sigoplimit: consensus.MAX_BLOCK_SIGOPS_COST / scale | 0, + sizelimit: consensus.MAX_BLOCK_SIZE, + weightlimit: undefined, + longpollid: this.chain.tip.rhash() + hex32(this.totalTX()), + submitold: false, + coinbaseaux: { + flags: attempt.coinbaseFlags.toString('hex') + }, + coinbasevalue: undefined, + coinbasetxn: undefined, + default_witness_commitment: undefined, + transactions: txs + }; + + // See: + // bitcoin/bitcoin#9fc7f0bce94f1cea0239b1543227f22a3f3b9274 + if (attempt.witness) { + json.sizelimit = consensus.MAX_RAW_BLOCK_SIZE; + json.weightlimit = consensus.MAX_BLOCK_WEIGHT; + } + + // The client wants a coinbasetxn + // instead of a coinbasevalue. + if (coinbase) { + const tx = attempt.toCoinbase(); + const input = tx.inputs[0]; + + // Pop off the nonces. + input.script.pop(); + input.script.compile(); + + if (attempt.witness) { + // We don't include the commitment + // output (see bip145). + const output = tx.outputs.pop(); + assert(output.script.isCommitment()); + + // Also not including the witness nonce. + input.witness.clear(); + } + + tx.refresh(); + + json.coinbasetxn = { + data: tx.toRaw().toString('hex'), + txid: tx.txid(), + hash: tx.wtxid(), + depends: [], + fee: 0, + sigops: tx.getSigopsCost() / scale | 0, + weight: tx.getWeight() + }; + } else { + json.coinbasevalue = attempt.getReward(); + } + + if (rules.indexOf('segwit') !== -1) + json.default_witness_commitment = attempt.getWitnessScript().toJSON(); + + return json; + } + + async getMiningInfo(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getmininginfo'); + + const attempt = this.attempt; + + let size = 0; + let weight = 0; + let txs = 0; + let diff = 0; + + if (attempt) { + weight = attempt.weight; + txs = attempt.items.length + 1; + diff = attempt.getDifficulty(); + size = 1000; + for (const item of attempt.items) + size += item.tx.getBaseSize(); + } + + return { + blocks: this.chain.height, + currentblocksize: size, + currentblockweight: weight, + currentblocktx: txs, + difficulty: diff, + errors: '', + genproclimit: this.procLimit, + networkhashps: await this.getHashRate(120), + pooledtx: this.totalTX(), + testnet: this.network !== Network.main, + chain: this.network.type !== 'testnet' + ? this.network.type + : 'test', + generate: this.mining + }; + } + + async getNetworkHashPS(args, help) { + if (help || args.length > 2) + throw new RPCError(errs.MISC_ERROR, 'getnetworkhashps ( blocks height )'); + + const valid = new Validator(args); + const lookup = valid.u32(0, 120); + const height = valid.u32(1); + + return await this.getHashRate(lookup, height); + } + + async prioritiseTransaction(args, help) { + if (help || args.length !== 3) { + throw new RPCError(errs.MISC_ERROR, + 'prioritisetransaction '); + } + + const valid = new Validator(args); + const hash = valid.brhash(0); + const pri = valid.i64(1); + const fee = valid.i64(2); + + if (!this.mempool) + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); + + if (!hash) + throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID'); + + if (pri == null || fee == null) + throw new RPCError(errs.TYPE_ERROR, 'Invalid fee or priority.'); + + const entry = this.mempool.getEntry(hash); + + if (!entry) + throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.'); + + this.mempool.prioritise(entry, pri, fee); + + return true; + } + + async verifyBlock(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'verifyblock "block-hex"'); + + const valid = new Validator(args); + const data = valid.buf(0); + + if (!data) + throw new RPCError(errs.TYPE_ERROR, 'Invalid block hex.'); + + if (this.chain.options.spv) + throw new RPCError(errs.MISC_ERROR, 'Cannot verify block in SPV mode.'); + + const block = Block.fromRaw(data); + + try { + await this.chain.verifyBlock(block); + } catch (e) { + if (e.type === 'VerifyError') + return e.reason; + throw e; + } + + return null; + } + + /* + * Coin generation + */ + + async getGenerate(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getgenerate'); + return this.mining; + } + + async setGenerate(args, help) { + if (help || args.length < 1 || args.length > 2) + throw new RPCError(errs.MISC_ERROR, 'setgenerate mine ( proclimit )'); + + const valid = new Validator(args); + const mine = valid.bool(0, false); + const limit = valid.u32(1, 0); + + if (mine && this.miner.addresses.length === 0) { + throw new RPCError(errs.MISC_ERROR, + 'No addresses available for coinbase.'); + } + + this.mining = mine; + this.procLimit = limit; + + if (mine) { + this.miner.cpu.start(); + return true; + } + + await this.miner.cpu.stop(); + + return false; + } + + async generate(args, help) { + if (help || args.length < 1 || args.length > 2) + throw new RPCError(errs.MISC_ERROR, 'generate numblocks ( maxtries )'); + + const valid = new Validator(args); + const blocks = valid.u32(0, 1); + const tries = valid.u32(1); + + if (this.miner.addresses.length === 0) { + throw new RPCError(errs.MISC_ERROR, + 'No addresses available for coinbase.'); + } + + return await this.mineBlocks(blocks, null, tries); + } + + async generateToAddress(args, help) { + if (help || args.length < 2 || args.length > 3) { + throw new RPCError(errs.MISC_ERROR, + 'generatetoaddress numblocks address ( maxtries )'); + } + + const valid = new Validator(args); + const blocks = valid.u32(0, 1); + const str = valid.str(1, ''); + const tries = valid.u32(2); + + const addr = parseAddress(str, this.network); + + return await this.mineBlocks(blocks, addr, tries); + } + + /* + * Raw transactions + */ + + async createRawTransaction(args, help) { + if (help || args.length < 2 || args.length > 3) { + throw new RPCError(errs.MISC_ERROR, + 'createrawtransaction' + + ' [{"txid":"id","vout":n},...]' + + ' {"address":amount,"data":"hex",...}' + + ' ( locktime )'); + } + + const valid = new Validator(args); + const inputs = valid.array(0); + const sendTo = valid.obj(1); + const locktime = valid.u32(2); + + if (!inputs || !sendTo) { + throw new RPCError(errs.TYPE_ERROR, + 'Invalid parameters (inputs and sendTo).'); + } + + const tx = new MTX(); + + if (locktime != null) + tx.locktime = locktime; + + for (const obj of inputs) { + const valid = new Validator(obj); + const hash = valid.brhash('txid'); + const index = valid.u32('vout'); + let sequence = valid.u32('sequence', 0xffffffff); + + if (tx.locktime) + sequence--; + + if (!hash || index == null) + throw new RPCError(errs.TYPE_ERROR, 'Invalid outpoint.'); + + const input = new Input(); + input.prevout.hash = hash; + input.prevout.index = index; + input.sequence = sequence; + + tx.inputs.push(input); + } + + const sends = new Validator(sendTo); + const uniq = new Set(); + + for (const key of Object.keys(sendTo)) { + if (key === 'data') { + const value = sends.buf(key); + + if (!value) + throw new RPCError(errs.TYPE_ERROR, 'Invalid nulldata..'); + + const output = new Output(); + output.value = 0; + output.script.fromNulldata(value); + tx.outputs.push(output); + + continue; + } + + const addr = parseAddress(key, this.network); + const b58 = addr.toString(this.network); + + if (uniq.has(b58)) + throw new RPCError(errs.INVALID_PARAMETER, 'Duplicate address'); + + uniq.add(b58); + + const value = sends.ufixed(key, 8); + + if (value == null) + throw new RPCError(errs.TYPE_ERROR, 'Invalid output value.'); + + const output = new Output(); + output.value = value; + output.script.fromAddress(addr); + + tx.outputs.push(output); + } + + return tx.toRaw().toString('hex'); + } + + async decodeRawTransaction(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'decoderawtransaction "hexstring"'); + + const valid = new Validator(args); + const data = valid.buf(0); + + if (!data) + throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.'); + + const tx = TX.fromRaw(data); + + return this.txToJSON(tx); + } + + async decodeScript(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'decodescript "hex"'); + + const valid = new Validator(args); + const data = valid.buf(0); + + let script = new Script(); + + if (data) + script = Script.fromRaw(data); + + const addr = Address.fromScripthash(script.hash160()); + + const json = this.scriptToJSON(script); + json.p2sh = addr.toString(this.network); + + return json; + } + + async getRawTransaction(args, help) { + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'getrawtransaction "txid" ( verbose )'); + } + + const valid = new Validator(args); + const hash = valid.brhash(0); + const verbose = valid.bool(1, false); + + if (!hash) + throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); + + const meta = await this.node.getMeta(hash); + + if (!meta) + throw new RPCError(errs.MISC_ERROR, 'Transaction not found.'); + + const tx = meta.tx; + + if (!verbose) + return tx.toRaw().toString('hex'); + + let entry; + if (meta.block) + entry = await this.chain.getEntry(meta.block); + + const json = this.txToJSON(tx, entry); + json.time = meta.mtime; + json.hex = tx.toRaw().toString('hex'); + + return json; + } + + async sendRawTransaction(args, help) { + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'sendrawtransaction "hexstring" ( allowhighfees )'); + } + + const valid = new Validator(args); + const data = valid.buf(0); + + if (!data) + throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.'); + + const tx = TX.fromRaw(data); + + this.node.relay(tx); + + return tx.txid(); + } + + async signRawTransaction(args, help) { + if (help || args.length < 1 || args.length > 4) { + throw new RPCError(errs.MISC_ERROR, + 'signrawtransaction' + + ' "hexstring" (' + + ' [{"txid":"id","vout":n,"scriptPubKey":"hex",' + + 'redeemScript":"hex"},...] ["privatekey1",...]' + + ' sighashtype )'); + } + + const valid = new Validator(args); + const data = valid.buf(0); + const prevout = valid.array(1); + const secrets = valid.array(2); + const sighash = valid.str(3); + + if (!data) + throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.'); + + if (!this.mempool) + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); + + const tx = MTX.fromRaw(data); + tx.view = await this.mempool.getSpentView(tx); + + const map = new BufferMap(); + const keys = []; + + if (secrets) { + const valid = new Validator(secrets); + for (let i = 0; i < secrets.length; i++) { + const secret = valid.str(i, ''); + const key = parseSecret(secret, this.network); + map.set(key.getPublicKey(), key); + keys.push(key); + } + } + + if (prevout) { + for (const prev of prevout) { + const valid = new Validator(prev); + const hash = valid.brhash('txid'); + const index = valid.u32('vout'); + const scriptRaw = valid.buf('scriptPubKey'); + const value = valid.ufixed('amount', 8); + const redeemRaw = valid.buf('redeemScript'); + + if (!hash || index == null || !scriptRaw || value == null) + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid UTXO.'); + + const outpoint = new Outpoint(hash, index); + + const script = Script.fromRaw(scriptRaw); + const coin = Output.fromScript(script, value); + + tx.view.addOutput(outpoint, coin); + + if (keys.length === 0 || !redeemRaw) + continue; + + if (!script.isScripthash() && !script.isWitnessScripthash()) + continue; + + if (!redeemRaw) { + throw new RPCError(errs.INVALID_PARAMETER, + 'P2SH requires redeem script.'); + } + + const redeem = Script.fromRaw(redeemRaw); + + for (const op of redeem.code) { + if (!op.data) + continue; + + const key = map.get(op.data); + + if (key) { + key.script = redeem; + key.witness = script.isWitnessScripthash(); + key.refresh(); + break; + } + } + } + } + + let type = Script.hashType.ALL; + if (sighash) { + const parts = sighash.split('|'); + + if (parts.length < 1 || parts.length > 2) + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid sighash type.'); + + type = Script.hashType[parts[0]]; + + if (type == null) + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid sighash type.'); + + if (parts.length === 2) { + if (parts[1] !== 'ANYONECANPAY') + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid sighash type.'); + type |= Script.hashType.ANYONECANPAY; + } + } + + await tx.signAsync(keys, type, this.workers); + + return { + hex: tx.toRaw().toString('hex'), + complete: tx.isSigned() + }; + } + + /* + * Utility Functions + */ + + async createMultisig(args, help) { + if (help || args.length < 2 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'createmultisig nrequired ["key",...]'); + } + + const valid = new Validator(args); + const keys = valid.array(1, []); + const m = valid.u32(0, 0); + const n = keys.length; + + if (m < 1 || n < m || n > 16) + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid m and n values.'); + + const items = new Validator(keys); + + for (let i = 0; i < keys.length; i++) { + const key = items.buf(i); + + if (!key) + throw new RPCError(errs.TYPE_ERROR, 'Invalid key.'); + + if (!secp256k1.publicKeyVerify(key)) + throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid key.'); + + keys[i] = key; + } + + const script = Script.fromMultisig(m, n, keys); + + if (script.getSize() > consensus.MAX_SCRIPT_PUSH) { + throw new RPCError(errs.VERIFY_ERROR, + 'Redeem script exceeds size limit.'); + } + + const addr = script.getAddress(); + + return { + address: addr.toString(this.network), + redeemScript: script.toJSON() + }; + } + + async createWitnessAddress(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'createwitnessaddress "script"'); + + const valid = new Validator(args); + const raw = valid.buf(0); + + if (!raw) + throw new RPCError(errs.TYPE_ERROR, 'Invalid script hex.'); + + const script = Script.fromRaw(raw); + const program = script.forWitness(); + const addr = program.getAddress(); + + return { + address: addr.toString(this.network), + witnessScript: program.toJSON() + }; + } + + async validateAddress(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'validateaddress "bitcoinaddress"'); + + const valid = new Validator(args); + const str = valid.str(0, ''); + + let addr; + try { + addr = Address.fromString(str, this.network); + } catch (e) { + return { + isvalid: false + }; + } + + const script = Script.fromAddress(addr); + const isWitness = addr.isProgram(); + const isScript = script.isScripthash() || script.isWitnessScripthash(); + + const result = { + isvalid: true, + address: addr.toString(this.network), + scriptPubKey: script.toJSON(), + isscript: isScript, + iswitness: isWitness + }; + + if (isWitness) { + result.witness_version = addr.version; + result.witness_program = addr.hash.toString('hex'); + } + + return result; + } + + async verifyMessage(args, help) { + if (help || args.length !== 3) { + throw new RPCError(errs.MISC_ERROR, + 'verifymessage "bitcoinaddress" "signature" "message"'); + } + + const valid = new Validator(args); + const b58 = valid.str(0, ''); + const sig = valid.buf(1, null, 'base64'); + const str = valid.str(2); + + if (!sig || !str) + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameters.'); + + const addr = parseAddress(b58, this.network); + const msg = Buffer.from(MAGIC_STRING + str, 'utf8'); + const hash = hash256.digest(msg); + + const key = secp256k1.recoverDER(hash, sig, 0, true); + + if (!key) + return false; + + return safeEqual(hash160.digest(key), addr.hash); + } + + async signMessageWithPrivkey(args, help) { + if (help || args.length !== 2) { + throw new RPCError(errs.MISC_ERROR, + 'signmessagewithprivkey "privkey" "message"'); + } + + const valid = new Validator(args); + const wif = valid.str(0, ''); + const str = valid.str(1, ''); + + const key = parseSecret(wif, this.network); + const msg = Buffer.from(MAGIC_STRING + str, 'utf8'); + const hash = hash256.digest(msg); + const sig = key.sign(hash); + + return sig.toString('base64'); + } + + async estimateFee(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'estimatefee nblocks'); + + const valid = new Validator(args); + const blocks = valid.u32(0, 1); + + if (!this.fees) + throw new RPCError(errs.MISC_ERROR, 'Fee estimation not available.'); + + const fee = this.fees.estimateFee(blocks, false); + + if (fee === 0) + return -1; + + return Amount.btc(fee, true); + } + + async estimatePriority(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'estimatepriority nblocks'); + + const valid = new Validator(args); + const blocks = valid.u32(0, 1); + + if (!this.fees) + throw new RPCError(errs.MISC_ERROR, 'Priority estimation not available.'); + + return this.fees.estimatePriority(blocks, false); + } + + async estimateSmartFee(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'estimatesmartfee nblocks'); + + const valid = new Validator(args); + const blocks = valid.u32(0, 1); + + if (!this.fees) + throw new RPCError(errs.MISC_ERROR, 'Fee estimation not available.'); + + let fee = this.fees.estimateFee(blocks, true); + + if (fee === 0) + fee = -1; + else + fee = Amount.btc(fee, true); + + return { + fee: fee, + blocks: blocks + }; + } + + async estimateSmartPriority(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'estimatesmartpriority nblocks'); + + const valid = new Validator(args); + const blocks = valid.u32(0, 1); + + if (!this.fees) + throw new RPCError(errs.MISC_ERROR, 'Priority estimation not available.'); + + const pri = this.fees.estimatePriority(blocks, true); + + return { + priority: pri, + blocks: blocks + }; + } + + async invalidateBlock(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'invalidateblock "hash"'); + + const valid = new Validator(args); + const hash = valid.brhash(0); + + if (!hash) + throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.'); + + await this.chain.invalidate(hash); + + return null; + } + + async reconsiderBlock(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'reconsiderblock "hash"'); + + const valid = new Validator(args); + const hash = valid.brhash(0); + + if (!hash) + throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.'); + + this.chain.removeInvalid(hash); + + return null; + } + + async setMockTime(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'setmocktime timestamp'); + + const valid = new Validator(args); + const time = valid.u32(0); + + if (time == null) + throw new RPCError(errs.TYPE_ERROR, 'Invalid timestamp.'); + + this.network.time.offset = 0; + + const delta = this.network.now() - time; + + this.network.time.offset = -delta; + + return null; + } + + async getMemoryInfo(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getmemoryinfo'); + + return this.logger.memoryUsage(); + } + + async setLogLevel(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'setloglevel "level"'); + + const valid = new Validator(args); + const level = valid.str(0, ''); + + this.logger.setLevel(level); + + return null; + } + + /* + * Helpers + */ + + async handleLongpoll(lpid) { + if (lpid.length !== 72) + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid longpoll ID.'); + + const watched = lpid.slice(0, 64); + const lastTX = parseInt(lpid.slice(64, 72), 16); + + if ((lastTX >>> 0) !== lastTX) + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid longpoll ID.'); + + const hash = util.revHex(watched); + + if (!this.chain.tip.hash.equals(hash)) + return; + + await this.longpoll(); + } + + longpoll() { + return new Promise((resolve, reject) => { + this.pollers.push({ resolve, reject }); + }); + } + + refreshBlock() { + const pollers = this.pollers; + + this.attempt = null; + this.lastActivity = 0; + this.merkleMap.clear(); + this.nonce1 = 0; + this.nonce2 = 0; + this.pollers = []; + + for (const job of pollers) + job.resolve(); + } + + bindChain() { + if (this.boundChain) + return; + + this.boundChain = true; + + this.node.on('connect', () => { + if (!this.attempt) + return; + + this.refreshBlock(); + }); + + if (!this.mempool) + return; + + this.node.on('tx', () => { + if (!this.attempt) + return; + + if (util.now() - this.lastActivity > 10) + this.refreshBlock(); + }); + } + + async getTemplate() { + this.bindChain(); + + let attempt = this.attempt; + + if (attempt) { + this.miner.updateTime(attempt); + } else { + attempt = await this.miner.createBlock(); + this.attempt = attempt; + this.lastActivity = util.now(); + } + + return attempt; + } + + async updateWork() { + this.bindChain(); + + let attempt = this.attempt; + + if (attempt) { + if (attempt.address.isNull()) { + throw new RPCError(errs.MISC_ERROR, + 'No addresses available for coinbase.'); + } + + this.miner.updateTime(attempt); + + if (++this.nonce2 === 0x100000000) { + this.nonce2 = 0; + this.nonce1++; + } + + const n1 = this.nonce1; + const n2 = this.nonce2; + + const root = attempt.getRoot(n1, n2); + + this.merkleMap.set(root, [n1, n2]); + + return attempt; + } + + if (this.miner.addresses.length === 0) { + throw new RPCError(errs.MISC_ERROR, + 'No addresses available for coinbase.'); + } + + attempt = await this.miner.createBlock(); + + const n1 = this.nonce1; + const n2 = this.nonce2; + + const root = attempt.getRoot(n1, n2); + + this.attempt = attempt; + this.lastActivity = util.now(); + this.merkleMap.set(root, [n1, n2]); + + return attempt; + } + + async addBlock(block) { + const unlock1 = await this.locker.lock(); + const unlock2 = await this.chain.locker.lock(); + try { + return await this._addBlock(block); + } finally { + unlock2(); + unlock1(); + } + } + + async _addBlock(block) { + this.logger.info('Handling submitted block: %h.', block.hash()); + + const prev = await this.chain.getEntry(block.prevBlock); + + if (prev) { + const state = await this.chain.getDeployments(block.time, prev); + + // Fix eloipool bug (witness nonce is not present). + if (state.hasWitness() && block.getCommitmentHash()) { + const tx = block.txs[0]; + const input = tx.inputs[0]; + if (!tx.hasWitness()) { + this.logger.warning('Submitted block had no witness nonce.'); + this.logger.debug(tx); + + // Recreate witness nonce (all zeroes). + input.witness.push(consensus.ZERO_HASH); + input.witness.compile(); + + tx.refresh(); + block.refresh(); + } + } + } + + let entry; + try { + entry = await this.chain._add(block); + } catch (err) { + if (err.type === 'VerifyError') { + this.logger.warning('RPC block rejected: %h (%s).', + block.hash(), err.reason); + return `rejected: ${err.reason}`; + } + throw err; + } + + if (!entry) { + this.logger.warning('RPC block rejected: %h (bad-prevblk).', + block.hash()); + return 'rejected: bad-prevblk'; + } + + return null; + } + + totalTX() { + return this.mempool ? this.mempool.map.size : 0; + } + + getSoftforks() { + return [ + toDeployment('bip34', 2, this.chain.state.hasBIP34()), + toDeployment('bip66', 3, this.chain.state.hasBIP66()), + toDeployment('bip65', 4, this.chain.state.hasCLTV()) + ]; + } + + async getBIP9Softforks() { + const tip = this.chain.tip; + const forks = {}; + + for (const deployment of this.network.deploys) { + const state = await this.chain.getState(tip, deployment); + let status; + + switch (state) { + case common.thresholdStates.DEFINED: + status = 'defined'; + break; + case common.thresholdStates.STARTED: + status = 'started'; + break; + case common.thresholdStates.LOCKED_IN: + status = 'locked_in'; + break; + case common.thresholdStates.ACTIVE: + status = 'active'; + break; + case common.thresholdStates.FAILED: + status = 'failed'; + break; + default: + assert(false, 'Bad state.'); + break; + } + + forks[deployment.name] = { + status: status, + bit: deployment.bit, + startTime: deployment.startTime, + timeout: deployment.timeout + }; + } + + return forks; + } + + async getHashRate(lookup, height) { + let tip = this.chain.tip; + + if (height != null) + tip = await this.chain.getEntry(height); + + if (!tip) + return 0; + + assert(typeof lookup === 'number'); + assert(lookup >= 0); + + if (lookup === 0) + lookup = tip.height % this.network.pow.retargetInterval + 1; + + if (lookup > tip.height) + lookup = tip.height; + + let min = tip.time; + let max = min; + let entry = tip; + + for (let i = 0; i < lookup; i++) { + entry = await this.chain.getPrevious(entry); + + if (!entry) + throw new RPCError(errs.DATABASE_ERROR, 'Not found.'); + + min = Math.min(entry.time, min); + max = Math.max(entry.time, max); + } + + const diff = max - min; + + if (diff === 0) + return 0; + + const work = tip.chainwork.sub(entry.chainwork); + + return Number(work.toString()) / diff; + } + + async mineBlocks(blocks, addr, tries) { + const unlock = await this.locker.lock(); + try { + return await this._mineBlocks(blocks, addr, tries); + } finally { + unlock(); + } + } + + async _mineBlocks(blocks, addr, tries) { + const hashes = []; + + for (let i = 0; i < blocks; i++) { + const block = await this.miner.mineBlock(null, addr); + const entry = await this.chain.add(block); + assert(entry); + hashes.push(entry.rhash()); + } + + return hashes; + } + + async findFork(entry) { + while (entry) { + if (await this.chain.isMainChain(entry)) + return entry; + entry = await this.chain.getPrevious(entry); + } + throw new Error('Fork not found.'); + } + + txToJSON(tx, entry) { + let height = -1; + let time = 0; + let hash = null; + let conf = 0; + + if (entry) { + height = entry.height; + time = entry.time; + hash = entry.rhash(); + conf = this.chain.height - height + 1; + } + + const vin = []; + + for (const input of tx.inputs) { + const json = { + coinbase: undefined, + txid: undefined, + scriptSig: undefined, + txinwitness: undefined, + sequence: input.sequence + }; + + if (tx.isCoinbase()) { + json.coinbase = input.script.toJSON(); + } else { + json.txid = input.prevout.txid(); + json.vout = input.prevout.index; + json.scriptSig = { + asm: input.script.toASM(), + hex: input.script.toJSON() + }; + } + + if (input.witness.items.length > 0) { + json.txinwitness = input.witness.items.map((item) => { + return item.toString('hex'); + }); + } + + vin.push(json); + } + + const vout = []; + + for (let i = 0; i < tx.outputs.length; i++) { + const output = tx.outputs[i]; + vout.push({ + value: Amount.btc(output.value, true), + n: i, + scriptPubKey: this.scriptToJSON(output.script, true) + }); + } + + return { + txid: tx.txid(), + hash: tx.wtxid(), + size: tx.getSize(), + vsize: tx.getVirtualSize(), + version: tx.version, + locktime: tx.locktime, + vin: vin, + vout: vout, + blockhash: hash, + confirmations: conf, + time: time, + blocktime: time, + hex: undefined + }; + } + + scriptToJSON(script, hex) { + const type = script.getType(); + + const json = { + asm: script.toASM(), + hex: undefined, + type: Script.typesByVal[type], + reqSigs: 1, + addresses: [], + p2sh: undefined + }; + + if (hex) + json.hex = script.toJSON(); + + const [m] = script.getMultisig(); + + if (m !== -1) + json.reqSigs = m; + + const addr = script.getAddress(); + + if (addr) { + const str = addr.toString(this.network); + json.addresses.push(str); + } + + return json; + } + + async headerToJSON(entry) { + const mtp = await this.chain.getMedianTime(entry); + const next = await this.chain.getNextHash(entry.hash); + + return { + hash: entry.rhash(), + confirmations: this.chain.height - entry.height + 1, + height: entry.height, + version: entry.version, + versionHex: hex32(entry.version), + merkleroot: util.revHex(entry.merkleRoot), + time: entry.time, + mediantime: mtp, + bits: entry.bits, + difficulty: toDifficulty(entry.bits), + chainwork: entry.chainwork.toString('hex', 64), + previousblockhash: !entry.prevBlock.equals(consensus.ZERO_HASH) + ? util.revHex(entry.prevBlock) + : null, + nextblockhash: next ? util.revHex(next) : null + }; + } + + async blockToJSON(entry, block, details) { + const mtp = await this.chain.getMedianTime(entry); + const next = await this.chain.getNextHash(entry.hash); + const txs = []; + + for (const tx of block.txs) { + if (details) { + const json = this.txToJSON(tx, entry); + txs.push(json); + continue; + } + txs.push(tx.txid()); + } + + return { + hash: entry.rhash(), + confirmations: this.chain.height - entry.height + 1, + strippedsize: block.getBaseSize(), + size: block.getSize(), + weight: block.getWeight(), + height: entry.height, + version: entry.version, + versionHex: hex32(entry.version), + merkleroot: util.revHex(entry.merkleRoot), + coinbase: block.txs[0].inputs[0].script.toJSON(), + tx: txs, + time: entry.time, + mediantime: mtp, + bits: entry.bits, + difficulty: toDifficulty(entry.bits), + chainwork: entry.chainwork.toString('hex', 64), + previousblockhash: !entry.prevBlock.equals(consensus.ZERO_HASH) + ? util.revHex(entry.prevBlock) + : null, + nextblockhash: next ? util.revHex(next) : null + }; + } + + entryToJSON(entry) { + return { + size: entry.size, + fee: Amount.btc(entry.deltaFee, true), + modifiedfee: 0, + time: entry.time, + height: entry.height, + startingpriority: entry.priority, + currentpriority: entry.getPriority(this.chain.height), + descendantcount: this.mempool.countDescendants(entry), + descendantsize: entry.descSize, + descendantfees: entry.descFee, + ancestorcount: this.mempool.countAncestors(entry), + ancestorsize: 0, + ancestorfees: 0, + depends: this.mempool.getDepends(entry.tx).map(util.revHex) + }; + } +} + +/* + * Helpers + */ + +function swap32(data) { + for (let i = 0; i < data.length; i += 4) { + const field = data.readUInt32LE(i, true); + data.writeUInt32BE(field, i, true); + } + return data; +} + +function toDeployment(id, version, status) { + return { + id: id, + version: version, + reject: { + status: status + } + }; +} + +function parseAddress(raw, network) { + try { + return Address.fromString(raw, network); + } catch (e) { + throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid address.'); + } +} + +function parseSecret(raw, network) { + try { + return KeyRing.fromSecret(raw, network); + } catch (e) { + throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid key.'); + } +} + +function parseIP(addr, network) { + try { + return IP.fromHostname(addr, network.port); + } catch (e) { + throw new RPCError(errs.CLIENT_INVALID_IP_OR_SUBNET, + 'Invalid IP address or subnet.'); + } +} + +function parseNetAddress(addr, network) { + try { + return NetAddress.fromHostname(addr, network); + } catch (e) { + throw new RPCError(errs.CLIENT_INVALID_IP_OR_SUBNET, + 'Invalid IP address or subnet.'); + } +} + +function toDifficulty(bits) { + let shift = (bits >>> 24) & 0xff; + let diff = 0x0000ffff / (bits & 0x00ffffff); + + while (shift < 29) { + diff *= 256.0; + shift++; + } + + while (shift > 29) { + diff /= 256.0; + shift--; + } + + return diff; +} + +function hex32(num) { + assert(num >= 0); + + num = num.toString(16); + + assert(num.length <= 8); + + while (num.length < 8) + num = '0' + num; + + return num; +} + +/* + * Expose + */ + +module.exports = RPC; diff --git a/lib/node/spvnode.js b/lib/node/spvnode.js index e266af735..bdcf81b2d 100644 --- a/lib/node/spvnode.js +++ b/lib/node/spvnode.js @@ -7,81 +7,77 @@ 'use strict'; -const Lock = require('../utils/lock'); +const assert = require('bsert'); +const {Lock} = require('bmutex'); const Chain = require('../blockchain/chain'); const Pool = require('../net/pool'); -const HTTPServer = require('../http/server'); -const RPC = require('../http/rpc'); const Node = require('./node'); +const HTTP = require('./http'); +const RPC = require('./rpc'); /** + * SPV Node * Create an spv node which only maintains * a chain, a pool, and an http server. * @alias module:node.SPVNode * @extends Node - * @constructor - * @param {Object?} options - * @param {Buffer?} options.sslKey - * @param {Buffer?} options.sslCert - * @param {Number?} options.httpPort - * @param {String?} options.httpHost - * @property {Boolean} loaded - * @property {Chain} chain - * @property {Pool} pool - * @property {HTTPServer} http - * @emits SPVNode#block - * @emits SPVNode#tx - * @emits SPVNode#error */ -function SPVNode(options) { - if (!(this instanceof SPVNode)) - return new SPVNode(options); - - Node.call(this, options); - - // SPV flag. - this.spv = true; - - this.chain = new Chain({ - network: this.network, - logger: this.logger, - db: this.config.str('db'), - prefix: this.config.prefix, - maxFiles: this.config.uint('max-files'), - cacheSize: this.config.mb('cache-size'), - entryCache: this.config.uint('entry-cache'), - forceFlags: this.config.bool('force-flags'), - checkpoints: this.config.bool('checkpoints'), - bip91: this.config.bool('bip91'), - bip148: this.config.bool('bip148'), - spv: true - }); - - this.pool = new Pool({ - network: this.network, - logger: this.logger, - chain: this.chain, - prefix: this.config.prefix, - proxy: this.config.str('proxy'), - onion: this.config.bool('onion'), - upnp: this.config.bool('upnp'), - seeds: this.config.array('seeds'), - nodes: this.config.array('nodes'), - only: this.config.array('only'), - bip151: this.config.bool('bip151'), - bip150: this.config.bool('bip150'), - identityKey: this.config.buf('identity-key'), - maxOutbound: this.config.uint('max-outbound'), - persistent: this.config.bool('persistent'), - selfish: true, - listen: false - }); - - this.rpc = new RPC(this); - - if (!HTTPServer.unsupported) { - this.http = new HTTPServer({ +class SPVNode extends Node { + /** + * Create SPV node. + * @constructor + * @param {Object?} options + * @param {Buffer?} options.sslKey + * @param {Buffer?} options.sslCert + * @param {Number?} options.httpPort + * @param {String?} options.httpHost + */ + + constructor(options) { + super('bcoin', 'bcoin.conf', 'debug.log', options); + + this.opened = false; + + // SPV flag. + this.spv = true; + + this.chain = new Chain({ + network: this.network, + logger: this.logger, + prefix: this.config.prefix, + memory: this.memory, + maxFiles: this.config.uint('max-files'), + cacheSize: this.config.mb('cache-size'), + entryCache: this.config.uint('entry-cache'), + forceFlags: this.config.bool('force-flags'), + checkpoints: this.config.bool('checkpoints'), + bip91: this.config.bool('bip91'), + bip148: this.config.bool('bip148'), + spv: true + }); + + this.pool = new Pool({ + network: this.network, + logger: this.logger, + chain: this.chain, + prefix: this.config.prefix, + proxy: this.config.str('proxy'), + onion: this.config.bool('onion'), + upnp: this.config.bool('upnp'), + seeds: this.config.array('seeds'), + nodes: this.config.array('nodes'), + only: this.config.array('only'), + maxOutbound: this.config.uint('max-outbound'), + createSocket: this.config.func('create-socket'), + memory: this.memory, + selfish: true, + listen: false + }); + + this.rpc = new RPC(this); + + this.http = new HTTP({ network: this.network, logger: this.logger, node: this, @@ -92,251 +88,258 @@ function SPVNode(options) { host: this.config.str('http-host'), port: this.config.uint('http-port'), apiKey: this.config.str('api-key'), - noAuth: this.config.bool('no-auth') + noAuth: this.config.bool('no-auth'), + cors: this.config.bool('cors') }); - } - this.rescanJob = null; - this.scanLock = new Lock(); - this.watchLock = new Lock(); + this.rescanJob = null; + this.scanLock = new Lock(); + this.watchLock = new Lock(); - this._init(); -} + this.init(); + } -Object.setPrototypeOf(SPVNode.prototype, Node.prototype); + /** + * Initialize the node. + * @private + */ -/** - * Initialize the node. - * @private - */ - -SPVNode.prototype._init = function _init() { - // Bind to errors - this.chain.on('error', err => this.error(err)); - this.pool.on('error', err => this.error(err)); + init() { + // Bind to errors + this.chain.on('error', err => this.error(err)); + this.pool.on('error', err => this.error(err)); - if (this.http) - this.http.on('error', err => this.error(err)); + if (this.http) + this.http.on('error', err => this.error(err)); - this.pool.on('tx', (tx) => { - if (this.rescanJob) - return; + this.pool.on('tx', (tx) => { + if (this.rescanJob) + return; - this.emit('tx', tx); - }); + this.emit('tx', tx); + }); - this.chain.on('block', (block) => { - this.emit('block', block); - }); + this.chain.on('block', (block) => { + this.emit('block', block); + }); - this.chain.on('connect', async (entry, block) => { - if (this.rescanJob) { - try { - await this.watchBlock(entry, block); - } catch (e) { - this.error(e); + this.chain.on('connect', async (entry, block) => { + if (this.rescanJob) { + try { + await this.watchBlock(entry, block); + } catch (e) { + this.error(e); + } + return; } - return; - } - this.emit('connect', entry, block); - }); - - this.chain.on('disconnect', (entry, block) => { - this.emit('disconnect', entry, block); - }); + this.emit('connect', entry, block); + }); - this.chain.on('reorganize', (tip, competitor) => { - this.emit('reorganize', tip, competitor); - }); + this.chain.on('disconnect', (entry, block) => { + this.emit('disconnect', entry, block); + }); - this.chain.on('reset', (tip) => { - this.emit('reset', tip); - }); + this.chain.on('reorganize', (tip, competitor) => { + this.emit('reorganize', tip, competitor); + }); - this.loadPlugins(); -}; + this.chain.on('reset', (tip) => { + this.emit('reset', tip); + }); -/** - * Open the node and all its child objects, - * wait for the database to load. - * @alias SPVNode#open - * @returns {Promise} - */ + this.loadPlugins(); + } -SPVNode.prototype._open = async function _open(callback) { - await this.chain.open(); - await this.pool.open(); + /** + * Open the node and all its child objects, + * wait for the database to load. + * @returns {Promise} + */ - await this.openPlugins(); + async open() { + assert(!this.opened, 'SPVNode is already open.'); + this.opened = true; - if (this.http) - await this.http.open(); + await this.handlePreopen(); + await this.chain.open(); + await this.pool.open(); - this.logger.info('Node is loaded.'); -}; + await this.openPlugins(); -/** - * Close the node, wait for the database to close. - * @alias SPVNode#close - * @returns {Promise} - */ + await this.http.open(); + await this.handleOpen(); -SPVNode.prototype._close = async function _close() { - if (this.http) - await this.http.close(); + this.logger.info('Node is loaded.'); + } - await this.closePlugins(); + /** + * Close the node, wait for the database to close. + * @returns {Promise} + */ - await this.pool.close(); - await this.chain.close(); -}; + async close() { + assert(this.opened, 'SPVNode is not open.'); + this.opened = false; -/** - * Scan for any missed transactions. - * Note that this will replay the blockchain sync. - * @param {Number|Hash} start - Start block. - * @param {Bloom} filter - * @param {Function} iter - Iterator. - * @returns {Promise} - */ + await this.handlePreclose(); + await this.http.close(); -SPVNode.prototype.scan = async function scan(start, filter, iter) { - const unlock = await this.scanLock.lock(); - const height = this.chain.height; + await this.closePlugins(); - try { - await this.chain.replay(start); + await this.pool.close(); + await this.chain.close(); + await this.handleClose(); + } - if (this.chain.height < height) { - // We need to somehow defer this. - // await this.connect(); - // this.startSync(); - // await this.watchUntil(height, iter); + /** + * Scan for any missed transactions. + * Note that this will replay the blockchain sync. + * @param {Number|Hash} start - Start block. + * @param {Bloom} filter + * @param {Function} iter - Iterator. + * @returns {Promise} + */ + + async scan(start, filter, iter) { + const unlock = await this.scanLock.lock(); + const height = this.chain.height; + + try { + await this.chain.replay(start); + + if (this.chain.height < height) { + // We need to somehow defer this. + // await this.connect(); + // this.startSync(); + // await this.watchUntil(height, iter); + } + } finally { + unlock(); } - } finally { - unlock(); } -}; -/** - * Watch the blockchain until a certain height. - * @param {Number} height - * @param {Function} iter - * @returns {Promise} - */ - -SPVNode.prototype.watchUntil = function watchUntil(height, iter) { - return new Promise((resolve, reject) => { - this.rescanJob = new RescanJob(resolve, reject, height, iter); - }); -}; + /** + * Watch the blockchain until a certain height. + * @param {Number} height + * @param {Function} iter + * @returns {Promise} + */ -/** - * Handled watched block. - * @param {ChainEntry} entry - * @param {MerkleBlock} block - * @returns {Promise} - */ + watchUntil(height, iter) { + return new Promise((resolve, reject) => { + this.rescanJob = new RescanJob(resolve, reject, height, iter); + }); + } -SPVNode.prototype.watchBlock = async function watchBlock(entry, block) { - const unlock = await this.watchLock.lock(); - try { - if (entry.height < this.rescanJob.height) { - await this.rescanJob.iter(entry, block.txs); - return; + /** + * Handled watched block. + * @param {ChainEntry} entry + * @param {MerkleBlock} block + * @returns {Promise} + */ + + async watchBlock(entry, block) { + const unlock = await this.watchLock.lock(); + try { + if (entry.height < this.rescanJob.height) { + await this.rescanJob.iter(entry, block.txs); + return; + } + this.rescanJob.resolve(); + this.rescanJob = null; + } catch (e) { + this.rescanJob.reject(e); + this.rescanJob = null; + } finally { + unlock(); } - this.rescanJob.resolve(); - this.rescanJob = null; - } catch (e) { - this.rescanJob.reject(e); - this.rescanJob = null; - } finally { - unlock(); } -}; -/** - * Broadcast a transaction (note that this will _not_ be verified - * by the mempool - use with care, lest you get banned from - * bitcoind nodes). - * @param {TX|Block} item - * @returns {Promise} - */ - -SPVNode.prototype.broadcast = async function broadcast(item) { - try { - await this.pool.broadcast(item); - } catch (e) { - this.emit('error', e); + /** + * Broadcast a transaction (note that this will _not_ be verified + * by the mempool - use with care, lest you get banned from + * bitcoind nodes). + * @param {TX|Block} item + * @returns {Promise} + */ + + async broadcast(item) { + try { + await this.pool.broadcast(item); + } catch (e) { + this.emit('error', e); + } } -}; -/** - * Broadcast a transaction (note that this will _not_ be verified - * by the mempool - use with care, lest you get banned from - * bitcoind nodes). - * @param {TX} tx - * @returns {Promise} - */ + /** + * Broadcast a transaction (note that this will _not_ be verified + * by the mempool - use with care, lest you get banned from + * bitcoind nodes). + * @param {TX} tx + * @returns {Promise} + */ -SPVNode.prototype.sendTX = function sendTX(tx) { - return this.broadcast(tx); -}; + sendTX(tx) { + return this.broadcast(tx); + } -/** - * Broadcast a transaction. Silence errors. - * @param {TX} tx - * @returns {Promise} - */ + /** + * Broadcast a transaction. Silence errors. + * @param {TX} tx + * @returns {Promise} + */ -SPVNode.prototype.relay = function relay(tx) { - return this.broadcast(tx); -}; + relay(tx) { + return this.broadcast(tx); + } -/** - * Connect to the network. - * @returns {Promise} - */ + /** + * Connect to the network. + * @returns {Promise} + */ -SPVNode.prototype.connect = function connect() { - return this.pool.connect(); -}; + connect() { + return this.pool.connect(); + } -/** - * Disconnect from the network. - * @returns {Promise} - */ + /** + * Disconnect from the network. + * @returns {Promise} + */ -SPVNode.prototype.disconnect = function disconnect() { - return this.pool.disconnect(); -}; + disconnect() { + return this.pool.disconnect(); + } -/** - * Start the blockchain sync. - */ + /** + * Start the blockchain sync. + */ -SPVNode.prototype.startSync = function startSync() { - return this.pool.startSync(); -}; + startSync() { + return this.pool.startSync(); + } -/** - * Stop syncing the blockchain. - */ + /** + * Stop syncing the blockchain. + */ -SPVNode.prototype.stopSync = function stopSync() { - return this.pool.stopSync(); -}; + stopSync() { + return this.pool.stopSync(); + } +} /* * Helpers */ -function RescanJob(resolve, reject, height, iter) { - this.resolve = resolve; - this.reject = reject; - this.height = height; - this.iter = iter; +class RescanJob { + constructor(resolve, reject, height, iter) { + this.resolve = resolve; + this.reject = reject; + this.height = height; + this.iter = iter; + } } /* diff --git a/lib/pkg.js b/lib/pkg.js index f35fdea2c..04dc52c0b 100644 --- a/lib/pkg.js +++ b/lib/pkg.js @@ -6,16 +6,75 @@ 'use strict'; +const pkg = exports; + /** - * Current version string. + * Package Name + * @const {String} + * @default + */ + +pkg.name = require('../package.json').name; + +/** + * Project Name + * @const {String} + * @default + */ + +pkg.core = 'fcoin'; + +/** + * Organization Name + * @const {String} + * @default + */ + +pkg.organization = 'oipwg'; + +/** + * Currency Name + * @const {String} + * @default + */ + +pkg.currency = 'flo'; + +/** + * Currency Unit + * @const {String} + * @default + */ + +pkg.unit = 'flo'; + +/** + * Base Unit * @const {String} + * @default */ -exports.version = 'v1.0.0-beta.16'; +pkg.base = 'satoshi'; + +/** + * Config file name. + * @const {String} + * @default + */ + +pkg.cfg = `${pkg.core}.conf`; /** * Repository URL. * @const {String} + * @default + */ + +pkg.url = `https://github.com/${pkg.organization}/${pkg.name}`; + +/** + * Current version string. + * @const {String} */ -exports.url = 'https://github.com/oipwg/fcoin'; +pkg.version = require('../package.json').version; diff --git a/lib/primitives/abstractblock.js b/lib/primitives/abstractblock.js index 51cbb911b..e779c4e56 100644 --- a/lib/primitives/abstractblock.js +++ b/lib/primitives/abstractblock.js @@ -7,266 +7,267 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); +const bio = require('bufio'); const util = require('../utils/util'); -const digest = require('../crypto/digest'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); const InvItem = require('./invitem'); -const encoding = require('../utils/encoding'); const consensus = require('../protocol/consensus'); -const scrypt = require('../crypto/scrypt').derive; +const hash256 = require('bcrypto/lib/hash256'); +const scrypt = require('bcrypto/lib/scrypt').derive; /** + * Abstract Block * The class which all block-like objects inherit from. * @alias module:primitives.AbstractBlock - * @constructor * @abstract - * @property {Number} version - Block version. Note - * that Bcoin reads versions as unsigned despite - * them being signed on the protocol level. This - * number will never be negative. - * @property {Hash} prevBlock - Previous block hash. - * @property {Hash} merkleRoot - Merkle root hash. - * @property {Number} time - Timestamp. + * @property {Number} version + * @property {Hash} prevBlock + * @property {Hash} merkleRoot + * @property {Number} time * @property {Number} bits * @property {Number} nonce */ -function AbstractBlock() { - if (!(this instanceof AbstractBlock)) - return new AbstractBlock(); +class AbstractBlock { + /** + * Create an abstract block. + * @constructor + */ - this.version = 1; - this.prevBlock = encoding.NULL_HASH; - this.merkleRoot = encoding.NULL_HASH; - this.time = 0; - this.bits = 0; - this.nonce = 0; + constructor() { + this.version = 1; + this.prevBlock = consensus.ZERO_HASH; + this.merkleRoot = consensus.ZERO_HASH; + this.time = 0; + this.bits = 0; + this.nonce = 0; - this.mutable = false; + this.mutable = false; - this._hash = null; - this._hhash = null; -} - -/** - * Inject properties from options object. - * @private - * @param {NakedBlock} options - */ - -AbstractBlock.prototype.parseOptions = function parseOptions(options) { - assert(options, 'Block data is required.'); - assert(util.isU32(options.version)); - assert(typeof options.prevBlock === 'string'); - assert(typeof options.merkleRoot === 'string'); - assert(util.isU32(options.time)); - assert(util.isU32(options.bits)); - assert(util.isU32(options.nonce)); - - this.version = options.version; - this.prevBlock = options.prevBlock; - this.merkleRoot = options.merkleRoot; - this.time = options.time; - this.bits = options.bits; - this.nonce = options.nonce; - - if (options.mutable != null) - this.mutable = Boolean(options.mutable); - - return this; -}; + this._hash = null; + this._hhash = null; + } -/** - * Inject properties from json object. - * @private - * @param {Object} json - */ + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + parseOptions(options) { + assert(options, 'Block data is required.'); + assert((options.version >>> 0) === options.version); + assert(Buffer.isBuffer(options.prevBlock)); + assert(Buffer.isBuffer(options.merkleRoot)); + assert((options.time >>> 0) === options.time); + assert((options.bits >>> 0) === options.bits); + assert((options.nonce >>> 0) === options.nonce); + + this.version = options.version; + this.prevBlock = options.prevBlock; + this.merkleRoot = options.merkleRoot; + this.time = options.time; + this.bits = options.bits; + this.nonce = options.nonce; + + if (options.mutable != null) { + assert(typeof options.mutable === 'boolean'); + this.mutable = options.mutable; + } -AbstractBlock.prototype.parseJSON = function parseJSON(json) { - assert(json, 'Block data is required.'); - assert(util.isU32(json.version)); - assert(typeof json.prevBlock === 'string'); - assert(typeof json.merkleRoot === 'string'); - assert(util.isU32(json.time)); - assert(util.isU32(json.bits)); - assert(util.isU32(json.nonce)); - - this.version = json.version; - this.prevBlock = util.revHex(json.prevBlock); - this.merkleRoot = util.revHex(json.merkleRoot); - this.time = json.time; - this.bits = json.bits; - this.nonce = json.nonce; - - return this; -}; + return this; + } -/** - * Test whether the block is a memblock. - * @returns {Boolean} - */ + /** + * Inject properties from json object. + * @private + * @param {Object} json + */ + + parseJSON(json) { + assert(json, 'Block data is required.'); + assert((json.version >>> 0) === json.version); + assert(typeof json.prevBlock === 'string'); + assert(typeof json.merkleRoot === 'string'); + assert((json.time >>> 0) === json.time); + assert((json.bits >>> 0) === json.bits); + assert((json.nonce >>> 0) === json.nonce); + + this.version = json.version; + this.prevBlock = util.fromRev(json.prevBlock); + this.merkleRoot = util.fromRev(json.merkleRoot); + this.time = json.time; + this.bits = json.bits; + this.nonce = json.nonce; + + return this; + } -AbstractBlock.prototype.isMemory = function isMemory() { - return false; -}; + /** + * Test whether the block is a memblock. + * @returns {Boolean} + */ -/** - * Clear any cached values (abstract). - */ + isMemory() { + return false; + } -AbstractBlock.prototype._refresh = function _refresh() { - this._hash = null; - this._hhash = null; -}; + /** + * Clear any cached values (abstract). + */ -/** - * Clear any cached values. - */ + _refresh() { + this._hash = null; + this._hhash = null; + } -AbstractBlock.prototype.refresh = function refresh() { - return this._refresh(); -}; + /** + * Clear any cached values. + */ -/** - * Hash the block headers. - * @param {String?} enc - Can be `'hex'` or `null`. - * @returns {Hash|Buffer} hash - */ + refresh() { + return this._refresh(); + } -AbstractBlock.prototype.hash = function hash(enc) { - let h = this._hash; + /** + * Hash the block headers. + * @param {String?} enc - Can be `'hex'` or `null`. + * @returns {Hash|Buffer} hash + */ - if (!h) { - h = digest.hash256(this.toHead()); - if (!this.mutable) - this._hash = h; - } + hash(enc) { + let h = this._hash; - if (enc === 'hex') { - let hex = this._hhash; - if (!hex) { - hex = h.toString('hex'); + if (!h) { + h = hash256.digest(this.toHead()); if (!this.mutable) - this._hhash = hex; + this._hash = h; } - h = hex; - } - return h; -}; - -/** - * Hash the block headers with scrypt. - * @param {String?} enc - Can be `'hex'` or `null`. - * @returns {Hash|Buffer} hash - */ + if (enc === 'hex') { + let hex = this._hhash; + if (!hex) { + hex = h.toString('hex'); + if (!this.mutable) + this._hhash = hex; + } + h = hex; + } -AbstractBlock.prototype.powHash = function powHash() { - var data = this.toHead(); - return scrypt(data, data, 1024, 1, 1, 32); -}; + return h; + } -/** - * Serialize the block headers. - * @returns {Buffer} - */ + /** + * Hash the block headers with scrypt. + * @param {String?} enc - Can be `'hex'` or `null`. + * @returns {Hash|Buffer} hash + */ -AbstractBlock.prototype.toHead = function toHead() { - return this.writeHead(new StaticWriter(80)).render(); -}; + powHash() { + var data = this.toHead() + return scrypt(data, data, 1024, 1, 1, 32) + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + /** + * Serialize the block headers. + * @returns {Buffer} + */ -AbstractBlock.prototype.fromHead = function fromHead(data) { - return this.readHead(new BufferReader(data)); -}; + toHead() { + return this.writeHead(bio.write(80)).render(); + } -/** - * Serialize the block headers. - * @param {BufferWriter} bw - */ + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -AbstractBlock.prototype.writeHead = function writeHead(bw) { - bw.writeU32(this.version); - bw.writeHash(this.prevBlock); - bw.writeHash(this.merkleRoot); - bw.writeU32(this.time); - bw.writeU32(this.bits); - bw.writeU32(this.nonce); - return bw; -}; + fromHead(data) { + return this.readHead(bio.read(data)); + } -/** - * Parse the block headers. - * @param {BufferReader} br - */ + /** + * Serialize the block headers. + * @param {BufferWriter} bw + */ + + writeHead(bw) { + bw.writeU32(this.version); + bw.writeHash(this.prevBlock); + bw.writeHash(this.merkleRoot); + bw.writeU32(this.time); + bw.writeU32(this.bits); + bw.writeU32(this.nonce); + return bw; + } -AbstractBlock.prototype.readHead = function readHead(br) { - this.version = br.readU32(); - this.prevBlock = br.readHash('hex'); - this.merkleRoot = br.readHash('hex'); - this.time = br.readU32(); - this.bits = br.readU32(); - this.nonce = br.readU32(); - return this; -}; + /** + * Parse the block headers. + * @param {BufferReader} br + */ + + readHead(br) { + this.version = br.readU32(); + this.prevBlock = br.readHash(); + this.merkleRoot = br.readHash(); + this.time = br.readU32(); + this.bits = br.readU32(); + this.nonce = br.readU32(); + return this; + } -/** - * Verify the block. - * @returns {Boolean} - */ + /** + * Verify the block. + * @returns {Boolean} + */ -AbstractBlock.prototype.verify = function verify() { - if (!this.verifyPOW()) - return false; + verify() { + if (!this.verifyPOW()) + return false; - if (!this.verifyBody()) - return false; + if (!this.verifyBody()) + return false; - return true; -}; + return true; + } -/** - * Verify proof-of-work. - * @returns {Boolean} - */ + /** + * Verify proof-of-work. + * @returns {Boolean} + */ -AbstractBlock.prototype.verifyPOW = function verifyPOW() { - return consensus.verifyPOW(this.powHash(), this.bits); -}; + verifyPOW() { + return consensus.verifyPOW(this.powHash(), this.bits); + } -/** - * Verify the block. - * @returns {Boolean} - */ + /** + * Verify the block. + * @returns {Boolean} + */ -AbstractBlock.prototype.verifyBody = function verifyBody() { - throw new Error('Abstract method.'); -}; + verifyBody() { + throw new Error('Abstract method.'); + } -/** - * Get little-endian block hash. - * @returns {Hash} - */ + /** + * Get little-endian block hash. + * @returns {Hash} + */ -AbstractBlock.prototype.rhash = function rhash() { - return util.revHex(this.hash('hex')); -}; + rhash() { + return util.revHex(this.hash()); + } -/** - * Convert the block to an inv item. - * @returns {InvItem} - */ + /** + * Convert the block to an inv item. + * @returns {InvItem} + */ -AbstractBlock.prototype.toInv = function toInv() { - return new InvItem(InvItem.types.BLOCK, this.hash('hex')); -}; + toInv() { + return new InvItem(InvItem.types.BLOCK, this.hash()); + } +} /* * Expose diff --git a/lib/primitives/address.js b/lib/primitives/address.js index 335772a4f..b5062862e 100644 --- a/lib/primitives/address.js +++ b/lib/primitives/address.js @@ -7,902 +7,871 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); +const bio = require('bufio'); +const {base58, bech32} = require('bstring'); +const sha256 = require('bcrypto/lib/sha256'); +const hash160 = require('bcrypto/lib/hash160'); +const hash256 = require('bcrypto/lib/hash256'); const Network = require('../protocol/network'); -const encoding = require('../utils/encoding'); -const util = require('../utils/util'); -const digest = require('../crypto/digest'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); -const base58 = require('../utils/base58'); -const bech32 = require('../utils/bech32'); +const consensus = require('../protocol/consensus'); +const {inspectSymbol} = require('../utils'); + +/* + * Constants + */ + +const ZERO_HASH160 = Buffer.alloc(20, 0x00); /** + * Address * Represents an address. * @alias module:primitives.Address - * @constructor - * @param {Object?} options * @property {Buffer} hash * @property {AddressPrefix} type * @property {Number} version - * @property {Network} network - */ - -function Address(options) { - if (!(this instanceof Address)) - return new Address(options); - - this.hash = encoding.ZERO_HASH160; - this.type = Address.types.PUBKEYHASH; - this.version = -1; - this.network = Network.primary; - - if (options) - this.fromOptions(options); -} - -/** - * Address types. - * @enum {Number} */ -Address.types = { - PUBKEYHASH: 2, - SCRIPTHASH: 3, - SCRIPTHASH2: 4, - WITNESS: 5 -}; +class Address { + /** + * Create an address. + * @constructor + * @param {Object?} options + */ -/** - * Address types by value. - * @const {RevMap} - */ + constructor(options, network) { + this.type = Address.types.PUBKEYHASH; + this.version = -1; + this.hash = ZERO_HASH160; -Address.typesByVal = util.reverse(Address.types); + if (options) + this.fromOptions(options, network); + } -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ -Address.prototype.fromOptions = function fromOptions(options) { - if (typeof options === 'string') - return this.fromString(options); + fromOptions(options, network) { + if (typeof options === 'string') + return this.fromString(options, network); - return this.fromHash( - options.hash, - options.type, - options.version, - options.network - ); -}; + assert(options); -/** - * Insantiate address from options. - * @param {Object} options - * @returns {Address} - */ + const {hash, type, version} = options; -Address.fromOptions = function fromOptions(options) { - return new Address().fromOptions(options); -}; - -/** - * Get the address hash. - * @param {String?} enc - Can be `"hex"` or `null`. - * @returns {Hash|Buffer} - */ - -Address.prototype.getHash = function getHash(enc) { - if (enc === 'hex') - return this.hash.toString(enc); - return this.hash; -}; - -/** - * Test whether the address is null. - * @returns {Boolean} - */ - -Address.prototype.isNull = function isNull() { - if (this.hash.length === 20) - return this.hash.equals(encoding.ZERO_HASH160); - - if (this.hash.length === 32) - return this.hash.equals(encoding.ZERO_HASH); - - for (let i = 0; i < this.hash.length; i++) { - if (this.hash[i] !== 0) - return false; + return this.fromHash(hash, type, version); } - return true; -}; + /** + * Insantiate address from options. + * @param {Object} options + * @returns {Address} + */ -/** - * Test equality against another address. - * @param {Address} addr - * @returns {Boolean} - */ + static fromOptions(options, network) { + return new this().fromOptions(options, network); + } -Address.prototype.equals = function equals(addr) { - assert(addr instanceof Address); + /** + * Get the address hash. + * @param {String?} enc - Can be `"hex"` or `null`. + * @returns {Hash|Buffer} + */ - return this.network === addr.network - && this.type === addr.type - && this.version === addr.version - && this.hash.equals(addr.hash); -}; - -/** - * Get the address type as a string. - * @returns {String} - */ + getHash(enc) { + if (enc === 'hex') + return this.hash.toString('hex'); + return this.hash; + } -Address.prototype.getType = function getType() { - return Address.typesByVal[this.type].toLowerCase(); -}; + /** + * Test whether the address is null. + * @returns {Boolean} + */ -/** - * Get a network address prefix for the address. - * @param {Network?} network - * @returns {Number} - */ + isNull() { + if (this.hash.length === 20) + return this.hash.equals(ZERO_HASH160); -Address.prototype.getPrefix = function getPrefix(network) { - if (!network) - network = this.network; + if (this.hash.length === 32) + return this.hash.equals(consensus.ZERO_HASH); - network = Network.get(network); + for (let i = 0; i < this.hash.length; i++) { + if (this.hash[i] !== 0) + return false; + } - const prefixes = network.addressPrefix; + return true; + } - switch (this.type) { - case Address.types.PUBKEYHASH: - return prefixes.pubkeyhash; - case Address.types.SCRIPTHASH: - return prefixes.scripthash; - case Address.types.SCRIPTHASH2: - return prefixes.scripthash2; - case Address.types.WITNESS: - if (this.hash.length === 20) - return prefixes.witnesspubkeyhash; + /** + * Test equality against another address. + * @param {Address} addr + * @returns {Boolean} + */ - if (this.hash.length === 32) - return prefixes.witnessscripthash; + equals(addr) { + assert(addr instanceof Address); - break; + return this.type === addr.type + && this.version === addr.version + && this.hash.equals(addr.hash); } - return -1; -}; + /** + * Get the address type as a string. + * @returns {String} + */ -/** - * Calculate size of serialized address. - * @returns {Number} - */ + getType() { + return Address.typesByVal[this.type].toLowerCase(); + } -Address.prototype.getSize = function getSize() { - let size = 5 + this.hash.length; + /** + * Get a network address prefix for the address. + * @param {Network?} network + * @returns {Number} + */ - if (this.version !== -1) - size += 2; + getPrefix(network) { + network = Network.get(network); - return size; -}; + const prefixes = network.addressPrefix; -/** - * Compile the address object to its raw serialization. - * @param {{NetworkType|Network)?} network - * @returns {Buffer} - * @throws Error on bad hash/prefix. - */ + switch (this.type) { + case Address.types.PUBKEYHASH: + return prefixes.pubkeyhash; + case Address.types.SCRIPTHASH: + return prefixes.scripthash; + case Address.types.SCRIPTHASH2: + return prefixes.scripthash2; + case Address.types.WITNESS: + if (this.hash.length === 20) + return prefixes.witnesspubkeyhash; -Address.prototype.toRaw = function toRaw(network) { - const size = this.getSize(); - const bw = new StaticWriter(size); - const prefix = this.getPrefix(network); + if (this.hash.length === 32) + return prefixes.witnessscripthash; - assert(prefix !== -1, 'Not a valid address prefix.'); - - bw.writeU8(prefix); + break; + } - if (this.version !== -1) { - bw.writeU8(this.version); - bw.writeU8(0); + return -1; } - bw.writeBytes(this.hash); - bw.writeChecksum(); - - return bw.render(); -}; - -/** - * Compile the address object to a base58 address. - * @param {{NetworkType|Network)?} network - * @returns {Base58Address} - * @throws Error on bad hash/prefix. - */ + /** + * Calculate size of serialized address. + * @returns {Number} + */ -Address.prototype.toBase58 = function toBase58(network) { - return base58.encode(this.toRaw(network)); -}; + getSize() { + let size = 5 + this.hash.length; -/** - * Compile the address object to a bech32 address. - * @param {{NetworkType|Network)?} network - * @returns {String} - * @throws Error on bad hash/prefix. - */ + if (this.version !== -1) + size += 2; -Address.prototype.toBech32 = function toBech32(network) { - const version = this.version; - const hash = this.hash; + return size; + } - assert(version !== -1, - 'Cannot convert non-program address to bech32.'); + /** + * Compile the address object to its raw serialization. + * @param {{NetworkType|Network)?} network + * @returns {Buffer} + * @throws Error on bad hash/prefix. + */ - if (!network) - network = this.network; + toRaw(network) { + const size = this.getSize(); + const bw = bio.write(size); + const prefix = this.getPrefix(network); - network = Network.get(network); + assert(prefix !== -1, 'Not a valid address prefix.'); - const hrp = network.addressPrefix.bech32; + bw.writeU8(prefix); - return bech32.encode(hrp, version, hash); -}; + if (this.version !== -1) { + bw.writeU8(this.version); + bw.writeU8(0); + } -/** - * Inject properties from string. - * @private - * @param {String} addr - * @param {(Network|NetworkType)?} network - * @returns {Address} - */ + bw.writeBytes(this.hash); + bw.writeChecksum(hash256.digest); -Address.prototype.fromString = function fromString(addr, network) { - assert(typeof addr === 'string'); - assert(addr.length > 0); - assert(addr.length <= 100); + return bw.render(); + } - // If the address is mixed case, - // it can only ever be base58. - if (isMixedCase(addr)) - return this.fromBase58(addr, network); + /** + * Compile the address object to a base58 address. + * @param {{NetworkType|Network)?} network + * @returns {AddressString} + * @throws Error on bad hash/prefix. + */ - // Otherwise, it's most likely bech32. - try { - return this.fromBech32(addr, network); - } catch (e) { - return this.fromBase58(addr, network); + toBase58(network) { + return base58.encode(this.toRaw(network)); } -}; -/** - * Instantiate address from string. - * @param {String} addr - * @param {(Network|NetworkType)?} network - * @returns {Address} - */ + /** + * Compile the address object to a bech32 address. + * @param {{NetworkType|Network)?} network + * @returns {String} + * @throws Error on bad hash/prefix. + */ -Address.fromString = function fromString(addr, network) { - return new Address().fromString(addr, network); -}; + toBech32(network) { + const version = this.version; + const hash = this.hash; -/** - * Convert the Address to a string. - * @param {(Network|NetworkType)?} network - * @returns {Base58Address} - */ + assert(version !== -1, + 'Cannot convert non-program address to bech32.'); -Address.prototype.toString = function toString(network) { - if (this.version !== -1) - return this.toBech32(network); - return this.toBase58(network); -}; + network = Network.get(network); -/** - * Inspect the Address. - * @returns {Object} - */ + const hrp = network.addressPrefix.bech32; -Address.prototype.inspect = function inspect() { - return ''; -}; + return bech32.encode(hrp, version, hash); + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @throws Parse error - */ + /** + * Inject properties from string. + * @private + * @param {String} addr + * @param {(Network|NetworkType)?} network + * @returns {Address} + */ + + fromString(addr, network) { + assert(typeof addr === 'string'); + assert(addr.length > 0); + assert(addr.length <= 100); + + // If the address is mixed case, + // it can only ever be base58. + if (isMixedCase(addr)) + return this.fromBase58(addr, network); + + // Otherwise, it's most likely bech32. + try { + return this.fromBech32(addr, network); + } catch (e) { + return this.fromBase58(addr, network); + } + } -Address.prototype.fromRaw = function fromRaw(data, network) { - const br = new BufferReader(data, true); + /** + * Instantiate address from string. + * @param {String} addr + * @param {(Network|NetworkType)?} network + * @returns {Address} + */ - if (data.length > 40) - throw new Error('Address is too long.'); + static fromString(addr, network) { + return new this().fromString(addr, network); + } - const prefix = br.readU8(); + /** + * Convert the Address to a string. + * @param {(Network|NetworkType)?} network + * @returns {AddressString} + */ - network = Network.fromAddress(prefix, network); + toString(network) { + if (this.version !== -1) + return this.toBech32(network); + return this.toBase58(network); + } - const type = Address.getType(prefix, network); + /** + * Inspect the Address. + * @returns {Object} + */ + + [inspectSymbol]() { + return ''; + } - let version = -1; - if (data.length > 25) { - version = br.readU8(); + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + * @throws Parse error + */ - if (br.readU8() !== 0) - throw new Error('Address version padding is non-zero.'); - } + fromRaw(data, network) { + const br = bio.read(data, true); + const prefix = br.readU8(); - const hash = br.readBytes(br.left() - 4); + network = Network.fromAddress(prefix, network); - br.verifyChecksum(); + const type = Address.getType(prefix, network); - return this.fromHash(hash, type, version, network); -}; + let version = -1; + if (type === Address.types.WITNESS) { + if (data.length > 38) + throw new Error('Address is too long.'); -/** - * Create an address object from a serialized address. - * @param {Buffer} data - * @returns {Address} - * @throws Parse error. - */ + version = br.readU8(); -Address.fromRaw = function fromRaw(data, network) { - return new Address().fromRaw(data, network); -}; + if (br.readU8() !== 0) + throw new Error('Address version padding is non-zero.'); + } else { + if (data.length !== 25) + throw new Error('Address is too long.'); + } -/** - * Inject properties from base58 address. - * @private - * @param {Base58Address} data - * @param {Network?} network - * @throws Parse error - */ + const hash = br.readBytes(br.left() - 4); -Address.prototype.fromBase58 = function fromBase58(data, network) { - assert(typeof data === 'string'); + br.verifyChecksum(hash256.digest); - if (data.length > 55) - throw new Error('Address is too long.'); + return this.fromHash(hash, type, version); + } - return this.fromRaw(base58.decode(data), network); -}; + /** + * Create an address object from a serialized address. + * @param {Buffer} data + * @returns {Address} + * @throws Parse error. + */ -/** - * Create an address object from a base58 address. - * @param {Base58Address} data - * @param {Network?} network - * @returns {Address} - * @throws Parse error. - */ + static fromRaw(data, network) { + return new this().fromRaw(data, network); + } -Address.fromBase58 = function fromBase58(data, network) { - return new Address().fromBase58(data, network); -}; + /** + * Inject properties from base58 address. + * @private + * @param {AddressString} data + * @param {Network?} network + * @throws Parse error + */ -/** - * Inject properties from bech32 address. - * @private - * @param {String} data - * @param {Network?} network - * @throws Parse error - */ + fromBase58(data, network) { + assert(typeof data === 'string'); -Address.prototype.fromBech32 = function fromBech32(data, network) { - const type = Address.types.WITNESS; + if (data.length > 55) + throw new Error('Address is too long.'); - assert(typeof data === 'string'); + return this.fromRaw(base58.decode(data), network); + } - const addr = bech32.decode(data); + /** + * Create an address object from a base58 address. + * @param {AddressString} data + * @param {Network?} network + * @returns {Address} + * @throws Parse error. + */ - network = Network.fromBech32(addr.hrp, network); + static fromBase58(data, network) { + return new this().fromBase58(data, network); + } - return this.fromHash(addr.hash, type, addr.version, network); -}; + /** + * Inject properties from bech32 address. + * @private + * @param {String} data + * @param {Network?} network + * @throws Parse error + */ -/** - * Create an address object from a bech32 address. - * @param {String} data - * @param {Network?} network - * @returns {Address} - * @throws Parse error. - */ + fromBech32(data, network) { + const type = Address.types.WITNESS; -Address.fromBech32 = function fromBech32(data, network) { - return new Address().fromBech32(data, network); -}; + assert(typeof data === 'string'); -/** - * Inject properties from output script. - * @private - * @param {Script} script - */ + const addr = bech32.decode(data); -Address.prototype.fromScript = function fromScript(script) { - const pk = script.getPubkey(); + // make sure HRP is correct. + Network.fromBech32(addr.hrp, network); - if (pk) { - this.hash = digest.hash160(pk); - this.type = Address.types.PUBKEYHASH; - this.version = -1; - return this; + return this.fromHash(addr.hash, type, addr.version); } - const pkh = script.getPubkeyhash(); + /** + * Create an address object from a bech32 address. + * @param {String} data + * @param {Network?} network + * @returns {Address} + * @throws Parse error. + */ - if (pkh) { - this.hash = pkh; - this.type = Address.types.PUBKEYHASH; - this.version = -1; - return this; + static fromBech32(data, network) { + return new this().fromBech32(data, network); } - const sh = script.getScripthash(); + /** + * Inject properties from output script. + * @private + * @param {Script} script + */ - if (sh) { - this.hash = sh; - this.type = Address.types.SCRIPTHASH; - this.version = -1; - return this; - } + fromScript(script) { + const pk = script.getPubkey(); - const program = script.getProgram(); + if (pk) { + this.hash = hash160.digest(pk); + this.type = Address.types.PUBKEYHASH; + this.version = -1; + return this; + } - if (program && !program.isMalformed()) { - this.hash = program.data; - this.type = Address.types.WITNESS; - this.version = program.version; - return this; - } + const pkh = script.getPubkeyhash(); - // Put this last: it's the slowest to check. - if (script.isMultisig()) { - this.hash = script.hash160(); - this.type = Address.types.SCRIPTHASH; - this.version = -1; - return this; - } + if (pkh) { + this.hash = pkh; + this.type = Address.types.PUBKEYHASH; + this.version = -1; + return this; + } - return null; -}; + const sh = script.getScripthash(); -/** - * Inject properties from witness. - * @private - * @param {Witness} witness - */ + if (sh) { + this.hash = sh; + this.type = Address.types.SCRIPTHASH; + this.version = -1; + return this; + } -Address.prototype.fromWitness = function fromWitness(witness) { - const [, pk] = witness.getPubkeyhashInput(); + const program = script.getProgram(); - // We're pretty much screwed here - // since we can't get the version. - if (pk) { - this.hash = digest.hash160(pk); - this.type = Address.types.WITNESS; - this.version = 0; - return this; - } + if (program && !program.isMalformed()) { + this.hash = program.data; + this.type = Address.types.WITNESS; + this.version = program.version; + return this; + } - const redeem = witness.getScripthashInput(); + // Put this last: it's the slowest to check. + if (script.isMultisig()) { + this.hash = script.hash160(); + this.type = Address.types.SCRIPTHASH; + this.version = -1; + return this; + } - if (redeem) { - this.hash = digest.sha256(redeem); - this.type = Address.types.WITNESS; - this.version = 0; - return this; + return null; } - return null; -}; + /** + * Inject properties from witness. + * @private + * @param {Witness} witness + */ + + fromWitness(witness) { + const [, pk] = witness.getPubkeyhashInput(); + + // We're pretty much screwed here + // since we can't get the version. + if (pk) { + this.hash = hash160.digest(pk); + this.type = Address.types.WITNESS; + this.version = 0; + return this; + } -/** - * Inject properties from input script. - * @private - * @param {Script} script - */ + const redeem = witness.getScripthashInput(); -Address.prototype.fromInputScript = function fromInputScript(script) { - const [, pk] = script.getPubkeyhashInput(); + if (redeem) { + this.hash = sha256.digest(redeem); + this.type = Address.types.WITNESS; + this.version = 0; + return this; + } - if (pk) { - this.hash = digest.hash160(pk); - this.type = Address.types.PUBKEYHASH; - this.version = -1; - return this; + return null; } - const redeem = script.getScripthashInput(); + /** + * Inject properties from input script. + * @private + * @param {Script} script + */ - if (redeem) { - this.hash = digest.hash160(redeem); - this.type = Address.types.SCRIPTHASH; - this.version = -1; - return this; - } + fromInputScript(script) { + const [, pk] = script.getPubkeyhashInput(); - return null; -}; + if (pk) { + this.hash = hash160.digest(pk); + this.type = Address.types.PUBKEYHASH; + this.version = -1; + return this; + } -/** - * Create an Address from a witness. - * Attempt to extract address - * properties from a witness. - * @param {Witness} - * @returns {Address|null} - */ + const redeem = script.getScripthashInput(); -Address.fromWitness = function fromWitness(witness) { - return new Address().fromWitness(witness); -}; - -/** - * Create an Address from an input script. - * Attempt to extract address - * properties from an input script. - * @param {Script} - * @returns {Address|null} - */ + if (redeem) { + this.hash = hash160.digest(redeem); + this.type = Address.types.SCRIPTHASH; + this.version = -1; + return this; + } -Address.fromInputScript = function fromInputScript(script) { - return new Address().fromInputScript(script); -}; + return null; + } -/** - * Create an Address from an output script. - * Parse an output script and extract address - * properties. Converts pubkey and multisig - * scripts to pubkeyhash and scripthash addresses. - * @param {Script} - * @returns {Address|null} - */ + /** + * Create an Address from a witness. + * Attempt to extract address + * properties from a witness. + * @param {Witness} + * @returns {Address|null} + */ -Address.fromScript = function fromScript(script) { - return new Address().fromScript(script); -}; + static fromWitness(witness) { + return new this().fromWitness(witness); + } -/** - * Inject properties from a hash. - * @private - * @param {Buffer|Hash} hash - * @param {AddressPrefix} type - * @param {Number} [version=-1] - * @param {(Network|NetworkType)?} network - * @throws on bad hash size - */ + /** + * Create an Address from an input script. + * Attempt to extract address + * properties from an input script. + * @param {Script} + * @returns {Address|null} + */ -Address.prototype.fromHash = function fromHash(hash, type, version, network) { - if (typeof hash === 'string') - hash = Buffer.from(hash, 'hex'); + static fromInputScript(script) { + return new this().fromInputScript(script); + } - if (typeof type === 'string') { - type = Address.types[type.toUpperCase()]; - assert(type != null, 'Not a valid address type.'); + /** + * Create an Address from an output script. + * Parse an output script and extract address + * properties. Converts pubkey and multisig + * scripts to pubkeyhash and scripthash addresses. + * @param {Script} + * @returns {Address|null} + */ + + static fromScript(script) { + return new this().fromScript(script); } - if (type == null) - type = Address.types.PUBKEYHASH; + /** + * Inject properties from a hash. + * @private + * @param {Buffer|Hash} hash + * @param {AddressPrefix} type + * @param {Number} [version=-1] + * @throws on bad hash size + */ + + fromHash(hash, type, version) { + if (typeof type === 'string') { + type = Address.types[type.toUpperCase()]; + assert(type != null, 'Not a valid address type.'); + } - if (version == null) - version = -1; + if (type == null) + type = Address.types.PUBKEYHASH; - network = Network.get(network); + if (version == null) + version = -1; - assert(Buffer.isBuffer(hash)); - assert(util.isU8(type)); - assert(util.isI8(version)); + assert(Buffer.isBuffer(hash)); + assert((type >>> 0) === type); + assert((version | 0) === version); - assert(type >= Address.types.PUBKEYHASH && type <= Address.types.WITNESS, - 'Not a valid address type.'); + assert(type >= Address.types.PUBKEYHASH && type <= Address.types.WITNESS, + 'Not a valid address type.'); - if (version === -1) { - assert(type !== Address.types.WITNESS, 'Wrong version (witness)'); - assert(hash.length === 20, 'Hash is the wrong size.'); - } else { - assert(type === Address.types.WITNESS, 'Wrong version (non-witness).'); - assert(version >= 0 && version <= 16, 'Bad program version.'); - if (version === 0 && type === Address.types.WITNESS) { - assert(hash.length === 20 || hash.length === 32, - 'Witness program hash is the wrong size.'); + if (version === -1) { + assert(type !== Address.types.WITNESS, 'Wrong version (witness)'); + assert(hash.length === 20, 'Hash is the wrong size.'); + } else { + assert(type === Address.types.WITNESS, 'Wrong version (non-witness).'); + assert(version >= 0 && version <= 16, 'Bad program version.'); + if (version === 0 && type === Address.types.WITNESS) { + assert(hash.length === 20 || hash.length === 32, + 'Witness program hash is the wrong size.'); + } + assert(hash.length >= 2 && hash.length <= 40, 'Hash is the wrong size.'); } - assert(hash.length >= 2 && hash.length <= 40, 'Hash is the wrong size.'); - } - this.hash = hash; - this.type = type; - this.version = version; - this.network = network; + this.hash = hash; + this.type = type; + this.version = version; - return this; -}; - -/** - * Create a naked address from hash/type/version. - * @param {Hash} hash - * @param {AddressPrefix} type - * @param {Number} [version=-1] - * @param {(Network|NetworkType)?} network - * @returns {Address} - * @throws on bad hash size - */ + return this; + } -Address.fromHash = function fromHash(hash, type, version, network) { - return new Address().fromHash(hash, type, version, network); -}; + /** + * Create a naked address from hash/type/version. + * @param {Hash} hash + * @param {AddressPrefix} type + * @param {Number} [version=-1] + * @returns {Address} + * @throws on bad hash size + */ + + static fromHash(hash, type, version) { + return new this().fromHash(hash, type, version); + } -/** - * Inject properties from pubkeyhash. - * @private - * @param {Buffer} hash - * @param {Network?} network - * @returns {Address} - */ + /** + * Inject properties from pubkeyhash. + * @private + * @param {Buffer} hash + * @returns {Address} + */ + + fromPubkeyhash(hash) { + const type = Address.types.PUBKEYHASH; + assert(hash.length === 20, 'P2PKH must be 20 bytes.'); + return this.fromHash(hash, type, -1); + } -Address.prototype.fromPubkeyhash = function fromPubkeyhash(hash, network) { - const type = Address.types.PUBKEYHASH; - assert(hash.length === 20, 'P2PKH must be 20 bytes.'); - return this.fromHash(hash, type, -1, network); -}; + /** + * Instantiate address from pubkeyhash. + * @param {Buffer} hash + * @returns {Address} + */ -/** - * Instantiate address from pubkeyhash. - * @param {Buffer} hash - * @param {Network?} network - * @returns {Address} - */ + static fromPubkeyhash(hash) { + return new this().fromPubkeyhash(hash); + } -Address.fromPubkeyhash = function fromPubkeyhash(hash, network) { - return new Address().fromPubkeyhash(hash, network); -}; + /** + * Inject properties from scripthash. + * @private + * @param {Buffer} hash + * @returns {Address} + */ + + fromScripthash(hash) { + const type = Address.types.SCRIPTHASH; + assert(hash && hash.length === 20, 'P2SH must be 20 bytes.'); + return this.fromHash(hash, type, -1); + } -/** - * Inject properties from scripthash. - * @private - * @param {Buffer} hash - * @param {Network?} network - * @returns {Address} - */ + /** + * Instantiate address from scripthash. + * @param {Buffer} hash + * @returns {Address} + */ -Address.prototype.fromScripthash = function fromScripthash(hash, network) { - const type = Address.types.SCRIPTHASH; - assert(hash && hash.length === 20, 'P2SH must be 20 bytes.'); - return this.fromHash(hash, type, -1, network); -}; + static fromScripthash(hash) { + return new this().fromScripthash(hash); + } -/** - * Instantiate address from scripthash. - * @param {Buffer} hash - * @param {Network?} network - * @returns {Address} - */ + /** + * Inject properties from witness pubkeyhash. + * @private + * @param {Buffer} hash + * @returns {Address} + */ + + fromWitnessPubkeyhash(hash) { + const type = Address.types.WITNESS; + assert(hash && hash.length === 20, 'P2WPKH must be 20 bytes.'); + return this.fromHash(hash, type, 0); + } -Address.fromScripthash = function fromScripthash(hash, network) { - return new Address().fromScripthash(hash, network); -}; + /** + * Instantiate address from witness pubkeyhash. + * @param {Buffer} hash + * @returns {Address} + */ -/** - * Inject properties from witness pubkeyhash. - * @private - * @param {Buffer} hash - * @param {Network?} network - * @returns {Address} - */ + static fromWitnessPubkeyhash(hash) { + return new this().fromWitnessPubkeyhash(hash); + } -Address.prototype.fromWitnessPubkeyhash = function fromWitnessPubkeyhash(hash, network) { - const type = Address.types.WITNESS; - assert(hash && hash.length === 20, 'P2WPKH must be 20 bytes.'); - return this.fromHash(hash, type, 0, network); -}; + /** + * Inject properties from witness scripthash. + * @private + * @param {Buffer} hash + * @returns {Address} + */ + + fromWitnessScripthash(hash) { + const type = Address.types.WITNESS; + assert(hash && hash.length === 32, 'P2WPKH must be 32 bytes.'); + return this.fromHash(hash, type, 0); + } -/** - * Instantiate address from witness pubkeyhash. - * @param {Buffer} hash - * @param {Network?} network - * @returns {Address} - */ + /** + * Instantiate address from witness scripthash. + * @param {Buffer} hash + * @returns {Address} + */ -Address.fromWitnessPubkeyhash = function fromWitnessPubkeyhash(hash, network) { - return new Address().fromWitnessPubkeyhash(hash, network); -}; + static fromWitnessScripthash(hash) { + return new this().fromWitnessScripthash(hash); + } -/** - * Inject properties from witness scripthash. - * @private - * @param {Buffer} hash - * @param {Network?} network - * @returns {Address} - */ + /** + * Inject properties from witness program. + * @private + * @param {Number} version + * @param {Buffer} hash + * @returns {Address} + */ -Address.prototype.fromWitnessScripthash = function fromWitnessScripthash(hash, network) { - const type = Address.types.WITNESS; - assert(hash && hash.length === 32, 'P2WPKH must be 32 bytes.'); - return this.fromHash(hash, type, 0, network); -}; + fromProgram(version, hash) { + const type = Address.types.WITNESS; -/** - * Instantiate address from witness scripthash. - * @param {Buffer} hash - * @param {Network?} network - * @returns {Address} - */ + assert(version >= 0, 'Bad version for witness program.'); -Address.fromWitnessScripthash = function fromWitnessScripthash(hash, network) { - return new Address().fromWitnessScripthash(hash, network); -}; + return this.fromHash(hash, type, version); + } -/** - * Inject properties from witness program. - * @private - * @param {Number} version - * @param {Buffer} hash - * @param {Network?} network - * @returns {Address} - */ + /** + * Instantiate address from witness program. + * @param {Number} version + * @param {Buffer} hash + * @returns {Address} + */ -Address.prototype.fromProgram = function fromProgram(version, hash, network) { - const type = Address.types.WITNESS; + static fromProgram(version, hash) { + return new this().fromProgram(version, hash); + } - assert(version >= 0, 'Bad version for witness program.'); + /** + * Test whether the address is pubkeyhash. + * @returns {Boolean} + */ - if (typeof hash === 'string') - hash = Buffer.from(hash, 'hex'); + isPubkeyhash() { + return this.type === Address.types.PUBKEYHASH; + } - return this.fromHash(hash, type, version, network); -}; + /** + * Test whether the address is scripthash. + * @returns {Boolean} + */ -/** - * Instantiate address from witness program. - * @param {Number} version - * @param {Buffer} hash - * @param {Network?} network - * @returns {Address} - */ + isScripthash() { + return this.type === Address.types.SCRIPTHASH || this.type === Address.types.SCRIPTHASH2; + } -Address.fromProgram = function fromProgram(version, hash, network) { - return new Address().fromProgram(version, hash, network); -}; + /** + * Test whether the address is witness pubkeyhash. + * @returns {Boolean} + */ -/** - * Test whether the address is pubkeyhash. - * @returns {Boolean} - */ + isWitnessPubkeyhash() { + return this.version === 0 && this.hash.length === 20; + } -Address.prototype.isPubkeyhash = function isPubkeyhash() { - return this.type === Address.types.PUBKEYHASH; -}; + /** + * Test whether the address is witness scripthash. + * @returns {Boolean} + */ -/** - * Test whether the address is scripthash. - * @returns {Boolean} - */ + isWitnessScripthash() { + return this.version === 0 && this.hash.length === 32; + } -Address.prototype.isScripthash = function isScripthash() { - return this.type === Address.types.SCRIPTHASH || this.type === Address.types.SCRIPTHASH2; -}; + /** + * Test whether the address is a witness program. + * @returns {Boolean} + */ -/** - * Test whether the address is witness pubkeyhash. - * @returns {Boolean} - */ + isProgram() { + return this.version !== -1; + } -Address.prototype.isWitnessPubkeyhash = function isWitnessPubkeyhash() { - return this.version === 0 && this.hash.length === 20; -}; + /** + * Test whether the address is an unknown witness program. + * @returns {Boolean} + */ -/** - * Test whether the address is witness scripthash. - * @returns {Boolean} - */ + isUnknown() { + if (this.version === -1) + return false; -Address.prototype.isWitnessScripthash = function isWitnessScripthash() { - return this.version === 0 && this.hash.length === 32; -}; + if (this.version > 0) + return true; -/** - * Test whether the address is witness masthash. - * @returns {Boolean} - */ + return this.hash.length !== 20 && this.hash.length !== 32; + } -Address.prototype.isWitnessMasthash = function isWitnessMasthash() { - return this.version === 1 && this.hash.length === 32; -}; + /** + * Get the hash of a base58 address or address-related object. + * @param {String|Address|Hash} data + * @param {String?} enc - Can be `"hex"` or `null`. + * @returns {Hash} + */ -/** - * Test whether the address is a witness program. - * @returns {Boolean} - */ + static getHash(data, enc) { + if (!data) + throw new Error('Object is not an address.'); -Address.prototype.isProgram = function isProgram() { - return this.version !== -1; -}; + let hash; -/** - * Test whether the address is an unknown witness program. - * @returns {Boolean} - */ + if (Buffer.isBuffer(data)) { + if (data.length !== 20 && data.length !== 32) + throw new Error('Object is not an address.'); + hash = data; + } else if (data instanceof Address) { + hash = data.hash; + } else { + throw new Error('Object is not an address.'); + } -Address.prototype.isUnknown = function isUnknown() { - if (this.version === -1) - return false; + if (enc === 'hex') + return hash.toString('hex'); - if (this.version > 0) - return true; + return hash; + } - return this.hash.length !== 20 && this.hash.length !== 32; -}; + /** + * Get an address type for a specified network address prefix. + * @param {Number} prefix + * @param {Network} network + * @returns {AddressType} + */ + + static getType(prefix, network) { + const prefixes = network.addressPrefix; + + switch (prefix) { + case prefixes.pubkeyhash: + return Address.types.PUBKEYHASH; + case prefixes.scripthash: + return Address.types.SCRIPTHASH; + case prefixes.scripthash2: + return Address.types.SCRIPTHASH2; + case prefixes.witnesspubkeyhash: + case prefixes.witnessscripthash: + return Address.types.WITNESS; + default: + throw new Error('Unknown address prefix.'); + } + } +} /** - * Get the hash of a base58 address or address-related object. - * @param {String|Address|Hash} data - * @param {String?} enc - * @param {Network?} network - * @returns {Hash} + * Address types. + * @enum {Number} */ -Address.getHash = function getHash(data, enc, network) { - if (!data) - throw new Error('Object is not an address.'); - - let hash; - - if (typeof data === 'string') { - if (data.length === 40 || data.length === 64) - return enc === 'hex' ? data : Buffer.from(data, 'hex'); - - hash = Address.fromString(data, network).hash; - } else if (Buffer.isBuffer(data)) { - if (data.length !== 20 && data.length !== 32) - throw new Error('Object is not an address.'); - hash = data; - } else if (data instanceof Address) { - hash = data.hash; - if (network) { - network = Network.get(network); - if (data.network !== network) - throw new Error('Network mismatch for address.'); - } - } else { - throw new Error('Object is not an address.'); - } - - return enc === 'hex' - ? hash.toString('hex') - : hash; +Address.types = { + PUBKEYHASH: 0, + SCRIPTHASH: 1, + SCRIPTHASH2: 2, + WITNESS: 3 }; /** - * Get an address type for a specified network address prefix. - * @param {Number} prefix - * @param {Network} network - * @returns {AddressType} + * Address types by value. + * @const {Object} */ -Address.getType = function getType(prefix, network) { - const prefixes = network.addressPrefix; - switch (prefix) { - case prefixes.pubkeyhash: - return Address.types.PUBKEYHASH; - case prefixes.scripthash: - return Address.types.SCRIPTHASH; - case prefixes.scripthash2: - return Address.types.SCRIPTHASH2; - case prefixes.witnesspubkeyhash: - case prefixes.witnessscripthash: - return Address.types.WITNESS; - default: - throw new Error('Unknown address prefix.'); - } -}; +Address.typesByVal = [ + 'PUBKEYHASH', + 'SCRIPTHASH', + 'SCRIPTHASH2', + 'WITNESS' +]; /* * Helpers diff --git a/lib/primitives/block.js b/lib/primitives/block.js index 9be79213c..f0766f62d 100644 --- a/lib/primitives/block.js +++ b/lib/primitives/block.js @@ -7,829 +7,850 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); -const encoding = require('../utils/encoding'); -const digest = require('../crypto/digest'); -const merkle = require('../crypto/merkle'); +const assert = require('bsert'); +const bio = require('bufio'); +const {BufferSet} = require('buffer-map'); +const hash256 = require('bcrypto/lib/hash256'); +const merkle = require('bcrypto/lib/merkle'); const consensus = require('../protocol/consensus'); const AbstractBlock = require('./abstractblock'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); const TX = require('./tx'); const MerkleBlock = require('./merkleblock'); const Headers = require('./headers'); const Network = require('../protocol/network'); +const util = require('../utils/util'); +const {encoding} = bio; +const {inspectSymbol} = require('../utils'); /** + * Block * Represents a full block. * @alias module:primitives.Block - * @constructor * @extends AbstractBlock - * @param {NakedBlock} options */ -function Block(options) { - if (!(this instanceof Block)) - return new Block(options); +class Block extends AbstractBlock { + /** + * Create a block. + * @constructor + * @param {Object} options + */ - AbstractBlock.call(this); + constructor(options) { + super(); - this.txs = []; + this.txs = []; - this._raw = null; - this._size = -1; - this._witness = -1; + this._raw = null; + this._size = -1; + this._witness = -1; - if (options) - this.fromOptions(options); -} + if (options) + this.fromOptions(options); + } -Object.setPrototypeOf(Block.prototype, AbstractBlock.prototype); + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + this.parseOptions(options); + + if (options.txs) { + assert(Array.isArray(options.txs)); + for (const tx of options.txs) { + assert(tx instanceof TX); + this.txs.push(tx); + } + } -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ + return this; + } -Block.prototype.fromOptions = function fromOptions(options) { - this.parseOptions(options); + /** + * Instantiate block from options. + * @param {Object} options + * @returns {Block} + */ - if (options.txs) { - assert(Array.isArray(options.txs)); - for (const tx of options.txs) { - assert(tx instanceof TX); - this.txs.push(tx); - } + static fromOptions(options) { + return new this().fromOptions(options); } -}; -/** - * Instantiate block from options. - * @param {Object} options - * @returns {Block} - */ + /** + * Clear any cached values. + * @param {Boolean?} all - Clear transactions. + */ -Block.fromOptions = function fromOptions(options) { - return new Block().fromOptions(options); -}; + refresh(all) { + this._refresh(); -/** - * Clear any cached values. - * @param {Boolean?} all - Clear transactions. - */ + this._raw = null; + this._size = -1; + this._witness = -1; -Block.prototype.refresh = function refresh(all) { - this._refresh(); + if (!all) + return this; - this._raw = null; - this._size = -1; - this._witness = -1; + for (const tx of this.txs) + tx.refresh(); - if (!all) - return; + return this; + } - for (const tx of this.txs) - tx.refresh(); -}; + /** + * Serialize the block. Include witnesses if present. + * @returns {Buffer} + */ -/** - * Serialize the block. Include witnesses if present. - * @returns {Buffer} - */ + toRaw() { + return this.frame().data; + } -Block.prototype.toRaw = function toRaw() { - return this.frame().data; -}; + /** + * Check if block has been serialized. + * @returns {Buffer} + */ -/** - * Serialize the block, do not include witnesses. - * @returns {Buffer} - */ + hasRaw() { + return Boolean(this._raw); + } -Block.prototype.toNormal = function toNormal() { - if (this.hasWitness()) - return this.frameNormal().data; - return this.toRaw(); -}; + /** + * Serialize the block, do not include witnesses. + * @returns {Buffer} + */ -/** - * Serialize the block. Include witnesses if present. - * @param {BufferWriter} bw - */ - -Block.prototype.toWriter = function toWriter(bw) { - if (this.mutable) - return this.writeWitness(bw); + toNormal() { + if (this.hasWitness()) + return this.frameNormal().data; + return this.toRaw(); + } - const raw = this.frame(); - bw.writeBytes(raw.data); + /** + * Serialize the block. Include witnesses if present. + * @param {BufferWriter} bw + */ - return bw; -}; + toWriter(bw) { + if (this.mutable) + return this.writeWitness(bw); -/** - * Serialize the block, do not include witnesses. - * @param {BufferWriter} bw - */ + const raw = this.frame(); + bw.writeBytes(raw.data); -Block.prototype.toNormalWriter = function toNormalWriter(bw) { - if (this.hasWitness()) { - this.writeNormal(bw); return bw; } - return this.toWriter(bw); -}; -/** - * Get the raw block serialization. - * Include witnesses if present. - * @private - * @returns {RawBlock} - */ - -Block.prototype.frame = function frame() { - if (this.mutable) { - assert(!this._raw); - return this.frameWitness(); - } + /** + * Serialize the block, do not include witnesses. + * @param {BufferWriter} bw + */ - if (this._raw) { - assert(this._size >= 0); - assert(this._witness >= 0); - const raw = new RawBlock(this._size, this._witness); - raw.data = this._raw; - return raw; + toNormalWriter(bw) { + if (this.hasWitness()) { + this.writeNormal(bw); + return bw; + } + return this.toWriter(bw); } - const raw = this.frameWitness(); - - this._raw = raw.data; - this._size = raw.size; - this._witness = raw.witness; + /** + * Get the raw block serialization. + * Include witnesses if present. + * @private + * @returns {RawBlock} + */ + + frame() { + if (this.mutable) { + assert(!this._raw); + return this.frameWitness(); + } - return raw; -}; + if (this._raw) { + assert(this._size >= 0); + assert(this._witness >= 0); + const raw = new RawBlock(this._size, this._witness); + raw.data = this._raw; + return raw; + } -/** - * Calculate real size and size of the witness bytes. - * @returns {Object} Contains `size` and `witness`. - */ + const raw = this.frameWitness(); -Block.prototype.getSizes = function getSizes() { - if (this.mutable) - return this.getWitnessSizes(); - return this.frame(); -}; + this._raw = raw.data; + this._size = raw.size; + this._witness = raw.witness; -/** - * Calculate virtual block size. - * @returns {Number} Virtual size. - */ + return raw; + } -Block.prototype.getVirtualSize = function getVirtualSize() { - const scale = consensus.WITNESS_SCALE_FACTOR; - return (this.getWeight() + scale - 1) / scale | 0; -}; + /** + * Calculate real size and size of the witness bytes. + * @returns {Object} Contains `size` and `witness`. + */ -/** - * Calculate block weight. - * @returns {Number} weight - */ + getSizes() { + if (this.mutable) + return this.getWitnessSizes(); + return this.frame(); + } -Block.prototype.getWeight = function getWeight() { - const raw = this.getSizes(); - const base = raw.size - raw.witness; - return base * (consensus.WITNESS_SCALE_FACTOR - 1) + raw.size; -}; + /** + * Calculate virtual block size. + * @returns {Number} Virtual size. + */ -/** - * Get real block size. - * @returns {Number} size - */ + getVirtualSize() { + const scale = consensus.WITNESS_SCALE_FACTOR; + return (this.getWeight() + scale - 1) / scale | 0; + } -Block.prototype.getSize = function getSize() { - return this.getSizes().size; -}; + /** + * Calculate block weight. + * @returns {Number} weight + */ -/** - * Get base block size (without witness). - * @returns {Number} size - */ + getWeight() { + const raw = this.getSizes(); + const base = raw.size - raw.witness; + return base * (consensus.WITNESS_SCALE_FACTOR - 1) + raw.size; + } -Block.prototype.getBaseSize = function getBaseSize() { - const raw = this.getSizes(); - return raw.size - raw.witness; -}; + /** + * Get real block size. + * @returns {Number} size + */ -/** - * Test whether the block contains a - * transaction with a non-empty witness. - * @returns {Boolean} - */ + getSize() { + return this.getSizes().size; + } -Block.prototype.hasWitness = function hasWitness() { - if (this._witness !== -1) - return this._witness !== 0; + /** + * Get base block size (without witness). + * @returns {Number} size + */ - for (const tx of this.txs) { - if (tx.hasWitness()) - return true; + getBaseSize() { + const raw = this.getSizes(); + return raw.size - raw.witness; } - return false; -}; + /** + * Test whether the block contains a + * transaction with a non-empty witness. + * @returns {Boolean} + */ -/** - * Test the block's transaction vector against a hash. - * @param {Hash} hash - * @returns {Boolean} - */ + hasWitness() { + if (this._witness !== -1) + return this._witness !== 0; -Block.prototype.hasTX = function hasTX(hash) { - return this.indexOf(hash) !== -1; -}; - -/** - * Find the index of a transaction in the block. - * @param {Hash} hash - * @returns {Number} index (-1 if not present). - */ + for (const tx of this.txs) { + if (tx.hasWitness()) + return true; + } -Block.prototype.indexOf = function indexOf(hash) { - for (let i = 0; i < this.txs.length; i++) { - const tx = this.txs[i]; - if (tx.hash('hex') === hash) - return i; + return false; } - return -1; -}; + /** + * Test the block's transaction vector against a hash. + * @param {Hash} hash + * @returns {Boolean} + */ -/** - * Calculate merkle root. Returns null - * if merkle tree has been malleated. - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {Hash|null} - */ - -Block.prototype.createMerkleRoot = function createMerkleRoot(enc) { - const leaves = []; - - for (const tx of this.txs) - leaves.push(tx.hash()); + hasTX(hash) { + return this.indexOf(hash) !== -1; + } - const [root, malleated] = merkle.createRoot(leaves); + /** + * Find the index of a transaction in the block. + * @param {Hash} hash + * @returns {Number} index (-1 if not present). + */ + + indexOf(hash) { + for (let i = 0; i < this.txs.length; i++) { + const tx = this.txs[i]; + if (tx.hash().equals(hash)) + return i; + } - if (malleated) - return null; + return -1; + } - return enc === 'hex' ? root.toString('hex') : root; -}; + /** + * Calculate merkle root. Returns null + * if merkle tree has been malleated. + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {Hash|null} + */ -/** - * Create a witness nonce (for mining). - * @returns {Buffer} - */ + createMerkleRoot(enc) { + const leaves = []; -Block.prototype.createWitnessNonce = function createWitnessNonce() { - return Buffer.from(encoding.ZERO_HASH); -}; + for (const tx of this.txs) + leaves.push(tx.hash()); -/** - * Calculate commitment hash (the root of the - * witness merkle tree hashed with the witnessNonce). - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {Hash} - */ + const [root, malleated] = merkle.createRoot(hash256, leaves); -Block.prototype.createCommitmentHash = function createCommitmentHash(enc) { - const nonce = this.getWitnessNonce(); - const leaves = []; + if (malleated) + return null; - assert(nonce, 'No witness nonce present.'); + return enc === 'hex' ? root.toString('hex') : root; + } - leaves.push(encoding.ZERO_HASH); + /** + * Create a witness nonce (for mining). + * @returns {Buffer} + */ - for (let i = 1; i < this.txs.length; i++) { - const tx = this.txs[i]; - leaves.push(tx.witnessHash()); + createWitnessNonce() { + return Buffer.from(consensus.ZERO_HASH); } - const [root] = merkle.createRoot(leaves); + /** + * Calculate commitment hash (the root of the + * witness merkle tree hashed with the witnessNonce). + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {Hash} + */ - // Note: malleation check ignored here. - // assert(!malleated); + createCommitmentHash(enc) { + const nonce = this.getWitnessNonce(); + const leaves = []; - const hash = digest.root256(root, nonce); + assert(nonce, 'No witness nonce present.'); - return enc === 'hex' - ? hash.toString('hex') - : hash; -}; + leaves.push(consensus.ZERO_HASH); -/** - * Retrieve the merkle root from the block header. - * @param {String?} enc - * @returns {Hash} - */ + for (let i = 1; i < this.txs.length; i++) { + const tx = this.txs[i]; + leaves.push(tx.witnessHash()); + } -Block.prototype.getMerkleRoot = function getMerkleRoot(enc) { - if (enc === 'hex') - return this.merkleRoot; - return Buffer.from(this.merkleRoot, 'hex'); -}; + const [root] = merkle.createRoot(hash256, leaves); -/** - * Retrieve the witness nonce from the - * coinbase's witness vector (if present). - * @returns {Buffer|null} - */ + // Note: malleation check ignored here. + // assert(!malleated); -Block.prototype.getWitnessNonce = function getWitnessNonce() { - if (this.txs.length === 0) - return null; + const hash = hash256.root(root, nonce); - const coinbase = this.txs[0]; + return enc === 'hex' + ? hash.toString('hex') + : hash; + } - if (coinbase.inputs.length !== 1) - return null; + /** + * Retrieve the merkle root from the block header. + * @param {String?} enc + * @returns {Hash} + */ - const input = coinbase.inputs[0]; + getMerkleRoot(enc) { + if (enc === 'hex') + return this.merkleRoot.toString('hex'); + return this.merkleRoot; + } - if (input.witness.items.length !== 1) - return null; + /** + * Retrieve the witness nonce from the + * coinbase's witness vector (if present). + * @returns {Buffer|null} + */ - if (input.witness.items[0].length !== 32) - return null; + getWitnessNonce() { + if (this.txs.length === 0) + return null; - return input.witness.items[0]; -}; + const coinbase = this.txs[0]; -/** - * Retrieve the commitment hash - * from the coinbase's outputs. - * @param {String?} enc - * @returns {Hash|null} - */ + if (coinbase.inputs.length !== 1) + return null; -Block.prototype.getCommitmentHash = function getCommitmentHash(enc) { - if (this.txs.length === 0) - return null; + const input = coinbase.inputs[0]; - const coinbase = this.txs[0]; - let hash; + if (input.witness.items.length !== 1) + return null; - for (let i = coinbase.outputs.length - 1; i >= 0; i--) { - const output = coinbase.outputs[i]; - if (output.script.isCommitment()) { - hash = output.script.getCommitment(); - break; - } + if (input.witness.items[0].length !== 32) + return null; + + return input.witness.items[0]; } - if (!hash) - return null; + /** + * Retrieve the commitment hash + * from the coinbase's outputs. + * @param {String?} enc + * @returns {Hash|null} + */ - return enc === 'hex' - ? hash.toString('hex') - : hash; -}; + getCommitmentHash(enc) { + if (this.txs.length === 0) + return null; -/** - * Do non-contextual verification on the block. Including checking the block - * size, the coinbase and the merkle root. This is consensus-critical. - * @returns {Boolean} - */ + const coinbase = this.txs[0]; -Block.prototype.verifyBody = function verifyBody() { - const [valid] = this.checkBody(); - return valid; -}; + let hash = null; -/** - * Do non-contextual verification on the block. Including checking the block - * size, the coinbase and the merkle root. This is consensus-critical. - * @returns {Array} [valid, reason, score] - */ + for (let i = coinbase.outputs.length - 1; i >= 0; i--) { + const output = coinbase.outputs[i]; + if (output.script.isCommitment()) { + hash = output.script.getCommitment(); + break; + } + } -Block.prototype.checkBody = function checkBody() { - // Check merkle root. - const root = this.createMerkleRoot('hex'); + if (!hash) + return null; - // If the merkle is mutated, - // we have duplicate txs. - if (!root) - return [false, 'bad-txns-duplicate', 100]; + return enc === 'hex' + ? hash.toString('hex') + : hash; + } - if (this.merkleRoot !== root) - return [false, 'bad-txnmrklroot', 100]; + /** + * Do non-contextual verification on the block. Including checking the block + * size, the coinbase and the merkle root. This is consensus-critical. + * @returns {Boolean} + */ - // Check base size. - if (this.txs.length === 0 - || this.txs.length > consensus.MAX_BLOCK_SIZE - || this.getBaseSize() > consensus.MAX_BLOCK_SIZE) { - return [false, 'bad-blk-length', 100]; + verifyBody() { + const [valid] = this.checkBody(); + return valid; } - // First TX must be a coinbase. - if (this.txs.length === 0 || !this.txs[0].isCoinbase()) - return [false, 'bad-cb-missing', 100]; + /** + * Do non-contextual verification on the block. Including checking the block + * size, the coinbase and the merkle root. This is consensus-critical. + * @returns {Array} [valid, reason, score] + */ + + checkBody() { + // Check base size. + if (this.txs.length === 0 + || this.txs.length > consensus.MAX_BLOCK_SIZE + || this.getBaseSize() > consensus.MAX_BLOCK_SIZE) { + return [false, 'bad-blk-length', 100]; + } - // Test all transactions. - const scale = consensus.WITNESS_SCALE_FACTOR; - let sigops = 0; + // First TX must be a coinbase. + if (this.txs.length === 0 || !this.txs[0].isCoinbase()) + return [false, 'bad-cb-missing', 100]; - for (let i = 0; i < this.txs.length; i++) { - const tx = this.txs[i]; + // Check merkle root. + const root = this.createMerkleRoot(); - // The rest of the txs must not be coinbases. - if (i > 0 && tx.isCoinbase()) - return [false, 'bad-cb-multiple', 100]; + // If the merkle is mutated, + // we have duplicate txs. + if (!root) + return [false, 'bad-txns-duplicate', 100]; - // Sanity checks. - const [valid, reason, score] = tx.checkSanity(); + if (!this.merkleRoot.equals(root)) + return [false, 'bad-txnmrklroot', 100]; - if (!valid) - return [valid, reason, score]; + // Test all transactions. + const scale = consensus.WITNESS_SCALE_FACTOR; - // Count legacy sigops (do not count scripthash or witness). - sigops += tx.getLegacySigops(); - if (sigops * scale > consensus.MAX_BLOCK_SIGOPS_COST) - return [false, 'bad-blk-sigops', 100]; - } + let sigops = 0; - return [true, 'valid', 0]; -}; + for (let i = 0; i < this.txs.length; i++) { + const tx = this.txs[i]; -/** - * Retrieve the coinbase height from the coinbase input script. - * @returns {Number} height (-1 if not present). - */ + // The rest of the txs must not be coinbases. + if (i > 0 && tx.isCoinbase()) + return [false, 'bad-cb-multiple', 100]; -Block.prototype.getCoinbaseHeight = function getCoinbaseHeight() { - if (this.version < 2) - return -1; + // Sanity checks. + const [valid, reason, score] = tx.checkSanity(); - if (this.txs.length === 0) - return -1; + if (!valid) + return [valid, reason, score]; - const coinbase = this.txs[0]; + // Count legacy sigops (do not count scripthash or witness). + sigops += tx.getLegacySigops(); + if (sigops * scale > consensus.MAX_BLOCK_SIGOPS_COST) + return [false, 'bad-blk-sigops', 100]; + } - if (coinbase.inputs.length === 0) - return -1; + return [true, 'valid', 0]; + } - return coinbase.inputs[0].script.getCoinbaseHeight(); -}; + /** + * Retrieve the coinbase height from the coinbase input script. + * @returns {Number} height (-1 if not present). + */ -/** - * Get the "claimed" reward by the coinbase. - * @returns {Amount} claimed - */ + getCoinbaseHeight() { + if (this.version < 2) + return -1; -Block.prototype.getClaimed = function getClaimed() { - assert(this.txs.length > 0); - assert(this.txs[0].isCoinbase()); - return this.txs[0].getOutputValue(); -}; + if (this.txs.length === 0) + return -1; -/** - * Get all unique outpoint hashes in the - * block. Coinbases are ignored. - * @returns {Hash[]} Outpoint hashes. - */ + const coinbase = this.txs[0]; + + if (coinbase.inputs.length === 0) + return -1; -Block.prototype.getPrevout = function getPrevout() { - const prevout = Object.create(null); + return coinbase.inputs[0].script.getCoinbaseHeight(); + } - for (let i = 1; i < this.txs.length; i++) { - const tx = this.txs[i]; + /** + * Get the "claimed" reward by the coinbase. + * @returns {Amount} claimed + */ - for (const input of tx.inputs) - prevout[input.prevout.hash] = true; + getClaimed() { + assert(this.txs.length > 0); + assert(this.txs[0].isCoinbase()); + return this.txs[0].getOutputValue(); } - return Object.keys(prevout); -}; + /** + * Get all unique outpoint hashes in the + * block. Coinbases are ignored. + * @returns {Hash[]} Outpoint hashes. + */ -/** - * Inspect the block and return a more - * user-friendly representation of the data. - * @returns {Object} - */ + getPrevout() { + const prevout = new BufferSet(); -Block.prototype.inspect = function inspect() { - return this.format(); -}; + for (let i = 1; i < this.txs.length; i++) { + const tx = this.txs[i]; -/** - * Inspect the block and return a more - * user-friendly representation of the data. - * @param {CoinView} view - * @param {Number} height - * @returns {Object} - */ + for (const input of tx.inputs) + prevout.add(input.prevout.hash); + } -Block.prototype.format = function format(view, height) { - const commitmentHash = this.getCommitmentHash('hex'); - return { - hash: this.rhash(), - height: height != null ? height : -1, - size: this.getSize(), - virtualSize: this.getVirtualSize(), - date: util.date(this.time), - version: util.hex32(this.version), - prevBlock: util.revHex(this.prevBlock), - merkleRoot: util.revHex(this.merkleRoot), - commitmentHash: commitmentHash - ? util.revHex(commitmentHash) - : null, - time: this.time, - bits: this.bits, - nonce: this.nonce, - txs: this.txs.map((tx, i) => { - return tx.format(view, null, i); - }) - }; -}; + return prevout.toArray(); + } -/** - * Convert the block to an object suitable - * for JSON serialization. - * @returns {Object} - */ + /** + * Inspect the block and return a more + * user-friendly representation of the data. + * @returns {Object} + */ -Block.prototype.toJSON = function toJSON() { - return this.getJSON(); -}; + [inspectSymbol]() { + return this.format(); + } -/** - * Convert the block to an object suitable - * for JSON serialization. Note that the hashes - * will be reversed to abide by bitcoind's legacy - * of little-endian uint256s. - * @param {Network} network - * @param {CoinView} view - * @param {Number} height - * @returns {Object} - */ + /** + * Inspect the block and return a more + * user-friendly representation of the data. + * @param {CoinView} view + * @param {Number} height + * @returns {Object} + */ + + format(view, height) { + const commitmentHash = this.getCommitmentHash(); + return { + hash: this.rhash(), + height: height != null ? height : -1, + size: this.getSize(), + virtualSize: this.getVirtualSize(), + date: util.date(this.time), + version: this.version.toString(16), + prevBlock: util.revHex(this.prevBlock), + merkleRoot: util.revHex(this.merkleRoot), + commitmentHash: commitmentHash + ? util.revHex(commitmentHash) + : null, + time: this.time, + bits: this.bits, + nonce: this.nonce, + txs: this.txs.map((tx, i) => { + return tx.format(view, null, i); + }) + }; + } -Block.prototype.getJSON = function getJSON(network, view, height, confirmations) { - network = Network.get(network); - return { - hash: this.rhash(), - height: height, - confirmations: confirmations, - version: this.version, - prevBlock: util.revHex(this.prevBlock), - merkleRoot: util.revHex(this.merkleRoot), - time: this.time, - bits: this.bits, - nonce: this.nonce, - txs: this.txs.map((tx, i) => { - return tx.getJSON(network, view, null, i); - }) - }; -}; + /** + * Convert the block to an object suitable + * for JSON serialization. + * @returns {Object} + */ -/** - * Inject properties from json object. - * @private - * @param {Object} json - */ + toJSON() { + return this.getJSON(); + } -Block.prototype.fromJSON = function fromJSON(json) { - assert(json, 'Block data is required.'); - assert(Array.isArray(json.txs)); + /** + * Convert the block to an object suitable + * for JSON serialization. Note that the hashes + * will be reversed to abide by bitcoind's legacy + * of little-endian uint256s. + * @param {Network} network + * @param {CoinView} view + * @param {Number} height + * @param {Number} depth + * @returns {Object} + */ + + getJSON(network, view, height, depth) { + network = Network.get(network); + return { + hash: this.rhash(), + height: height, + depth: depth, + version: this.version, + prevBlock: util.revHex(this.prevBlock), + merkleRoot: util.revHex(this.merkleRoot), + time: this.time, + bits: this.bits, + nonce: this.nonce, + txs: this.txs.map((tx, i) => { + return tx.getJSON(network, view, null, i); + }) + }; + } - this.parseJSON(json); + /** + * Inject properties from json object. + * @private + * @param {Object} json + */ - for (const tx of json.txs) - this.txs.push(TX.fromJSON(tx)); + fromJSON(json) { + assert(json, 'Block data is required.'); + assert(Array.isArray(json.txs)); - return this; -}; + this.parseJSON(json); -/** - * Instantiate a block from a jsonified block object. - * @param {Object} json - The jsonified block object. - * @returns {Block} - */ + for (const tx of json.txs) + this.txs.push(TX.fromJSON(tx)); -Block.fromJSON = function fromJSON(json) { - return new Block().fromJSON(json); -}; + return this; + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + /** + * Instantiate a block from a jsonified block object. + * @param {Object} json - The jsonified block object. + * @returns {Block} + */ -Block.prototype.fromReader = function fromReader(br) { - br.start(); + static fromJSON(json) { + return new this().fromJSON(json); + } - this.readHead(br); + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ - const count = br.readVarint(); - let witness = 0; + fromReader(br) { + br.start(); - for (let i = 0; i < count; i++) { - const tx = TX.fromReader(br); - witness += tx._witness; - this.txs.push(tx); - } + this.readHead(br); - if (!this.mutable) { - this._raw = br.endData(); - this._size = this._raw.length; - this._witness = witness; - } + const count = br.readVarint(); + let witness = 0; - return this; -}; + for (let i = 0; i < count; i++) { + const tx = TX.fromReader(br, true); + witness += tx._witness; + this.txs.push(tx); + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + if (!this.mutable) { + this._raw = br.endData(); + this._size = this._raw.length; + this._witness = witness; + } -Block.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + return this; + } -/** - * Instantiate a block from a serialized Buffer. - * @param {Buffer} data - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {Block} - */ + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -Block.fromReader = function fromReader(data) { - return new Block().fromReader(data); -}; + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -/** - * Instantiate a block from a serialized Buffer. - * @param {Buffer} data - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {Block} - */ + /** + * Instantiate a block from a serialized Buffer. + * @param {Buffer} data + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {Block} + */ -Block.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new Block().fromRaw(data); -}; + static fromReader(data) { + return new this().fromReader(data); + } -/** - * Convert the Block to a MerkleBlock. - * @param {Bloom} filter - Bloom filter for transactions - * to match. The merkle block will contain only the - * matched transactions. - * @returns {MerkleBlock} - */ + /** + * Instantiate a block from a serialized Buffer. + * @param {Buffer} data + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {Block} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } -Block.prototype.toMerkle = function toMerkle(filter) { - return MerkleBlock.fromBlock(this, filter); -}; + /** + * Convert the Block to a MerkleBlock. + * @param {Bloom} filter - Bloom filter for transactions + * to match. The merkle block will contain only the + * matched transactions. + * @returns {MerkleBlock} + */ -/** - * Serialze block with or without witness data. - * @private - * @param {Boolean} witness - * @param {BufferWriter?} writer - * @returns {Buffer} - */ + toMerkle(filter) { + return MerkleBlock.fromBlock(this, filter); + } -Block.prototype.writeNormal = function writeNormal(bw) { - this.writeHead(bw); + /** + * Serialze block with or without witness data. + * @private + * @param {Boolean} witness + * @param {BufferWriter?} writer + * @returns {Buffer} + */ - bw.writeVarint(this.txs.length); + writeNormal(bw) { + this.writeHead(bw); - for (const tx of this.txs) - tx.toNormalWriter(bw); + bw.writeVarint(this.txs.length); - return bw; -}; + for (const tx of this.txs) + tx.toNormalWriter(bw); -/** - * Serialze block with or without witness data. - * @private - * @param {Boolean} witness - * @param {BufferWriter?} writer - * @returns {Buffer} - */ + return bw; + } -Block.prototype.writeWitness = function writeWitness(bw) { - this.writeHead(bw); + /** + * Serialze block with or without witness data. + * @private + * @param {Boolean} witness + * @param {BufferWriter?} writer + * @returns {Buffer} + */ - bw.writeVarint(this.txs.length); + writeWitness(bw) { + this.writeHead(bw); - for (const tx of this.txs) - tx.toWriter(bw); + bw.writeVarint(this.txs.length); - return bw; -}; + for (const tx of this.txs) + tx.toWriter(bw, true); -/** - * Serialze block with or without witness data. - * @private - * @param {Boolean} witness - * @param {BufferWriter?} writer - * @returns {Buffer} - */ + return bw; + } -Block.prototype.frameNormal = function frameNormal() { - const raw = this.getNormalSizes(); - const bw = new StaticWriter(raw.size); - this.writeNormal(bw); - raw.data = bw.render(); - return raw; -}; + /** + * Serialze block with or without witness data. + * @private + * @param {Boolean} witness + * @param {BufferWriter?} writer + * @returns {Buffer} + */ + + frameNormal() { + const raw = this.getNormalSizes(); + const bw = bio.write(raw.size); + this.writeNormal(bw); + raw.data = bw.render(); + return raw; + } -/** - * Serialze block without witness data. - * @private - * @param {BufferWriter?} writer - * @returns {Buffer} - */ + /** + * Serialze block without witness data. + * @private + * @param {BufferWriter?} writer + * @returns {Buffer} + */ + + frameWitness() { + const raw = this.getWitnessSizes(); + const bw = bio.write(raw.size); + this.writeWitness(bw); + raw.data = bw.render(); + return raw; + } -Block.prototype.frameWitness = function frameWitness() { - const raw = this.getWitnessSizes(); - const bw = new StaticWriter(raw.size); - this.writeWitness(bw); - raw.data = bw.render(); - return raw; -}; + /** + * Convert the block to a headers object. + * @returns {Headers} + */ -/** - * Convert the block to a headers object. - * @returns {Headers} - */ + toHeaders() { + return Headers.fromBlock(this); + } -Block.prototype.toHeaders = function toHeaders() { - return Headers.fromBlock(this); -}; + /** + * Get real block size without witness. + * @returns {RawBlock} + */ -/** - * Get real block size without witness. - * @returns {RawBlock} - */ + getNormalSizes() { + let size = 0; -Block.prototype.getNormalSizes = function getNormalSizes() { - let size = 0; + size += 80; + size += encoding.sizeVarint(this.txs.length); - size += 80; - size += encoding.sizeVarint(this.txs.length); + for (const tx of this.txs) + size += tx.getBaseSize(); - for (const tx of this.txs) - size += tx.getBaseSize(); + return new RawBlock(size, 0); + } - return new RawBlock(size, 0); -}; + /** + * Get real block size with witness. + * @returns {RawBlock} + */ -/** - * Get real block size with witness. - * @returns {RawBlock} - */ + getWitnessSizes() { + let size = 0; + let witness = 0; -Block.prototype.getWitnessSizes = function getWitnessSizes() { - let size = 0; - let witness = 0; + size += 80; + size += encoding.sizeVarint(this.txs.length); - size += 80; - size += encoding.sizeVarint(this.txs.length); + for (const tx of this.txs) { + const raw = tx.getSizes(); + size += raw.size; + witness += raw.witness; + } - for (const tx of this.txs) { - const raw = tx.getSizes(); - size += raw.size; - witness += raw.witness; + return new RawBlock(size, witness); } - return new RawBlock(size, witness); -}; + /** + * Test whether an object is a Block. + * @param {Object} obj + * @returns {Boolean} + */ -/** - * Test whether an object is a Block. - * @param {Object} obj - * @returns {Boolean} - */ - -Block.isBlock = function isBlock(obj) { - return obj instanceof Block; -}; + static isBlock(obj) { + return obj instanceof Block; + } +} /* * Helpers */ -function RawBlock(size, witness) { - this.data = null; - this.size = size; - this.witness = witness; +class RawBlock { + constructor(size, witness) { + this.data = null; + this.size = size; + this.witness = witness; + } } /* diff --git a/lib/primitives/coin.js b/lib/primitives/coin.js index f77bb8597..a3d922d99 100644 --- a/lib/primitives/coin.js +++ b/lib/primitives/coin.js @@ -7,425 +7,431 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); +const bio = require('bufio'); const util = require('../utils/util'); const Amount = require('../btc/amount'); const Output = require('./output'); -const Script = require('../script/script'); const Network = require('../protocol/network'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); -const encoding = require('../utils/encoding'); +const consensus = require('../protocol/consensus'); +const Outpoint = require('./outpoint'); +const {inspectSymbol} = require('../utils'); /** + * Coin * Represents an unspent output. * @alias module:primitives.Coin - * @constructor * @extends Output - * @param {NakedCoin|Coin} options - * @property {Number} version - Transaction version. - * @property {Number} height - Transaction height (-1 if unconfirmed). - * @property {Amount} value - Output value in satoshis. - * @property {Script} script - Output script. - * @property {Boolean} coinbase - Whether the containing - * transaction is a coinbase. - * @property {Hash} hash - Transaction hash. - * @property {Number} index - Output index. + * @property {Number} version + * @property {Number} height + * @property {Amount} value + * @property {Script} script + * @property {Boolean} coinbase + * @property {Hash} hash + * @property {Number} index */ -function Coin(options) { - if (!(this instanceof Coin)) - return new Coin(options); +class Coin extends Output { + /** + * Create a coin. + * @constructor + * @param {Object} options + */ - this.version = 1; - this.height = -1; - this.value = 0; - this.script = new Script(); - this.coinbase = false; - this.hash = encoding.NULL_HASH; - this.index = 0; + constructor(options) { + super(); - if (options) - this.fromOptions(options); -} - -Object.setPrototypeOf(Coin.prototype, Output.prototype); - -/** - * Inject options into coin. - * @private - * @param {Object} options - */ - -Coin.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Coin data is required.'); - - if (options.version != null) { - assert(util.isU32(options.version), 'Version must be a uint32.'); - this.version = options.version; - } - - if (options.height != null) { - if (options.height !== -1) { - assert(util.isU32(options.height), 'Height must be a uint32.'); - this.height = options.height; - } else { - this.height = -1; - } - } - - if (options.value != null) { - assert(util.isU64(options.value), 'Value must be a uint64.'); - this.value = options.value; - } - - if (options.script) - this.script.fromOptions(options.script); - - if (options.coinbase != null) { - assert(typeof options.coinbase === 'boolean', - 'Coinbase must be a boolean.'); - this.coinbase = options.coinbase; - } + this.version = 1; + this.height = -1; + this.coinbase = false; + this.hash = consensus.ZERO_HASH; + this.index = 0; - if (options.hash != null) { - assert(typeof options.hash === 'string', 'Hash must be a string.'); - this.hash = options.hash; + if (options) + this.fromOptions(options); } - if (options.index != null) { - assert(util.isU32(options.index), 'Index must be a uint32.'); - this.index = options.index; - } + /** + * Inject options into coin. + * @private + * @param {Object} options + */ - return this; -}; + fromOptions(options) { + assert(options, 'Coin data is required.'); -/** - * Instantiate Coin from options object. - * @private - * @param {Object} options - */ + if (options.version != null) { + assert((options.version >>> 0) === options.version, + 'Version must be a uint32.'); + this.version = options.version; + } -Coin.fromOptions = function fromOptions(options) { - return new Coin().fromOptions(options); -}; + if (options.height != null) { + if (options.height !== -1) { + assert((options.height >>> 0) === options.height, + 'Height must be a uint32.'); + this.height = options.height; + } else { + this.height = -1; + } + } -/** - * Clone the coin. - * @private - * @returns {Coin} - */ + if (options.value != null) { + assert(Number.isSafeInteger(options.value) && options.value >= 0, + 'Value must be a uint64.'); + this.value = options.value; + } -Coin.prototype.clone = function clone() { - assert(false, 'Coins are not cloneable.'); -}; + if (options.script) + this.script.fromOptions(options.script); -/** - * Calculate number of confirmations since coin was created. - * @param {Number?} height - Current chain height. Network - * height is used if not passed in. - * @return {Number} - */ + if (options.coinbase != null) { + assert(typeof options.coinbase === 'boolean', + 'Coinbase must be a boolean.'); + this.coinbase = options.coinbase; + } -Coin.prototype.getDepth = function getDepth(height) { - assert(typeof height === 'number', 'Must pass a height.'); + if (options.hash != null) { + assert(Buffer.isBuffer(options.hash)); + this.hash = options.hash; + } - if (this.height === -1) - return 0; + if (options.index != null) { + assert((options.index >>> 0) === options.index, + 'Index must be a uint32.'); + this.index = options.index; + } - if (height === -1) - return 0; + return this; + } - if (height < this.height) - return 0; + /** + * Instantiate Coin from options object. + * @private + * @param {Object} options + */ - return height - this.height + 1; -}; + static fromOptions(options) { + return new this().fromOptions(options); + } -/** - * Serialize coin to a key - * suitable for a hash table. - * @returns {String} - */ + /** + * Clone the coin. + * @private + * @returns {Coin} + */ -Coin.prototype.toKey = function toKey() { - return this.hash + this.index; -}; + clone() { + assert(false, 'Coins are not cloneable.'); + } -/** - * Inject properties from hash table key. - * @private - * @param {String} key - * @returns {Coin} - */ + /** + * Calculate number of confirmations since coin was created. + * @param {Number?} height - Current chain height. Network + * height is used if not passed in. + * @return {Number} + */ -Coin.prototype.fromKey = function fromKey(key) { - assert(key.length > 64); - this.hash = key.slice(0, 64); - this.index = parseInt(key.slice(64), 10); - return this; -}; + getDepth(height) { + assert(typeof height === 'number', 'Must pass a height.'); -/** - * Instantiate coin from hash table key. - * @param {String} key - * @returns {Coin} - */ + if (this.height === -1) + return 0; -Coin.fromKey = function fromKey(key) { - return new Coin().fromKey(key); -}; + if (height === -1) + return 0; -/** - * Get little-endian hash. - * @returns {Hash} - */ + if (height < this.height) + return 0; -Coin.prototype.rhash = function rhash() { - return util.revHex(this.hash); -}; + return height - this.height + 1; + } -/** - * Get little-endian hash. - * @returns {Hash} - */ + /** + * Serialize coin to a key + * suitable for a hash table. + * @returns {String} + */ -Coin.prototype.txid = function txid() { - return this.rhash(); -}; + toKey() { + return Outpoint.toKey(this.hash, this.index); + } -/** - * Convert the coin to a more user-friendly object. - * @returns {Object} - */ + /** + * Inject properties from hash table key. + * @private + * @param {String} key + * @returns {Coin} + */ + + fromKey(key) { + const {hash, index} = Outpoint.fromKey(key); + this.hash = hash; + this.index = index; + return this; + } -Coin.prototype.inspect = function inspect() { - return { - type: this.getType(), - version: this.version, - height: this.height, - value: Amount.btc(this.value), - script: this.script, - coinbase: this.coinbase, - hash: this.hash ? util.revHex(this.hash) : null, - index: this.index, - address: this.getAddress() - }; -}; + /** + * Instantiate coin from hash table key. + * @param {String} key + * @returns {Coin} + */ -/** - * Convert the coin to an object suitable - * for JSON serialization. - * @returns {Object} - */ + static fromKey(key) { + return new this().fromKey(key); + } -Coin.prototype.toJSON = function toJSON() { - return this.getJSON(); -}; + /** + * Get little-endian hash. + * @returns {Hash} + */ -/** - * Convert the coin to an object suitable - * for JSON serialization. Note that the hash - * will be reversed to abide by bitcoind's legacy - * of little-endian uint256s. - * @param {Network} network - * @param {Boolean} minimal - * @returns {Object} - */ + rhash() { + return util.revHex(this.hash); + } -Coin.prototype.getJSON = function getJSON(network, minimal) { - let addr = this.getAddress(); + /** + * Get little-endian hash. + * @returns {Hash} + */ - network = Network.get(network); + txid() { + return this.rhash(); + } - if (addr) - addr = addr.toString(network); + /** + * Convert the coin to a more user-friendly object. + * @returns {Object} + */ + + [inspectSymbol]() { + return { + type: this.getType(), + version: this.version, + height: this.height, + value: Amount.btc(this.value), + script: this.script, + coinbase: this.coinbase, + hash: this.hash ? util.revHex(this.hash) : null, + index: this.index, + address: this.getAddress() + }; + } - return { - version: this.version, - height: this.height, - value: this.value, - script: this.script.toJSON(), - address: addr, - coinbase: this.coinbase, - hash: !minimal ? this.rhash() : undefined, - index: !minimal ? this.index : undefined - }; -}; + /** + * Convert the coin to an object suitable + * for JSON serialization. + * @returns {Object} + */ -/** - * Inject JSON properties into coin. - * @private - * @param {Object} json - */ + toJSON() { + return this.getJSON(); + } -Coin.prototype.fromJSON = function fromJSON(json) { - assert(json, 'Coin data required.'); - assert(util.isU32(json.version), 'Version must be a uint32.'); - assert(json.height === -1 || util.isU32(json.height), - 'Height must be a uint32.'); - assert(util.isU64(json.value), 'Value must be a uint64.'); - assert(typeof json.coinbase === 'boolean', 'Coinbase must be a boolean.'); - - this.version = json.version; - this.height = json.height; - this.value = json.value; - this.script.fromJSON(json.script); - this.coinbase = json.coinbase; - - if (json.hash != null) { - assert(typeof json.hash === 'string', 'Hash must be a string.'); - assert(json.hash.length === 64, 'Hash must be a string.'); - assert(util.isU32(json.index), 'Index must be a uint32.'); - this.hash = util.revHex(json.hash); - this.index = json.index; + /** + * Convert the coin to an object suitable + * for JSON serialization. Note that the hash + * will be reversed to abide by bitcoind's legacy + * of little-endian uint256s. + * @param {Network} network + * @param {Boolean} minimal + * @returns {Object} + */ + + getJSON(network, minimal) { + let addr = this.getAddress(); + + network = Network.get(network); + + if (addr) + addr = addr.toString(network); + + return { + version: this.version, + height: this.height, + value: this.value, + script: this.script.toJSON(), + address: addr, + coinbase: this.coinbase, + hash: !minimal ? this.rhash() : undefined, + index: !minimal ? this.index : undefined + }; } - return this; -}; + /** + * Inject JSON properties into coin. + * @private + * @param {Object} json + */ + + fromJSON(json) { + assert(json, 'Coin data required.'); + assert((json.version >>> 0) === json.version, 'Version must be a uint32.'); + assert(json.height === -1 || (json.height >>> 0) === json.height, + 'Height must be a uint32.'); + assert(Number.isSafeInteger(json.value) && json.value >= 0, + 'Value must be a uint64.'); + assert(typeof json.coinbase === 'boolean', 'Coinbase must be a boolean.'); + + this.version = json.version; + this.height = json.height; + this.value = json.value; + this.script.fromJSON(json.script); + this.coinbase = json.coinbase; + + if (json.hash != null) { + assert(typeof json.hash === 'string', 'Hash must be a string.'); + assert(json.hash.length === 64, 'Hash must be a string.'); + assert((json.index >>> 0) === json.index, 'Index must be a uint32.'); + this.hash = util.fromRev(json.hash); + this.index = json.index; + } -/** - * Instantiate an Coin from a jsonified coin object. - * @param {Object} json - The jsonified coin object. - * @returns {Coin} - */ + return this; + } -Coin.fromJSON = function fromJSON(json) { - return new Coin().fromJSON(json); -}; + /** + * Instantiate an Coin from a jsonified coin object. + * @param {Object} json - The jsonified coin object. + * @returns {Coin} + */ -/** - * Calculate size of coin. - * @returns {Number} - */ + static fromJSON(json) { + return new this().fromJSON(json); + } -Coin.prototype.getSize = function getSize() { - return 17 + this.script.getVarSize(); -}; + /** + * Calculate size of coin. + * @returns {Number} + */ -/** - * Write the coin to a buffer writer. - * @param {BufferWriter} bw - */ + getSize() { + return 17 + this.script.getVarSize(); + } -Coin.prototype.toWriter = function toWriter(bw) { - let height = this.height; + /** + * Write the coin to a buffer writer. + * @param {BufferWriter} bw + */ - if (height === -1) - height = 0x7fffffff; + toWriter(bw) { + let height = this.height; - bw.writeU32(this.version); - bw.writeU32(height); - bw.writeI64(this.value); - bw.writeVarBytes(this.script.toRaw()); - bw.writeU8(this.coinbase ? 1 : 0); + if (height === -1) + height = 0x7fffffff; - return bw; -}; + bw.writeU32(this.version); + bw.writeU32(height); + bw.writeI64(this.value); + bw.writeVarBytes(this.script.toRaw()); + bw.writeU8(this.coinbase ? 1 : 0); -/** - * Serialize the coin. - * @returns {Buffer|String} - */ + return bw; + } -Coin.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + /** + * Serialize the coin. + * @returns {Buffer|String} + */ -/** - * Inject properties from serialized buffer writer. - * @private - * @param {BufferReader} br - */ - -Coin.prototype.fromReader = function fromReader(br) { - this.version = br.readU32(); - this.height = br.readU32(); - this.value = br.readI64(); - this.script.fromRaw(br.readVarBytes()); - this.coinbase = br.readU8() === 1; + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } - if (this.height === 0x7fffffff) - this.height = -1; + /** + * Inject properties from serialized buffer writer. + * @private + * @param {BufferReader} br + */ - return this; -}; + fromReader(br) { + this.version = br.readU32(); + this.height = br.readU32(); + this.value = br.readI64(); + this.script.fromRaw(br.readVarBytes()); + this.coinbase = br.readU8() === 1; -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + if (this.height === 0x7fffffff) + this.height = -1; -Coin.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + return this; + } -/** - * Instantiate a coin from a buffer reader. - * @param {BufferReader} br - * @returns {Coin} - */ + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -Coin.fromReader = function fromReader(br) { - return new Coin().fromReader(br); -}; + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -/** - * Instantiate a coin from a serialized Buffer. - * @param {Buffer} data - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {Coin} - */ + /** + * Instantiate a coin from a buffer reader. + * @param {BufferReader} br + * @returns {Coin} + */ -Coin.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new Coin().fromRaw(data); -}; + static fromReader(br) { + return new this().fromReader(br); + } -/** - * Inject properties from TX. - * @param {TX} tx - * @param {Number} index - */ + /** + * Instantiate a coin from a serialized Buffer. + * @param {Buffer} data + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {Coin} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } -Coin.prototype.fromTX = function fromTX(tx, index, height) { - assert(typeof index === 'number'); - assert(typeof height === 'number'); - assert(index >= 0 && index < tx.outputs.length); - this.version = tx.version; - this.height = height; - this.value = tx.outputs[index].value; - this.script = tx.outputs[index].script; - this.coinbase = tx.isCoinbase(); - this.hash = tx.hash('hex'); - this.index = index; - return this; -}; + /** + * Inject properties from TX. + * @param {TX} tx + * @param {Number} index + */ + + fromTX(tx, index, height) { + assert(typeof index === 'number'); + assert(typeof height === 'number'); + assert(index >= 0 && index < tx.outputs.length); + this.version = tx.version; + this.height = height; + this.value = tx.outputs[index].value; + this.script = tx.outputs[index].script; + this.coinbase = tx.isCoinbase(); + this.hash = tx.hash(); + this.index = index; + return this; + } -/** - * Instantiate a coin from a TX - * @param {TX} tx - * @param {Number} index - Output index. - * @returns {Coin} - */ + /** + * Instantiate a coin from a TX + * @param {TX} tx + * @param {Number} index - Output index. + * @returns {Coin} + */ -Coin.fromTX = function fromTX(tx, index, height) { - return new Coin().fromTX(tx, index, height); -}; + static fromTX(tx, index, height) { + return new this().fromTX(tx, index, height); + } -/** - * Test an object to see if it is a Coin. - * @param {Object} obj - * @returns {Boolean} - */ + /** + * Test an object to see if it is a Coin. + * @param {Object} obj + * @returns {Boolean} + */ -Coin.isCoin = function isCoin(obj) { - return obj instanceof Coin; -}; + static isCoin(obj) { + return obj instanceof Coin; + } +} /* * Expose diff --git a/lib/primitives/headers.js b/lib/primitives/headers.js index a8f3587a8..9b5e1cda4 100644 --- a/lib/primitives/headers.js +++ b/lib/primitives/headers.js @@ -7,268 +7,270 @@ 'use strict'; +const bio = require('bufio'); const util = require('../utils/util'); const AbstractBlock = require('./abstractblock'); -const StaticWriter = require('../utils/staticwriter'); -const BufferReader = require('../utils/reader'); +const {inspectSymbol} = require('../utils'); /** - * Represents block headers obtained from the network via `headers`. + * Headers + * Represents block headers obtained + * from the network via `headers`. * @alias module:primitives.Headers - * @constructor * @extends AbstractBlock - * @param {NakedBlock} options */ -function Headers(options) { - if (!(this instanceof Headers)) - return new Headers(options); - - AbstractBlock.call(this); - - if (options) - this.parseOptions(options); +class Headers extends AbstractBlock { + /** + * Create headers. + * @constructor + * @param {Object} options + */ + + constructor(options) { + super(); + + if (options) + this.parseOptions(options); + } + + /** + * Perform non-contextual + * verification on the headers. + * @returns {Boolean} + */ + + verifyBody() { + return true; + } + + /** + * Get size of the headers. + * @returns {Number} + */ + + getSize() { + return 81; + } + + /** + * Serialize the headers to a buffer writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + this.writeHead(bw); + bw.writeVarint(0); + return bw; + } + + /** + * Serialize the headers. + * @returns {Buffer|String} + */ + + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {Buffer} data + */ + + fromReader(br) { + this.readHead(br); + br.readVarint(); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(bio.read(data)); + } + + /** + * Instantiate headers from buffer reader. + * @param {BufferReader} br + * @returns {Headers} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate headers from serialized data. + * @param {Buffer} data + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {Headers} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } + + /** + * Instantiate headers from serialized data. + * @param {Buffer} data + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {Headers} + */ + + static fromHead(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromHead(data); + } + + /** + * Instantiate headers from a chain entry. + * @param {ChainEntry} entry + * @returns {Headers} + */ + + static fromEntry(entry) { + const headers = new this(); + headers.version = entry.version; + headers.prevBlock = entry.prevBlock; + headers.merkleRoot = entry.merkleRoot; + headers.time = entry.time; + headers.bits = entry.bits; + headers.nonce = entry.nonce; + headers._hash = entry.hash; + headers._hhash = entry.hash; + return headers; + } + + /** + * Convert the block to a headers object. + * @returns {Headers} + */ + + toHeaders() { + return this; + } + + /** + * Convert the block to a headers object. + * @param {Block|MerkleBlock} block + * @returns {Headers} + */ + + static fromBlock(block) { + const headers = new this(block); + headers._hash = block._hash; + headers._hhash = block._hhash; + return headers; + } + + /** + * Convert the block to an object suitable + * for JSON serialization. + * @returns {Object} + */ + + toJSON() { + return this.getJSON(); + } + + /** + * Convert the block to an object suitable + * for JSON serialization. Note that the hashes + * will be reversed to abide by bitcoind's legacy + * of little-endian uint256s. + * @param {Network} network + * @param {CoinView} view + * @param {Number} height + * @returns {Object} + */ + + getJSON(network, view, height) { + return { + hash: this.rhash(), + height: height, + version: this.version, + prevBlock: util.revHex(this.prevBlock), + merkleRoot: util.revHex(this.merkleRoot), + time: this.time, + bits: this.bits, + nonce: this.nonce + }; + } + + /** + * Inject properties from json object. + * @private + * @param {Object} json + */ + + fromJSON(json) { + this.parseJSON(json); + return this; + } + + /** + * Instantiate a merkle block from a jsonified block object. + * @param {Object} json - The jsonified block object. + * @returns {Headers} + */ + + static fromJSON(json) { + return new this().fromJSON(json); + } + + /** + * Inspect the headers and return a more + * user-friendly representation of the data. + * @returns {Object} + */ + + [inspectSymbol]() { + return this.format(); + } + + /** + * Inspect the headers and return a more + * user-friendly representation of the data. + * @param {CoinView} view + * @param {Number} height + * @returns {Object} + */ + + format(view, height) { + return { + hash: this.rhash(), + height: height != null ? height : -1, + date: util.date(this.time), + version: this.version.toString(16), + prevBlock: util.revHex(this.prevBlock), + merkleRoot: util.revHex(this.merkleRoot), + time: this.time, + bits: this.bits, + nonce: this.nonce + }; + } + + /** + * Test an object to see if it is a Headers object. + * @param {Object} obj + * @returns {Boolean} + */ + + static isHeaders(obj) { + return obj instanceof Headers; + } } -Object.setPrototypeOf(Headers.prototype, AbstractBlock.prototype); - -/** - * Do non-contextual verification on the headers. - * @param {Object?} ret - Return object, may be - * set with properties `reason` and `score`. - * @returns {Boolean} - */ - -Headers.prototype.verifyBody = function verifyBody(ret) { - return true; -}; - -/** - * Get size of the headers. - * @returns {Number} - */ - -Headers.prototype.getSize = function getSize() { - return 81; -}; - -/** - * Serialize the headers to a buffer writer. - * @param {BufferWriter} bw - */ - -Headers.prototype.toWriter = function toWriter(bw) { - this.writeHead(bw); - bw.writeVarint(0); - return bw; -}; - -/** - * Serialize the headers. - * @returns {Buffer|String} - */ - -Headers.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {Buffer} data - */ - -Headers.prototype.fromReader = function fromReader(br) { - this.readHead(br); - br.readVarint(); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -Headers.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate headers from buffer reader. - * @param {BufferReader} br - * @returns {Headers} - */ - -Headers.fromReader = function fromReader(br) { - return new Headers().fromReader(br); -}; - -/** - * Instantiate headers from serialized data. - * @param {Buffer} data - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {Headers} - */ - -Headers.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new Headers().fromRaw(data); -}; - -/** - * Instantiate headers from serialized data. - * @param {Buffer} data - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {Headers} - */ - -Headers.fromHead = function fromHead(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new Headers().fromHead(data); -}; - -/** - * Instantiate headers from a chain entry. - * @param {ChainEntry} entry - * @returns {Headers} - */ - -Headers.fromEntry = function fromEntry(entry) { - const headers = new Headers(); - headers.version = entry.version; - headers.prevBlock = entry.prevBlock; - headers.merkleRoot = entry.merkleRoot; - headers.time = entry.time; - headers.bits = entry.bits; - headers.nonce = entry.nonce; - headers._hash = Buffer.from(entry.hash, 'hex'); - headers._hhash = entry.hash; - return headers; -}; - -/** - * Convert the block to a headers object. - * @returns {Headers} - */ - -Headers.prototype.toHeaders = function toHeaders() { - return this; -}; - -/** - * Convert the block to a headers object. - * @param {Block|MerkleBlock} block - * @returns {Headers} - */ - -Headers.fromBlock = function fromBlock(block) { - const headers = new Headers(block); - headers._hash = block._hash; - headers._hhash = block._hhash; - return headers; -}; - -/** - * Convert the block to an object suitable - * for JSON serialization. - * @returns {Object} - */ - -Headers.prototype.toJSON = function toJSON() { - return this.getJSON(); -}; - -/** - * Convert the block to an object suitable - * for JSON serialization. Note that the hashes - * will be reversed to abide by bitcoind's legacy - * of little-endian uint256s. - * @param {Network} network - * @param {CoinView} view - * @param {Number} height - * @returns {Object} - */ - -Headers.prototype.getJSON = function getJSON(network, view, height) { - return { - hash: this.rhash(), - height: height, - version: this.version, - prevBlock: util.revHex(this.prevBlock), - merkleRoot: util.revHex(this.merkleRoot), - time: this.time, - bits: this.bits, - nonce: this.nonce - }; -}; - -/** - * Inject properties from json object. - * @private - * @param {Object} json - */ - -Headers.prototype.fromJSON = function fromJSON(json) { - this.parseJSON(json); - return this; -}; - -/** - * Instantiate a merkle block from a jsonified block object. - * @param {Object} json - The jsonified block object. - * @returns {Headers} - */ - -Headers.fromJSON = function fromJSON(json) { - return new Headers().fromJSON(json); -}; - -/** - * Inspect the headers and return a more - * user-friendly representation of the data. - * @returns {Object} - */ - -Headers.prototype.inspect = function inspect() { - return this.format(); -}; - -/** - * Inspect the headers and return a more - * user-friendly representation of the data. - * @param {CoinView} view - * @param {Number} height - * @returns {Object} - */ - -Headers.prototype.format = function format(view, height) { - return { - hash: this.rhash(), - height: height != null ? height : -1, - date: util.date(this.time), - version: util.hex32(this.version), - prevBlock: util.revHex(this.prevBlock), - merkleRoot: util.revHex(this.merkleRoot), - time: this.time, - bits: this.bits, - nonce: this.nonce - }; -}; - -/** - * Test an object to see if it is a Headers object. - * @param {Object} obj - * @returns {Boolean} - */ - -Headers.isHeaders = function isHeaders(obj) { - return obj instanceof Headers; -}; - /* * Expose */ diff --git a/lib/primitives/index.js b/lib/primitives/index.js index b37920fb3..1d2cebdc7 100644 --- a/lib/primitives/index.js +++ b/lib/primitives/index.js @@ -21,7 +21,6 @@ exports.KeyRing = require('./keyring'); exports.MemBlock = require('./memblock'); exports.MerkleBlock = require('./merkleblock'); exports.MTX = require('./mtx'); -exports.NetAddress = require('./netaddress'); exports.Outpoint = require('./outpoint'); exports.Output = require('./output'); exports.TX = require('./tx'); diff --git a/lib/primitives/input.js b/lib/primitives/input.js index 1a272533c..d1d676401 100644 --- a/lib/primitives/input.js +++ b/lib/primitives/input.js @@ -7,511 +7,520 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); +const assert = require('bsert'); +const bio = require('bufio'); const Network = require('../protocol/network'); const Script = require('../script/script'); const Witness = require('../script/witness'); const Outpoint = require('./outpoint'); -const StaticWriter = require('../utils/staticwriter'); -const BufferReader = require('../utils/reader'); +const {inspectSymbol} = require('../utils'); /** + * Input * Represents a transaction input. * @alias module:primitives.Input - * @constructor - * @param {NakedInput} options * @property {Outpoint} prevout - Outpoint. * @property {Script} script - Input script / scriptSig. * @property {Number} sequence - nSequence. * @property {Witness} witness - Witness (empty if not present). */ -function Input(options) { - if (!(this instanceof Input)) - return new Input(options); +class Input { + /** + * Create transaction input. + * @constructor + * @param {Object} options + */ - this.prevout = new Outpoint(); - this.script = new Script(); - this.sequence = 0xffffffff; - this.witness = new Witness(); + constructor(options) { + this.prevout = new Outpoint(); + this.script = new Script(); + this.sequence = 0xffffffff; + this.witness = new Witness(); - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -Input.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Input data is required.'); - - this.prevout.fromOptions(options.prevout); - - if (options.script) - this.script.fromOptions(options.script); - - if (options.sequence != null) { - assert(util.isU32(options.sequence), 'Sequence must be a uint32.'); - this.sequence = options.sequence; + if (options) + this.fromOptions(options); } - if (options.witness) - this.witness.fromOptions(options.witness); + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ - return this; -}; + fromOptions(options) { + assert(options, 'Input data is required.'); -/** - * Instantiate an Input from options object. - * @param {NakedInput} options - * @returns {Input} - */ + this.prevout.fromOptions(options.prevout); -Input.fromOptions = function fromOptions(options) { - return new Input().fromOptions(options); -}; + if (options.script) + this.script.fromOptions(options.script); -/** - * Clone the input. - * @returns {Input} - */ + if (options.sequence != null) { + assert((options.sequence >>> 0) === options.sequence, + 'Sequence must be a uint32.'); + this.sequence = options.sequence; + } -Input.prototype.clone = function clone() { - const input = new Input(); - input.prevout = this.prevout; - input.script.inject(this.script); - input.sequence = this.sequence; - input.witness.inject(this.witness); - return input; -}; - -/** - * Test equality against another input. - * @param {Input} input - * @returns {Boolean} - */ + if (options.witness) + this.witness.fromOptions(options.witness); -Input.prototype.equals = function equals(input) { - assert(Input.isInput(input)); - return this.prevout.equals(input.prevout); -}; + return this; + } -/** - * Compare against another input (BIP69). - * @param {Input} input - * @returns {Number} - */ + /** + * Instantiate an Input from options object. + * @param {Object} options + * @returns {Input} + */ -Input.prototype.compare = function compare(input) { - assert(Input.isInput(input)); - return this.prevout.compare(input.prevout); -}; + static fromOptions(options) { + return new this().fromOptions(options); + } -/** - * Get the previous output script type as a string. - * Will "guess" based on the input script and/or - * witness if coin is not available. - * @param {Coin?} coin - * @returns {ScriptType} type - */ + /** + * Clone the input. + * @returns {Input} + */ + + clone() { + const input = new this.constructor(); + input.prevout = this.prevout; + input.script.inject(this.script); + input.sequence = this.sequence; + input.witness.inject(this.witness); + return input; + } -Input.prototype.getType = function getType(coin) { - if (this.isCoinbase()) - return 'coinbase'; + /** + * Test equality against another input. + * @param {Input} input + * @returns {Boolean} + */ - if (coin) - return coin.getType(); + equals(input) { + assert(Input.isInput(input)); + return this.prevout.equals(input.prevout); + } - let type; + /** + * Compare against another input (BIP69). + * @param {Input} input + * @returns {Number} + */ - if (this.witness.items.length > 0) - type = this.witness.getInputType(); - else - type = this.script.getInputType(); + compare(input) { + assert(Input.isInput(input)); + return this.prevout.compare(input.prevout); + } - return Script.typesByVal[type].toLowerCase(); -}; + /** + * Get the previous output script type as a string. + * Will "guess" based on the input script and/or + * witness if coin is not available. + * @param {Coin?} coin + * @returns {ScriptType} type + */ -/** - * Get the redeem script. Will attempt to resolve nested - * redeem scripts if witnessscripthash is behind a scripthash. - * @param {Coin?} coin - * @returns {Script?} Redeem script. - */ + getType(coin) { + if (this.isCoinbase()) + return 'coinbase'; -Input.prototype.getRedeem = function getRedeem(coin) { - if (this.isCoinbase()) - return null; + if (coin) + return coin.getType(); - if (!coin) { - if (this.witness.isScripthashInput()) - return this.witness.getRedeem(); + let type; - if (this.script.isScripthashInput()) - return this.script.getRedeem(); + if (this.witness.items.length > 0) + type = this.witness.getInputType(); + else + type = this.script.getInputType(); - return null; + return Script.typesByVal[type].toLowerCase(); } - let prev = coin.script; - let redeem = null; + /** + * Get the redeem script. Will attempt to resolve nested + * redeem scripts if witnessscripthash is behind a scripthash. + * @param {Coin?} coin + * @returns {Script?} Redeem script. + */ - if (prev.isScripthash()) { - prev = this.script.getRedeem(); - redeem = prev; - } + getRedeem(coin) { + if (this.isCoinbase()) + return null; - if (prev && prev.isWitnessScripthash()) { - prev = this.witness.getRedeem(); - redeem = prev; - } + if (!coin) { + if (this.witness.isScripthashInput()) + return this.witness.getRedeem(); - return redeem; -}; + if (this.script.isScripthashInput()) + return this.script.getRedeem(); -/** - * Get the redeem script type. - * @param {Coin?} coin - * @returns {String} subtype - */ + return null; + } -Input.prototype.getSubtype = function getSubtype(coin) { - if (this.isCoinbase()) - return null; + let prev = coin.script; + let redeem = null; - const redeem = this.getRedeem(coin); + if (prev.isScripthash()) { + prev = this.script.getRedeem(); + redeem = prev; + } - if (!redeem) - return null; + if (prev && prev.isWitnessScripthash()) { + prev = this.witness.getRedeem(); + redeem = prev; + } - const type = redeem.getType(); + return redeem; + } - return Script.typesByVal[type].toLowerCase(); -}; + /** + * Get the redeem script type. + * @param {Coin?} coin + * @returns {String} subtype + */ -/** - * Get the previous output script's address. Will "guess" - * based on the input script and/or witness if coin - * is not available. - * @param {Coin?} coin - * @returns {Address?} addr - */ + getSubtype(coin) { + if (this.isCoinbase()) + return null; -Input.prototype.getAddress = function getAddress(coin) { - if (this.isCoinbase()) - return null; + const redeem = this.getRedeem(coin); - if (coin) - return coin.getAddress(); + if (!redeem) + return null; - if (this.witness.items.length > 0) - return this.witness.getInputAddress(); + const type = redeem.getType(); - return this.script.getInputAddress(); -}; + return Script.typesByVal[type].toLowerCase(); + } -/** - * Get the address hash. - * @param {String?} enc - * @returns {Hash} hash - */ + /** + * Get the previous output script's address. Will "guess" + * based on the input script and/or witness if coin + * is not available. + * @param {Coin?} coin + * @returns {Address?} addr + */ -Input.prototype.getHash = function getHash(enc) { - const addr = this.getAddress(); + getAddress(coin) { + if (this.isCoinbase()) + return null; - if (!addr) - return null; + if (coin) + return coin.getAddress(); - return addr.getHash(enc); -}; - -/** - * Test to see if nSequence is equal to uint32max. - * @returns {Boolean} - */ + if (this.script.code.length > 0) + return this.script.getInputAddress(); -Input.prototype.isFinal = function isFinal() { - return this.sequence === 0xffffffff; -}; + if (this.witness.items.length > 0) + return this.witness.getInputAddress(); -/** - * Test to see if nSequence is less than 0xfffffffe. - * @returns {Boolean} - */ - -Input.prototype.isRBF = function isRBF() { - return this.sequence < 0xfffffffe; -}; - -/** - * Test to see if outpoint is null. - * @returns {Boolean} - */ + return null; + } -Input.prototype.isCoinbase = function isCoinbase() { - return this.prevout.isNull(); -}; + /** + * Get the address hash. + * @param {Coin?} coin + * @param {String?} enc + * @returns {Hash} hash + */ -/** - * Convert the input to a more user-friendly object. - * @returns {Object} - */ + getHash(coin, enc) { + const addr = this.getAddress(coin); -Input.prototype.inspect = function inspect() { - return this.format(); -}; + if (!addr) + return null; -/** - * Convert the input to a more user-friendly object. - * @param {Coin?} coin - * @returns {Object} - */ + return addr.getHash(enc); + } -Input.prototype.format = function format(coin) { - return { - type: this.getType(coin), - subtype: this.getSubtype(coin), - address: this.getAddress(coin), - script: this.script, - witness: this.witness, - redeem: this.getRedeem(coin), - sequence: this.sequence, - prevout: this.prevout, - coin: coin || null - }; -}; + /** + * Test to see if nSequence is equal to uint32max. + * @returns {Boolean} + */ -/** - * Convert the input to an object suitable - * for JSON serialization. - * @returns {Object} - */ + isFinal() { + return this.sequence === 0xffffffff; + } -Input.prototype.toJSON = function toJSON(network, coin) { - return this.getJSON(); -}; + /** + * Test to see if nSequence is less than 0xfffffffe. + * @returns {Boolean} + */ -/** - * Convert the input to an object suitable - * for JSON serialization. Note that the hashes - * will be reversed to abide by bitcoind's legacy - * of little-endian uint256s. - * @param {Network} network - * @param {Coin} coin - * @returns {Object} - */ + isRBF() { + return this.sequence < 0xfffffffe; + } -Input.prototype.getJSON = function getJSON(network, coin) { - network = Network.get(network); + /** + * Test to see if outpoint is null. + * @returns {Boolean} + */ - let addr; - if (!coin) { - addr = this.getAddress(); - if (addr) - addr = addr.toString(network); + isCoinbase() { + return this.prevout.isNull(); } - return { - prevout: this.prevout.toJSON(), - script: this.script.toJSON(), - witness: this.witness.toJSON(), - sequence: this.sequence, - address: addr, - coin: coin ? coin.getJSON(network, true) : undefined - }; -}; - -/** - * Inject properties from a JSON object. - * @private - * @param {Object} json - */ + /** + * Convert the input to a more user-friendly object. + * @returns {Object} + */ -Input.prototype.fromJSON = function fromJSON(json) { - assert(json, 'Input data is required.'); - assert(util.isU32(json.sequence), 'Sequence must be a uint32.'); - this.prevout.fromJSON(json.prevout); - this.script.fromJSON(json.script); - this.witness.fromJSON(json.witness); - this.sequence = json.sequence; - return this; -}; + [inspectSymbol]() { + return this.format(); + } -/** - * Instantiate an Input from a jsonified input object. - * @param {Object} json - The jsonified input object. - * @returns {Input} - */ + /** + * Convert the input to a more user-friendly object. + * @param {Coin?} coin + * @returns {Object} + */ + + format(coin) { + return { + type: this.getType(coin), + subtype: this.getSubtype(coin), + address: this.getAddress(coin), + script: this.script, + witness: this.witness, + redeem: this.getRedeem(coin), + sequence: this.sequence, + prevout: this.prevout, + coin: coin || null + }; + } -Input.fromJSON = function fromJSON(json) { - return new Input().fromJSON(json); -}; + /** + * Convert the input to an object suitable + * for JSON serialization. + * @returns {Object} + */ -/** - * Calculate size of serialized input. - * @returns {Number} - */ + toJSON(network, coin) { + return this.getJSON(); + } -Input.prototype.getSize = function getSize() { - return 40 + this.script.getVarSize(); -}; + /** + * Convert the input to an object suitable + * for JSON serialization. Note that the hashes + * will be reversed to abide by bitcoind's legacy + * of little-endian uint256s. + * @param {Network} network + * @param {Coin} coin + * @returns {Object} + */ + + getJSON(network, coin) { + network = Network.get(network); + + let addr; + if (!coin) { + addr = this.getAddress(); + if (addr) + addr = addr.toString(network); + } + + return { + prevout: this.prevout.toJSON(), + script: this.script.toJSON(), + witness: this.witness.toJSON(), + sequence: this.sequence, + address: addr, + coin: coin ? coin.getJSON(network, true) : undefined + }; + } -/** - * Serialize the input. - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {Buffer|String} - */ + /** + * Inject properties from a JSON object. + * @private + * @param {Object} json + */ + + fromJSON(json) { + assert(json, 'Input data is required.'); + assert((json.sequence >>> 0) === json.sequence, + 'Sequence must be a uint32.'); + this.prevout.fromJSON(json.prevout); + this.script.fromJSON(json.script); + this.witness.fromJSON(json.witness); + this.sequence = json.sequence; + return this; + } -Input.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + /** + * Instantiate an Input from a jsonified input object. + * @param {Object} json - The jsonified input object. + * @returns {Input} + */ -/** - * Write the input to a buffer writer. - * @param {BufferWriter} bw - */ + static fromJSON(json) { + return new this().fromJSON(json); + } -Input.prototype.toWriter = function toWriter(bw) { - this.prevout.toWriter(bw); - bw.writeVarBytes(this.script.toRaw()); - bw.writeU32(this.sequence); - return bw; -}; + /** + * Calculate size of serialized input. + * @returns {Number} + */ -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + getSize() { + return 40 + this.script.getVarSize(); + } -Input.prototype.fromReader = function fromReader(br) { - this.prevout.fromReader(br); - this.script.fromRaw(br.readVarBytes()); - this.sequence = br.readU32(); - return this; -}; + /** + * Serialize the input. + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {Buffer|String} + */ -/** - * Inject properties from serialized data. - * @param {Buffer} data - */ + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } -Input.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + /** + * Write the input to a buffer writer. + * @param {BufferWriter} bw + */ -/** - * Instantiate an input from a buffer reader. - * @param {BufferReader} br - * @returns {Input} - */ + toWriter(bw) { + this.prevout.toWriter(bw); + bw.writeVarBytes(this.script.toRaw()); + bw.writeU32(this.sequence); + return bw; + } -Input.fromReader = function fromReader(br) { - return new Input().fromReader(br); -}; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.prevout.fromReader(br); + this.script.fromRaw(br.readVarBytes()); + this.sequence = br.readU32(); + return this; + } -/** - * Instantiate an input from a serialized Buffer. - * @param {Buffer} data - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {Input} - */ + /** + * Inject properties from serialized data. + * @param {Buffer} data + */ -Input.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new Input().fromRaw(data); -}; + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -/** - * Inject properties from outpoint. - * @private - * @param {Outpoint} outpoint - */ + /** + * Instantiate an input from a buffer reader. + * @param {BufferReader} br + * @returns {Input} + */ -Input.prototype.fromOutpoint = function fromOutpoint(outpoint) { - assert(typeof outpoint.hash === 'string'); - assert(typeof outpoint.index === 'number'); - this.prevout.hash = outpoint.hash; - this.prevout.index = outpoint.index; - return this; -}; + static fromReader(br) { + return new this().fromReader(br); + } -/** - * Instantiate input from outpoint. - * @param {Outpoint} - * @returns {Input} - */ + /** + * Instantiate an input from a serialized Buffer. + * @param {Buffer} data + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {Input} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } -Input.fromOutpoint = function fromOutpoint(outpoint) { - return new Input().fromOutpoint(outpoint); -}; + /** + * Inject properties from outpoint. + * @private + * @param {Outpoint} outpoint + */ + + fromOutpoint(outpoint) { + assert(Buffer.isBuffer(outpoint.hash)); + assert(typeof outpoint.index === 'number'); + this.prevout.hash = outpoint.hash; + this.prevout.index = outpoint.index; + return this; + } -/** - * Inject properties from coin. - * @private - * @param {Coin} coin - */ + /** + * Instantiate input from outpoint. + * @param {Outpoint} + * @returns {Input} + */ -Input.prototype.fromCoin = function fromCoin(coin) { - assert(typeof coin.hash === 'string'); - assert(typeof coin.index === 'number'); - this.prevout.hash = coin.hash; - this.prevout.index = coin.index; - return this; -}; + static fromOutpoint(outpoint) { + return new this().fromOutpoint(outpoint); + } -/** - * Instantiate input from coin. - * @param {Coin} - * @returns {Input} - */ + /** + * Inject properties from coin. + * @private + * @param {Coin} coin + */ + + fromCoin(coin) { + assert(Buffer.isBuffer(coin.hash)); + assert(typeof coin.index === 'number'); + this.prevout.hash = coin.hash; + this.prevout.index = coin.index; + return this; + } -Input.fromCoin = function fromCoin(coin) { - return new Input().fromCoin(coin); -}; + /** + * Instantiate input from coin. + * @param {Coin} + * @returns {Input} + */ -/** - * Inject properties from transaction. - * @private - * @param {TX} tx - * @param {Number} index - */ + static fromCoin(coin) { + return new this().fromCoin(coin); + } -Input.prototype.fromTX = function fromTX(tx, index) { - assert(tx); - assert(typeof index === 'number'); - assert(index >= 0 && index < tx.outputs.length); - this.prevout.hash = tx.hash('hex'); - this.prevout.index = index; - return this; -}; + /** + * Inject properties from transaction. + * @private + * @param {TX} tx + * @param {Number} index + */ + + fromTX(tx, index) { + assert(tx); + assert(typeof index === 'number'); + assert(index >= 0 && index < tx.outputs.length); + this.prevout.hash = tx.hash(); + this.prevout.index = index; + return this; + } -/** - * Instantiate input from tx. - * @param {TX} tx - * @param {Number} index - * @returns {Input} - */ + /** + * Instantiate input from tx. + * @param {TX} tx + * @param {Number} index + * @returns {Input} + */ -Input.fromTX = function fromTX(tx, index) { - return new Input().fromTX(tx, index); -}; + static fromTX(tx, index) { + return new this().fromTX(tx, index); + } -/** - * Test an object to see if it is an Input. - * @param {Object} obj - * @returns {Boolean} - */ + /** + * Test an object to see if it is an Input. + * @param {Object} obj + * @returns {Boolean} + */ -Input.isInput = function isInput(obj) { - return obj instanceof Input; -}; + static isInput(obj) { + return obj instanceof Input; + } +} /* * Expose diff --git a/lib/primitives/invitem.js b/lib/primitives/invitem.js index 74d9a0663..e00f59a5a 100644 --- a/lib/primitives/invitem.js +++ b/lib/primitives/invitem.js @@ -7,183 +7,193 @@ 'use strict'; -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); +const bio = require('bufio'); const util = require('../utils/util'); /** * Inv Item * @alias module:primitives.InvItem * @constructor - * @param {Number} type - * @param {Hash} hash * @property {InvType} type * @property {Hash} hash */ -function InvItem(type, hash) { - if (!(this instanceof InvItem)) - return new InvItem(type, hash); +class InvItem { + /** + * Create an inv item. + * @constructor + * @param {Number} type + * @param {Hash} hash + */ - this.type = type; - this.hash = hash; -} - -/** - * Inv types. - * @enum {Number} - * @default - */ - -InvItem.types = { - ERROR: 0, - TX: 1, - BLOCK: 2, - FILTERED_BLOCK: 3, - WITNESS_TX: 1 | (1 << 30), - WITNESS_BLOCK: 2 | (1 << 30), - WITNESS_FILTERED_BLOCK: 3 | (1 << 30), - CMPCT_BLOCK: 4 -}; - -/** - * Inv types by value. - * @const {RevMap} - */ + constructor(type, hash) { + this.type = type; + this.hash = hash; + } -InvItem.typesByVal = util.reverse(InvItem.types); + /** + * Write inv item to buffer writer. + * @param {BufferWriter} bw + */ -/** - * Witness bit for inv types. - * @const {Number} - * @default - */ + getSize() { + return 36; + } -InvItem.WITNESS_FLAG = 1 << 30; + /** + * Write inv item to buffer writer. + * @param {BufferWriter} bw + */ -/** - * Write inv item to buffer writer. - * @param {BufferWriter} bw - */ + toWriter(bw) { + bw.writeU32(this.type); + bw.writeHash(this.hash); + return bw; + } -InvItem.prototype.getSize = function getSize() { - return 36; -}; + /** + * Serialize inv item. + * @returns {Buffer} + */ -/** - * Write inv item to buffer writer. - * @param {BufferWriter} bw - */ + toRaw() { + return this.toWriter(bio.write(36)).render(); + } -InvItem.prototype.toWriter = function toWriter(bw) { - bw.writeU32(this.type); - bw.writeHash(this.hash); - return bw; -}; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -/** - * Serialize inv item. - * @returns {Buffer} - */ + fromReader(br) { + this.type = br.readU32(); + this.hash = br.readHash(); + return this; + } -InvItem.prototype.toRaw = function toRaw() { - return this.toWriter(new StaticWriter(36)).render(); -}; + /** + * Inject properties from serialized data. + * @param {Buffer} data + */ -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -InvItem.prototype.fromReader = function fromReader(br) { - this.type = br.readU32(); - this.hash = br.readHash('hex'); - return this; -}; + /** + * Instantiate inv item from buffer reader. + * @param {BufferReader} br + * @returns {InvItem} + */ -/** - * Inject properties from serialized data. - * @param {Buffer} data - */ + static fromReader(br) { + return new this().fromReader(br); + } -InvItem.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + /** + * Instantiate inv item from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {InvItem} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } -/** - * Instantiate inv item from buffer reader. - * @param {BufferReader} br - * @returns {InvItem} - */ + /** + * Test whether the inv item is a block. + * @returns {Boolean} + */ + + isBlock() { + switch (this.type) { + case InvItem.types.BLOCK: + case InvItem.types.WITNESS_BLOCK: + case InvItem.types.FILTERED_BLOCK: + case InvItem.types.WITNESS_FILTERED_BLOCK: + case InvItem.types.CMPCT_BLOCK: + return true; + default: + return false; + } + } -InvItem.fromReader = function fromReader(br) { - return new InvItem().fromReader(br); -}; + /** + * Test whether the inv item is a tx. + * @returns {Boolean} + */ + + isTX() { + switch (this.type) { + case InvItem.types.TX: + case InvItem.types.WITNESS_TX: + return true; + default: + return false; + } + } -/** - * Instantiate inv item from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {InvItem} - */ + /** + * Test whether the inv item has the witness bit set. + * @returns {Boolean} + */ -InvItem.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new InvItem().fromRaw(data); -}; + hasWitness() { + return (this.type & InvItem.WITNESS_FLAG) !== 0; + } -/** - * Test whether the inv item is a block. - * @returns {Boolean} - */ + /** + * Get little-endian hash. + * @returns {Hash} + */ -InvItem.prototype.isBlock = function isBlock() { - switch (this.type) { - case InvItem.types.BLOCK: - case InvItem.types.WITNESS_BLOCK: - case InvItem.types.FILTERED_BLOCK: - case InvItem.types.WITNESS_FILTERED_BLOCK: - case InvItem.types.CMPCT_BLOCK: - return true; - default: - return false; + rhash() { + return util.revHex(this.hash); } -}; +} /** - * Test whether the inv item is a tx. - * @returns {Boolean} + * Inv types. + * @enum {Number} + * @default */ -InvItem.prototype.isTX = function isTX() { - switch (this.type) { - case InvItem.types.TX: - case InvItem.types.WITNESS_TX: - return true; - default: - return false; - } +InvItem.types = { + TX: 1, + BLOCK: 2, + FILTERED_BLOCK: 3, + CMPCT_BLOCK: 4, + WITNESS_TX: 1 | (1 << 30), + WITNESS_BLOCK: 2 | (1 << 30), + WITNESS_FILTERED_BLOCK: 3 | (1 << 30) }; /** - * Test whether the inv item has the witness bit set. - * @returns {Boolean} + * Inv types by value. + * @const {Object} */ -InvItem.prototype.hasWitness = function hasWitness() { - return (this.type & InvItem.WITNESS_FLAG) !== 0; +InvItem.typesByVal = { + 1: 'TX', + 2: 'BLOCK', + 3: 'FILTERED_BLOCK', + 4: 'CMPCT_BLOCK', + [1 | (1 << 30)]: 'WITNESS_TX', + [2 | (1 << 30)]: 'WITNESS_BLOCK', + [3 | (1 << 30)]: 'WITNESS_FILTERED_BLOCK' }; /** - * Get little-endian hash. - * @returns {Hash} + * Witness bit for inv types. + * @const {Number} + * @default */ -InvItem.prototype.rhash = function rhash() { - return util.revHex(this.hash); -}; +InvItem.WITNESS_FLAG = 1 << 30; /* * Expose diff --git a/lib/primitives/keyring.js b/lib/primitives/keyring.js index aeea5fc58..de5588051 100644 --- a/lib/primitives/keyring.js +++ b/lib/primitives/keyring.js @@ -7,951 +7,945 @@ 'use strict'; -const assert = require('assert'); -const encoding = require('../utils/encoding'); -const digest = require('../crypto/digest'); +const assert = require('bsert'); +const {base58} = require('bstring'); +const bio = require('bufio'); +const hash160 = require('bcrypto/lib/hash160'); +const hash256 = require('bcrypto/lib/hash256'); const Network = require('../protocol/network'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); -const base58 = require('../utils/base58'); const Script = require('../script/script'); const Address = require('./address'); const Output = require('./output'); -const secp256k1 = require('../crypto/secp256k1'); +const secp256k1 = require('bcrypto/lib/secp256k1'); +const {encoding} = bio; +const {inspectSymbol} = require('../utils'); -/** - * Represents a key ring which amounts to an address. - * @alias module:primitives.KeyRing - * @constructor - * @param {Object} options - * @param {Network} network +/* + * Constants */ -function KeyRing(options, network) { - if (!(this instanceof KeyRing)) - return new KeyRing(options, network); - - this.network = Network.primary; - this.witness = false; - this.nested = false; - this.publicKey = encoding.ZERO_KEY; - this.privateKey = null; - this.script = null; - - this._keyHash = null; - this._keyAddress = null; - this._program = null; - this._nestedHash = null; - this._nestedAddress = null; - this._scriptHash160 = null; - this._scriptHash256 = null; - this._scriptAddress = null; - - if (options) - this.fromOptions(options, network); -} +const ZERO_KEY = Buffer.alloc(33, 0x00); /** - * Inject properties from options object. - * @private - * @param {Object} options + * Key Ring + * Represents a key ring which amounts to an address. + * @alias module:primitives.KeyRing */ -KeyRing.prototype.fromOptions = function fromOptions(options, network) { - if (!network) - network = options.network; - - let key = toKey(options); - - if (options.witness != null) { - assert(typeof options.witness === 'boolean'); - this.witness = options.witness; +class KeyRing { + /** + * Create a key ring. + * @constructor + * @param {Object} options + */ + + constructor(options) { + this.witness = false; + this.nested = false; + this.publicKey = ZERO_KEY; + this.privateKey = null; + this.script = null; + + this._keyHash = null; + this._keyAddress = null; + this._program = null; + this._nestedHash = null; + this._nestedAddress = null; + this._scriptHash160 = null; + this._scriptHash256 = null; + this._scriptAddress = null; + + if (options) + this.fromOptions(options); } - if (options.nested != null) { - assert(typeof options.nested === 'boolean'); - this.nested = options.nested; - } + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ - if (Buffer.isBuffer(key)) - return this.fromKey(key, network); + fromOptions(options) { + let key = toKey(options); - key = toKey(options.key); + if (options.witness != null) { + assert(typeof options.witness === 'boolean'); + this.witness = options.witness; + } - if (options.publicKey) - key = toKey(options.publicKey); + if (options.nested != null) { + assert(typeof options.nested === 'boolean'); + this.nested = options.nested; + } - if (options.privateKey) - key = toKey(options.privateKey); + if (Buffer.isBuffer(key)) + return this.fromKey(key); - const script = options.script; - const compress = options.compressed; + key = toKey(options.key); - if (script) - return this.fromScript(key, script, compress, network); + if (options.publicKey) + key = toKey(options.publicKey); - return this.fromKey(key, compress, network); -}; + if (options.privateKey) + key = toKey(options.privateKey); -/** - * Instantiate key ring from options. - * @param {Object} options - * @returns {KeyRing} - */ + const script = options.script; + const compress = options.compressed; -KeyRing.fromOptions = function fromOptions(options) { - return new KeyRing().fromOptions(options); -}; + if (script) + return this.fromScript(key, script, compress); -/** - * Clear cached key/script hashes. - */ + return this.fromKey(key, compress); + } -KeyRing.prototype.refresh = function refresh() { - this._keyHash = null; - this._keyAddress = null; - this._program = null; - this._nestedHash = null; - this._nestedAddress = null; - this._scriptHash160 = null; - this._scriptHash256 = null; - this._scriptAddress = null; -}; + /** + * Instantiate key ring from options. + * @param {Object} options + * @returns {KeyRing} + */ -/** - * Inject data from private key. - * @private - * @param {Buffer} key - * @param {Boolean?} compress - * @param {(NetworkType|Network)?} network - */ - -KeyRing.prototype.fromPrivate = function fromPrivate(key, compress, network) { - assert(Buffer.isBuffer(key), 'Private key must be a buffer.'); - assert(secp256k1.privateKeyVerify(key), 'Not a valid private key.'); + static fromOptions(options) { + return new this().fromOptions(options); + } - if (typeof compress !== 'boolean') { - network = compress; - compress = null; + /** + * Clear cached key/script hashes. + */ + + refresh() { + this._keyHash = null; + this._keyAddress = null; + this._program = null; + this._nestedHash = null; + this._nestedAddress = null; + this._scriptHash160 = null; + this._scriptHash256 = null; + this._scriptAddress = null; } - this.network = Network.get(network); - this.privateKey = key; - this.publicKey = secp256k1.publicKeyCreate(key, compress !== false); + /** + * Inject data from private key. + * @private + * @param {Buffer} key + * @param {Boolean?} compress + */ - return this; -}; + fromPrivate(key, compress) { + assert(Buffer.isBuffer(key), 'Private key must be a buffer.'); + assert(secp256k1.privateKeyVerify(key), 'Not a valid private key.'); -/** - * Instantiate keyring from a private key. - * @param {Buffer} key - * @param {Boolean?} compress - * @param {(NetworkType|Network)?} network - * @returns {KeyRing} - */ + this.privateKey = key; + this.publicKey = secp256k1.publicKeyCreate(key, compress !== false); -KeyRing.fromPrivate = function fromPrivate(key, compress, network) { - return new KeyRing().fromPrivate(key, compress, network); -}; + return this; + } -/** - * Inject data from public key. - * @private - * @param {Buffer} key - * @param {(NetworkType|Network)?} network - */ + /** + * Instantiate keyring from a private key. + * @param {Buffer} key + * @param {Boolean?} compress + * @returns {KeyRing} + */ -KeyRing.prototype.fromPublic = function fromPublic(key, network) { - assert(Buffer.isBuffer(key), 'Public key must be a buffer.'); - assert(secp256k1.publicKeyVerify(key), 'Not a valid public key.'); - this.network = Network.get(network); - this.publicKey = key; - return this; -}; + static fromPrivate(key, compress) { + return new this().fromPrivate(key, compress); + } -/** - * Generate a keyring. - * @private - * @param {Boolean?} compress - * @param {(Network|NetworkType)?} network - * @returns {KeyRing} - */ + /** + * Inject data from public key. + * @private + * @param {Buffer} key + */ -KeyRing.prototype.generate = function generate(compress, network) { - if (typeof compress !== 'boolean') { - network = compress; - compress = null; + fromPublic(key) { + assert(Buffer.isBuffer(key), 'Public key must be a buffer.'); + assert(secp256k1.publicKeyVerify(key), 'Not a valid public key.'); + this.publicKey = key; + return this; } - const key = secp256k1.generatePrivateKey(); + /** + * Generate a keyring. + * @private + * @param {Boolean?} compress + * @returns {KeyRing} + */ - return this.fromKey(key, compress, network); -}; + generate(compress) { + const key = secp256k1.privateKeyGenerate(); + return this.fromKey(key, compress); + } -/** - * Generate a keyring. - * @param {Boolean?} compress - * @param {(Network|NetworkType)?} network - * @returns {KeyRing} - */ + /** + * Generate a keyring. + * @param {Boolean?} compress + * @returns {KeyRing} + */ -KeyRing.generate = function generate(compress, network) { - return new KeyRing().generate(compress, network); -}; + static generate(compress) { + return new this().generate(compress); + } -/** - * Instantiate keyring from a public key. - * @param {Buffer} publicKey - * @param {(NetworkType|Network)?} network - * @returns {KeyRing} - */ + /** + * Instantiate keyring from a public key. + * @param {Buffer} publicKey + * @returns {KeyRing} + */ -KeyRing.fromPublic = function fromPublic(key, network) { - return new KeyRing().fromPublic(key, network); -}; + static fromPublic(key) { + return new this().fromPublic(key); + } -/** - * Inject data from public key. - * @private - * @param {Buffer} privateKey - * @param {Boolean?} compress - * @param {(NetworkType|Network)?} network - */ + /** + * Inject data from public key. + * @private + * @param {Buffer} privateKey + * @param {Boolean?} compress + */ -KeyRing.prototype.fromKey = function fromKey(key, compress, network) { - assert(Buffer.isBuffer(key), 'Key must be a buffer.'); + fromKey(key, compress) { + assert(Buffer.isBuffer(key), 'Key must be a buffer.'); - if (typeof compress !== 'boolean') { - network = compress; - compress = null; - } + if (key.length === 32) + return this.fromPrivate(key, compress !== false); - if (key.length === 32) - return this.fromPrivate(key, compress !== false, network); + return this.fromPublic(key); + } - return this.fromPublic(key, network); -}; + /** + * Instantiate keyring from a public key. + * @param {Buffer} publicKey + * @param {Boolean?} compress + * @returns {KeyRing} + */ -/** - * Instantiate keyring from a public key. - * @param {Buffer} publicKey - * @param {Boolean?} compress - * @param {(NetworkType|Network)?} network - * @returns {KeyRing} - */ + static fromKey(key, compress) { + return new this().fromKey(key, compress); + } -KeyRing.fromKey = function fromKey(key, compress, network) { - return new KeyRing().fromKey(key, compress, network); -}; + /** + * Inject data from script. + * @private + * @param {Buffer} key + * @param {Script} script + * @param {Boolean?} compress + */ -/** - * Inject data from script. - * @private - * @param {Buffer} key - * @param {Script} script - * @param {Boolean?} compress - * @param {(NetworkType|Network)?} network - */ + fromScript(key, script, compress) { + assert(script instanceof Script, 'Non-script passed into KeyRing.'); -KeyRing.prototype.fromScript = function fromScript(key, script, compress, network) { - assert(script instanceof Script, 'Non-script passed into KeyRing.'); + this.fromKey(key, compress); + this.script = script; - if (typeof compress !== 'boolean') { - network = compress; - compress = null; + return this; } - this.fromKey(key, compress, network); - this.script = script; - - return this; -}; + /** + * Instantiate keyring from script. + * @param {Buffer} key + * @param {Script} script + * @param {Boolean?} compress + * @returns {KeyRing} + */ -/** - * Instantiate keyring from script. - * @param {Buffer} key - * @param {Script} script - * @param {Boolean?} compress - * @param {(NetworkType|Network)?} network - * @returns {KeyRing} - */ + static fromScript(key, script, compress) { + return new this().fromScript(key, script, compress); + } -KeyRing.fromScript = function fromScript(key, script, compress, network) { - return new KeyRing().fromScript(key, script, compress, network); -}; + /** + * Get ith public key from multisig script. + * @private + * @param {Script} script + * @param {Number} i + * @returns {KeyRing} + */ -/** - * Calculate WIF serialization size. - * @returns {Number} - */ + fromMultisigScript(script, i) { + assert(script instanceof Script, 'Non-script passed.'); + assert(script.isMultisig(), 'Script must be multisig'); -KeyRing.prototype.getSecretSize = function getSecretSize() { - let size = 0; + const n = script.getSmall(-2); + assert(i >= 1 && i <= n, 'Requested `i`th key, `n` available'); - size += 1; - size += this.privateKey.length; + this.fromKey(script.code[i].toData()); - if (this.publicKey.length === 33) - size += 1; + return this; + } - size += 4; + /** + * Instantiate keyring from ith key in multisig script. + * @param {Script} script + * @param {Number} i + * @returns {KeyRing} + */ - return size; -}; + static fromMultisigScript(script, i) { + return new this().fromMultisigScript(script, i); + } -/** - * Convert key to a CBitcoinSecret. - * @param {(Network|NetworkType)?} network - * @returns {Base58String} - */ + /** + * Calculate WIF serialization size. + * @returns {Number} + */ -KeyRing.prototype.toSecret = function toSecret(network) { - const size = this.getSecretSize(); - const bw = new StaticWriter(size); + getSecretSize() { + let size = 0; - assert(this.privateKey, 'Cannot serialize without private key.'); + size += 1; + size += this.privateKey.length; - if (!network) - network = this.network; + if (this.publicKey.length === 33) + size += 1; - network = Network.get(network); + size += 4; - bw.writeU8(network.keyPrefix.privkey); - bw.writeBytes(this.privateKey); + return size; + } - if (this.publicKey.length === 33) - bw.writeU8(1); + /** + * Convert key to a CBitcoinSecret. + * @param {(Network|NetworkType)?} network + * @returns {Base58String} + */ - bw.writeChecksum(); + toSecret(network) { + const size = this.getSecretSize(); + const bw = bio.write(size); - return base58.encode(bw.render()); -}; + assert(this.privateKey, 'Cannot serialize without private key.'); -/** - * Inject properties from serialized CBitcoinSecret. - * @private - * @param {Base58String} secret - * @param {(Network|NetworkType)?} network - */ + network = Network.get(network); -KeyRing.prototype.fromSecret = function fromSecret(data, network) { - const br = new BufferReader(base58.decode(data), true); + bw.writeU8(network.keyPrefix.privkey); + bw.writeBytes(this.privateKey); - const version = br.readU8(); + if (this.publicKey.length === 33) + bw.writeU8(1); - network = Network.fromWIF(version, network); + bw.writeChecksum(hash256.digest); - const key = br.readBytes(32); + return base58.encode(bw.render()); + } - let compress = false; + /** + * Inject properties from serialized CBitcoinSecret. + * @private + * @param {Base58String} secret + * @param {(Network|NetworkType)?} network + */ - if (br.left() > 4) { - assert(br.readU8() === 1, 'Bad compression flag.'); - compress = true; - } + fromSecret(data, network) { + const br = bio.read(base58.decode(data), true); - br.verifyChecksum(); + const version = br.readU8(); - return this.fromPrivate(key, compress, network); -}; + Network.fromWIF(version, network); -/** - * Instantiate a keyring from a serialized CBitcoinSecret. - * @param {Base58String} secret - * @param {(Network|NetworkType)?} network - * @returns {KeyRing} - */ + const key = br.readBytes(32); -KeyRing.fromSecret = function fromSecret(data, network) { - return new KeyRing().fromSecret(data, network); -}; + let compress = false; -/** - * Get private key. - * @param {String?} enc - Can be `"hex"`, `"base58"`, or `null`. - * @returns {Buffer} Private key. - */ + if (br.left() > 4) { + assert(br.readU8() === 1, 'Bad compression flag.'); + compress = true; + } -KeyRing.prototype.getPrivateKey = function getPrivateKey(enc) { - if (!this.privateKey) - return null; + br.verifyChecksum(hash256.digest); - if (enc === 'base58') - return this.toSecret(); + return this.fromPrivate(key, compress); + } - if (enc === 'hex') - return this.privateKey.toString('hex'); + /** + * Instantiate a keyring from a serialized CBitcoinSecret. + * @param {Base58String} secret + * @param {(Network|NetworkType)?} network + * @returns {KeyRing} + */ - return this.privateKey; -}; + static fromSecret(data, network) { + return new this().fromSecret(data, network); + } -/** - * Get public key. - * @param {String?} enc - `"hex"` or `null`. - * @returns {Buffer} - */ + /** + * Get private key. + * @param {String?} enc - Can be `"hex"`, `"base58"`, or `null`. + * @returns {Buffer} Private key. + */ -KeyRing.prototype.getPublicKey = function getPublicKey(enc) { - if (enc === 'base58') - return base58.encode(this.publicKey); + getPrivateKey(enc, network) { + if (!this.privateKey) + return null; - if (enc === 'hex') - return this.publicKey.toString('hex'); + if (enc === 'base58') + return this.toSecret(network); - return this.publicKey; -}; + if (enc === 'hex') + return this.privateKey.toString('hex'); -/** - * Get redeem script. - * @returns {Script} - */ + return this.privateKey; + } -KeyRing.prototype.getScript = function getScript() { - return this.script; -}; + /** + * Get public key. + * @param {String?} enc - `"hex"` or `null`. + * @returns {Buffer} + */ -/** - * Get witness program. - * @returns {Buffer} - */ + getPublicKey(enc) { + if (enc === 'base58') + return base58.encode(this.publicKey); -KeyRing.prototype.getProgram = function getProgram() { - if (!this.witness) - return null; + if (enc === 'hex') + return this.publicKey.toString('hex'); - if (!this._program) { - let program; - if (!this.script) { - const hash = digest.hash160(this.publicKey); - program = Script.fromProgram(0, hash); - } else { - const hash = this.script.sha256(); - program = Script.fromProgram(0, hash); - } - this._program = program; + return this.publicKey; } - return this._program; -}; + /** + * Get redeem script. + * @returns {Script} + */ -/** - * Get address' ripemd160 program scripthash - * (for witness programs behind a scripthash). - * @param {String?} enc - `"hex"` or `null`. - * @returns {Buffer} - */ + getScript() { + return this.script; + } -KeyRing.prototype.getNestedHash = function getNestedHash(enc) { - if (!this.witness) - return null; + /** + * Get witness program. + * @returns {Buffer} + */ + + getProgram() { + if (!this.witness) + return null; + + if (!this._program) { + let program; + if (!this.script) { + const hash = hash160.digest(this.publicKey); + program = Script.fromProgram(0, hash); + } else { + const hash = this.script.sha256(); + program = Script.fromProgram(0, hash); + } + this._program = program; + } - if (!this._nestedHash) - this._nestedHash = this.getProgram().hash160(); + return this._program; + } - return enc === 'hex' - ? this._nestedHash.toString('hex') - : this._nestedHash; -}; + /** + * Get address' ripemd160 program scripthash + * (for witness programs behind a scripthash). + * @param {String?} enc - `"hex"` or `null`. + * @returns {Buffer} + */ -/** - * Get address' scripthash address for witness program. - * @param {String?} enc - `"base58"` or `null`. - * @returns {Address|Base58Address} - */ + getNestedHash(enc) { + if (!this.witness) + return null; -KeyRing.prototype.getNestedAddress = function getNestedAddress(enc) { - if (!this.witness) - return null; + if (!this._nestedHash) + this._nestedHash = this.getProgram().hash160(); - if (!this._nestedAddress) { - const hash = this.getNestedHash(); - const addr = Address.fromScripthash(hash, this.network); - this._nestedAddress = addr; + return enc === 'hex' + ? this._nestedHash.toString('hex') + : this._nestedHash; } - if (enc === 'base58') - return this._nestedAddress.toBase58(); + /** + * Get address' scripthash address for witness program. + * @param {String?} enc - `"base58"` or `null`. + * @returns {Address|AddressString} + */ - if (enc === 'string') - return this._nestedAddress.toString(); + getNestedAddress(enc, network) { + if (!this.witness) + return null; - return this._nestedAddress; -}; + if (!this._nestedAddress) { + const hash = this.getNestedHash(); + const addr = Address.fromScripthash(hash); + this._nestedAddress = addr; + } -/** - * Get scripthash. - * @param {String?} enc - `"hex"` or `null`. - * @returns {Buffer} - */ + if (enc === 'base58') + return this._nestedAddress.toBase58(network); -KeyRing.prototype.getScriptHash = function getScriptHash(enc) { - if (this.witness) - return this.getScriptHash256(enc); - return this.getScriptHash160(enc); -}; + if (enc === 'string') + return this._nestedAddress.toString(network); -/** - * Get ripemd160 scripthash. - * @param {String?} enc - `"hex"` or `null`. - * @returns {Buffer} - */ + return this._nestedAddress; + } -KeyRing.prototype.getScriptHash160 = function getScriptHash160(enc) { - if (!this.script) - return null; + /** + * Get scripthash. + * @param {String?} enc - `"hex"` or `null`. + * @returns {Buffer} + */ - if (!this._scriptHash160) - this._scriptHash160 = this.script.hash160(); + getScriptHash(enc) { + if (this.witness) + return this.getScriptHash256(enc); + return this.getScriptHash160(enc); + } - return enc === 'hex' - ? this._scriptHash160.toString('hex') - : this._scriptHash160; -}; + /** + * Get ripemd160 scripthash. + * @param {String?} enc - `"hex"` or `null`. + * @returns {Buffer} + */ -/** - * Get sha256 scripthash. - * @param {String?} enc - `"hex"` or `null`. - * @returns {Buffer} - */ + getScriptHash160(enc) { + if (!this.script) + return null; -KeyRing.prototype.getScriptHash256 = function getScriptHash256(enc) { - if (!this.script) - return null; + if (!this._scriptHash160) + this._scriptHash160 = this.script.hash160(); - if (!this._scriptHash256) - this._scriptHash256 = this.script.sha256(); + return enc === 'hex' + ? this._scriptHash160.toString('hex') + : this._scriptHash160; + } - return enc === 'hex' - ? this._scriptHash256.toString('hex') - : this._scriptHash256; -}; + /** + * Get sha256 scripthash. + * @param {String?} enc - `"hex"` or `null`. + * @returns {Buffer} + */ -/** - * Get scripthash address. - * @param {String?} enc - `"base58"` or `null`. - * @returns {Address|Base58Address} - */ + getScriptHash256(enc) { + if (!this.script) + return null; -KeyRing.prototype.getScriptAddress = function getScriptAddress(enc) { - if (!this.script) - return null; + if (!this._scriptHash256) + this._scriptHash256 = this.script.sha256(); - if (!this._scriptAddress) { - let addr; - if (this.witness) { - const hash = this.getScriptHash256(); - addr = Address.fromWitnessScripthash(hash, this.network); - } else { - const hash = this.getScriptHash160(); - addr = Address.fromScripthash(hash, this.network); - } - this._scriptAddress = addr; + return enc === 'hex' + ? this._scriptHash256.toString('hex') + : this._scriptHash256; } - if (enc === 'base58') - return this._scriptAddress.toBase58(); + /** + * Get scripthash address. + * @param {String?} enc - `"base58"` or `null`. + * @returns {Address|AddressString} + */ + + getScriptAddress(enc, network) { + if (!this.script) + return null; + + if (!this._scriptAddress) { + let addr; + if (this.witness) { + const hash = this.getScriptHash256(); + addr = Address.fromWitnessScripthash(hash); + } else { + const hash = this.getScriptHash160(); + addr = Address.fromScripthash(hash); + } + this._scriptAddress = addr; + } - if (enc === 'string') - return this._scriptAddress.toString(); + if (enc === 'base58') + return this._scriptAddress.toBase58(network); - return this._scriptAddress; -}; + if (enc === 'string') + return this._scriptAddress.toString(network); -/** - * Get public key hash. - * @param {String?} enc - `"hex"` or `null`. - * @returns {Buffer} - */ + return this._scriptAddress; + } -KeyRing.prototype.getKeyHash = function getKeyHash(enc) { - if (!this._keyHash) - this._keyHash = digest.hash160(this.publicKey); + /** + * Get public key hash. + * @param {String?} enc - `"hex"` or `null`. + * @returns {Buffer} + */ - return enc === 'hex' - ? this._keyHash.toString('hex') - : this._keyHash; -}; + getKeyHash(enc) { + if (!this._keyHash) + this._keyHash = hash160.digest(this.publicKey); -/** - * Get pubkeyhash address. - * @param {String?} enc - `"base58"` or `null`. - * @returns {Address|Base58Address} - */ + return enc === 'hex' + ? this._keyHash.toString('hex') + : this._keyHash; + } -KeyRing.prototype.getKeyAddress = function getKeyAddress(enc) { - if (!this._keyAddress) { - const hash = this.getKeyHash(); + /** + * Get pubkeyhash address. + * @param {String?} enc - `"base58"` or `null`. + * @returns {Address|AddressString} + */ - let addr; - if (this.witness) - addr = Address.fromWitnessPubkeyhash(hash, this.network); - else - addr = Address.fromPubkeyhash(hash, this.network); + getKeyAddress(enc, network) { + if (!this._keyAddress) { + const hash = this.getKeyHash(); - this._keyAddress = addr; - } + let addr; + if (this.witness) + addr = Address.fromWitnessPubkeyhash(hash); + else + addr = Address.fromPubkeyhash(hash); - if (enc === 'base58') - return this._keyAddress.toBase58(); + this._keyAddress = addr; + } - if (enc === 'string') - return this._keyAddress.toString(); + if (enc === 'base58') + return this._keyAddress.toBase58(network); - return this._keyAddress; -}; + if (enc === 'string') + return this._keyAddress.toString(network); -/** - * Get hash. - * @param {String?} enc - `"hex"` or `null`. - * @returns {Buffer} - */ + return this._keyAddress; + } -KeyRing.prototype.getHash = function getHash(enc) { - if (this.nested) - return this.getNestedHash(enc); + /** + * Get hash. + * @param {String?} enc - `"hex"` or `null`. + * @returns {Buffer} + */ - if (this.script) - return this.getScriptHash(enc); + getHash(enc) { + if (this.nested) + return this.getNestedHash(enc); - return this.getKeyHash(enc); -}; + if (this.script) + return this.getScriptHash(enc); -/** - * Get base58 address. - * @param {String?} enc - `"base58"` or `null`. - * @returns {Address|Base58Address} - */ + return this.getKeyHash(enc); + } -KeyRing.prototype.getAddress = function getAddress(enc) { - if (this.nested) - return this.getNestedAddress(enc); + /** + * Get base58 address. + * @param {String?} enc - `"base58"` or `null`. + * @returns {Address|AddressString} + */ - if (this.script) - return this.getScriptAddress(enc); + getAddress(enc, network) { + if (this.nested) + return this.getNestedAddress(enc, network); - return this.getKeyAddress(enc); -}; + if (this.script) + return this.getScriptAddress(enc, network); -/** - * Test an address hash against hash and program hash. - * @param {Buffer} hash - * @returns {Boolean} - */ + return this.getKeyAddress(enc, network); + } -KeyRing.prototype.ownHash = function ownHash(hash) { - if (!hash) - return false; + /** + * Test an address hash against hash and program hash. + * @param {Buffer} hash + * @returns {Boolean} + */ - if (hash.equals(this.getKeyHash())) - return true; + ownHash(hash) { + if (!hash) + return false; - if (this.script) { - if (hash.equals(this.getScriptHash())) + if (hash.equals(this.getKeyHash())) return true; - } - if (this.witness) { - if (hash.equals(this.getNestedHash())) - return true; + if (this.script) { + if (hash.equals(this.getScriptHash())) + return true; + } + + if (this.witness) { + if (hash.equals(this.getNestedHash())) + return true; + } + + return false; } - return false; -}; + /** + * Check whether transaction output belongs to this address. + * @param {TX|Output} tx - Transaction or Output. + * @param {Number?} index - Output index. + * @returns {Boolean} + */ -/** - * Check whether transaction output belongs to this address. - * @param {TX|Output} tx - Transaction or Output. - * @param {Number?} index - Output index. - * @returns {Boolean} - */ + ownOutput(tx, index) { + let output; -KeyRing.prototype.ownOutput = function ownOutput(tx, index) { - let output; + if (tx instanceof Output) { + output = tx; + } else { + output = tx.outputs[index]; + assert(output, 'Output does not exist.'); + } - if (tx instanceof Output) { - output = tx; - } else { - output = tx.outputs[index]; - assert(output, 'Output does not exist.'); + return this.ownHash(output.getHash()); } - return this.ownHash(output.getHash()); -}; + /** + * Test a hash against script hashes to + * find the correct redeem script, if any. + * @param {Buffer} hash + * @returns {Script|null} + */ -/** - * Test a hash against script hashes to - * find the correct redeem script, if any. - * @param {Buffer} hash - * @returns {Script|null} - */ + getRedeem(hash) { + if (this.witness) { + if (hash.equals(this.getNestedHash())) + return this.getProgram(); + } -KeyRing.prototype.getRedeem = function getRedeem(hash) { - if (this.witness) { - if (hash.equals(this.getNestedHash())) - return this.getProgram(); - } + if (this.script) { + if (hash.equals(this.getScriptHash160())) + return this.script; - if (this.script) { - if (hash.equals(this.getScriptHash160())) - return this.script; + if (hash.equals(this.getScriptHash256())) + return this.script; + } - if (hash.equals(this.getScriptHash256())) - return this.script; + return null; } - return null; -}; + /** + * Sign a message. + * @param {Buffer} msg + * @returns {Buffer} Signature in DER format. + */ -/** - * Sign a message. - * @param {Buffer} msg - * @returns {Buffer} Signature in DER format. - */ - -KeyRing.prototype.sign = function sign(msg) { - assert(this.privateKey, 'Cannot sign without private key.'); - return secp256k1.sign(msg, this.privateKey); -}; - -/** - * Verify a message. - * @param {Buffer} msg - * @param {Buffer} sig - Signature in DER format. - * @returns {Boolean} - */ + sign(msg) { + assert(this.privateKey, 'Cannot sign without private key.'); + return secp256k1.signDER(msg, this.privateKey); + } -KeyRing.prototype.verify = function verify(msg, sig) { - return secp256k1.verify(msg, sig, this.publicKey); -}; + /** + * Verify a message. + * @param {Buffer} msg + * @param {Buffer} sig - Signature in DER format. + * @returns {Boolean} + */ -/** - * Get witness program version. - * @returns {Number} - */ + verify(msg, sig) { + return secp256k1.verifyDER(msg, sig, this.publicKey); + } -KeyRing.prototype.getVersion = function getVersion() { - if (!this.witness) - return -1; + /** + * Get witness program version. + * @returns {Number} + */ - if (this.nested) - return -1; + getVersion() { + if (!this.witness) + return -1; - return 0; -}; + if (this.nested) + return -1; -/** - * Get address type. - * @returns {ScriptType} - */ + return 0; + } -KeyRing.prototype.getType = function getType() { - if (this.nested) - return Address.types.SCRIPTHASH; + /** + * Get address type. + * @returns {ScriptType} + */ - if (this.witness) - return Address.types.WITNESS; + getType() { + if (this.nested) + return Address.types.SCRIPTHASH; - if (this.script) - return Address.types.SCRIPTHASH; + if (this.witness) + return Address.types.WITNESS; - return Address.types.PUBKEYHASH; -}; + if (this.script) + return Address.types.SCRIPTHASH; -/** - * Inspect keyring. - * @returns {Object} - */ + return Address.types.PUBKEYHASH; + } -KeyRing.prototype.inspect = function inspect() { - return this.toJSON(); -}; + /** + * Inspect keyring. + * @returns {Object} + */ -/** - * Convert an KeyRing to a more json-friendly object. - * @returns {Object} - */ + [inspectSymbol]() { + return this.toJSON(); + } -KeyRing.prototype.toJSON = function toJSON() { - return { - network: this.network.type, - witness: this.witness, - nested: this.nested, - publicKey: this.publicKey.toString('hex'), - script: this.script ? this.script.toRaw().toString('hex') : null, - program: this.witness ? this.getProgram().toRaw().toString('hex') : null, - type: Address.typesByVal[this.getType()].toLowerCase(), - address: this.getAddress('string') - }; -}; + /** + * Convert an KeyRing to a more json-friendly object. + * @returns {Object} + */ + + toJSON(network) { + return { + witness: this.witness, + nested: this.nested, + publicKey: this.publicKey.toString('hex'), + script: this.script ? this.script.toRaw().toString('hex') : null, + program: this.witness ? this.getProgram().toRaw().toString('hex') : null, + type: Address.typesByVal[this.getType()].toLowerCase(), + address: this.getAddress('string', network) + }; + } -/** - * Inject properties from json object. - * @private - * @param {Object} json - */ + /** + * Inject properties from json object. + * @private + * @param {Object} json + */ -KeyRing.prototype.fromJSON = function fromJSON(json) { - assert(json); - assert(typeof json.network === 'string'); - assert(typeof json.witness === 'boolean'); - assert(typeof json.nested === 'boolean'); - assert(typeof json.publicKey === 'string'); - assert(!json.script || typeof json.script === 'string'); + fromJSON(json) { + assert(json); + assert(typeof json.witness === 'boolean'); + assert(typeof json.nested === 'boolean'); + assert(typeof json.publicKey === 'string'); + assert(!json.script || typeof json.script === 'string'); - this.nework = Network.get(json.network); - this.witness = json.witness; - this.nested = json.nested; - this.publicKey = Buffer.from(json.publicKey, 'hex'); + this.witness = json.witness; + this.nested = json.nested; + this.publicKey = Buffer.from(json.publicKey, 'hex'); - if (json.script) - this.script = Buffer.from(json.script, 'hex'); + if (json.script) + this.script = Buffer.from(json.script, 'hex'); - return this; -}; + return this; + } -/** - * Instantiate an KeyRing from a jsonified transaction object. - * @param {Object} json - The jsonified transaction object. - * @returns {KeyRing} - */ + /** + * Instantiate an KeyRing from a jsonified transaction object. + * @param {Object} json - The jsonified transaction object. + * @returns {KeyRing} + */ -KeyRing.fromJSON = function fromJSON(json) { - return new KeyRing().fromJSON(json); -}; + static fromJSON(json) { + return new this().fromJSON(json); + } -/** - * Calculate serialization size. - * @returns {Number} - */ + /** + * Calculate serialization size. + * @returns {Number} + */ -KeyRing.prototype.getSize = function getSize() { - let size = 0; - size += 1; - if (this.privateKey) { - size += encoding.sizeVarBytes(this.privateKey); + getSize() { + let size = 0; size += 1; - } else { - size += encoding.sizeVarBytes(this.publicKey); + if (this.privateKey) { + size += encoding.sizeVarBytes(this.privateKey); + size += 1; + } else { + size += encoding.sizeVarBytes(this.publicKey); + } + size += this.script ? this.script.getVarSize() : 1; + return size; } - size += this.script ? this.script.getVarSize() : 1; - return size; -}; -/** - * Write the keyring to a buffer writer. - * @param {BufferWriter} bw - */ - -KeyRing.prototype.toWriter = function toWriter(bw) { - let field = 0; + /** + * Write the keyring to a buffer writer. + * @param {BufferWriter} bw + */ - if (this.witness) - field |= 1; + toWriter(bw) { + let field = 0; - if (this.nested) - field |= 2; + if (this.witness) + field |= 1; - bw.writeU8(field); + if (this.nested) + field |= 2; - if (this.privateKey) { - bw.writeVarBytes(this.privateKey); - bw.writeU8(this.publicKey.length === 33); - } else { - bw.writeVarBytes(this.publicKey); - } + bw.writeU8(field); - if (this.script) - bw.writeVarBytes(this.script.toRaw()); - else - bw.writeVarint(0); + if (this.privateKey) { + bw.writeVarBytes(this.privateKey); + bw.writeU8(this.publicKey.length === 33 ? 1 : 0); + } else { + bw.writeVarBytes(this.publicKey); + } - return bw; -}; + if (this.script) + bw.writeVarBytes(this.script.toRaw()); + else + bw.writeVarint(0); -/** - * Serialize the keyring. - * @returns {Buffer} - */ + return bw; + } -KeyRing.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + /** + * Serialize the keyring. + * @returns {Buffer} + */ -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - * @param {Network?} network - */ + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } -KeyRing.prototype.fromReader = function fromReader(br, network) { - this.network = Network.get(network); + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ - const field = br.readU8(); + fromReader(br) { + const field = br.readU8(); - this.witness = (field & 1) !== 0; - this.nested = (field & 2) !== 0; + this.witness = (field & 1) !== 0; + this.nested = (field & 2) !== 0; - const key = br.readVarBytes(); + const key = br.readVarBytes(); - if (key.length === 32) { - const compress = br.readU8() === 1; - this.privateKey = key; - this.publicKey = secp256k1.publicKeyCreate(key, compress); - } else { - this.publicKey = key; - assert(secp256k1.publicKeyVerify(key), 'Invalid public key.'); - } + if (key.length === 32) { + const compress = br.readU8() === 1; + this.privateKey = key; + this.publicKey = secp256k1.publicKeyCreate(key, compress); + } else { + this.publicKey = key; + assert(secp256k1.publicKeyVerify(key), 'Invalid public key.'); + } - const script = br.readVarBytes(); + const script = br.readVarBytes(); - if (script.length > 0) - this.script = Script.fromRaw(script); + if (script.length > 0) + this.script = Script.fromRaw(script); - return this; -}; + return this; + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @param {Network?} network - */ + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -KeyRing.prototype.fromRaw = function fromRaw(data, network) { - return this.fromReader(new BufferReader(data), network); -}; + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -/** - * Instantiate a keyring from buffer reader. - * @param {BufferReader} br - * @returns {KeyRing} - */ + /** + * Instantiate a keyring from buffer reader. + * @param {BufferReader} br + * @returns {KeyRing} + */ -KeyRing.fromReader = function fromReader(br) { - return new KeyRing().fromReader(br); -}; + static fromReader(br) { + return new this().fromReader(br); + } -/** - * Instantiate a keyring from serialized data. - * @param {Buffer} data - * @returns {KeyRing} - */ + /** + * Instantiate a keyring from serialized data. + * @param {Buffer} data + * @returns {KeyRing} + */ -KeyRing.fromRaw = function fromRaw(data) { - return new KeyRing().fromRaw(data); -}; + static fromRaw(data) { + return new this().fromRaw(data); + } -/** - * Test whether an object is a KeyRing. - * @param {Object} obj - * @returns {Boolean} - */ + /** + * Test whether an object is a KeyRing. + * @param {Object} obj + * @returns {Boolean} + */ -KeyRing.isKeyRing = function isKeyRing(obj) { - return obj instanceof KeyRing; -}; + static isKeyRing(obj) { + return obj instanceof KeyRing; + } +} /* * Helpers diff --git a/lib/primitives/memblock.js b/lib/primitives/memblock.js index 075b76101..0ec636559 100644 --- a/lib/primitives/memblock.js +++ b/lib/primitives/memblock.js @@ -7,14 +7,15 @@ 'use strict'; +const bio = require('bufio'); const AbstractBlock = require('./abstractblock'); const Block = require('./block'); const Headers = require('./headers'); const Script = require('../script/script'); -const BufferReader = require('../utils/reader'); const DUMMY = Buffer.alloc(0); /** + * Mem Block * A block object which is essentially a "placeholder" * for a full {@link Block} object. The v8 garbage * collector's head will explode if there is too much @@ -31,188 +32,189 @@ const DUMMY = Buffer.alloc(0); * 500mb of blocks on the js heap would not be a good * thing. * @alias module:primitives.MemBlock - * @constructor - * @param {NakedBlock} options + * @extends AbstractBlock */ -function MemBlock() { - if (!(this instanceof MemBlock)) - return new MemBlock(); +class MemBlock extends AbstractBlock { + /** + * Create a mem block. + * @constructor + */ - AbstractBlock.call(this); + constructor() { + super(); - this._raw = DUMMY; -} - -Object.setPrototypeOf(MemBlock.prototype, AbstractBlock.prototype); - -/** - * Test whether the block is a memblock. - * @returns {Boolean} - */ - -MemBlock.prototype.isMemory = function isMemory() { - return true; -}; + this._raw = DUMMY; + } -/** - * Serialize the block headers. - * @returns {Buffer} - */ + /** + * Test whether the block is a memblock. + * @returns {Boolean} + */ -MemBlock.prototype.toHead = function toHead() { - return this._raw.slice(0, 80); -}; + isMemory() { + return true; + } -/** - * Get the full block size. - * @returns {Number} - */ + /** + * Serialize the block headers. + * @returns {Buffer} + */ -MemBlock.prototype.getSize = function getSize() { - return this._raw.length; -}; + toHead() { + return this._raw.slice(0, 80); + } -/** - * Verify the block. - * @returns {Boolean} - */ + /** + * Get the full block size. + * @returns {Number} + */ -MemBlock.prototype.verifyBody = function verifyBody() { - return true; -}; + getSize() { + return this._raw.length; + } -/** - * Retrieve the coinbase height - * from the coinbase input script. - * @returns {Number} height (-1 if not present). - */ + /** + * Verify the block. + * @returns {Boolean} + */ -MemBlock.prototype.getCoinbaseHeight = function getCoinbaseHeight() { - if (this.version < 2) - return -1; + verifyBody() { + return true; + } - try { - return this.parseCoinbaseHeight(); - } catch (e) { - return -1; + /** + * Retrieve the coinbase height + * from the coinbase input script. + * @returns {Number} height (-1 if not present). + */ + + getCoinbaseHeight() { + if (this.version < 2) + return -1; + + try { + return this.parseCoinbaseHeight(); + } catch (e) { + return -1; + } } -}; -/** - * Parse the coinbase height - * from the coinbase input script. - * @private - * @returns {Number} height (-1 if not present). - */ + /** + * Parse the coinbase height + * from the coinbase input script. + * @private + * @returns {Number} height (-1 if not present). + */ -MemBlock.prototype.parseCoinbaseHeight = function parseCoinbaseHeight() { - const br = new BufferReader(this._raw, true); + parseCoinbaseHeight() { + const br = bio.read(this._raw, true); - br.seek(80); + br.seek(80); - const txCount = br.readVarint(); + const txCount = br.readVarint(); - if (txCount === 0) - return -1; + if (txCount === 0) + return -1; - br.seek(4); + br.seek(4); - let inCount = br.readVarint(); + let inCount = br.readVarint(); - if (inCount === 0) { - if (br.readU8() !== 0) - inCount = br.readVarint(); - } + if (inCount === 0) { + if (br.readU8() !== 0) + inCount = br.readVarint(); + } - if (inCount === 0) - return -1; + if (inCount === 0) + return -1; - br.seek(36); + br.seek(36); - const script = br.readVarBytes(); + const script = br.readVarBytes(); - return Script.getCoinbaseHeight(script); -}; + return Script.getCoinbaseHeight(script); + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -MemBlock.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); + fromRaw(data) { + const br = bio.read(data, true); - this.readHead(br); + this.readHead(br); - this._raw = br.data; + this._raw = br.data; - return this; -}; + return this; + } -/** - * Insantiate a memblock from serialized data. - * @param {Buffer} data - * @returns {MemBlock} - */ + /** + * Insantiate a memblock from serialized data. + * @param {Buffer} data + * @returns {MemBlock} + */ -MemBlock.fromRaw = function fromRaw(data) { - return new MemBlock().fromRaw(data); -}; + static fromRaw(data) { + return new this().fromRaw(data); + } -/** - * Return serialized block data. - * @returns {Buffer} - */ + /** + * Return serialized block data. + * @returns {Buffer} + */ -MemBlock.prototype.toRaw = function toRaw() { - return this._raw; -}; + toRaw() { + return this._raw; + } -/** - * Return serialized block data. - * @returns {Buffer} - */ + /** + * Return serialized block data. + * @returns {Buffer} + */ -MemBlock.prototype.toNormal = function toNormal() { - return this._raw; -}; + toNormal() { + return this._raw; + } -/** - * Parse the serialized block data - * and create an actual {@link Block}. - * @returns {Block} - * @throws Parse error - */ + /** + * Parse the serialized block data + * and create an actual {@link Block}. + * @returns {Block} + * @throws Parse error + */ -MemBlock.prototype.toBlock = function toBlock() { - const block = Block.fromRaw(this._raw); + toBlock() { + const block = Block.fromRaw(this._raw); - block._hash = this._hash; - block._hhash = this._hhash; + block._hash = this._hash; + block._hhash = this._hhash; - return block; -}; + return block; + } -/** - * Convert the block to a headers object. - * @returns {Headers} - */ + /** + * Convert the block to a headers object. + * @returns {Headers} + */ -MemBlock.prototype.toHeaders = function toHeaders() { - return Headers.fromBlock(this); -}; + toHeaders() { + return Headers.fromBlock(this); + } -/** - * Test whether an object is a MemBlock. - * @param {Object} obj - * @returns {Boolean} - */ + /** + * Test whether an object is a MemBlock. + * @param {Object} obj + * @returns {Boolean} + */ -MemBlock.isMemBlock = function isMemBlock(obj) { - return obj instanceof MemBlock; -}; + static isMemBlock(obj) { + return obj instanceof MemBlock; + } +} /* * Expose diff --git a/lib/primitives/merkleblock.js b/lib/primitives/merkleblock.js index 737313490..f9d699bea 100644 --- a/lib/primitives/merkleblock.js +++ b/lib/primitives/merkleblock.js @@ -7,657 +7,657 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); +const bio = require('bufio'); +const {BufferMap, BufferSet} = require('buffer-map'); const util = require('../utils/util'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); -const encoding = require('../utils/encoding'); -const digest = require('../crypto/digest'); +const hash256 = require('bcrypto/lib/hash256'); const consensus = require('../protocol/consensus'); const AbstractBlock = require('./abstractblock'); const Headers = require('./headers'); const DUMMY = Buffer.from([0]); +const {encoding} = bio; +const {inspectSymbol} = require('../utils'); /** + * Merkle Block * Represents a merkle (filtered) block. * @alias module:primitives.MerkleBlock - * @constructor * @extends AbstractBlock - * @param {NakedBlock} options */ -function MerkleBlock(options) { - if (!(this instanceof MerkleBlock)) - return new MerkleBlock(options); +class MerkleBlock extends AbstractBlock { + /** + * Create a merkle block. + * @constructor + * @param {Object} options + */ - AbstractBlock.call(this); + constructor(options) { + super(); - this.txs = []; - this.hashes = []; - this.flags = DUMMY; + this.txs = []; + this.hashes = []; + this.flags = DUMMY; - this.totalTX = 0; - this._tree = null; + this.totalTX = 0; + this._tree = null; - if (options) - this.fromOptions(options); -} + if (options) + this.fromOptions(options); + } -Object.setPrototypeOf(MerkleBlock.prototype, AbstractBlock.prototype); + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ -/** - * Inject properties from options object. - * @private - * @param {NakedBlock} options - */ + fromOptions(options) { + this.parseOptions(options); -MerkleBlock.prototype.fromOptions = function fromOptions(options) { - this.parseOptions(options); + assert(options, 'MerkleBlock data is required.'); + assert(Array.isArray(options.hashes)); + assert(Buffer.isBuffer(options.flags)); + assert((options.totalTX >>> 0) === options.totalTX); - assert(options, 'MerkleBlock data is required.'); - assert(Array.isArray(options.hashes)); - assert(Buffer.isBuffer(options.flags)); - assert(util.isU32(options.totalTX)); + if (options.hashes) { + for (const hash of options.hashes) { + assert(Buffer.isBuffer(hash)); + this.hashes.push(hash); + } + } - if (options.hashes) { - for (let hash of options.hashes) { - if (typeof hash === 'string') - hash = Buffer.from(hash, 'hex'); - assert(Buffer.isBuffer(hash)); - this.hashes.push(hash); + if (options.flags) { + assert(Buffer.isBuffer(options.flags)); + this.flags = options.flags; } - } - if (options.flags) { - assert(Buffer.isBuffer(options.flags)); - this.flags = options.flags; - } + if (options.totalTX != null) { + assert((options.totalTX >>> 0) === options.totalTX); + this.totalTX = options.totalTX; + } - if (options.totalTX != null) { - assert(util.isU32(options.totalTX)); - this.totalTX = options.totalTX; + return this; } - return this; -}; - -/** - * Instantiate merkle block from options object. - * @param {NakedBlock} options - * @returns {MerkleBlock} - */ - -MerkleBlock.fromOptions = function fromOptions(data) { - return new MerkleBlock().fromOptions(data); -}; + /** + * Instantiate merkle block from options object. + * @param {Object} options + * @returns {MerkleBlock} + */ -/** - * Clear any cached values. - * @param {Boolean?} all - Clear transactions. - */ + static fromOptions(data) { + return new this().fromOptions(data); + } -MerkleBlock.prototype.refresh = function refresh(all) { - this._refresh(); - this._tree = null; + /** + * Clear any cached values. + * @param {Boolean?} all - Clear transactions. + */ - if (!all) - return; + refresh(all) { + this._refresh(); + this._tree = null; - for (const tx of this.txs) - tx.refresh(); -}; + if (!all) + return; -/** - * Test the block's _matched_ transaction vector against a hash. - * @param {Hash} hash - * @returns {Boolean} - */ + for (const tx of this.txs) + tx.refresh(); + } -MerkleBlock.prototype.hasTX = function hasTX(hash) { - return this.indexOf(hash) !== -1; -}; + /** + * Test the block's _matched_ transaction vector against a hash. + * @param {Hash} hash + * @returns {Boolean} + */ -/** - * Test the block's _matched_ transaction vector against a hash. - * @param {Hash} hash - * @returns {Number} Index. - */ + hasTX(hash) { + return this.indexOf(hash) !== -1; + } -MerkleBlock.prototype.indexOf = function indexOf(hash) { - const tree = this.getTree(); - const index = tree.map.get(hash); + /** + * Test the block's _matched_ transaction vector against a hash. + * @param {Hash} hash + * @returns {Number} Index. + */ - if (index == null) - return -1; + indexOf(hash) { + const tree = this.getTree(); + const index = tree.map.get(hash); - return index; -}; + if (index == null) + return -1; -/** - * Verify the partial merkletree. - * @private - * @returns {Boolean} - */ - -MerkleBlock.prototype.verifyBody = function verifyBody() { - const [valid] = this.checkBody(); - return valid; -}; + return index; + } -/** - * Verify the partial merkletree. - * @private - * @returns {Array} [valid, reason, score] - */ + /** + * Verify the partial merkletree. + * @private + * @returns {Boolean} + */ -MerkleBlock.prototype.checkBody = function checkBody() { - const tree = this.getTree(); + verifyBody() { + const [valid] = this.checkBody(); + return valid; + } - if (tree.root !== this.merkleRoot) - return [false, 'bad-txnmrklroot', 100]; + /** + * Verify the partial merkletree. + * @private + * @returns {Array} [valid, reason, score] + */ - return [true, 'valid', 0]; -}; + checkBody() { + const tree = this.getTree(); -/** - * Extract the matches from partial merkle - * tree and calculate merkle root. - * @returns {Object} - */ + if (!tree.root.equals(this.merkleRoot)) + return [false, 'bad-txnmrklroot', 100]; -MerkleBlock.prototype.getTree = function getTree() { - if (!this._tree) { - try { - this._tree = this.extractTree(); - } catch (e) { - this._tree = new PartialTree(); - } + return [true, 'valid', 0]; } - return this._tree; -}; -/** - * Extract the matches from partial merkle - * tree and calculate merkle root. - * @private - * @returns {Object} - */ - -MerkleBlock.prototype.extractTree = function extractTree() { - const matches = []; - const indexes = []; - const map = new Map(); - const hashes = this.hashes; - const flags = this.flags; - const totalTX = this.totalTX; - let bitsUsed = 0; - let hashUsed = 0; - let failed = false; - let height = 0; - - const width = (height) => { - return (totalTX + (1 << height) - 1) >>> height; - }; - - const traverse = (height, pos) => { - if (bitsUsed >= flags.length * 8) { - failed = true; - return encoding.ZERO_HASH; + /** + * Extract the matches from partial merkle + * tree and calculate merkle root. + * @returns {Object} + */ + + getTree() { + if (!this._tree) { + try { + this._tree = this.extractTree(); + } catch (e) { + this._tree = new PartialTree(); + } } + return this._tree; + } - const parent = (flags[bitsUsed / 8 | 0] >>> (bitsUsed % 8)) & 1; - - bitsUsed++; - - if (height === 0 || !parent) { - if (hashUsed >= hashes.length) { + /** + * Extract the matches from partial merkle + * tree and calculate merkle root. + * @private + * @returns {Object} + */ + + extractTree() { + const matches = []; + const indexes = []; + const map = new BufferMap(); + const hashes = this.hashes; + const flags = this.flags; + const totalTX = this.totalTX; + + let bitsUsed = 0; + let hashUsed = 0; + let failed = false; + let height = 0; + + const width = (height) => { + return (totalTX + (1 << height) - 1) >>> height; + }; + + const traverse = (height, pos) => { + if (bitsUsed >= flags.length * 8) { failed = true; - return encoding.ZERO_HASH; + return consensus.ZERO_HASH; } - const hash = hashes[hashUsed++]; + const parent = (flags[bitsUsed / 8 | 0] >>> (bitsUsed % 8)) & 1; - if (height === 0 && parent) { - const txid = hash.toString('hex'); - matches.push(hash); - indexes.push(pos); - map.set(txid, pos); - } + bitsUsed += 1; - return hash; - } + if (height === 0 || !parent) { + if (hashUsed >= hashes.length) { + failed = true; + return consensus.ZERO_HASH; + } - const left = traverse(height - 1, pos * 2); - let right; + const hash = hashes[hashUsed]; - if (pos * 2 + 1 < width(height - 1)) { - right = traverse(height - 1, pos * 2 + 1); - if (right.equals(left)) - failed = true; - } else { - right = left; - } + hashUsed += 1; - return digest.root256(left, right); - }; + if (height === 0 && parent) { + matches.push(hash); + indexes.push(pos); + map.set(hash, pos); + } - if (totalTX === 0) - throw new Error('Zero transactions.'); + return hash; + } - if (totalTX > consensus.MAX_BLOCK_SIZE / 60) - throw new Error('Too many transactions.'); + const left = traverse(height - 1, pos * 2); + let right; - if (hashes.length > totalTX) - throw new Error('Too many hashes.'); + if (pos * 2 + 1 < width(height - 1)) { + right = traverse(height - 1, pos * 2 + 1); + if (right.equals(left)) + failed = true; + } else { + right = left; + } - if (flags.length * 8 < hashes.length) - throw new Error('Flags too small.'); + return hash256.root(left, right); + }; - while (width(height) > 1) - height++; + if (totalTX === 0) + throw new Error('Zero transactions.'); - const root = traverse(height, 0); + if (totalTX > consensus.MAX_BLOCK_SIZE / 60) + throw new Error('Too many transactions.'); - if (failed) - throw new Error('Mutated merkle tree.'); + if (hashes.length > totalTX) + throw new Error('Too many hashes.'); - if (((bitsUsed + 7) / 8 | 0) !== flags.length) - throw new Error('Too many flag bits.'); + if (flags.length * 8 < hashes.length) + throw new Error('Flags too small.'); - if (hashUsed !== hashes.length) - throw new Error('Incorrect number of hashes.'); + while (width(height) > 1) + height += 1; - return new PartialTree(root, matches, indexes, map); -}; + const root = traverse(height, 0); -/** - * Extract the coinbase height (always -1). - * @returns {Number} - */ + if (failed) + throw new Error('Mutated merkle tree.'); -MerkleBlock.prototype.getCoinbaseHeight = function getCoinbaseHeight() { - return -1; -}; + if (((bitsUsed + 7) / 8 | 0) !== flags.length) + throw new Error('Too many flag bits.'); -/** - * Inspect the block and return a more - * user-friendly representation of the data. - * @returns {Object} - */ + if (hashUsed !== hashes.length) + throw new Error('Incorrect number of hashes.'); -MerkleBlock.prototype.inspect = function inspect() { - return this.format(); -}; + return new PartialTree(root, matches, indexes, map); + } -/** - * Inspect the block and return a more - * user-friendly representation of the data. - * @param {CoinView} view - * @param {Number} height - * @returns {Object} - */ + /** + * Extract the coinbase height (always -1). + * @returns {Number} + */ -MerkleBlock.prototype.format = function format(view, height) { - return { - hash: this.rhash(), - height: height != null ? height : -1, - date: util.date(this.time), - version: util.hex32(this.version), - prevBlock: util.revHex(this.prevBlock), - merkleRoot: util.revHex(this.merkleRoot), - time: this.time, - bits: this.bits, - nonce: this.nonce, - totalTX: this.totalTX, - hashes: this.hashes.map((hash) => { - return hash.toString('hex'); - }), - flags: this.flags, - map: this.getTree().map, - txs: this.txs.length - }; -}; + getCoinbaseHeight() { + return -1; + } -/** - * Get merkleblock size. - * @returns {Number} Size. - */ + /** + * Inspect the block and return a more + * user-friendly representation of the data. + * @returns {Object} + */ -MerkleBlock.prototype.getSize = function getSize() { - let size = 0; - size += 80; - size += 4; - size += encoding.sizeVarint(this.hashes.length); - size += this.hashes.length * 32; - size += encoding.sizeVarint(this.flags.length); - size += this.flags.length; - return size; -}; + [inspectSymbol]() { + return this.format(); + } -/** - * Write the merkleblock to a buffer writer. - * @param {BufferWriter} bw - */ + /** + * Inspect the block and return a more + * user-friendly representation of the data. + * @param {CoinView} view + * @param {Number} height + * @returns {Object} + */ + + format(view, height) { + return { + hash: this.rhash(), + height: height != null ? height : -1, + date: util.date(this.time), + version: this.version.toString(16), + prevBlock: util.revHex(this.prevBlock), + merkleRoot: util.revHex(this.merkleRoot), + time: this.time, + bits: this.bits, + nonce: this.nonce, + totalTX: this.totalTX, + hashes: this.hashes.map((hash) => { + return hash.toString('hex'); + }), + flags: this.flags, + map: this.getTree().map, + txs: this.txs.length + }; + } -MerkleBlock.prototype.toWriter = function toWriter(bw) { - this.writeHead(bw); + /** + * Get merkleblock size. + * @returns {Number} Size. + */ + + getSize() { + let size = 0; + size += 80; + size += 4; + size += encoding.sizeVarint(this.hashes.length); + size += this.hashes.length * 32; + size += encoding.sizeVarint(this.flags.length); + size += this.flags.length; + return size; + } - bw.writeU32(this.totalTX); + /** + * Write the merkleblock to a buffer writer. + * @param {BufferWriter} bw + */ - bw.writeVarint(this.hashes.length); + toWriter(bw) { + this.writeHead(bw); - for (const hash of this.hashes) - bw.writeHash(hash); + bw.writeU32(this.totalTX); - bw.writeVarBytes(this.flags); + bw.writeVarint(this.hashes.length); - return bw; -}; + for (const hash of this.hashes) + bw.writeHash(hash); -/** - * Serialize the merkleblock. - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {Buffer|String} - */ + bw.writeVarBytes(this.flags); -MerkleBlock.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + return bw; + } -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + /** + * Serialize the merkleblock. + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {Buffer|String} + */ -MerkleBlock.prototype.fromReader = function fromReader(br) { - this.readHead(br); + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } - this.totalTX = br.readU32(); + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ - const count = br.readVarint(); + fromReader(br) { + this.readHead(br); - for (let i = 0; i < count; i++) - this.hashes.push(br.readHash()); + this.totalTX = br.readU32(); - this.flags = br.readVarBytes(); + const count = br.readVarint(); - return this; -}; + for (let i = 0; i < count; i++) + this.hashes.push(br.readHash()); -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + this.flags = br.readVarBytes(); -MerkleBlock.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + return this; + } -/** - * Instantiate a merkleblock from a buffer reader. - * @param {BufferReader} br - * @returns {MerkleBlock} - */ + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -MerkleBlock.fromReader = function fromReader(br) { - return new MerkleBlock().fromReader(br); -}; + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -/** - * Instantiate a merkleblock from a serialized data. - * @param {Buffer} data - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {MerkleBlock} - */ + /** + * Instantiate a merkleblock from a buffer reader. + * @param {BufferReader} br + * @returns {MerkleBlock} + */ -MerkleBlock.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new MerkleBlock().fromRaw(data); -}; + static fromReader(br) { + return new this().fromReader(br); + } -/** - * Convert the block to an object suitable - * for JSON serialization. - * @returns {Object} - */ + /** + * Instantiate a merkleblock from a serialized data. + * @param {Buffer} data + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {MerkleBlock} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } -MerkleBlock.prototype.toJSON = function toJSON() { - return this.getJSON(); -}; + /** + * Convert the block to an object suitable + * for JSON serialization. + * @returns {Object} + */ -/** - * Convert the block to an object suitable - * for JSON serialization. Note that the hashes - * will be reversed to abide by bitcoind's legacy - * of little-endian uint256s. - * @param {Network} network - * @param {CoinView} view - * @param {Number} height - * @returns {Object} - */ + toJSON() { + return this.getJSON(); + } -MerkleBlock.prototype.getJSON = function getJSON(network, view, height) { - return { - hash: this.rhash(), - height: height, - version: this.version, - prevBlock: util.revHex(this.prevBlock), - merkleRoot: util.revHex(this.merkleRoot), - time: this.time, - bits: this.bits, - nonce: this.nonce, - totalTX: this.totalTX, - hashes: this.hashes.map((hash) => { - return util.revHex(hash.toString('hex')); - }), - flags: this.flags.toString('hex') - }; -}; + /** + * Convert the block to an object suitable + * for JSON serialization. Note that the hashes + * will be reversed to abide by bitcoind's legacy + * of little-endian uint256s. + * @param {Network} network + * @param {CoinView} view + * @param {Number} height + * @returns {Object} + */ + + getJSON(network, view, height) { + return { + hash: this.rhash(), + height: height, + version: this.version, + prevBlock: util.revHex(this.prevBlock), + merkleRoot: util.revHex(this.merkleRoot), + time: this.time, + bits: this.bits, + nonce: this.nonce, + totalTX: this.totalTX, + hashes: this.hashes.map((hash) => { + return util.revHex(hash); + }), + flags: this.flags.toString('hex') + }; + } -/** - * Inject properties from json object. - * @private - * @param {Object} json - */ + /** + * Inject properties from json object. + * @private + * @param {Object} json + */ -MerkleBlock.prototype.fromJSON = function fromJSON(json) { - assert(json, 'MerkleBlock data is required.'); - assert(Array.isArray(json.hashes)); - assert(typeof json.flags === 'string'); - assert(util.isU32(json.totalTX)); + fromJSON(json) { + assert(json, 'MerkleBlock data is required.'); + assert(Array.isArray(json.hashes)); + assert(typeof json.flags === 'string'); + assert((json.totalTX >>> 0) === json.totalTX); - this.parseJSON(json); + this.parseJSON(json); - for (let hash of json.hashes) { - hash = util.revHex(hash); - this.hashes.push(Buffer.from(hash, 'hex')); - } + for (const hash of json.hashes) + this.hashes.push(util.fromRev(hash)); - this.flags = Buffer.from(json.flags, 'hex'); + this.flags = Buffer.from(json.flags, 'hex'); - this.totalTX = json.totalTX; + this.totalTX = json.totalTX; - return this; -}; + return this; + } -/** - * Instantiate a merkle block from a jsonified block object. - * @param {Object} json - The jsonified block object. - * @returns {MerkleBlock} - */ + /** + * Instantiate a merkle block from a jsonified block object. + * @param {Object} json - The jsonified block object. + * @returns {MerkleBlock} + */ -MerkleBlock.fromJSON = function fromJSON(json) { - return new MerkleBlock().fromJSON(json); -}; + static fromJSON(json) { + return new this().fromJSON(json); + } -/** - * Create a merkleblock from a {@link Block} object, passing - * it through a filter first. This will build the partial - * merkle tree. - * @param {Block} block - * @param {Bloom} filter - * @returns {MerkleBlock} - */ + /** + * Create a merkleblock from a {@link Block} object, passing + * it through a filter first. This will build the partial + * merkle tree. + * @param {Block} block + * @param {Bloom} filter + * @returns {MerkleBlock} + */ -MerkleBlock.fromBlock = function fromBlock(block, filter) { - const matches = []; + static fromBlock(block, filter) { + const matches = []; - for (const tx of block.txs) - matches.push(tx.isWatched(filter) ? 1 : 0); + for (const tx of block.txs) + matches.push(tx.isWatched(filter) ? 1 : 0); - return MerkleBlock.fromMatches(block, matches); -}; + return this.fromMatches(block, matches); + } -/** - * Create a merkleblock from an array of txids. - * This will build the partial merkle tree. - * @param {Block} block - * @param {Hash[]} hashes - * @returns {MerkleBlock} - */ + /** + * Create a merkleblock from an array of txids. + * This will build the partial merkle tree. + * @param {Block} block + * @param {Hash[]} hashes + * @returns {MerkleBlock} + */ -MerkleBlock.fromHashes = function fromHashes(block, hashes) { - const filter = new Set(); + static fromHashes(block, hashes) { + const filter = new BufferSet(); - for (let hash of hashes) { - if (Buffer.isBuffer(hash)) - hash = hash.toString('hex'); - filter.add(hash); - } + for (const hash of hashes) + filter.add(hash); - const matches = []; + const matches = []; - for (const tx of block.txs) { - const hash = tx.hash('hex'); - matches.push(filter.has(hash) ? 1 : 0); - } + for (const tx of block.txs) { + const hash = tx.hash(); + matches.push(filter.has(hash) ? 1 : 0); + } - return MerkleBlock.fromMatches(block, matches); -}; + return this.fromMatches(block, matches); + } -/** - * Create a merkleblock from an array of matches. - * This will build the partial merkle tree. - * @param {Block} block - * @param {Number[]} matches - * @returns {MerkleBlock} - */ + /** + * Create a merkleblock from an array of matches. + * This will build the partial merkle tree. + * @param {Block} block + * @param {Number[]} matches + * @returns {MerkleBlock} + */ -MerkleBlock.fromMatches = function fromMatches(block, matches) { - const txs = []; - const leaves = []; - const bits = []; - const hashes = []; - const totalTX = block.txs.length; - let height = 0; + static fromMatches(block, matches) { + const txs = []; + const leaves = []; + const bits = []; + const hashes = []; + const totalTX = block.txs.length; + let height = 0; - const width = (height) => { - return (totalTX + (1 << height) - 1) >>> height; - }; + const width = (height) => { + return (totalTX + (1 << height) - 1) >>> height; + }; - const hash = (height, pos, leaves) => { - if (height === 0) - return leaves[pos]; + const hash = (height, pos, leaves) => { + if (height === 0) + return leaves[pos]; - const left = hash(height - 1, pos * 2, leaves); - let right; + const left = hash(height - 1, pos * 2, leaves); + let right; - if (pos * 2 + 1 < width(height - 1)) - right = hash(height - 1, pos * 2 + 1, leaves); - else - right = left; + if (pos * 2 + 1 < width(height - 1)) + right = hash(height - 1, pos * 2 + 1, leaves); + else + right = left; - return digest.root256(left, right); - }; + return hash256.root(left, right); + }; - const traverse = (height, pos, leaves, matches) => { - let parent = 0; + const traverse = (height, pos, leaves, matches) => { + let parent = 0; - for (let p = (pos << height); p < ((pos + 1) << height) && p < totalTX; p++) - parent |= matches[p]; + for (let p = pos << height; p < ((pos + 1) << height) && p < totalTX; p++) + parent |= matches[p]; - bits.push(parent); + bits.push(parent); - if (height === 0 || !parent) { - hashes.push(hash(height, pos, leaves)); - return; - } + if (height === 0 || !parent) { + hashes.push(hash(height, pos, leaves)); + return; + } - traverse(height - 1, pos * 2, leaves, matches); + traverse(height - 1, pos * 2, leaves, matches); - if (pos * 2 + 1 < width(height - 1)) - traverse(height - 1, pos * 2 + 1, leaves, matches); - }; + if (pos * 2 + 1 < width(height - 1)) + traverse(height - 1, pos * 2 + 1, leaves, matches); + }; - for (let i = 0; i < block.txs.length; i++) { - const tx = block.txs[i]; + for (let i = 0; i < block.txs.length; i++) { + const tx = block.txs[i]; - if (matches[i]) - txs.push(tx); + if (matches[i]) + txs.push(tx); - leaves.push(tx.hash()); - } + leaves.push(tx.hash()); + } - while (width(height) > 1) - height++; + while (width(height) > 1) + height += 1; - traverse(height, 0, leaves, matches); + traverse(height, 0, leaves, matches); - const flags = Buffer.allocUnsafe((bits.length + 7) / 8 | 0); - flags.fill(0); + const flags = Buffer.allocUnsafe((bits.length + 7) / 8 | 0); + flags.fill(0); - for (let p = 0; p < bits.length; p++) - flags[p / 8 | 0] |= bits[p] << (p % 8); + for (let p = 0; p < bits.length; p++) + flags[p / 8 | 0] |= bits[p] << (p % 8); - const merkle = new MerkleBlock(); - merkle._hash = block._hash; - merkle._hhash = block._hhash; - merkle.version = block.version; - merkle.prevBlock = block.prevBlock; - merkle.merkleRoot = block.merkleRoot; - merkle.time = block.time; - merkle.bits = block.bits; - merkle.nonce = block.nonce; - merkle.totalTX = totalTX; - merkle.hashes = hashes; - merkle.flags = flags; - merkle.txs = txs; + const merkle = new this(); + merkle._hash = block._hash; + merkle._hhash = block._hhash; + merkle.version = block.version; + merkle.prevBlock = block.prevBlock; + merkle.merkleRoot = block.merkleRoot; + merkle.time = block.time; + merkle.bits = block.bits; + merkle.nonce = block.nonce; + merkle.totalTX = totalTX; + merkle.hashes = hashes; + merkle.flags = flags; + merkle.txs = txs; - return merkle; -}; + return merkle; + } -/** - * Test whether an object is a MerkleBlock. - * @param {Object} obj - * @returns {Boolean} - */ + /** + * Test whether an object is a MerkleBlock. + * @param {Object} obj + * @returns {Boolean} + */ -MerkleBlock.isMerkleBlock = function isMerkleBlock(obj) { - return obj instanceof MerkleBlock; -}; + static isMerkleBlock(obj) { + return obj instanceof MerkleBlock; + } -/** - * Convert the block to a headers object. - * @returns {Headers} - */ + /** + * Convert the block to a headers object. + * @returns {Headers} + */ -MerkleBlock.prototype.toHeaders = function toHeaders() { - return Headers.fromBlock(this); -}; + toHeaders() { + return Headers.fromBlock(this); + } +} /* * Helpers */ -function PartialTree(root, matches, indexes, map) { - this.root = root ? root.toString('hex') : encoding.NULL_HASH; - this.matches = matches || []; - this.indexes = indexes || []; - this.map = map || new Map(); +class PartialTree { + constructor(root, matches, indexes, map) { + this.root = root || consensus.ZERO_HASH; + this.matches = matches || []; + this.indexes = indexes || []; + this.map = map || new BufferMap(); + } } /* diff --git a/lib/primitives/mtx.js b/lib/primitives/mtx.js index fccc29aef..cec58beb7 100644 --- a/lib/primitives/mtx.js +++ b/lib/primitives/mtx.js @@ -7,8 +7,9 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); +const assert = require('bsert'); +const {encoding} = require('bufio'); +const {BufferMap} = require('buffer-map'); const Script = require('../script/script'); const TX = require('./tx'); const Input = require('./input'); @@ -17,1918 +18,2014 @@ const Coin = require('./coin'); const Outpoint = require('./outpoint'); const CoinView = require('../coins/coinview'); const Address = require('./address'); -const encoding = require('../utils/encoding'); const consensus = require('../protocol/consensus'); const policy = require('../protocol/policy'); const Amount = require('../btc/amount'); const Stack = require('../script/stack'); +const util = require('../utils/util'); +const {inspectSymbol} = require('../utils'); /** + * MTX * A mutable transaction object. * @alias module:primitives.MTX * @extends TX - * @constructor - * @param {Object} options - * @param {Number?} options.version - * @param {Number?} options.changeIndex - * @param {Input[]?} options.inputs - * @param {Output[]?} options.outputs - * @property {Number} version - Transaction version. - * @property {Number} flag - Flag field for segregated witness. - * Always non-zero (1 if not present). - * @property {Input[]} inputs - * @property {Output[]} outputs - * @property {Number} locktime - nLockTime + * @property {Number} changeIndex * @property {CoinView} view */ -function MTX(options) { - if (!(this instanceof MTX)) - return new MTX(options); +class MTX extends TX { + /** + * Create a mutable transaction. + * @alias module:primitives.MTX + * @constructor + * @param {Object} options + */ - TX.call(this); + constructor(options) { + super(); - this.mutable = true; - this.changeIndex = -1; - this.view = new CoinView(); + this.mutable = true; + this.changeIndex = -1; + this.view = new CoinView(); - if (options) - this.fromOptions(options); -} + if (options) + this.fromOptions(options); + } -Object.setPrototypeOf(MTX.prototype, TX.prototype); + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + if (options.version != null) { + assert((options.version >>> 0) === options.version, + 'Version must a be uint32.'); + this.version = options.version; + } -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ + if (options.inputs) { + assert(Array.isArray(options.inputs), 'Inputs must be an array.'); + for (const input of options.inputs) + this.addInput(input); + } -MTX.prototype.fromOptions = function fromOptions(options) { - if (options.version != null) { - assert(util.isU32(options.version), 'Version must a be uint32.'); - this.version = options.version; - } + if (options.outputs) { + assert(Array.isArray(options.outputs), 'Outputs must be an array.'); + for (const output of options.outputs) + this.addOutput(output); + } + + if (options.locktime != null) { + assert((options.locktime >>> 0) === options.locktime, + 'Locktime must be a uint32.'); + this.locktime = options.locktime; + } + + if (options.changeIndex != null) { + if (options.changeIndex !== -1) { + assert((options.changeIndex >>> 0) === options.changeIndex, + 'Change index must be a uint32.'); + this.changeIndex = options.changeIndex; + } else { + this.changeIndex = -1; + } + } - if (options.inputs) { - assert(Array.isArray(options.inputs), 'Inputs must be an array.'); - for (const input of options.inputs) - this.addInput(input); + return this; } - if (options.outputs) { - assert(Array.isArray(options.outputs), 'Outputs must be an array.'); - for (const output of options.outputs) - this.addOutput(output); + /** + * Instantiate MTX from options. + * @param {Object} options + * @returns {MTX} + */ + + static fromOptions(options) { + return new this().fromOptions(options); } - if (options.locktime != null) { - assert(util.isU32(options.locktime), 'Locktime must be a uint32.'); - this.locktime = options.locktime; + /** + * Clone the transaction. Note that + * this will not carry over the view. + * @returns {MTX} + */ + + clone() { + const mtx = new this.constructor(); + mtx.inject(this); + mtx.changeIndex = this.changeIndex; + return mtx; } - if (options.changeIndex != null) { - if (options.changeIndex !== -1) { - assert(util.isU32(options.changeIndex), - 'Change index must be a uint32.'); - this.changeIndex = options.changeIndex; - } else { - this.changeIndex = -1; - } + /** + * Add an input to the transaction. + * @param {Input|Object} options + * @returns {Input} + * + * @example + * mtx.addInput({ prevout: { hash: ... }, script: ... }); + * mtx.addInput(new Input()); + */ + + addInput(options) { + const input = Input.fromOptions(options); + this.inputs.push(input); + return input; } - return this; -}; + /** + * Add an outpoint as an input. + * @param {Outpoint|Object} outpoint + * @returns {Input} + * + * @example + * mtx.addOutpoint({ hash: ..., index: 0 }); + * mtx.addOutpoint(new Outpoint(hash, index)); + */ + + addOutpoint(outpoint) { + const prevout = Outpoint.fromOptions(outpoint); + const input = Input.fromOutpoint(prevout); + this.inputs.push(input); + return input; + } -/** - * Instantiate MTX from options. - * @param {Object} options - * @returns {MTX} - */ + /** + * Add a coin as an input. Note that this will + * add the coin to the internal coin viewpoint. + * @param {Coin} coin + * @returns {Input} + * + * @example + * mtx.addCoin(Coin.fromTX(tx, 0, -1)); + */ -MTX.fromOptions = function fromOptions(options) { - return new MTX().fromOptions(options); -}; + addCoin(coin) { + assert(coin instanceof Coin, 'Cannot add non-coin.'); -/** - * Clone the transaction. Note that - * this will not carry over the view. - * @returns {MTX} - */ + const input = Input.fromCoin(coin); -MTX.prototype.clone = function clone() { - const mtx = new MTX(); - mtx.inject(this); - mtx.changeIndex = this.changeIndex; - return mtx; -}; + this.inputs.push(input); + this.view.addCoin(coin); -/** - * Add an input to the transaction. - * @param {Input|Object} options - * @returns {Input} - * - * @example - * mtx.addInput({ prevout: { hash: ... }, script: ... }); - * mtx.addInput(new Input()); - */ + return input; + } -MTX.prototype.addInput = function addInput(options) { - const input = Input.fromOptions(options); - this.inputs.push(input); - return input; -}; + /** + * Add a transaction as an input. Note that + * this will add the coin to the internal + * coin viewpoint. + * @param {TX} tx + * @param {Number} index + * @param {Number?} height + * @returns {Input} + * + * @example + * mtx.addTX(tx, 0); + */ -/** - * Add an outpoint as an input. - * @param {Outpoint|Object} outpoint - * @returns {Input} - * - * @example - * mtx.addOutpoint({ hash: ..., index: 0 }); - * mtx.addOutpoint(new Outpoint(hash, index)); - */ + addTX(tx, index, height) { + assert(tx instanceof TX, 'Cannot add non-transaction.'); -MTX.prototype.addOutpoint = function addOutpoint(outpoint) { - const prevout = Outpoint.fromOptions(outpoint); - const input = Input.fromOutpoint(prevout); - this.inputs.push(input); - return input; -}; + if (height == null) + height = -1; -/** - * Add a coin as an input. Note that this will - * add the coin to the internal coin viewpoint. - * @param {Coin} coin - * @returns {Input} - * - * @example - * mtx.addCoin(Coin.fromTX(tx, 0, -1)); - */ + const input = Input.fromTX(tx, index); -MTX.prototype.addCoin = function addCoin(coin) { - assert(coin instanceof Coin, 'Cannot add non-coin.'); + this.inputs.push(input); - const input = Input.fromCoin(coin); + this.view.addIndex(tx, index, height); - this.inputs.push(input); - this.view.addCoin(coin); + return input; + } - return input; -}; + /** + * Add an output. + * @param {Address|Script|Output|Object} script - Script or output options. + * @param {Amount?} value + * @returns {Output} + * + * @example + * mtx.addOutput(new Output()); + * mtx.addOutput({ address: ..., value: 100000 }); + * mtx.addOutput(address, 100000); + * mtx.addOutput(script, 100000); + */ + + addOutput(script, value) { + let output; + + if (value != null) + output = Output.fromScript(script, value); + else + output = Output.fromOptions(script); -/** - * Add a transaction as an input. Note that - * this will add the coin to the internal - * coin viewpoint. - * @param {TX} tx - * @param {Number} index - * @param {Number?} height - * @returns {Input} - * - * @example - * mtx.addTX(tx, 0); - */ + this.outputs.push(output); -MTX.prototype.addTX = function addTX(tx, index, height) { - assert(tx instanceof TX, 'Cannot add non-transaction.'); + return output; + } - if (height == null) - height = -1; + /** + * Verify all transaction inputs. + * @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS] + * @returns {Boolean} Whether the inputs are valid. + * @throws {ScriptError} on invalid inputs + */ - const input = Input.fromTX(tx, index); + check(flags) { + return super.check(this.view, flags); + } - this.inputs.push(input); + /** + * Verify the transaction inputs on the worker pool + * (if workers are enabled). + * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] + * @param {WorkerPool?} pool + * @returns {Promise} + */ - this.view.addIndex(tx, index, height); + checkAsync(flags, pool) { + return super.checkAsync(this.view, flags, pool); + } - return input; -}; + /** + * Verify all transaction inputs. + * @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS] + * @returns {Boolean} Whether the inputs are valid. + */ + + verify(flags) { + try { + this.check(flags); + } catch (e) { + if (e.type === 'ScriptError') + return false; + throw e; + } + return true; + } -/** - * Add an output. - * @param {Address|Script|Output|Object} script - Script or output options. - * @param {Amount?} value - * @returns {Output} - * - * @example - * mtx.addOutput(new Output()); - * mtx.addOutput({ address: ..., value: 100000 }); - * mtx.addOutput(address, 100000); - * mtx.addOutput(script, 100000); - */ + /** + * Verify the transaction inputs on the worker pool + * (if workers are enabled). + * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] + * @param {WorkerPool?} pool + * @returns {Promise} + */ + + async verifyAsync(flags, pool) { + try { + await this.checkAsync(flags, pool); + } catch (e) { + if (e.type === 'ScriptError') + return false; + throw e; + } + return true; + } -MTX.prototype.addOutput = function addOutput(script, value) { - let output; + /** + * Calculate the fee for the transaction. + * @returns {Amount} fee (zero if not all coins are available). + */ - if (value != null) { - assert(util.isU64(value), 'Value must be a uint64.'); - output = Output.fromScript(script, value); - } else { - output = Output.fromOptions(script); + getFee() { + return super.getFee(this.view); } - this.outputs.push(output); + /** + * Calculate the total input value. + * @returns {Amount} value + */ - return output; -}; + getInputValue() { + return super.getInputValue(this.view); + } -/** - * Verify all transaction inputs. - * @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS] - * @returns {Boolean} Whether the inputs are valid. - * @throws {ScriptError} on invalid inputs - */ + /** + * Get all input addresses. + * @returns {Address[]} addresses + */ -MTX.prototype.check = function check(flags) { - return TX.prototype.check.call(this, this.view, flags); -}; + getInputAddresses() { + return super.getInputAddresses(this.view); + } -/** - * Verify the transaction inputs on the worker pool - * (if workers are enabled). - * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] - * @param {WorkerPool?} pool - * @returns {Promise} - */ + /** + * Get all addresses. + * @returns {Address[]} addresses + */ -MTX.prototype.checkAsync = function checkAsync(flags, pool) { - return TX.prototype.checkAsync.call(this, this.view, flags, pool); -}; + getAddresses() { + return super.getAddresses(this.view); + } -/** - * Verify all transaction inputs. - * @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS] - * @returns {Boolean} Whether the inputs are valid. - */ + /** + * Get all input address hashes. + * @returns {Hash[]} hashes + */ -MTX.prototype.verify = function verify(flags) { - try { - this.check(flags); - } catch (e) { - if (e.type === 'ScriptError') - return false; - throw e; + getInputHashes(enc) { + return super.getInputHashes(this.view, enc); } - return true; -}; -/** - * Verify the transaction inputs on the worker pool - * (if workers are enabled). - * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] - * @param {WorkerPool?} pool - * @returns {Promise} - */ + /** + * Get all address hashes. + * @returns {Hash[]} hashes + */ -MTX.prototype.verifyAsync = async function verifyAsync(flags, pool) { - try { - await this.checkAsync(flags, pool); - } catch (e) { - if (e.type === 'ScriptError') - return false; - throw e; + getHashes(enc) { + return super.getHashes(this.view, enc); } - return true; -}; -/** - * Calculate the fee for the transaction. - * @returns {Amount} fee (zero if not all coins are available). - */ + /** + * Test whether the transaction has + * all coins available/filled. + * @returns {Boolean} + */ -MTX.prototype.getFee = function getFee() { - return TX.prototype.getFee.call(this, this.view); -}; + hasCoins() { + return super.hasCoins(this.view); + } -/** - * Calculate the total input value. - * @returns {Amount} value - */ + /** + * Calculate virtual sigop count. + * @param {VerifyFlags?} flags + * @returns {Number} sigop count + */ -MTX.prototype.getInputValue = function getInputValue() { - return TX.prototype.getInputValue.call(this, this.view); -}; + getSigops(flags) { + return super.getSigops(this.view, flags); + } -/** - * Get all input addresses. - * @returns {Address[]} addresses - */ + /** + * Calculate sigops weight, taking into account witness programs. + * @param {VerifyFlags?} flags + * @returns {Number} sigop weight + */ -MTX.prototype.getInputAddresses = function getInputAddresses() { - return TX.prototype.getInputAddresses.call(this, this.view); -}; + getSigopsCost(flags) { + return super.getSigopsCost(this.view, flags); + } -/** - * Get all addresses. - * @returns {Address[]} addresses - */ + /** + * Calculate the virtual size of the transaction + * (weighted against bytes per sigop cost). + * @returns {Number} vsize + */ -MTX.prototype.getAddresses = function getAddresses() { - return TX.prototype.getAddresses.call(this, this.view); -}; + getSigopsSize() { + return super.getSigopsSize(this.getSigopsCost()); + } -/** - * Get all input address hashes. - * @returns {Hash[]} hashes - */ + /** + * Perform contextual checks to verify input, output, + * and fee values, as well as coinbase spend maturity + * (coinbases can only be spent 100 blocks or more + * after they're created). Note that this function is + * consensus critical. + * @param {Number} height - Height at which the + * transaction is being spent. In the mempool this is + * the chain height plus one at the time it entered the pool. + * @returns {Boolean} + */ + + verifyInputs(height) { + const [fee] = this.checkInputs(height); + return fee !== -1; + } -MTX.prototype.getInputHashes = function getInputHashes(enc) { - return TX.prototype.getInputHashes.call(this, this.view, enc); -}; + /** + * Perform contextual checks to verify input, output, + * and fee values, as well as coinbase spend maturity + * (coinbases can only be spent 100 blocks or more + * after they're created). Note that this function is + * consensus critical. + * @param {Number} height - Height at which the + * transaction is being spent. In the mempool this is + * the chain height plus one at the time it entered the pool. + * @returns {Array} [fee, reason, score] + */ + + checkInputs(height) { + return super.checkInputs(this.view, height); + } -/** - * Get all address hashes. - * @returns {Hash[]} hashes - */ + /** + * Build input script (or witness) templates (with + * OP_0 in place of signatures). + * @param {Number} index - Input index. + * @param {Coin|Output} coin + * @param {KeyRing} ring + * @returns {Boolean} Whether the script was able to be built. + */ + + scriptInput(index, coin, ring) { + const input = this.inputs[index]; + + assert(input, 'Input does not exist.'); + assert(coin, 'No coin passed.'); + + // Don't bother with any below calculation + // if the output is already templated. + if (input.script.raw.length !== 0 + || input.witness.items.length !== 0) { + return true; + } -MTX.prototype.getHashes = function getHashes(enc) { - return TX.prototype.getHashes.call(this, this.view, enc); -}; + // Get the previous output's script + const prev = coin.script; -/** - * Test whether the transaction has - * all coins available/filled. - * @returns {Boolean} - */ + // This is easily the hardest part about + // building a transaction with segwit: + // figuring out where the redeem script + // and witness redeem scripts go. + const sh = prev.getScripthash(); -MTX.prototype.hasCoins = function hasCoins() { - return TX.prototype.hasCoins.call(this, this.view); -}; + if (sh) { + const redeem = ring.getRedeem(sh); -/** - * Calculate virtual sigop count. - * @param {VerifyFlags?} flags - * @returns {Number} sigop count - */ + if (!redeem) + return false; -MTX.prototype.getSigops = function getSigops(flags) { - return TX.prototype.getSigops.call(this, this.view, flags); -}; + // Witness program nested in regular P2SH. + if (redeem.isProgram()) { + // P2WSH nested within pay-to-scripthash. + const wsh = redeem.getWitnessScripthash(); + if (wsh) { + const wredeem = ring.getRedeem(wsh); -/** - * Calculate sigops weight, taking into account witness programs. - * @param {VerifyFlags?} flags - * @returns {Number} sigop weight - */ + if (!wredeem) + return false; -MTX.prototype.getSigopsCost = function getSigopsCost(flags) { - return TX.prototype.getSigopsCost.call(this, this.view, flags); -}; + const witness = this.scriptVector(wredeem, ring); -/** - * Calculate the virtual size of the transaction - * (weighted against bytes per sigop cost). - * @returns {Number} vsize - */ + if (!witness) + return false; -MTX.prototype.getSigopsSize = function getSigopsSize() { - return TX.prototype.getSigopsSize.call(this, this.getSigopsCost()); -}; + witness.push(wredeem.toRaw()); -/** - * Perform contextual checks to verify input, output, - * and fee values, as well as coinbase spend maturity - * (coinbases can only be spent 100 blocks or more - * after they're created). Note that this function is - * consensus critical. - * @param {Number} height - Height at which the - * transaction is being spent. In the mempool this is - * the chain height plus one at the time it entered the pool. - * @returns {Boolean} - */ + input.witness.fromStack(witness); + input.script.fromItems([redeem.toRaw()]); -MTX.prototype.verifyInputs = function verifyInputs(height) { - const [fee] = this.checkInputs(height); - return fee !== -1; -}; + return true; + } -/** - * Perform contextual checks to verify input, output, - * and fee values, as well as coinbase spend maturity - * (coinbases can only be spent 100 blocks or more - * after they're created). Note that this function is - * consensus critical. - * @param {Number} height - Height at which the - * transaction is being spent. In the mempool this is - * the chain height plus one at the time it entered the pool. - * @returns {Array} [fee, reason, score] - */ + // P2WPKH nested within pay-to-scripthash. + const wpkh = redeem.getWitnessPubkeyhash(); + if (wpkh) { + const pkh = Script.fromPubkeyhash(wpkh); + const witness = this.scriptVector(pkh, ring); -MTX.prototype.checkInputs = function checkInputs(height) { - return TX.prototype.checkInputs.call(this, this.view, height); -}; + if (!witness) + return false; -/** - * Build input script (or witness) templates (with - * OP_0 in place of signatures). - * @param {Number} index - Input index. - * @param {Coin|Output} coin - * @param {KeyRing} ring - * @returns {Boolean} Whether the script was able to be built. - */ + input.witness.fromStack(witness); + input.script.fromItems([redeem.toRaw()]); -MTX.prototype.scriptInput = function scriptInput(index, coin, ring) { - const input = this.inputs[index]; + return true; + } - assert(input, 'Input does not exist.'); - assert(coin, 'No coin passed.'); + // Unknown witness program. + return false; + } - // Don't bother with any below calculation - // if the output is already templated. - if (input.script.raw.length !== 0 - || input.witness.items.length !== 0) { - return true; - } + // Regular P2SH. + const vector = this.scriptVector(redeem, ring); - // Get the previous output's script - const prev = coin.script; + if (!vector) + return false; - // This is easily the hardest part about - // building a transaction with segwit: - // figuring out where the redeem script - // and witness redeem scripts go. - const sh = prev.getScripthash(); + vector.push(redeem.toRaw()); - if (sh) { - const redeem = ring.getRedeem(sh); + input.script.fromStack(vector); - if (!redeem) - return false; + return true; + } - // Witness program nested in regular P2SH. - if (redeem.isProgram()) { - // P2WSH nested within pay-to-scripthash. - const wsh = redeem.getWitnessScripthash(); + // Witness program. + if (prev.isProgram()) { + // Bare P2WSH. + const wsh = prev.getWitnessScripthash(); if (wsh) { const wredeem = ring.getRedeem(wsh); if (!wredeem) return false; - const witness = this.scriptVector(wredeem, ring); + const vector = this.scriptVector(wredeem, ring); - if (!witness) + if (!vector) return false; - witness.push(wredeem.toRaw()); + vector.push(wredeem.toRaw()); - input.witness.fromStack(witness); - input.script.fromItems([redeem.toRaw()]); + input.witness.fromStack(vector); return true; } - // P2WPKH nested within pay-to-scripthash. - const wpkh = redeem.getWitnessPubkeyhash(); + // Bare P2WPKH. + const wpkh = prev.getWitnessPubkeyhash(); if (wpkh) { const pkh = Script.fromPubkeyhash(wpkh); - const witness = this.scriptVector(pkh, ring); + const vector = this.scriptVector(pkh, ring); - if (!witness) + if (!vector) return false; - input.witness.fromStack(witness); - input.script.fromItems([redeem.toRaw()]); + input.witness.fromStack(vector); return true; } - // Unknown witness program. + // Bare... who knows? return false; } - // Regular P2SH. - const vector = this.scriptVector(redeem, ring); + // Wow, a normal output! Praise be to Jengus and Gord. + const vector = this.scriptVector(prev, ring); if (!vector) return false; - vector.push(redeem.toRaw()); - input.script.fromStack(vector); return true; } - // Witness program. - if (prev.isProgram()) { - // Bare P2WSH. - const wsh = prev.getWitnessScripthash(); - if (wsh) { - const wredeem = ring.getRedeem(wsh); + /** + * Build script for a single vector + * based on a previous script. + * @param {Script} prev + * @param {Buffer} ring + * @return {Stack} + */ - if (!wredeem) - return false; - - const vector = this.scriptVector(wredeem, ring); - - if (!vector) - return false; + scriptVector(prev, ring) { + // P2PK + const pk = prev.getPubkey(); + if (pk) { + if (!pk.equals(ring.publicKey)) + return null; - vector.push(wredeem.toRaw()); + const stack = new Stack(); - input.witness.fromStack(vector); + stack.pushInt(0); - return true; + return stack; } - // Bare P2WPKH. - const wpkh = prev.getWitnessPubkeyhash(); - if (wpkh) { - const pkh = Script.fromPubkeyhash(wpkh); - const vector = this.scriptVector(pkh, ring); + // P2PKH + const pkh = prev.getPubkeyhash(); + if (pkh) { + if (!pkh.equals(ring.getKeyHash())) + return null; - if (!vector) - return false; + const stack = new Stack(); - input.witness.fromStack(vector); + stack.pushInt(0); + stack.pushData(ring.publicKey); - return true; + return stack; } - // Bare... who knows? - return false; - } - - // Wow, a normal output! Praise be to Jengus and Gord. - const vector = this.scriptVector(prev, ring); - - if (!vector) - return false; - - input.script.fromStack(vector); - - return true; -}; + // Multisig + const [, n] = prev.getMultisig(); + if (n !== -1) { + if (prev.indexOf(ring.publicKey) === -1) + return null; -/** - * Build script for a single vector - * based on a previous script. - * @param {Script} prev - * @param {Buffer} ring - * @return {Boolean} - */ + // Technically we should create m signature slots, + // but we create n signature slots so we can order + // the signatures properly. + const stack = new Stack(); -MTX.prototype.scriptVector = function scriptVector(prev, ring) { - // P2PK - const pk = prev.getPubkey(); - if (pk) { - if (!pk.equals(ring.publicKey)) - return null; + stack.pushInt(0); - const stack = new Stack(); + // Fill script with `n` signature slots. + for (let i = 0; i < n; i++) + stack.pushInt(0); - stack.pushInt(0); + return stack; + } - return stack; + return null; } - // P2PKH - const pkh = prev.getPubkeyhash(); - if (pkh) { - if (!pkh.equals(ring.getKeyHash())) - return null; - - const stack = new Stack(); - - stack.pushInt(0); - stack.pushData(ring.publicKey); - - return stack; + /** + * Sign a transaction input on the worker pool + * (if workers are enabled). + * @param {Number} index + * @param {Coin|Output} coin + * @param {KeyRing} ring + * @param {SighashType?} type + * @param {WorkerPool?} pool + * @returns {Promise} + */ + + async signInputAsync(index, coin, ring, type, pool) { + if (!pool) + return this.signInput(index, coin, ring, type); + + return await pool.signInput(this, index, coin, ring, type, pool); } - // Multisig - const [, n] = prev.getMultisig(); - if (n !== -1) { - if (prev.indexOf(ring.publicKey) === -1) - return null; - - // Technically we should create m signature slots, - // but we create n signature slots so we can order - // the signatures properly. - const stack = new Stack(); - - stack.pushInt(0); + /** + * Sign an input. + * @param {Number} index - Index of input being signed. + * @param {Coin|Output} coin + * @param {KeyRing} ring - Private key. + * @param {SighashType} type + * @returns {Boolean} Whether the input was able to be signed. + */ + + signInput(index, coin, ring, type) { + const input = this.inputs[index]; + const key = ring.privateKey; + + assert(input, 'Input does not exist.'); + assert(coin, 'No coin passed.'); + + // Get the previous output's script + const value = coin.value; + let prev = coin.script; + let vector = input.script; + let version = 0; + let redeem = false; + + // Grab regular p2sh redeem script. + if (prev.isScripthash()) { + prev = input.script.getRedeem(); + if (!prev) + throw new Error('Input has not been templated.'); + redeem = true; + } - // Fill script with `n` signature slots. - for (let i = 0; i < n; i++) - stack.pushInt(0); + // If the output script is a witness program, + // we have to switch the vector to the witness + // and potentially alter the length. Note that + // witnesses are stack items, so the `dummy` + // _has_ to be an empty buffer (what OP_0 + // pushes onto the stack). + if (prev.isWitnessScripthash()) { + prev = input.witness.getRedeem(); + if (!prev) + throw new Error('Input has not been templated.'); + vector = input.witness; + redeem = true; + version = 1; + } else { + const wpkh = prev.getWitnessPubkeyhash(); + if (wpkh) { + prev = Script.fromPubkeyhash(wpkh); + vector = input.witness; + redeem = false; + version = 1; + } + } - return stack; - } + // Create our signature. + const sig = this.signature(index, prev, value, key, type, version); - return null; -}; + if (redeem) { + const stack = vector.toStack(); + const redeem = stack.pop(); -/** - * Sign a transaction input on the worker pool - * (if workers are enabled). - * @param {Number} index - * @param {Coin|Output} coin - * @param {KeyRing} ring - * @param {SighashType?} type - * @param {WorkerPool?} pool - * @returns {Promise} - */ + const result = this.signVector(prev, stack, sig, ring); -MTX.prototype.signInputAsync = async function signInputAsync(index, coin, ring, type, pool) { - if (!pool) - return this.signInput(index, coin, ring, type); + if (!result) + return false; - return await pool.signInput(this, index, coin, ring, type, pool); -}; + result.push(redeem); -/** - * Sign an input. - * @param {Number} index - Index of input being signed. - * @param {Coin|Output} coin - * @param {KeyRing} ring - Private key. - * @param {SighashType} type - * @returns {Boolean} Whether the input was able to be signed. - */ + vector.fromStack(result); -MTX.prototype.signInput = function signInput(index, coin, ring, type) { - const input = this.inputs[index]; - const key = ring.privateKey; - - assert(input, 'Input does not exist.'); - assert(coin, 'No coin passed.'); - - // Get the previous output's script - const value = coin.value; - let prev = coin.script; - let vector = input.script; - let version = 0; - let redeem = false; - - // Grab regular p2sh redeem script. - if (prev.isScripthash()) { - prev = input.script.getRedeem(); - if (!prev) - throw new Error('Input has not been templated.'); - redeem = true; - } - - // If the output script is a witness program, - // we have to switch the vector to the witness - // and potentially alter the length. Note that - // witnesses are stack items, so the `dummy` - // _has_ to be an empty buffer (what OP_0 - // pushes onto the stack). - if (prev.isWitnessScripthash()) { - prev = input.witness.getRedeem(); - if (!prev) - throw new Error('Input has not been templated.'); - vector = input.witness; - redeem = true; - version = 1; - } else { - const wpkh = prev.getWitnessPubkeyhash(); - if (wpkh) { - prev = Script.fromPubkeyhash(wpkh); - vector = input.witness; - redeem = false; - version = 1; + return true; } - } - - // Create our signature. - const sig = this.signature(index, prev, value, key, type, version); - if (redeem) { const stack = vector.toStack(); - const redeem = stack.pop(); - const result = this.signVector(prev, stack, sig, ring); if (!result) return false; - result.push(redeem); - vector.fromStack(result); return true; } - const stack = vector.toStack(); - const result = this.signVector(prev, stack, sig, ring); - - if (!result) - return false; - - vector.fromStack(result); - - return true; -}; + /** + * Add a signature to a vector + * based on a previous script. + * @param {Script} prev + * @param {Stack} vector + * @param {Buffer} sig + * @param {KeyRing} ring + * @return {Boolean} + */ + + signVector(prev, vector, sig, ring) { + // P2PK + const pk = prev.getPubkey(); + if (pk) { + // Make sure the pubkey is ours. + if (!ring.publicKey.equals(pk)) + return null; -/** - * Add a signature to a vector - * based on a previous script. - * @param {Script} prev - * @param {Stack} vector - * @param {Buffer} sig - * @param {KeyRing} ring - * @return {Boolean} - */ + if (vector.length === 0) + throw new Error('Input has not been templated.'); -MTX.prototype.signVector = function signVector(prev, vector, sig, ring) { - // P2PK - const pk = prev.getPubkey(); - if (pk) { - // Make sure the pubkey is ours. - if (!ring.publicKey.equals(pk)) - return null; + // Already signed. + if (vector.get(0).length > 0) + return vector; - if (vector.length === 0) - throw new Error('Input has not been templated.'); + vector.set(0, sig); - // Already signed. - if (vector.get(0).length > 0) return vector; + } - vector.set(0, sig); + // P2PKH + const pkh = prev.getPubkeyhash(); + if (pkh) { + // Make sure the pubkey hash is ours. + if (!ring.getKeyHash().equals(pkh)) + return null; - return vector; - } + if (vector.length !== 2) + throw new Error('Input has not been templated.'); - // P2PKH - const pkh = prev.getPubkeyhash(); - if (pkh) { - // Make sure the pubkey hash is ours. - if (!ring.getKeyHash().equals(pkh)) - return null; + if (vector.get(1).length === 0) + throw new Error('Input has not been templated.'); - if (vector.length !== 2) - throw new Error('Input has not been templated.'); + // Already signed. + if (vector.get(0).length > 0) + return vector; - if (vector.get(1).length === 0) - throw new Error('Input has not been templated.'); + vector.set(0, sig); - // Already signed. - if (vector.get(0).length > 0) return vector; + } - vector.set(0, sig); - - return vector; - } + // Multisig + const [m, n] = prev.getMultisig(); + if (m !== -1) { + if (vector.length < 2) + throw new Error('Input has not been templated.'); - // Multisig - const [m, n] = prev.getMultisig(); - if (m !== -1) { - if (vector.length < 2) - throw new Error('Input has not been templated.'); + if (vector.get(0).length !== 0) + throw new Error('Input has not been templated.'); - if (vector.get(0).length !== 0) - throw new Error('Input has not been templated.'); + // Too many signature slots. Abort. + if (vector.length - 1 > n) + throw new Error('Input has not been templated.'); - // Too many signature slots. Abort. - if (vector.length - 1 > n) - throw new Error('Input has not been templated.'); + // Count the number of current signatures. + let total = 0; + for (let i = 1; i < vector.length; i++) { + const item = vector.get(i); + if (item.length > 0) + total += 1; + } - // Count the number of current signatures. - let total = 0; - for (let i = 1; i < vector.length; i++) { - const item = vector.get(i); - if (item.length > 0) - total++; - } + // Signatures are already finalized. + if (total === m && vector.length - 1 === m) + return vector; + + // Add some signature slots for us to use if + // there was for some reason not enough. + while (vector.length - 1 < n) + vector.pushInt(0); + + // Grab the redeem script's keys to figure + // out where our key should go. + const keys = []; + for (const op of prev.code) { + if (op.data) + keys.push(op.data); + } - // Signatures are already finalized. - if (total === m && vector.length - 1 === m) - return vector; + // Find the key index so we can place + // the signature in the same index. + let keyIndex = -1; - // Add some signature slots for us to use if - // there was for some reason not enough. - while (vector.length - 1 < n) - vector.pushInt(0); - - // Grab the redeem script's keys to figure - // out where our key should go. - const keys = []; - for (const op of prev.code) { - if (op.data) - keys.push(op.data); - } - - // Find the key index so we can place - // the signature in the same index. - let keyIndex = util.indexOf(keys, ring.publicKey); - - // Our public key is not in the prev_out - // script. We tried to sign a transaction - // that is not redeemable by us. - if (keyIndex === -1) - return null; - - // Offset key index by one to turn it into - // "sig index". Accounts for OP_0 byte at - // the start. - keyIndex++; - - // Add our signature to the correct slot - // and increment the total number of - // signatures. - if (keyIndex < vector.length && total < m) { - if (vector.get(keyIndex).length === 0) { - vector.set(keyIndex, sig); - total++; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + if (key.equals(ring.publicKey)) { + keyIndex = i; + break; + } } - } - // All signatures added. Finalize. - if (total >= m) { - // Remove empty slots left over. - for (let i = vector.length - 1; i >= 1; i--) { - const item = vector.get(i); - if (item.length === 0) - vector.remove(i); + // Our public key is not in the prev_out + // script. We tried to sign a transaction + // that is not redeemable by us. + if (keyIndex === -1) + return null; + + // Offset key index by one to turn it into + // "sig index". Accounts for OP_0 byte at + // the start. + keyIndex += 1; + + // Add our signature to the correct slot + // and increment the total number of + // signatures. + if (keyIndex < vector.length && total < m) { + if (vector.get(keyIndex).length === 0) { + vector.set(keyIndex, sig); + total += 1; + } } - // Remove signatures which are not required. - // This should never happen. - while (total > m) { - vector.pop(); - total--; + // All signatures added. Finalize. + if (total >= m) { + // Remove empty slots left over. + for (let i = vector.length - 1; i >= 1; i--) { + const item = vector.get(i); + if (item.length === 0) + vector.remove(i); + } + + // Remove signatures which are not required. + // This should never happen. + while (total > m) { + vector.pop(); + total -= 1; + } + + // Sanity checks. + assert(total === m); + assert(vector.length - 1 === m); } - // Sanity checks. - assert(total === m); - assert(vector.length - 1 === m); + return vector; } - return vector; + return null; } - return null; -}; + /** + * Test whether the transaction is fully-signed. + * @returns {Boolean} + */ -/** - * Test whether the transaction is fully-signed. - * @returns {Boolean} - */ + isSigned() { + for (let i = 0; i < this.inputs.length; i++) { + const {prevout} = this.inputs[i]; + const coin = this.view.getOutput(prevout); -MTX.prototype.isSigned = function isSigned() { - for (let i = 0; i < this.inputs.length; i++) { - const {prevout} = this.inputs[i]; - const coin = this.view.getOutput(prevout); + if (!coin) + return false; - if (!coin) - return false; + if (!this.isInputSigned(i, coin)) + return false; + } - if (!this.isInputSigned(i, coin)) - return false; + return true; } - return true; -}; + /** + * Test whether an input is fully-signed. + * @param {Number} index + * @param {Coin|Output} coin + * @returns {Boolean} + */ -/** - * Test whether an input is fully-signed. - * @param {Number} index - * @param {Coin|Output} coin - * @returns {Boolean} - */ + isInputSigned(index, coin) { + const input = this.inputs[index]; -MTX.prototype.isInputSigned = function isInputSigned(index, coin) { - const input = this.inputs[index]; + assert(input, 'Input does not exist.'); + assert(coin, 'No coin passed.'); - assert(input, 'Input does not exist.'); - assert(coin, 'No coin passed.'); + let prev = coin.script; + let vector = input.script; + let redeem = false; - let prev = coin.script; - let vector = input.script; - let redeem = false; - - // Grab redeem script if possible. - if (prev.isScripthash()) { - prev = input.script.getRedeem(); - if (!prev) - return false; - redeem = true; - } + // Grab redeem script if possible. + if (prev.isScripthash()) { + prev = input.script.getRedeem(); + if (!prev) + return false; + redeem = true; + } - // If the output script is a witness program, - // we have to switch the vector to the witness - // and potentially alter the length. - if (prev.isWitnessScripthash()) { - prev = input.witness.getRedeem(); - if (!prev) - return false; - vector = input.witness; - redeem = true; - } else { - const wpkh = prev.getWitnessPubkeyhash(); - if (wpkh) { - prev = Script.fromPubkeyhash(wpkh); + // If the output script is a witness program, + // we have to switch the vector to the witness + // and potentially alter the length. + if (prev.isWitnessScripthash()) { + prev = input.witness.getRedeem(); + if (!prev) + return false; vector = input.witness; - redeem = false; + redeem = true; + } else { + const wpkh = prev.getWitnessPubkeyhash(); + if (wpkh) { + prev = Script.fromPubkeyhash(wpkh); + vector = input.witness; + redeem = false; + } } - } - - const stack = vector.toStack(); - if (redeem) - stack.pop(); + const stack = vector.toStack(); - return this.isVectorSigned(prev, stack); -}; + if (redeem) + stack.pop(); -/** - * Test whether a vector is fully-signed. - * @param {Script} prev - * @param {Stack} vector - * @returns {Boolean} - */ + return this.isVectorSigned(prev, stack); + } -MTX.prototype.isVectorSigned = function isVectorSigned(prev, vector) { - if (prev.isPubkey()) { - if (vector.length !== 1) - return false; + /** + * Test whether a vector is fully-signed. + * @param {Script} prev + * @param {Stack} vector + * @returns {Boolean} + */ - if (vector.get(0).length === 0) - return false; + isVectorSigned(prev, vector) { + if (prev.isPubkey()) { + if (vector.length !== 1) + return false; - return true; - } + if (vector.get(0).length === 0) + return false; - if (prev.isPubkeyhash()) { - if (vector.length !== 2) - return false; + return true; + } - if (vector.get(0).length === 0) - return false; + if (prev.isPubkeyhash()) { + if (vector.length !== 2) + return false; - if (vector.get(1).length === 0) - return false; + if (vector.get(0).length === 0) + return false; - return true; - } + if (vector.get(1).length === 0) + return false; - const [m] = prev.getMultisig(); + return true; + } - if (m !== -1) { - // Ensure we have the correct number - // of required signatures. - if (vector.length - 1 !== m) - return false; + const [m] = prev.getMultisig(); - // Ensure all members are signatures. - for (let i = 1; i < vector.length; i++) { - const item = vector.get(i); - if (item.length === 0) + if (m !== -1) { + // Ensure we have the correct number + // of required signatures. + if (vector.length - 1 !== m) return false; + + // Ensure all members are signatures. + for (let i = 1; i < vector.length; i++) { + const item = vector.get(i); + if (item.length === 0) + return false; + } + + return true; } - return true; + return false; } - return false; -}; - -/** - * Build input scripts (or witnesses). - * @param {KeyRing} ring - Address used to sign. The address - * must be able to redeem the coin. - * @returns {Number} Number of inputs templated. - */ + /** + * Build input scripts (or witnesses). + * @param {KeyRing} ring - Address used to sign. The address + * must be able to redeem the coin. + * @returns {Number} Number of inputs templated. + */ + + template(ring) { + if (Array.isArray(ring)) { + let total = 0; + for (const key of ring) + total += this.template(key); + return total; + } -MTX.prototype.template = function template(ring) { - if (Array.isArray(ring)) { let total = 0; - for (const key of ring) - total += this.template(key); - return total; - } - let total = 0; + for (let i = 0; i < this.inputs.length; i++) { + const {prevout} = this.inputs[i]; + const coin = this.view.getOutput(prevout); - for (let i = 0; i < this.inputs.length; i++) { - const {prevout} = this.inputs[i]; - const coin = this.view.getOutput(prevout); + if (!coin) + continue; - if (!coin) - continue; + if (!ring.ownOutput(coin)) + continue; - if (!ring.ownOutput(coin)) - continue; + // Build script for input + if (!this.scriptInput(i, coin, ring)) + continue; - // Build script for input - if (!this.scriptInput(i, coin, ring)) - continue; + total += 1; + } - total++; + return total; } - return total; -}; + /** + * Built input scripts (or witnesses) and sign the inputs. + * @param {KeyRing} ring - Address used to sign. The address + * must be able to redeem the coin. + * @param {SighashType} type + * @returns {Number} Number of inputs signed. + */ + + sign(ring, type) { + if (Array.isArray(ring)) { + let total = 0; + for (const key of ring) + total += this.sign(key, type); + return total; + } -/** - * Built input scripts (or witnesses) and sign the inputs. - * @param {KeyRing} ring - Address used to sign. The address - * must be able to redeem the coin. - * @param {SighashType} type - * @returns {Number} Number of inputs signed. - */ + assert(ring.privateKey, 'No private key available.'); -MTX.prototype.sign = function sign(ring, type) { - if (Array.isArray(ring)) { let total = 0; - for (const key of ring) - total += this.sign(key, type); - return total; - } - assert(ring.privateKey, 'No private key available.'); + for (let i = 0; i < this.inputs.length; i++) { + const {prevout} = this.inputs[i]; + const coin = this.view.getOutput(prevout); - let total = 0; + if (!coin) + continue; - for (let i = 0; i < this.inputs.length; i++) { - const {prevout} = this.inputs[i]; - const coin = this.view.getOutput(prevout); + if (!ring.ownOutput(coin)) + continue; - if (!coin) - continue; + // Build script for input + if (!this.scriptInput(i, coin, ring)) + continue; - if (!ring.ownOutput(coin)) - continue; + // Sign input + if (!this.signInput(i, coin, ring, type)) + continue; - // Build script for input - if (!this.scriptInput(i, coin, ring)) - continue; + total += 1; + } + + return total; + } + + /** + * Sign the transaction inputs on the worker pool + * (if workers are enabled). + * @param {KeyRing} ring + * @param {SighashType?} type + * @param {WorkerPool?} pool + * @returns {Promise} + */ - // Sign input - if (!this.signInput(i, coin, ring, type)) - continue; + async signAsync(ring, type, pool) { + if (!pool) + return this.sign(ring, type); - total++; + return await pool.sign(this, ring, type); } - return total; -}; + /** + * Estimate maximum possible size. + * @param {Function?} estimate - Input script size estimator. + * @returns {Number} + */ -/** - * Sign the transaction inputs on the worker pool - * (if workers are enabled). - * @param {KeyRing} ring - * @param {SighashType?} type - * @param {WorkerPool?} pool - * @returns {Promise} - */ + async estimateSize(estimate) { + const scale = consensus.WITNESS_SCALE_FACTOR; -MTX.prototype.signAsync = async function signAsync(ring, type, pool) { - if (!pool) - return this.sign(ring, type); + let total = 0; - return await pool.sign(this, ring, type); -}; + // Calculate the size, minus the input scripts. + total += 4; + total += encoding.sizeVarint(this.inputs.length); + total += this.inputs.length * 40; -/** - * Estimate maximum possible size. - * @param {Function?} estimate - Input script size estimator. - * @returns {Number} - */ + total += encoding.sizeVarint(this.outputs.length); -MTX.prototype.estimateSize = async function estimateSize(estimate) { - const scale = consensus.WITNESS_SCALE_FACTOR; + for (const output of this.outputs) + total += output.getSize(); - let total = 0; + total += 4; - // Calculate the size, minus the input scripts. - total += 4; - total += encoding.sizeVarint(this.inputs.length); - total += this.inputs.length * 40; + // Add size for signatures and public keys + for (const {prevout} of this.inputs) { + const coin = this.view.getOutput(prevout); - total += encoding.sizeVarint(this.outputs.length); + // We're out of luck here. + // Just assume it's a p2pkh. + if (!coin) { + total += 110; + continue; + } - for (const output of this.outputs) - total += output.getSize(); + // Previous output script. + const prev = coin.script; - total += 4; + // P2PK + if (prev.isPubkey()) { + // varint script size + total += 1; + // OP_PUSHDATA0 [signature] + total += 1 + 73; + continue; + } - // Add size for signatures and public keys - for (const {prevout} of this.inputs) { - const coin = this.view.getOutput(prevout); + // P2PKH + if (prev.isPubkeyhash()) { + // varint script size + total += 1; + // OP_PUSHDATA0 [signature] + total += 1 + 73; + // OP_PUSHDATA0 [key] + total += 1 + 33; + continue; + } - // We're out of luck here. - // Just assume it's a p2pkh. - if (!coin) { - total += 110; - continue; - } + const [m] = prev.getMultisig(); + if (m !== -1) { + let size = 0; + // Bare Multisig + // OP_0 + size += 1; + // OP_PUSHDATA0 [signature] ... + size += (1 + 73) * m; + // varint len + size += encoding.sizeVarint(size); + total += size; + continue; + } - // Previous output script. - const prev = coin.script; + // P2WPKH + if (prev.isWitnessPubkeyhash()) { + let size = 0; + // varint-items-len + size += 1; + // varint-len [signature] + size += 1 + 73; + // varint-len [key] + size += 1 + 33; + // vsize + size = (size + scale - 1) / scale | 0; + total += size; + continue; + } - // P2PK - if (prev.isPubkey()) { - // varint script size - total += 1; - // OP_PUSHDATA0 [signature] - total += 1 + 73; - continue; - } + // Call out to the custom estimator. + if (estimate) { + const size = await estimate(prev); + if (size !== -1) { + total += size; + continue; + } + } - // P2PKH - if (prev.isPubkeyhash()) { - // varint script size - total += 1; - // OP_PUSHDATA0 [signature] - total += 1 + 73; - // OP_PUSHDATA0 [key] - total += 1 + 33; - continue; - } + // P2SH + if (prev.isScripthash()) { + // varint size + total += 1; + // 2-of-3 multisig input + total += 149; + continue; + } - const [m] = prev.getMultisig(); - if (m !== -1) { - let size = 0; - // Bare Multisig - // OP_0 - size += 1; - // OP_PUSHDATA0 [signature] ... - size += (1 + 73) * m; - // varint len - size += encoding.sizeVarint(size); - total += size; - continue; - } - - // P2WPKH - if (prev.isWitnessPubkeyhash()) { - let size = 0; - // varint-items-len - size += 1; - // varint-len [signature] - size += 1 + 73; - // varint-len [key] - size += 1 + 33; - // vsize - size = (size + scale - 1) / scale | 0; - total += size; - continue; - } - - // Call out to the custom estimator. - if (estimate) { - const size = await estimate(prev); - if (size !== -1) { + // P2WSH + if (prev.isWitnessScripthash()) { + let size = 0; + // varint-items-len + size += 1; + // 2-of-3 multisig input + size += 149; + // vsize + size = (size + scale - 1) / scale | 0; total += size; continue; } - } - // P2SH - if (prev.isScripthash()) { - // varint size - total += 1; - // 2-of-3 multisig input - total += 149; - continue; + // Unknown. + total += 110; } - // P2WSH - if (prev.isWitnessScripthash()) { - let size = 0; - // varint-items-len - size += 1; - // 2-of-3 multisig input - size += 149; - // vsize - size = (size + scale - 1) / scale | 0; - total += size; - continue; - } + return total; + } - // Unknown. - total += 110; + /** + * Select necessary coins based on total output value. + * @param {Coin[]} coins + * @param {Object?} options + * @returns {CoinSelection} + * @throws on not enough funds available. + */ + + selectCoins(coins, options) { + const selector = new CoinSelector(this, options); + return selector.select(coins); } - return total; -}; + /** + * Attempt to subtract a fee from a single output. + * @param {Number} index + * @param {Amount} fee + */ -/** - * Select necessary coins based on total output value. - * @param {Coin[]} coins - * @param {Object?} options - * @returns {CoinSelection} - * @throws on not enough funds available. - */ + subtractIndex(index, fee) { + assert(typeof index === 'number'); + assert(typeof fee === 'number'); -MTX.prototype.selectCoins = function selectCoins(coins, options) { - const selector = new CoinSelector(this, options); - return selector.select(coins); -}; + const output = this.outputs[index]; -/** - * Attempt to subtract a fee from all outputs evenly. - * @param {Amount} fee - * @param {Set|null} set - */ + if (!output) + throw new Error('Subtraction index does not exist.'); -MTX.prototype.subtractFee = function subtractFee(fee, set) { - assert(typeof fee === 'number'); + if (output.value < fee + output.getDustThreshold()) + throw new Error('Could not subtract fee.'); - let outputs = 0; + output.value -= fee; + } - for (let i = 0; i < this.outputs.length; i++) { - const output = this.outputs[i]; + /** + * Attempt to subtract a fee from all outputs evenly. + * @param {Amount} fee + */ - if (set && !set.has(i)) - continue; + subtractFee(fee) { + assert(typeof fee === 'number'); - // Ignore nulldatas and - // other OP_RETURN scripts. - if (output.script.isUnspendable()) - continue; + let outputs = 0; - outputs += 1; - } + for (const output of this.outputs) { + // Ignore nulldatas and + // other OP_RETURN scripts. + if (output.script.isUnspendable()) + continue; + outputs += 1; + } - if (outputs === 0) - throw new Error('Could not subtract fee.'); + if (outputs === 0) + throw new Error('Could not subtract fee.'); - const left = fee % outputs; - const share = (fee - left) / outputs; + const left = fee % outputs; + const share = (fee - left) / outputs; - // First pass, remove even shares. - for (let i = 0; i < this.outputs.length; i++) { - const output = this.outputs[i]; + // First pass, remove even shares. + for (const output of this.outputs) { + if (output.script.isUnspendable()) + continue; - if (set && !set.has(i)) - continue; + if (output.value < share + output.getDustThreshold()) + throw new Error('Could not subtract fee.'); - if (output.script.isUnspendable()) - continue; + output.value -= share; + } - if (output.value < share + output.getDustThreshold()) - throw new Error('Could not subtract fee.'); + // Second pass, remove the remainder + // for the one unlucky output. + for (const output of this.outputs) { + if (output.script.isUnspendable()) + continue; - output.value -= share; - } + if (output.value >= left + output.getDustThreshold()) { + output.value -= left; + return; + } + } - // Second pass, remove the remainder - // for the one unlucky output. - for (let i = 0; i < this.outputs.length; i++) { - const output = this.outputs[i]; + throw new Error('Could not subtract fee.'); + } - if (set && !set.has(i)) - continue; + /** + * Select coins and fill the inputs. + * @param {Coin[]} coins + * @param {Object} options - See {@link MTX#selectCoins} options. + * @returns {CoinSelector} + */ + + async fund(coins, options) { + assert(options, 'Options are required.'); + assert(options.changeAddress, 'Change address is required.'); + assert(this.inputs.length === 0, 'TX is already funded.'); + + // Select necessary coins. + const select = await this.selectCoins(coins, options); + + // Add coins to transaction. + for (const coin of select.chosen) + this.addCoin(coin); + + // Attempt to subtract fee. + if (select.subtractFee) { + const index = select.subtractIndex; + if (index !== -1) + this.subtractIndex(index, select.fee); + else + this.subtractFee(select.fee); + } - if (output.script.isUnspendable()) - continue; + // Add a change output. + const output = new Output(); + output.value = select.change; + output.script.fromAddress(select.changeAddress); - if (output.value >= left + output.getDustThreshold()) { - output.value -= left; - return; + if (output.isDust(policy.MIN_RELAY)) { + // Do nothing. Change is added to fee. + this.changeIndex = -1; + assert.strictEqual(this.getFee(), select.fee + select.change); + } else { + this.outputs.push(output); + this.changeIndex = this.outputs.length - 1; + assert.strictEqual(this.getFee(), select.fee); } + + return select; } - throw new Error('Could not subtract fee.'); -}; + /** + * Sort inputs and outputs according to BIP69. + * @see https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki + */ -/** - * Select coins and fill the inputs. - * @param {Coin[]} coins - * @param {Object} options - See {@link MTX#selectCoins} options. - * @returns {CoinSelector} - */ + sortMembers() { + let changeOutput = null; + + if (this.changeIndex !== -1) { + changeOutput = this.outputs[this.changeIndex]; + assert(changeOutput); + } + + this.inputs.sort(sortInputs); + this.outputs.sort(sortOutputs); -MTX.prototype.fund = async function fund(coins, options) { - assert(options, 'Options are required.'); - assert(options.changeAddress, 'Change address is required.'); - assert(this.inputs.length === 0, 'TX is already funded.'); + if (this.changeIndex !== -1) { + this.changeIndex = this.outputs.indexOf(changeOutput); + assert(this.changeIndex !== -1); + } + } - // Select necessary coins. - const select = await this.selectCoins(coins, options); + /** + * Avoid fee sniping. + * @param {Number} - Current chain height. + * @see bitcoin/src/wallet/wallet.cpp + */ - // Add coins to transaction. - for (const coin of select.chosen) - this.addCoin(coin); + avoidFeeSniping(height) { + assert(typeof height === 'number', 'Must pass in height.'); - // Attempt to subtract fee. - if (select.subtractFee) - this.subtractFee(select.fee, select.subtractIndex); + if ((Math.random() * 10 | 0) === 0) { + height -= Math.random() * 100 | 0; - // Add a change output. - const output = new Output(); - output.value = select.change; - output.script.fromAddress(select.changeAddress); + if (height < 0) + height = 0; + } - if (output.isDust(policy.MIN_RELAY)) { - // Do nothing. Change is added to fee. - this.changeIndex = -1; - assert.strictEqual(this.getFee(), select.fee + select.change); - } else { - this.outputs.push(output); - this.changeIndex = this.outputs.length - 1; - assert.strictEqual(this.getFee(), select.fee); + this.setLocktime(height); } - return select; -}; + /** + * Set locktime and sequences appropriately. + * @param {Number} locktime + */ -/** - * Sort inputs and outputs according to BIP69. - * @see https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki - */ + setLocktime(locktime) { + assert((locktime >>> 0) === locktime, 'Locktime must be a uint32.'); + assert(this.inputs.length > 0, 'Cannot set sequence with no inputs.'); -MTX.prototype.sortMembers = function sortMembers() { - let changeOutput = null; + for (const input of this.inputs) { + if (input.sequence === 0xffffffff) + input.sequence = 0xfffffffe; + } - if (this.changeIndex !== -1) { - changeOutput = this.outputs[this.changeIndex]; - assert(changeOutput); + this.locktime = locktime; } - this.inputs.sort(sortInputs); - this.outputs.sort(sortOutputs); + /** + * Set sequence locktime. + * @param {Number} index - Input index. + * @param {Number} locktime + * @param {Boolean?} seconds + */ - if (this.changeIndex !== -1) { - this.changeIndex = this.outputs.indexOf(changeOutput); - assert(this.changeIndex !== -1); - } -}; + setSequence(index, locktime, seconds) { + const input = this.inputs[index]; -/** - * Avoid fee sniping. - * @param {Number} - Current chain height. - * @see bitcoin/src/wallet/wallet.cpp - */ + assert(input, 'Input does not exist.'); + assert((locktime >>> 0) === locktime, 'Locktime must be a uint32.'); -MTX.prototype.avoidFeeSniping = function avoidFeeSniping(height) { - assert(typeof height === 'number', 'Must pass in height.'); + this.version = 2; - if (util.random(0, 10) === 0) { - height -= util.random(0, 100); + if (seconds) { + locktime >>>= consensus.SEQUENCE_GRANULARITY; + locktime &= consensus.SEQUENCE_MASK; + locktime |= consensus.SEQUENCE_TYPE_FLAG; + } else { + locktime &= consensus.SEQUENCE_MASK; + } - if (height < 0) - height = 0; + input.sequence = locktime; } - this.setLocktime(height); -}; - -/** - * Set locktime and sequences appropriately. - * @param {Number} locktime - */ - -MTX.prototype.setLocktime = function setLocktime(locktime) { - assert(util.isU32(locktime), 'Locktime must be a uint32.'); - assert(this.inputs.length > 0, 'Cannot set sequence with no inputs.'); + /** + * Inspect the transaction. + * @returns {Object} + */ - for (const input of this.inputs) { - if (input.sequence === 0xffffffff) - input.sequence = 0xfffffffe; + [inspectSymbol]() { + return this.format(); } - this.locktime = locktime; -}; + /** + * Inspect the transaction. + * @returns {Object} + */ -/** - * Set sequence locktime. - * @param {Number} index - Input index. - * @param {Number} locktime - * @param {Boolean?} seconds - */ + format() { + return super.format(this.view); + } -MTX.prototype.setSequence = function setSequence(index, locktime, seconds) { - const input = this.inputs[index]; + /** + * Convert transaction to JSON. + * @returns {Object} + */ - assert(input, 'Input does not exist.'); - assert(util.isU32(locktime), 'Locktime must be a uint32.'); + toJSON() { + return super.toJSON(null, this.view); + } - this.version = 2; + /** + * Convert transaction to JSON. + * @param {Network} network + * @returns {Object} + */ - if (seconds) { - locktime >>>= consensus.SEQUENCE_GRANULARITY; - locktime &= consensus.SEQUENCE_MASK; - locktime |= consensus.SEQUENCE_TYPE_FLAG; - } else { - locktime &= consensus.SEQUENCE_MASK; + getJSON(network) { + return super.getJSON(network, this.view); } - input.sequence = locktime; -}; + /** + * Inject properties from a json object + * @param {Object} json + */ -/** - * Inspect the transaction. - * @returns {Object} - */ + fromJSON(json) { + super.fromJSON(json); -MTX.prototype.inspect = function inspect() { - return this.format(); -}; + for (let i = 0; i < json.inputs.length; i++) { + const input = json.inputs[i]; + const {prevout} = input; -/** - * Inspect the transaction. - * @returns {Object} - */ + if (!input.coin) + continue; -MTX.prototype.format = function format() { - return TX.prototype.format.call(this, this.view); -}; + const coin = Coin.fromJSON(input.coin); -/** - * Convert transaction to JSON. - * @returns {Object} - */ + coin.hash = util.fromRev(prevout.hash); + coin.index = prevout.index; -MTX.prototype.toJSON = function toJSON() { - return TX.prototype.getJSON.call(this, null, this.view); -}; + this.view.addCoin(coin); + } -/** - * Convert transaction to JSON. - * @param {Network} network - * @returns {Object} - */ + return this; + } -MTX.prototype.getJSON = function getJSON(network) { - return TX.prototype.getJSON.call(this, network, this.view); -}; + /** + * Instantiate a transaction from a + * jsonified transaction object. + * @param {Object} json - The jsonified transaction object. + * @returns {MTX} + */ -/** - * Instantiate a transaction from a - * jsonified transaction object. - * @param {Object} json - The jsonified transaction object. - * @returns {MTX} - */ - -MTX.fromJSON = function fromJSON(json) { - return new MTX().fromJSON(json); -}; + static fromJSON(json) { + return new this().fromJSON(json); + } -/** - * Instantiate a transaction from a buffer reader. - * @param {BufferReader} br - * @returns {MTX} - */ + /** + * Instantiate a transaction from a buffer reader. + * @param {BufferReader} br + * @returns {MTX} + */ -MTX.fromReader = function fromReader(br) { - return new MTX().fromReader(br); -}; + static fromReader(br) { + return new this().fromReader(br); + } -/** - * Instantiate a transaction from a serialized Buffer. - * @param {Buffer} data - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {MTX} - */ + /** + * Instantiate a transaction from a serialized Buffer. + * @param {Buffer} data + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {MTX} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } -MTX.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new MTX().fromRaw(data); -}; + /** + * Convert the MTX to a TX. + * @returns {TX} + */ -/** - * Convert the MTX to a TX. - * @returns {TX} - */ + toTX() { + return new TX().inject(this); + } -MTX.prototype.toTX = function toTX() { - return new TX().inject(this); -}; + /** + * Convert the MTX to a TX. + * @returns {Array} [tx, view] + */ -/** - * Convert the MTX to a TX. - * @returns {Array} [tx, view] - */ + commit() { + return [this.toTX(), this.view]; + } -MTX.prototype.commit = function commit() { - return [this.toTX(), this.view]; -}; + /** + * Instantiate MTX from TX. + * @param {TX} tx + * @returns {MTX} + */ -/** - * Instantiate MTX from TX. - * @param {TX} tx - * @returns {MTX} - */ + static fromTX(tx) { + return new this().inject(tx); + } -MTX.fromTX = function fromTX(tx) { - return new MTX().inject(tx); -}; + /** + * Test whether an object is an MTX. + * @param {Object} obj + * @returns {Boolean} + */ -/** - * Test whether an object is an MTX. - * @param {Object} obj - * @returns {Boolean} - */ - -MTX.isMTX = function isMTX(obj) { - return obj instanceof MTX; -}; + static isMTX(obj) { + return obj instanceof MTX; + } +} /** * Coin Selector * @alias module:primitives.CoinSelector - * @constructor - * @param {TX} tx - * @param {Object?} options */ -function CoinSelector(tx, options) { - if (!(this instanceof CoinSelector)) - return new CoinSelector(tx, options); - - this.tx = tx.clone(); - this.coins = []; - this.outputValue = 0; - this.index = 0; - this.chosen = []; - this.change = 0; - this.fee = CoinSelector.MIN_FEE; - - this.selection = 'value'; - this.subtractFee = false; - this.subtractIndex = null; - this.height = -1; - this.depth = -1; - this.hardFee = -1; - this.rate = CoinSelector.FEE_RATE; - this.maxFee = -1; - this.round = false; - this.changeAddress = null; - - // Needed for size estimation. - this.estimate = null; - - if (options) - this.fromOptions(options); -} - -/** - * Default fee rate - * for coin selection. - * @const {Amount} - * @default - */ +class CoinSelector { + /** + * Create a coin selector. + * @constructor + * @param {TX} tx + * @param {Object?} options + */ + + constructor(tx, options) { + this.tx = tx.clone(); + this.coins = []; + this.outputValue = 0; + this.index = 0; + this.chosen = []; + this.change = 0; + this.fee = CoinSelector.MIN_FEE; + + this.selection = 'value'; + this.subtractFee = false; + this.subtractIndex = -1; + this.height = -1; + this.depth = -1; + this.hardFee = -1; + this.rate = CoinSelector.FEE_RATE; + this.maxFee = -1; + this.round = false; + this.changeAddress = null; + this.inputs = new BufferMap(); + + // Needed for size estimation. + this.estimate = null; + + this.injectInputs(); + + if (options) + this.fromOptions(options); + } -CoinSelector.FEE_RATE = 10000; + /** + * Initialize selector options. + * @param {Object} options + * @private + */ -/** - * Minimum fee to start with - * during coin selection. - * @const {Amount} - * @default - */ + fromOptions(options) { + if (options.selection) { + assert(typeof options.selection === 'string'); + this.selection = options.selection; + } -CoinSelector.MIN_FEE = 10000; + if (options.subtractFee != null) { + if (typeof options.subtractFee === 'number') { + assert(Number.isSafeInteger(options.subtractFee)); + assert(options.subtractFee >= -1); + this.subtractIndex = options.subtractFee; + this.subtractFee = this.subtractIndex !== -1; + } else { + assert(typeof options.subtractFee === 'boolean'); + this.subtractFee = options.subtractFee; + } + } -/** - * Maximum fee to allow - * after coin selection. - * @const {Amount} - * @default - */ + if (options.subtractIndex != null) { + assert(Number.isSafeInteger(options.subtractIndex)); + assert(options.subtractIndex >= -1); + this.subtractIndex = options.subtractIndex; + this.subtractFee = this.subtractIndex !== -1; + } -CoinSelector.MAX_FEE = consensus.COIN / 10; + if (options.height != null) { + assert(Number.isSafeInteger(options.height)); + assert(options.height >= -1); + this.height = options.height; + } -/** - * Initialize selector options. - * @param {Object} options - * @private - */ + if (options.confirmations != null) { + assert(Number.isSafeInteger(options.confirmations)); + assert(options.confirmations >= -1); + this.depth = options.confirmations; + } -CoinSelector.prototype.fromOptions = function fromOptions(options) { - if (options.selection) { - assert(typeof options.selection === 'string'); - this.selection = options.selection; - } + if (options.depth != null) { + assert(Number.isSafeInteger(options.depth)); + assert(options.depth >= -1); + this.depth = options.depth; + } - if (options.subtractFee != null) { - assert(typeof options.subtractFee === 'boolean'); - this.subtractFee = options.subtractFee; - } + if (options.hardFee != null) { + assert(Number.isSafeInteger(options.hardFee)); + assert(options.hardFee >= -1); + this.hardFee = options.hardFee; + } - if (options.subtractIndex != null) { - let indicies = null; + if (options.rate != null) { + assert(Number.isSafeInteger(options.rate)); + assert(options.rate >= 0); + this.rate = options.rate; + } - if (typeof options.subtractIndex === 'number') { - indicies = [options.subtractIndex]; - } else { - assert(Array.isArray(options.subtractIndex)); - indicies = options.subtractIndex; + if (options.maxFee != null) { + assert(Number.isSafeInteger(options.maxFee)); + assert(options.maxFee >= -1); + this.maxFee = options.maxFee; } - if (indicies.length > 0) { - const set = new Set(); + if (options.round != null) { + assert(typeof options.round === 'boolean'); + this.round = options.round; + } - for (const index of indicies) { - assert(util.isU32(index)); - assert(index < this.tx.outputs.length); - set.add(index); + if (options.changeAddress) { + const addr = options.changeAddress; + if (typeof addr === 'string') { + this.changeAddress = Address.fromString(addr); + } else { + assert(addr instanceof Address); + this.changeAddress = addr; } + } - this.subtractIndex = set; - this.subtractFee = true; + if (options.estimate) { + assert(typeof options.estimate === 'function'); + this.estimate = options.estimate; } - } - if (options.height != null) { - assert(util.isInt(options.height)); - assert(options.height >= -1); - this.height = options.height; - } + if (options.inputs) { + assert(Array.isArray(options.inputs)); + for (let i = 0; i < options.inputs.length; i++) { + const prevout = options.inputs[i]; + assert(prevout && typeof prevout === 'object'); + const {hash, index} = prevout; + assert(Buffer.isBuffer(hash)); + assert(typeof index === 'number'); + this.inputs.set(Outpoint.toKey(hash, index), i); + } + } - if (options.confirmations != null) { - assert(util.isInt(options.confirmations)); - assert(options.confirmations >= -1); - this.depth = options.confirmations; + return this; } - if (options.depth != null) { - assert(util.isInt(options.depth)); - assert(options.depth >= -1); - this.depth = options.depth; - } + /** + * Attempt to inject existing inputs. + * @private + */ - if (options.hardFee != null) { - assert(util.isInt(options.hardFee)); - assert(options.hardFee >= -1); - this.hardFee = options.hardFee; + injectInputs() { + if (this.tx.inputs.length > 0) { + for (let i = 0; i < this.tx.inputs.length; i++) { + const {prevout} = this.tx.inputs[i]; + this.inputs.set(prevout.toKey(), i); + } + } } - if (options.rate != null) { - assert(util.isU64(options.rate)); - this.rate = options.rate; + /** + * Initialize the selector with coins to select from. + * @param {Coin[]} coins + */ + + init(coins) { + this.coins = coins.slice(); + this.outputValue = this.tx.getOutputValue(); + this.index = 0; + this.chosen = []; + this.change = 0; + this.fee = CoinSelector.MIN_FEE; + this.tx.inputs.length = 0; + + switch (this.selection) { + case 'all': + case 'random': + this.coins.sort(sortRandom); + break; + case 'age': + this.coins.sort(sortAge); + break; + case 'value': + this.coins.sort(sortValue); + break; + default: + throw new FundingError(`Bad selection type: ${this.selection}.`); + } } - if (options.maxFee != null) { - assert(util.isInt(options.maxFee)); - assert(options.maxFee >= -1); - this.maxFee = options.maxFee; - } + /** + * Calculate total value required. + * @returns {Amount} + */ - if (options.round != null) { - assert(typeof options.round === 'boolean'); - this.round = options.round; + total() { + if (this.subtractFee) + return this.outputValue; + return this.outputValue + this.fee; } - if (options.changeAddress) { - const addr = options.changeAddress; - if (typeof addr === 'string') { - this.changeAddress = Address.fromString(addr); - } else { - assert(addr instanceof Address); - this.changeAddress = addr; - } - } + /** + * Test whether the selector has + * completely funded the transaction. + * @returns {Boolean} + */ - if (options.estimate) { - assert(typeof options.estimate === 'function'); - this.estimate = options.estimate; + isFull() { + return this.tx.getInputValue() >= this.total(); } - return this; -}; - -/** - * Initialize the selector with coins to select from. - * @param {Coin[]} coins - */ + /** + * Test whether a coin is spendable + * with regards to the options. + * @param {Coin} coin + * @returns {Boolean} + */ -CoinSelector.prototype.init = function init(coins) { - this.coins = coins.slice(); - this.outputValue = this.tx.getOutputValue(); - this.index = 0; - this.chosen = []; - this.change = 0; - this.fee = CoinSelector.MIN_FEE; - this.tx.inputs.length = 0; - - switch (this.selection) { - case 'all': - case 'random': - this.coins.sort(sortRandom); - break; - case 'age': - this.coins.sort(sortAge); - break; - case 'value': - this.coins.sort(sortValue); - break; - default: - throw new FundingError(`Bad selection type: ${this.selection}.`); - } -}; - -/** - * Calculate total value required. - * @returns {Amount} - */ + isSpendable(coin) { + if (this.tx.view.hasEntry(coin)) + return false; -CoinSelector.prototype.total = function total() { - if (this.subtractFee) - return this.outputValue; - return this.outputValue + this.fee; -}; + if (this.height === -1) + return true; -/** - * Test whether the selector has - * completely funded the transaction. - * @returns {Boolean} - */ + if (coin.coinbase) { + if (coin.height === -1) + return false; -CoinSelector.prototype.isFull = function isFull() { - return this.tx.getInputValue() >= this.total(); -}; + if (this.height + 1 < coin.height + consensus.COINBASE_MATURITY) + return false; -/** - * Test whether a coin is spendable - * with regards to the options. - * @param {Coin} coin - * @returns {Boolean} - */ + return true; + } -CoinSelector.prototype.isSpendable = function isSpendable(coin) { - if (this.height === -1) - return true; + if (this.depth === -1) + return true; - if (coin.coinbase) { - if (coin.height === -1) - return false; + const depth = coin.getDepth(this.height); - if (this.height + 1 < coin.height + consensus.COINBASE_MATURITY) + if (depth < this.depth) return false; return true; } - if (this.depth === -1) - return true; + /** + * Get the current fee based on a size. + * @param {Number} size + * @returns {Amount} + */ + + getFee(size) { + // This is mostly here for testing. + // i.e. A fee rounded to the nearest + // kb is easier to predict ahead of time. + if (this.round) { + const fee = policy.getRoundFee(size, this.rate); + return Math.min(fee, CoinSelector.MAX_FEE); + } - const depth = coin.getDepth(this.height); + const fee = policy.getMinFee(size, this.rate); + return Math.min(fee, CoinSelector.MAX_FEE); + } - if (depth < this.depth) - return false; + /** + * Fund the transaction with more + * coins if the `output value + fee` + * total was updated. + */ + + fund() { + // Ensure all preferred inputs first. + if (this.inputs.size > 0) { + const coins = []; + + for (let i = 0; i < this.inputs.size; i++) + coins.push(null); + + for (const coin of this.coins) { + const {hash, index} = coin; + const key = Outpoint.toKey(hash, index); + const i = this.inputs.get(key); + + if (i != null) { + coins[i] = coin; + this.inputs.delete(key); + } + } - return true; -}; + if (this.inputs.size > 0) + throw new Error('Could not resolve preferred inputs.'); -/** - * Get the current fee based on a size. - * @param {Number} size - * @returns {Amount} - */ + for (const coin of coins) { + this.tx.addCoin(coin); + this.chosen.push(coin); + } + } -CoinSelector.prototype.getFee = function getFee(size) { - // This is mostly here for testing. - // i.e. A fee rounded to the nearest - // kb is easier to predict ahead of time. - if (this.round) { - const fee = policy.getRoundFee(size, this.rate); - return Math.min(fee, CoinSelector.MAX_FEE); - } + while (this.index < this.coins.length) { + const coin = this.coins[this.index++]; - const fee = policy.getMinFee(size, this.rate); - return Math.min(fee, CoinSelector.MAX_FEE); -}; + if (!this.isSpendable(coin)) + continue; -/** - * Fund the transaction with more - * coins if the `output value + fee` - * total was updated. - */ + this.tx.addCoin(coin); + this.chosen.push(coin); -CoinSelector.prototype.fund = function fund() { - while (this.index < this.coins.length) { - const coin = this.coins[this.index++]; + if (this.selection === 'all') + continue; - if (!this.isSpendable(coin)) - continue; + if (this.isFull()) + break; + } + } - this.tx.addCoin(coin); - this.chosen.push(coin); + /** + * Initiate selection from `coins`. + * @param {Coin[]} coins + * @returns {CoinSelector} + */ - if (this.selection === 'all') - continue; + async select(coins) { + this.init(coins); - if (this.isFull()) - break; - } -}; + if (this.hardFee !== -1) { + this.selectHard(); + } else { + // This is potentially asynchronous: + // it may invoke the size estimator + // required for redeem scripts (we + // may be calling out to a wallet + // or something similar). + await this.selectEstimate(); + } -/** - * Initiate selection from `coins`. - * @param {Coin[]} coins - * @returns {CoinSelector} - */ + if (!this.isFull()) { + // Still failing to get enough funds. + throw new FundingError( + 'Not enough funds.', + this.tx.getInputValue(), + this.total()); + } -CoinSelector.prototype.select = async function select(coins) { - this.init(coins); + // How much money is left after filling outputs. + this.change = this.tx.getInputValue() - this.total(); - if (this.hardFee !== -1) { - this.selectHard(); - } else { - // This is potentially asynchronous: - // it may invoke the size estimator - // required for redeem scripts (we - // may be calling out to a wallet - // or something similar). - await this.selectEstimate(); + return this; } - if (!this.isFull()) { - // Still failing to get enough funds. - throw new FundingError( - 'Not enough funds.', - this.tx.getInputValue(), - this.total()); - } + /** + * Initialize selection based on size estimate. + */ - // How much money is left after filling outputs. - this.change = this.tx.getInputValue() - this.total(); + async selectEstimate() { + // Set minimum fee and do + // an initial round of funding. + this.fee = CoinSelector.MIN_FEE; + this.fund(); - return this; -}; + // Add dummy output for change. + const change = new Output(); -/** - * Initialize selection based on size estimate. - */ + if (this.changeAddress) { + change.script.fromAddress(this.changeAddress); + } else { + // In case we don't have a change address, + // we use a fake p2pkh output to gauge size. + change.script.fromPubkeyhash(Buffer.allocUnsafe(20)); + } -CoinSelector.prototype.selectEstimate = async function selectEstimate() { - // Set minimum fee and do - // an initial round of funding. - this.fee = CoinSelector.MIN_FEE; - this.fund(); + this.tx.outputs.push(change); - // Add dummy output for change. - const change = new Output(); + // Keep recalculating the fee and funding + // until we reach some sort of equilibrium. + do { + const size = await this.tx.estimateSize(this.estimate); - if (this.changeAddress) { - change.script.fromAddress(this.changeAddress); - } else { - // In case we don't have a change address, - // we use a fake p2pkh output to gauge size. - change.script.fromPubkeyhash(encoding.ZERO_HASH160); + this.fee = this.getFee(size); + + if (this.maxFee > 0 && this.fee > this.maxFee) + throw new FundingError('Fee is too high.'); + + // Failed to get enough funds, add more coins. + if (!this.isFull()) + this.fund(); + } while (!this.isFull() && this.index < this.coins.length); } - this.tx.outputs.push(change); + /** + * Initiate selection based on a hard fee. + */ - // Keep recalculating the fee and funding - // until we reach some sort of equilibrium. - do { - const size = await this.tx.estimateSize(this.estimate); + selectHard() { + this.fee = Math.min(this.hardFee, CoinSelector.MAX_FEE); + this.fund(); + } +} - this.fee = this.getFee(size); +/** + * Default fee rate + * for coin selection. + * @const {Amount} + * @default + */ - if (this.maxFee > 0 && this.fee > this.maxFee) - throw new FundingError('Fee is too high.'); +CoinSelector.FEE_RATE = 10000; - // Failed to get enough funds, add more coins. - if (!this.isFull()) - this.fund(); - } while (!this.isFull() && this.index < this.coins.length); -}; +/** + * Minimum fee to start with + * during coin selection. + * @const {Amount} + * @default + */ + +CoinSelector.MIN_FEE = 10000; /** - * Initiate selection based on a hard fee. + * Maximum fee to allow + * after coin selection. + * @const {Amount} + * @default */ -CoinSelector.prototype.selectHard = function selectHard() { - this.fee = Math.min(this.hardFee, CoinSelector.MAX_FEE); - this.fund(); -}; +CoinSelector.MAX_FEE = consensus.COIN / 10; /** + * Funding Error * An error thrown from the coin selector. - * @constructor * @ignore * @extends Error - * @param {String} msg - * @param {Amount} available - * @param {Amount} required * @property {String} message - Error message. * @property {Amount} availableFunds * @property {Amount} requiredFunds */ -function FundingError(msg, available, required) { - Error.call(this); - - this.type = 'FundingError'; - this.message = msg; - this.availableFunds = -1; - this.requiredFunds = -1; +class FundingError extends Error { + /** + * Create a funding error. + * @constructor + * @param {String} msg + * @param {Amount} available + * @param {Amount} required + */ + + constructor(msg, available, required) { + super(); + + this.type = 'FundingError'; + this.message = msg; + this.availableFunds = -1; + this.requiredFunds = -1; + + if (available != null) { + this.message += ` (available=${Amount.btc(available)},`; + this.message += ` required=${Amount.btc(required)})`; + this.availableFunds = available; + this.requiredFunds = required; + } - if (available != null) { - this.message += ` (available=${Amount.btc(available)},`; - this.message += ` required=${Amount.btc(required)})`; - this.availableFunds = available; - this.requiredFunds = required; + if (Error.captureStackTrace) + Error.captureStackTrace(this, FundingError); } - - if (Error.captureStackTrace) - Error.captureStackTrace(this, FundingError); } -Object.setPrototypeOf(FundingError.prototype, Error.prototype); - /* * Helpers */ diff --git a/lib/primitives/netaddress.js b/lib/primitives/netaddress.js deleted file mode 100644 index 497b3c6d3..000000000 --- a/lib/primitives/netaddress.js +++ /dev/null @@ -1,481 +0,0 @@ -/*! - * netaddress.js - network address object for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const common = require('../net/common'); -const Network = require('../protocol/network'); -const util = require('../utils/util'); -const IP = require('../utils/ip'); -const StaticWriter = require('../utils/staticwriter'); -const BufferReader = require('../utils/reader'); - -/** - * Represents a network address. - * @alias module:primitives.NetAddress - * @constructor - * @param {Object} options - * @param {Number?} options.time - Timestamp. - * @param {Number?} options.services - Service bits. - * @param {String?} options.host - IP address (IPv6 or IPv4). - * @param {Number?} options.port - Port. - * @property {Host} host - * @property {Number} port - * @property {Number} services - * @property {Number} time - */ - -function NetAddress(options) { - if (!(this instanceof NetAddress)) - return new NetAddress(options); - - this.host = '0.0.0.0'; - this.port = 0; - this.services = 0; - this.time = 0; - this.hostname = '0.0.0.0:0'; - this.raw = IP.ZERO_IP; - - if (options) - this.fromOptions(options); -} - -/** - * Default services for - * unknown outbound peers. - * @const {Number} - * @default - */ - -NetAddress.DEFAULT_SERVICES = 0 - | common.services.NETWORK - | common.services.WITNESS - | common.services.BLOOM; - -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -NetAddress.prototype.fromOptions = function fromOptions(options) { - assert(typeof options.host === 'string'); - assert(typeof options.port === 'number'); - - this.raw = IP.toBuffer(options.host); - this.host = IP.toString(this.raw); - this.port = options.port; - - if (options.services) { - assert(typeof options.services === 'number'); - this.services = options.services; - } - - if (options.time) { - assert(typeof options.time === 'number'); - this.time = options.time; - } - - this.hostname = IP.toHostname(this.host, this.port); - - return this; -}; - -/** - * Instantiate network address from options. - * @param {Object} options - * @returns {NetAddress} - */ - -NetAddress.fromOptions = function fromOptions(options) { - return new NetAddress().fromOptions(options); -}; - -/** - * Test whether required services are available. - * @param {Number} services - * @returns {Boolean} - */ - -NetAddress.prototype.hasServices = function hasServices(services) { - return (this.services & services) === services; -}; - -/** - * Test whether the address is IPv4. - * @returns {Boolean} - */ - -NetAddress.isIPv4 = function isIPv4() { - return IP.isIPv4(this.raw); -}; - -/** - * Test whether the address is IPv6. - * @returns {Boolean} - */ - -NetAddress.isIPv6 = function isIPv6() { - return IP.isIPv6(this.raw); -}; - -/** - * Test whether the host is null. - * @returns {Boolean} - */ - -NetAddress.prototype.isNull = function isNull() { - return IP.isNull(this.raw); -}; - -/** - * Test whether the host is a local address. - * @returns {Boolean} - */ - -NetAddress.prototype.isLocal = function isLocal() { - return IP.isLocal(this.raw); -}; - -/** - * Test whether the host is valid. - * @returns {Boolean} - */ - -NetAddress.prototype.isValid = function isValid() { - return IP.isValid(this.raw); -}; - -/** - * Test whether the host is routable. - * @returns {Boolean} - */ - -NetAddress.prototype.isRoutable = function isRoutable() { - return IP.isRoutable(this.raw); -}; - -/** - * Test whether the host is an onion address. - * @returns {Boolean} - */ - -NetAddress.prototype.isOnion = function isOnion() { - return IP.isOnion(this.raw); -}; - -/** - * Compare against another network address. - * @returns {Boolean} - */ - -NetAddress.prototype.equal = function equal(addr) { - return this.compare(addr) === 0; -}; - -/** - * Compare against another network address. - * @returns {Number} - */ - -NetAddress.prototype.compare = function compare(addr) { - const cmp = this.raw.compare(addr.raw); - - if (cmp !== 0) - return cmp; - - return this.port - addr.port; -}; - -/** - * Get reachable score to destination. - * @param {NetAddress} dest - * @returns {Number} - */ - -NetAddress.prototype.getReachability = function getReachability(dest) { - return IP.getReachability(this.raw, dest.raw); -}; - -/** - * Set null host. - */ - -NetAddress.prototype.setNull = function setNull() { - this.raw = IP.ZERO_IP; - this.host = '0.0.0.0'; - this.hostname = IP.toHostname(this.host, this.port); -}; - -/** - * Set host. - * @param {String} host - */ - -NetAddress.prototype.setHost = function setHost(host) { - this.raw = IP.toBuffer(host); - this.host = IP.toString(this.raw); - this.hostname = IP.toHostname(this.host, this.port); -}; - -/** - * Set port. - * @param {Number} port - */ - -NetAddress.prototype.setPort = function setPort(port) { - assert(port >= 0 && port <= 0xffff); - this.port = port; - this.hostname = IP.toHostname(this.host, port); -}; - -/** - * Inject properties from host, port, and network. - * @private - * @param {String} host - * @param {Number} port - * @param {(Network|NetworkType)?} network - */ - -NetAddress.prototype.fromHost = function fromHost(host, port, network) { - network = Network.get(network); - - assert(port >= 0 && port <= 0xffff); - - this.raw = IP.toBuffer(host); - this.host = IP.toString(this.raw); - this.port = port; - this.services = NetAddress.DEFAULT_SERVICES; - this.time = network.now(); - - this.hostname = IP.toHostname(this.host, this.port); - - return this; -}; - -/** - * Instantiate a network address - * from a host and port. - * @param {String} host - * @param {Number} port - * @param {(Network|NetworkType)?} network - * @returns {NetAddress} - */ - -NetAddress.fromHost = function fromHost(host, port, network) { - return new NetAddress().fromHost(host, port, network); -}; - -/** - * Inject properties from hostname and network. - * @private - * @param {String} hostname - * @param {(Network|NetworkType)?} network - */ - -NetAddress.prototype.fromHostname = function fromHostname(hostname, network) { - network = Network.get(network); - - const addr = IP.fromHostname(hostname, network.port); - - return this.fromHost(addr.host, addr.port, network); -}; - -/** - * Instantiate a network address - * from a hostname (i.e. 127.0.0.1:8333). - * @param {String} hostname - * @param {(Network|NetworkType)?} network - * @returns {NetAddress} - */ - -NetAddress.fromHostname = function fromHostname(hostname, network) { - return new NetAddress().fromHostname(hostname, network); -}; - -/** - * Inject properties from socket. - * @private - * @param {net.Socket} socket - */ - -NetAddress.prototype.fromSocket = function fromSocket(socket, network) { - const host = socket.remoteAddress; - const port = socket.remotePort; - assert(typeof host === 'string'); - assert(typeof port === 'number'); - return this.fromHost(IP.normalize(host), port, network); -}; - -/** - * Instantiate a network address - * from a socket. - * @param {net.Socket} socket - * @returns {NetAddress} - */ - -NetAddress.fromSocket = function fromSocket(hostname, network) { - return new NetAddress().fromSocket(hostname, network); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - * @param {Boolean?} full - Include timestamp. - */ - -NetAddress.prototype.fromReader = function fromReader(br, full) { - this.time = full ? br.readU32() : 0; - this.services = br.readU32(); - - // Note: hi service bits - // are currently unused. - br.readU32(); - - this.raw = br.readBytes(16); - this.host = IP.toString(this.raw); - this.port = br.readU16BE(); - this.hostname = IP.toHostname(this.host, this.port); - - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @param {Boolean?} full - Include timestamp. - */ - -NetAddress.prototype.fromRaw = function fromRaw(data, full) { - return this.fromReader(new BufferReader(data), full); -}; - -/** - * Insantiate a network address from buffer reader. - * @param {BufferReader} br - * @param {Boolean?} full - Include timestamp. - * @returns {NetAddress} - */ - -NetAddress.fromReader = function fromReader(br, full) { - return new NetAddress().fromReader(br, full); -}; - -/** - * Insantiate a network address from serialized data. - * @param {Buffer} data - * @param {Boolean?} full - Include timestamp. - * @returns {NetAddress} - */ - -NetAddress.fromRaw = function fromRaw(data, full) { - return new NetAddress().fromRaw(data, full); -}; - -/** - * Write network address to a buffer writer. - * @param {BufferWriter} bw - * @param {Boolean?} full - Include timestamp. - * @returns {Buffer} - */ - -NetAddress.prototype.toWriter = function toWriter(bw, full) { - if (full) - bw.writeU32(this.time); - - bw.writeU32(this.services); - bw.writeU32(0); - bw.writeBytes(this.raw); - bw.writeU16BE(this.port); - - return bw; -}; - -/** - * Calculate serialization size of address. - * @returns {Number} - */ - -NetAddress.prototype.getSize = function getSize(full) { - return 26 + (full ? 4 : 0); -}; - -/** - * Serialize network address. - * @param {Boolean?} full - Include timestamp. - * @returns {Buffer} - */ - -NetAddress.prototype.toRaw = function toRaw(full) { - const size = this.getSize(full); - return this.toWriter(new StaticWriter(size), full).render(); -}; - -/** - * Convert net address to json-friendly object. - * @returns {Object} - */ - -NetAddress.prototype.toJSON = function toJSON() { - return { - host: this.host, - port: this.port, - services: this.services, - time: this.time - }; -}; - -/** - * Inject properties from json object. - * @private - * @param {Object} json - * @returns {NetAddress} - */ - -NetAddress.prototype.fromJSON = function fromJSON(json) { - assert(util.isU16(json.port)); - assert(util.isU32(json.services)); - assert(util.isU32(json.time)); - this.raw = IP.toBuffer(json.host); - this.host = json.host; - this.port = json.port; - this.services = json.services; - this.time = json.time; - this.hostname = IP.toHostname(this.host, this.port); - return this; -}; - -/** - * Instantiate net address from json object. - * @param {Object} json - * @returns {NetAddress} - */ - -NetAddress.fromJSON = function fromJSON(json) { - return new NetAddress().fromJSON(json); -}; - -/** - * Inspect the network address. - * @returns {Object} - */ - -NetAddress.prototype.inspect = function inspect() { - return ''; -}; - -/* - * Expose - */ - -module.exports = NetAddress; diff --git a/lib/primitives/outpoint.js b/lib/primitives/outpoint.js index 10292dd5d..8c3386f1b 100644 --- a/lib/primitives/outpoint.js +++ b/lib/primitives/outpoint.js @@ -6,336 +6,359 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); +const bio = require('bufio'); const util = require('../utils/util'); -const StaticWriter = require('../utils/staticwriter'); -const BufferReader = require('../utils/reader'); -const encoding = require('../utils/encoding'); +const consensus = require('../protocol/consensus'); +const {inspectSymbol} = require('../utils'); /** + * Outpoint * Represents a COutPoint. * @alias module:primitives.Outpoint - * @constructor - * @param {Hash?} hash - * @param {Number?} index * @property {Hash} hash * @property {Number} index */ -function Outpoint(hash, index) { - if (!(this instanceof Outpoint)) - return new Outpoint(hash, index); - - this.hash = encoding.NULL_HASH; - this.index = 0xffffffff; +class Outpoint { + /** + * Create an outpoint. + * @constructor + * @param {Hash?} hash + * @param {Number?} index + */ + + constructor(hash, index) { + this.hash = consensus.ZERO_HASH; + this.index = 0xffffffff; + + if (hash != null) { + assert(Buffer.isBuffer(hash)); + assert((index >>> 0) === index, 'Index must be a uint32.'); + this.hash = hash; + this.index = index; + } + } - if (hash != null) { - assert(typeof hash === 'string', 'Hash must be a string.'); - assert(util.isU32(index), 'Index must be a uint32.'); - this.hash = hash; - this.index = index; + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + assert(options, 'Outpoint data is required.'); + assert(Buffer.isBuffer(options.hash)); + assert((options.index >>> 0) === options.index, 'Index must be a uint32.'); + this.hash = options.hash; + this.index = options.index; + return this; } -} -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ + /** + * Instantate outpoint from options object. + * @param {Object} options + * @returns {Outpoint} + */ -Outpoint.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Outpoint data is required.'); - assert(typeof options.hash === 'string', 'Hash must be a string.'); - assert(util.isU32(options.index), 'Index must be a uint32.'); - this.hash = options.hash; - this.index = options.index; - return this; -}; + static fromOptions(options) { + return new this().fromOptions(options); + } -/** - * Instantate outpoint from options object. - * @param {Object} options - * @returns {Outpoint} - */ + /** + * Clone the outpoint. + * @returns {Outpoint} + */ -Outpoint.fromOptions = function fromOptions(options) { - return new Outpoint().fromOptions(options); -}; + clone() { + const outpoint = new this.constructor(); + outpoint.hash = this.hash; + outpoint.index = this.index; + return outpoint; + } -/** - * Clone the outpoint. - * @returns {Outpoint} - */ + /** + * Test equality against another outpoint. + * @param {Outpoint} prevout + * @returns {Boolean} + */ -Outpoint.prototype.clone = function clone() { - const outpoint = new Outpoint(); - outpoint.hash = this.value; - outpoint.index = this.index; - return outpoint; -}; + equals(prevout) { + assert(Outpoint.isOutpoint(prevout)); + return this.hash.equals(prevout.hash) + && this.index === prevout.index; + } -/** - * Test equality against another outpoint. - * @param {Outpoint} prevout - * @returns {Boolean} - */ + /** + * Compare against another outpoint (BIP69). + * @param {Outpoint} prevout + * @returns {Number} + */ -Outpoint.prototype.equals = function equals(prevout) { - assert(Outpoint.isOutpoint(prevout)); - return this.hash === prevout.hash - && this.index === prevout.index; -}; + compare(prevout) { + assert(Outpoint.isOutpoint(prevout)); -/** - * Compare against another outpoint (BIP69). - * @param {Outpoint} prevout - * @returns {Number} - */ + const cmp = strcmp(this.txid(), prevout.txid()); -Outpoint.prototype.compare = function compare(prevout) { - assert(Outpoint.isOutpoint(prevout)); + if (cmp !== 0) + return cmp; - const cmp = util.strcmp(this.txid(), prevout.txid()); + return this.index - prevout.index; + } - if (cmp !== 0) - return cmp; + /** + * Test whether the outpoint is null (hash of zeroes + * with max-u32 index). Used to detect coinbases. + * @returns {Boolean} + */ - return this.index - prevout.index; -}; + isNull() { + return this.index === 0xffffffff && this.hash.equals(consensus.ZERO_HASH); + } -/** - * Test whether the outpoint is null (hash of zeroes - * with max-u32 index). Used to detect coinbases. - * @returns {Boolean} - */ + /** + * Get little-endian hash. + * @returns {Hash} + */ -Outpoint.prototype.isNull = function isNull() { - return this.index === 0xffffffff && this.hash === encoding.NULL_HASH; -}; + rhash() { + return util.revHex(this.hash); + } -/** - * Get little-endian hash. - * @returns {Hash} - */ + /** + * Get little-endian hash. + * @returns {Hash} + */ -Outpoint.prototype.rhash = function rhash() { - return util.revHex(this.hash); -}; + txid() { + return this.rhash(); + } -/** - * Get little-endian hash. - * @returns {Hash} - */ + /** + * Serialize outpoint to a key + * suitable for a hash table. + * @returns {String} + */ -Outpoint.prototype.txid = function txid() { - return this.rhash(); -}; + toKey() { + return this.toRaw(); + } -/** - * Serialize outpoint to a key - * suitable for a hash table. - * @returns {String} - */ + /** + * Inject properties from hash table key. + * @private + * @param {String} key + * @returns {Outpoint} + */ + + fromKey(key) { + this.hash = key.slice(0, 32); + this.index = bio.readU32(key, 32); + return this; + } -Outpoint.prototype.toKey = function toKey() { - return Outpoint.toKey(this.hash, this.index); -}; + /** + * Instantiate outpoint from hash table key. + * @param {String} key + * @returns {Outpoint} + */ -/** - * Inject properties from hash table key. - * @private - * @param {String} key - * @returns {Outpoint} - */ + static fromKey(key) { + return new this().fromKey(key); + } -Outpoint.prototype.fromKey = function fromKey(key) { - assert(key.length > 64); - this.hash = key.slice(0, 64); - this.index = parseInt(key.slice(64), 10); - return this; -}; + /** + * Write outpoint to a buffer writer. + * @param {BufferWriter} bw + */ -/** - * Instantiate outpoint from hash table key. - * @param {String} key - * @returns {Outpoint} - */ + toWriter(bw) { + bw.writeHash(this.hash); + bw.writeU32(this.index); + return bw; + } -Outpoint.fromKey = function fromKey(key) { - return new Outpoint().fromKey(key); -}; + /** + * Calculate size of outpoint. + * @returns {Number} + */ -/** - * Write outpoint to a buffer writer. - * @param {BufferWriter} bw - */ + getSize() { + return 36; + } -Outpoint.prototype.toWriter = function toWriter(bw) { - bw.writeHash(this.hash); - bw.writeU32(this.index); - return bw; -}; + /** + * Serialize outpoint. + * @returns {Buffer} + */ -/** - * Calculate size of outpoint. - * @returns {Number} - */ + toRaw() { + return this.toWriter(bio.write(36)).render(); + } -Outpoint.prototype.getSize = function getSize() { - return 36; -}; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -/** - * Serialize outpoint. - * @returns {Buffer} - */ + fromReader(br) { + this.hash = br.readHash(); + this.index = br.readU32(); + return this; + } -Outpoint.prototype.toRaw = function toRaw() { - return this.toWriter(new StaticWriter(36)).render(); -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -Outpoint.prototype.fromReader = function fromReader(br) { - this.hash = br.readHash('hex'); - this.index = br.readU32(); - return this; -}; + /** + * Instantiate outpoint from a buffer reader. + * @param {BufferReader} br + * @returns {Outpoint} + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + static fromReader(br) { + return new this().fromReader(br); + } -Outpoint.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + /** + * Instantiate outpoint from serialized data. + * @param {Buffer} data + * @returns {Outpoint} + */ -/** - * Instantiate outpoint from a buffer reader. - * @param {BufferReader} br - * @returns {Outpoint} - */ + static fromRaw(data) { + return new this().fromRaw(data); + } -Outpoint.fromReader = function fromReader(br) { - return new Outpoint().fromReader(br); -}; + /** + * Inject properties from json object. + * @private + * @params {Object} json + */ + + fromJSON(json) { + assert(json, 'Outpoint data is required.'); + assert(typeof json.hash === 'string', 'Hash must be a string.'); + assert((json.index >>> 0) === json.index, 'Index must be a uint32.'); + this.hash = util.fromRev(json.hash); + this.index = json.index; + return this; + } -/** - * Instantiate outpoint from serialized data. - * @param {Buffer} data - * @returns {Outpoint} - */ + /** + * Convert the outpoint to an object suitable + * for JSON serialization. Note that the hash + * will be reversed to abide by bitcoind's legacy + * of little-endian uint256s. + * @returns {Object} + */ + + toJSON() { + return { + hash: util.revHex(this.hash), + index: this.index + }; + } -Outpoint.fromRaw = function fromRaw(data) { - return new Outpoint().fromRaw(data); -}; + /** + * Instantiate outpoint from json object. + * @param {Object} json + * @returns {Outpoint} + */ -/** - * Inject properties from json object. - * @private - * @params {Object} json - */ + static fromJSON(json) { + return new this().fromJSON(json); + } -Outpoint.prototype.fromJSON = function fromJSON(json) { - assert(json, 'Outpoint data is required.'); - assert(typeof json.hash === 'string', 'Hash must be a string.'); - assert(util.isU32(json.index), 'Index must be a uint32.'); - this.hash = util.revHex(json.hash); - this.index = json.index; - return this; -}; + /** + * Inject properties from tx. + * @private + * @param {TX} tx + * @param {Number} index + */ + + fromTX(tx, index) { + assert(tx); + assert(typeof index === 'number'); + assert(index >= 0); + this.hash = tx.hash(); + this.index = index; + return this; + } -/** - * Convert the outpoint to an object suitable - * for JSON serialization. Note that the hash - * will be reversed to abide by bitcoind's legacy - * of little-endian uint256s. - * @returns {Object} - */ + /** + * Instantiate outpoint from tx. + * @param {TX} tx + * @param {Number} index + * @returns {Outpoint} + */ -Outpoint.prototype.toJSON = function toJSON() { - return { - hash: util.revHex(this.hash), - index: this.index - }; -}; + static fromTX(tx, index) { + return new this().fromTX(tx, index); + } -/** - * Instantiate outpoint from json object. - * @param {Object} json - * @returns {Outpoint} - */ + /** + * Serialize outpoint to a key + * suitable for a hash table. + * @param {Hash} hash + * @param {Number} index + * @returns {String} + */ -Outpoint.fromJSON = function fromJSON(json) { - return new Outpoint().fromJSON(json); -}; + static toKey(hash, index) { + return new Outpoint(hash, index).toKey(); + } -/** - * Inject properties from tx. - * @private - * @param {TX} tx - * @param {Number} index - */ + /** + * Convert the outpoint to a user-friendly string. + * @returns {String} + */ -Outpoint.prototype.fromTX = function fromTX(tx, index) { - assert(tx); - assert(typeof index === 'number'); - assert(index >= 0); - this.hash = tx.hash('hex'); - this.index = index; - return this; -}; + [inspectSymbol]() { + return ``; + } -/** - * Instantiate outpoint from tx. - * @param {TX} tx - * @param {Number} index - * @returns {Outpoint} - */ + /** + * Test an object to see if it is an outpoint. + * @param {Object} obj + * @returns {Boolean} + */ -Outpoint.fromTX = function fromTX(tx, index) { - return new Outpoint().fromTX(tx, index); -}; + static isOutpoint(obj) { + return obj instanceof Outpoint; + } +} -/** - * Serialize outpoint to a key - * suitable for a hash table. - * @param {Hash} hash - * @param {Number} index - * @returns {String} +/* + * Helpers */ -Outpoint.toKey = function toKey(hash, index) { - assert(typeof hash === 'string'); - assert(hash.length === 64); - assert(index >= 0); - return hash + index; -}; +function strcmp(a, b) { + const len = Math.min(a.length, b.length); -/** - * Convert the outpoint to a user-friendly string. - * @returns {String} - */ + for (let i = 0; i < len; i++) { + if (a[i] < b[i]) + return -1; + if (a[i] > b[i]) + return 1; + } -Outpoint.prototype.inspect = function inspect() { - return ``; -}; + if (a.length < b.length) + return -1; -/** - * Test an object to see if it is an outpoint. - * @param {Object} obj - * @returns {Boolean} - */ + if (a.length > b.length) + return 1; -Outpoint.isOutpoint = function isOutpoint(obj) { - return obj instanceof Outpoint; -}; + return 0; +} /* * Expose diff --git a/lib/primitives/output.js b/lib/primitives/output.js index 4d21f035a..c24c11185 100644 --- a/lib/primitives/output.js +++ b/lib/primitives/output.js @@ -7,369 +7,374 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); +const assert = require('bsert'); +const bio = require('bufio'); const Amount = require('../btc/amount'); const Network = require('../protocol/network'); const Address = require('../primitives/address'); const Script = require('../script/script'); -const StaticWriter = require('../utils/staticwriter'); -const BufferReader = require('../utils/reader'); const consensus = require('../protocol/consensus'); const policy = require('../protocol/policy'); +const {inspectSymbol} = require('../utils'); /** * Represents a transaction output. * @alias module:primitives.Output - * @constructor - * @param {NakedOutput} options - * @property {Amount} value - Value in satoshis. + * @property {Amount} value * @property {Script} script */ -function Output(options) { - if (!(this instanceof Output)) - return new Output(options); +class Output { + /** + * Create an output. + * @constructor + * @param {Object?} options + */ - this.value = 0; - this.script = new Script(); + constructor(options) { + this.value = 0; + this.script = new Script(); - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options object. - * @private - * @param {NakedOutput} options - */ - -Output.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Output data is required.'); - - if (options.value) { - assert(util.isU64(options.value), 'Value must be a uint64.'); - this.value = options.value; + if (options) + this.fromOptions(options); } - if (options.script) - this.script.fromOptions(options.script); + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ - if (options.address) - this.script.fromAddress(options.address); + fromOptions(options) { + assert(options, 'Output data is required.'); - return this; -}; + if (options.value) { + assert(Number.isSafeInteger(options.value) && options.value >= 0, + 'Value must be a uint64.'); + this.value = options.value; + } -/** - * Instantiate output from options object. - * @param {NakedOutput} options - * @returns {Output} - */ + if (options.script) + this.script.fromOptions(options.script); -Output.fromOptions = function fromOptions(options) { - return new Output().fromOptions(options); -}; + if (options.address) + this.script.fromAddress(options.address); -/** - * Inject properties from script/value pair. - * @private - * @param {Script|Address} script - * @param {Amount} value - * @returns {Output} - */ + return this; + } -Output.prototype.fromScript = function fromScript(script, value) { - if (typeof script === 'string') - script = Address.fromString(script); + /** + * Instantiate output from options object. + * @param {Object} options + * @returns {Output} + */ - if (script instanceof Address) - script = Script.fromAddress(script); + static fromOptions(options) { + return new this().fromOptions(options); + } - assert(script instanceof Script, 'Script must be a Script.'); - assert(util.isU64(value), 'Value must be a uint64.'); + /** + * Inject properties from script/value pair. + * @private + * @param {Script|Address} script + * @param {Amount} value + * @returns {Output} + */ - this.script = script; - this.value = value; + fromScript(script, value) { + if (typeof script === 'string') + script = Address.fromString(script); - return this; -}; + if (script instanceof Address) + script = Script.fromAddress(script); -/** - * Instantiate output from script/value pair. - * @param {Script|Address} script - * @param {Amount} value - * @returns {Output} - */ + assert(script instanceof Script, 'Script must be a Script.'); + assert(Number.isSafeInteger(value) && value >= 0, + 'Value must be a uint64.'); -Output.fromScript = function fromScript(script, value) { - return new Output().fromScript(script, value); -}; + this.script = script; + this.value = value; -/** - * Clone the output. - * @returns {Output} - */ + return this; + } -Output.prototype.clone = function clone() { - const output = new Output(); - output.value = this.value; - output.script.inject(this.script); - return output; -}; + /** + * Instantiate output from script/value pair. + * @param {Script|Address} script + * @param {Amount} value + * @returns {Output} + */ -/** - * Test equality against another output. - * @param {Output} output - * @returns {Boolean} - */ + static fromScript(script, value) { + return new this().fromScript(script, value); + } -Output.prototype.equals = function equals(output) { - assert(Output.isOutput(output)); - return this.value === output.value - && this.script.equals(output.script); -}; + /** + * Clone the output. + * @returns {Output} + */ -/** - * Compare against another output (BIP69). - * @param {Output} output - * @returns {Number} - */ + clone() { + const output = new this.constructor(); + output.value = this.value; + output.script.inject(this.script); + return output; + } -Output.prototype.compare = function compare(output) { - assert(Output.isOutput(output)); + /** + * Test equality against another output. + * @param {Output} output + * @returns {Boolean} + */ - const cmp = this.value - output.value; + equals(output) { + assert(Output.isOutput(output)); + return this.value === output.value + && this.script.equals(output.script); + } - if (cmp !== 0) - return cmp; + /** + * Compare against another output (BIP69). + * @param {Output} output + * @returns {Number} + */ - return this.script.compare(output.script); -}; + compare(output) { + assert(Output.isOutput(output)); -/** - * Get the script type as a string. - * @returns {ScriptType} type - */ + const cmp = this.value - output.value; -Output.prototype.getType = function getType() { - return Script.typesByVal[this.script.getType()].toLowerCase(); -}; + if (cmp !== 0) + return cmp; -/** - * Get the address. - * @returns {Address} address - */ + return this.script.compare(output.script); + } -Output.prototype.getAddress = function getAddress() { - return this.script.getAddress(); -}; + /** + * Get the script type as a string. + * @returns {ScriptType} type + */ -/** - * Get the address hash. - * @param {String?} enc - * @returns {Hash} hash - */ + getType() { + return Script.typesByVal[this.script.getType()].toLowerCase(); + } -Output.prototype.getHash = function getHash(enc) { - const addr = this.getAddress(); + /** + * Get the address. + * @returns {Address} address + */ - if (!addr) - return null; + getAddress() { + return this.script.getAddress(); + } - return addr.getHash(enc); -}; + /** + * Get the address hash. + * @param {String?} enc + * @returns {Hash} hash + */ -/** - * Convert the input to a more user-friendly object. - * @returns {Object} - */ + getHash(enc) { + const addr = this.getAddress(); -Output.prototype.inspect = function inspect() { - return { - type: this.getType(), - value: Amount.btc(this.value), - script: this.script, - address: this.getAddress() - }; -}; + if (!addr) + return null; -/** - * Convert the output to an object suitable - * for JSON serialization. - * @returns {Object} - */ + return addr.getHash(enc); + } -Output.prototype.toJSON = function toJSON() { - return this.getJSON(); -}; + /** + * Convert the input to a more user-friendly object. + * @returns {Object} + */ + + [inspectSymbol]() { + return { + type: this.getType(), + value: Amount.btc(this.value), + script: this.script, + address: this.getAddress() + }; + } -/** - * Convert the output to an object suitable - * for JSON serialization. - * @param {Network} network - * @returns {Object} - */ + /** + * Convert the output to an object suitable + * for JSON serialization. + * @returns {Object} + */ -Output.prototype.getJSON = function getJSON(network) { - let addr = this.getAddress(); + toJSON() { + return this.getJSON(); + } - network = Network.get(network); + /** + * Convert the output to an object suitable + * for JSON serialization. + * @param {Network} network + * @returns {Object} + */ - if (addr) - addr = addr.toString(network); + getJSON(network) { + let addr = this.getAddress(); - return { - value: this.value, - script: this.script.toJSON(), - address: addr - }; -}; + network = Network.get(network); -/** - * Calculate the dust threshold for this - * output, based on serialize size and rate. - * @param {Rate?} rate - * @returns {Amount} - */ + if (addr) + addr = addr.toString(network); -Output.prototype.getDustThreshold = function getDustThreshold(rate) { - const scale = consensus.WITNESS_SCALE_FACTOR; + return { + value: this.value, + script: this.script.toJSON(), + address: addr + }; + } - if (this.script.isUnspendable()) - return 0; + /** + * Calculate the dust threshold for this + * output, based on serialize size and rate. + * @param {Rate?} rate + * @returns {Amount} + */ - let size = this.getSize(); + getDustThreshold(rate) { + const scale = consensus.WITNESS_SCALE_FACTOR; - if (this.script.isProgram()) { - // 75% segwit discount applied to script size. - size += 32 + 4 + 1 + (107 / scale | 0) + 4; - } else { - size += 32 + 4 + 1 + 107 + 4; - } + if (this.script.isUnspendable()) + return 0; - return 3 * policy.getMinFee(size, rate); -}; + let size = this.getSize(); -/** - * Calculate size of serialized output. - * @returns {Number} - */ + if (this.script.isProgram()) { + // 75% segwit discount applied to script size. + size += 32 + 4 + 1 + (107 / scale | 0) + 4; + } else { + size += 32 + 4 + 1 + 107 + 4; + } -Output.prototype.getSize = function getSize() { - return 8 + this.script.getVarSize(); -}; + return 3 * policy.getMinFee(size, rate); + } -/** - * Test whether the output should be considered dust. - * @param {Rate?} rate - * @returns {Boolean} - */ + /** + * Calculate size of serialized output. + * @returns {Number} + */ -Output.prototype.isDust = function isDust(rate) { - return this.value < this.getDustThreshold(rate); -}; + getSize() { + return 8 + this.script.getVarSize(); + } -/** - * Inject properties from a JSON object. - * @private - * @param {Object} json - */ + /** + * Test whether the output should be considered dust. + * @param {Rate?} rate + * @returns {Boolean} + */ -Output.prototype.fromJSON = function fromJSON(json) { - assert(json, 'Output data is required.'); - assert(util.isU64(json.value), 'Value must be a uint64.'); - this.value = json.value; - this.script.fromJSON(json.script); - return this; -}; + isDust(rate) { + return this.value < this.getDustThreshold(rate); + } -/** - * Instantiate an Output from a jsonified output object. - * @param {Object} json - The jsonified output object. - * @returns {Output} - */ + /** + * Inject properties from a JSON object. + * @private + * @param {Object} json + */ + + fromJSON(json) { + assert(json, 'Output data is required.'); + assert(Number.isSafeInteger(json.value) && json.value >= 0, + 'Value must be a uint64.'); + this.value = json.value; + this.script.fromJSON(json.script); + return this; + } -Output.fromJSON = function fromJSON(json) { - return new Output().fromJSON(json); -}; + /** + * Instantiate an Output from a jsonified output object. + * @param {Object} json - The jsonified output object. + * @returns {Output} + */ -/** - * Write the output to a buffer writer. - * @param {BufferWriter} bw - */ + static fromJSON(json) { + return new this().fromJSON(json); + } -Output.prototype.toWriter = function toWriter(bw) { - bw.writeI64(this.value); - bw.writeVarBytes(this.script.toRaw()); - return bw; -}; + /** + * Write the output to a buffer writer. + * @param {BufferWriter} bw + */ -/** - * Serialize the output. - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {Buffer|String} - */ + toWriter(bw) { + bw.writeI64(this.value); + bw.writeVarBytes(this.script.toRaw()); + return bw; + } -Output.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + /** + * Serialize the output. + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {Buffer|String} + */ -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } -Output.prototype.fromReader = function fromReader(br) { - this.value = br.readI64(); - this.script.fromRaw(br.readVarBytes()); - return this; -}; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + fromReader(br) { + this.value = br.readI64(); + this.script.fromRaw(br.readVarBytes()); + return this; + } -Output.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -/** - * Instantiate an output from a buffer reader. - * @param {BufferReader} br - * @returns {Output} - */ + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -Output.fromReader = function fromReader(br) { - return new Output().fromReader(br); -}; + /** + * Instantiate an output from a buffer reader. + * @param {BufferReader} br + * @returns {Output} + */ -/** - * Instantiate an output from a serialized Buffer. - * @param {Buffer} data - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {Output} - */ + static fromReader(br) { + return new this().fromReader(br); + } -Output.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new Output().fromRaw(data); -}; + /** + * Instantiate an output from a serialized Buffer. + * @param {Buffer} data + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {Output} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } -/** - * Test an object to see if it is an Output. - * @param {Object} obj - * @returns {Boolean} - */ + /** + * Test an object to see if it is an Output. + * @param {Object} obj + * @returns {Boolean} + */ -Output.isOutput = function isOutput(obj) { - return obj instanceof Output; -}; + static isOutput(obj) { + return obj instanceof Output; + } +} /* * Expose diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index 5a99f4f56..5882f79ef 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -7,1712 +7,1650 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); +const bio = require('bufio'); +const hash256 = require('bcrypto/lib/hash256'); +const secp256k1 = require('bcrypto/lib/secp256k1'); +const {BufferSet} = require('buffer-map'); const util = require('../utils/util'); -const encoding = require('../utils/encoding'); -const digest = require('../crypto/digest'); -const secp256k1 = require('../crypto/secp256k1'); const Amount = require('../btc/amount'); const Network = require('../protocol/network'); const Script = require('../script/script'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); const Input = require('./input'); const Output = require('./output'); const Outpoint = require('./outpoint'); const InvItem = require('./invitem'); -const Bloom = require('../utils/bloom'); const consensus = require('../protocol/consensus'); const policy = require('../protocol/policy'); const ScriptError = require('../script/scripterror'); -const hashType = Script.hashType; +const {encoding} = bio; +const {hashType} = Script; +const {inspectSymbol} = require('../utils'); /** + * TX * A static transaction object. * @alias module:primitives.TX - * @constructor - * @param {Object} options - Transaction fields. - * @property {Number} version - Transaction version. Note that Bcoin reads - * versions as unsigned even though they are signed at the protocol level. - * This value will never be negative. - * @property {Number} flag - Flag field for segregated witness. - * Always non-zero (1 if not present). + * @property {Number} version * @property {Input[]} inputs * @property {Output[]} outputs - * @property {Number} locktime - nLockTime + * @property {Number} locktime */ -function TX(options) { - if (!(this instanceof TX)) - return new TX(options); +class TX { + /** + * Create a transaction. + * @constructor + * @param {Object?} options + */ - // Default version to 2 for Flo - this.version = 2; - this.inputs = []; - this.outputs = []; - this.locktime = 0; + constructor(options) { + this.version = 2; + this.inputs = []; + this.outputs = []; + this.locktime = 0; - this.strFloData = ""; + this.strFloData = ""; - this.mutable = false; + this.mutable = false; - this._hash = null; - this._hhash = null; - this._whash = null; + this._hash = null; + this._hhash = null; + this._whash = null; - this._raw = null; - this._size = -1; - this._witness = -1; - this._sigops = -1; + this._raw = null; + this._offset = -1; + this._block = false; + this._size = -1; + this._witness = -1; + this._sigops = -1; - this._hashPrevouts = null; - this._hashSequence = null; - this._hashOutputs = null; + this._hashPrevouts = null; + this._hashSequence = null; + this._hashOutputs = null; - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options object. - * @private - * @param {NakedTX} options - */ - -TX.prototype.fromOptions = function fromOptions(options) { - assert(options, 'TX data is required.'); - - if (options.version != null) { - assert(util.isU32(options.version), 'Version must be a uint32.'); - this.version = options.version; + if (options) + this.fromOptions(options); } - if (options.inputs) { - assert(Array.isArray(options.inputs), 'Inputs must be an array.'); - for (const input of options.inputs) - this.inputs.push(new Input(input)); - } + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ - if (options.outputs) { - assert(Array.isArray(options.outputs), 'Outputs must be an array.'); - for (const output of options.outputs) - this.outputs.push(new Output(output)); - } - - if (options.locktime != null) { - assert(util.isU32(options.locktime), 'Locktime must be a uint32.'); - this.locktime = options.locktime; - } + fromOptions(options) { + assert(options, 'TX data is required.'); - return this; -}; + if (options.version != null) { + assert((options.version >>> 0) === options.version, + 'Version must be a uint32.'); + this.version = options.version; + } -/** - * Instantiate TX from options object. - * @param {NakedTX} options - * @returns {TX} - */ + if (options.inputs) { + assert(Array.isArray(options.inputs), 'Inputs must be an array.'); + for (const input of options.inputs) + this.inputs.push(new Input(input)); + } -TX.fromOptions = function fromOptions(options) { - return new TX().fromOptions(options); -}; + if (options.outputs) { + assert(Array.isArray(options.outputs), 'Outputs must be an array.'); + for (const output of options.outputs) + this.outputs.push(new Output(output)); + } -/** - * Clone the transaction. - * @returns {TX} - */ + if (options.locktime != null) { + assert((options.locktime >>> 0) === options.locktime, + 'Locktime must be a uint32.'); + this.locktime = options.locktime; + } -TX.prototype.clone = function clone() { - return new TX().inject(this); -}; + return this; + } -/** - * Inject properties from tx. - * Used for cloning. - * @private - * @param {TX} tx - * @returns {TX} - */ + /** + * Instantiate TX from options object. + * @param {Object} options + * @returns {TX} + */ -TX.prototype.inject = function inject(tx) { - this.version = tx.version; + static fromOptions(options) { + return new this().fromOptions(options); + } - for (const input of tx.inputs) - this.inputs.push(input.clone()); + /** + * Clone the transaction. + * @returns {TX} + */ - for (const output of tx.outputs) - this.outputs.push(output.clone()); + clone() { + return new this.constructor().inject(this); + } - this.strFloData = tx.strFloData; + /** + * Inject properties from tx. + * Used for cloning. + * @private + * @param {TX} tx + * @returns {TX} + */ - this.locktime = tx.locktime; + inject(tx) { + this.version = tx.version; - return this; -}; + for (const input of tx.inputs) + this.inputs.push(input.clone()); -/** - * Clear any cached values. - */ + for (const output of tx.outputs) + this.outputs.push(output.clone()); -TX.prototype.refresh = function refresh() { - this._hash = null; - this._hhash = null; - this._whash = null; + this.strFloData = tx.strFloData; - this._raw = null; - this._size = -1; - this._witness = -1; - this._sigops = -1; + this.locktime = tx.locktime; - this._hashPrevouts = null; - this._hashSequence = null; - this._hashOutputs = null; -}; + return this; + } -/** - * Hash the transaction with the non-witness serialization. - * @param {String?} enc - Can be `'hex'` or `null`. - * @returns {Hash|Buffer} hash - */ + /** + * Clear any cached values. + */ -TX.prototype.hash = function hash(enc) { - let h = this._hash; + refresh() { + this._hash = null; + this._hhash = null; + this._whash = null; - if (!h) { - h = digest.hash256(this.toNormal()); - if (!this.mutable) - this._hash = h; - } + this._raw = null; + this._size = -1; + this._offset = -1; + this._block = false; + this._witness = -1; + this._sigops = -1; - if (enc === 'hex') { - let hex = this._hhash; - if (!hex) { - hex = h.toString('hex'); - if (!this.mutable) - this._hhash = hex; - } - h = hex; + this._hashPrevouts = null; + this._hashSequence = null; + this._hashOutputs = null; } - return h; -}; + /** + * Hash the transaction with the non-witness serialization. + * @param {String?} enc - Can be `'hex'` or `null`. + * @returns {Hash|Buffer} hash + */ -/** - * Hash the transaction with the witness - * serialization, return the wtxid (normal - * hash if no witness is present, all zeroes - * if coinbase). - * @param {String?} enc - Can be `'hex'` or `null`. - * @returns {Hash|Buffer} hash - */ + hash(enc) { + let h = this._hash; -TX.prototype.witnessHash = function witnessHash(enc) { - if (!this.hasWitness()) - return this.hash(enc); + if (!h) { + h = hash256.digest(this.toNormal()); + if (!this.mutable) + this._hash = h; + } - let hash = this._whash; + if (enc === 'hex') { + let hex = this._hhash; + if (!hex) { + hex = h.toString('hex'); + if (!this.mutable) + this._hhash = hex; + } + h = hex; + } - if (!hash) { - hash = digest.hash256(this.toRaw()); - if (!this.mutable) - this._whash = hash; + return h; } - return enc === 'hex' ? hash.toString('hex') : hash; -}; + /** + * Hash the transaction with the witness + * serialization, return the wtxid (normal + * hash if no witness is present, all zeroes + * if coinbase). + * @param {String?} enc - Can be `'hex'` or `null`. + * @returns {Hash|Buffer} hash + */ -/** - * Serialize the transaction. Note - * that this is cached. This will use - * the witness serialization if a - * witness is present. - * @returns {Buffer} Serialized transaction. - */ + witnessHash(enc) { + if (!this.hasWitness()) + return this.hash(enc); -TX.prototype.toRaw = function toRaw() { - return this.frame().data; -}; + let hash = this._whash; -/** - * Serialize the transaction without the - * witness vector, regardless of whether it - * is a witness transaction or not. - * @returns {Buffer} Serialized transaction. - */ + if (!hash) { + hash = hash256.digest(this.toRaw()); + if (!this.mutable) + this._whash = hash; + } -TX.prototype.toNormal = function toNormal() { - if (this.hasWitness()) - return this.frameNormal().data; - return this.toRaw(); -}; + return enc === 'hex' ? hash.toString('hex') : hash; + } -/** - * Write the transaction to a buffer writer. - * @param {BufferWriter} bw - */ + /** + * Serialize the transaction. Note + * that this is cached. This will use + * the witness serialization if a + * witness is present. + * @returns {Buffer} Serialized transaction. + */ -TX.prototype.toWriter = function toWriter(bw) { - if (this.mutable) { - if (this.hasWitness()) - return this.writeWitness(bw); - return this.writeNormal(bw); + toRaw() { + return this.frame().data; } - bw.writeBytes(this.toRaw()); + /** + * Serialize the transaction without the + * witness vector, regardless of whether it + * is a witness transaction or not. + * @returns {Buffer} Serialized transaction. + */ - return bw; -}; + toNormal() { + if (this.hasWitness()) + return this.frameNormal().data; + return this.toRaw(); + } -/** - * Write the transaction to a buffer writer. - * Uses non-witness serialization. - * @param {BufferWriter} bw - */ + /** + * Write the transaction to a buffer writer. + * @param {BufferWriter} bw + * @param {Boolean} block + */ -TX.prototype.toNormalWriter = function toNormalWriter(bw) { - if (this.hasWitness()) { - this.writeNormal(bw); - return bw; - } - return this.toWriter(bw); -}; + toWriter(bw, block) { + if (this.mutable) { + if (this.hasWitness()) + return this.writeWitness(bw); + return this.writeNormal(bw); + } -/** - * Serialize the transaction. Note - * that this is cached. This will use - * the witness serialization if a - * witness is present. - * @private - * @returns {RawTX} - */ + if (block) { + this._offset = bw.offset; + this._block = true; + } -TX.prototype.frame = function frame() { - if (this.mutable) { - assert(!this._raw); - if (this.hasWitness()) - return this.frameWitness(); - return this.frameNormal(); - } + bw.writeBytes(this.toRaw()); - if (this._raw) { - assert(this._size >= 0); - assert(this._witness >= 0); - const raw = new RawTX(this._size, this._witness); - raw.data = this._raw; - return raw; + return bw; } - let raw; - if (this.hasWitness()) - raw = this.frameWitness(); - else - raw = this.frameNormal(); - - this._raw = raw.data; - this._size = raw.size; - this._witness = raw.witness; + /** + * Write the transaction to a buffer writer. + * Uses non-witness serialization. + * @param {BufferWriter} bw + */ - return raw; -}; + toNormalWriter(bw) { + if (this.hasWitness()) { + this.writeNormal(bw); + return bw; + } + return this.toWriter(bw); + } + + /** + * Serialize the transaction. Note + * that this is cached. This will use + * the witness serialization if a + * witness is present. + * @private + * @returns {RawTX} + */ + + frame() { + if (this.mutable) { + assert(!this._raw); + if (this.hasWitness()) + return this.frameWitness(); + return this.frameNormal(); + } -/** - * Calculate total size and size of the witness bytes. - * @returns {Object} Contains `size` and `witness`. - */ + if (this._raw) { + assert(this._size >= 0); + assert(this._witness >= 0); + const raw = new RawTX(this._size, this._witness); + raw.data = this._raw; + return raw; + } -TX.prototype.getSizes = function getSizes() { - if (this.mutable) { + let raw; if (this.hasWitness()) - return this.getWitnessSizes(); - return this.getNormalSizes(); - } - return this.frame(); -}; + raw = this.frameWitness(); + else + raw = this.frameNormal(); -/** - * Calculate the virtual size of the transaction. - * Note that this is cached. - * @returns {Number} vsize - */ + this._raw = raw.data; + this._size = raw.size; + this._witness = raw.witness; -TX.prototype.getVirtualSize = function getVirtualSize() { - const scale = consensus.WITNESS_SCALE_FACTOR; - return (this.getWeight() + scale - 1) / scale | 0; -}; + return raw; + } -/** - * Calculate the virtual size of the transaction - * (weighted against bytes per sigop cost). - * @param {Number} sigops - Sigops cost. - * @returns {Number} vsize - */ + /** + * Return the offset and size of the transaction. Useful + * when the transaction is deserialized within a block. + * @returns {Object} Contains `size` and `offset`. + */ -TX.prototype.getSigopsSize = function getSigopsSize(sigops) { - const scale = consensus.WITNESS_SCALE_FACTOR; - const bytes = policy.BYTES_PER_SIGOP; - const weight = Math.max(this.getWeight(), sigops * bytes); - return (weight + scale - 1) / scale | 0; -}; + getPosition() { + assert(this._block && this._offset > 80, 'Position not available.'); -/** - * Calculate the weight of the transaction. - * Note that this is cached. - * @returns {Number} weight - */ + return { + offset: this._offset, + size: this._size + }; + } -TX.prototype.getWeight = function getWeight() { - const raw = this.getSizes(); - const base = raw.size - raw.witness; - return base * (consensus.WITNESS_SCALE_FACTOR - 1) + raw.size; -}; + /** + * Calculate total size and size of the witness bytes. + * @returns {Object} Contains `size` and `witness`. + */ -/** - * Calculate the real size of the transaction - * with the witness included. - * @returns {Number} size - */ + getSizes() { + if (this.mutable) { + if (this.hasWitness()) + return this.getWitnessSizes(); + return this.getNormalSizes(); + } + return this.frame(); + } -TX.prototype.getSize = function getSize() { - return this.getSizes().size; -}; + /** + * Calculate the virtual size of the transaction. + * Note that this is cached. + * @returns {Number} vsize + */ -/** - * Calculate the size of the transaction - * without the witness. - * with the witness included. - * @returns {Number} size - */ + getVirtualSize() { + const scale = consensus.WITNESS_SCALE_FACTOR; + return (this.getWeight() + scale - 1) / scale | 0; + } -TX.prototype.getBaseSize = function getBaseSize() { - const raw = this.getSizes(); - return raw.size - raw.witness; -}; + /** + * Calculate the virtual size of the transaction + * (weighted against bytes per sigop cost). + * @param {Number} sigops - Sigops cost. + * @returns {Number} vsize + */ -/** - * Test whether the transaction has a non-empty witness. - * @returns {Boolean} - */ + getSigopsSize(sigops) { + const scale = consensus.WITNESS_SCALE_FACTOR; + const bytes = policy.BYTES_PER_SIGOP; + const weight = Math.max(this.getWeight(), sigops * bytes); + return (weight + scale - 1) / scale | 0; + } -TX.prototype.hasWitness = function hasWitness() { - if (this._witness !== -1) - return this._witness !== 0; + /** + * Calculate the weight of the transaction. + * Note that this is cached. + * @returns {Number} weight + */ - for (const input of this.inputs) { - if (input.witness.items.length > 0) - return true; + getWeight() { + const raw = this.getSizes(); + const base = raw.size - raw.witness; + return base * (consensus.WITNESS_SCALE_FACTOR - 1) + raw.size; } - return false; -}; + /** + * Calculate the real size of the transaction + * with the witness included. + * @returns {Number} size + */ -/** - * Get the signature hash of the transaction for signing verifying. - * @param {Number} index - Index of input being signed/verified. - * @param {Script} prev - Previous output script or redeem script - * (in the case of witnesspubkeyhash, this should be the generated - * p2pkh script). - * @param {Amount} value - Previous output value. - * @param {SighashType} type - Sighash type. - * @param {Number} version - Sighash version (0=legacy, 1=segwit). - * @returns {Buffer} Signature hash. - */ + getSize() { + return this.getSizes().size; + } -TX.prototype.signatureHash = function signatureHash(index, prev, value, type, version) { - assert(index >= 0 && index < this.inputs.length); - assert(prev instanceof Script); - assert(typeof value === 'number'); - assert(typeof type === 'number'); + /** + * Calculate the size of the transaction + * without the witness. + * with the witness included. + * @returns {Number} size + */ - // Traditional sighashing - if (version === 0) - return this.signatureHashV0(index, prev, type); + getBaseSize() { + const raw = this.getSizes(); + return raw.size - raw.witness; + } - // Segwit sighashing - if (version === 1) - return this.signatureHashV1(index, prev, value, type); + /** + * Test whether the transaction has a non-empty witness. + * @returns {Boolean} + */ - throw new Error('Unknown sighash version.'); -}; + hasWitness() { + if (this._witness !== -1) + return this._witness !== 0; -/** - * Legacy sighashing -- O(n^2). - * @private - * @param {Number} index - * @param {Script} prev - * @param {SighashType} type - * @returns {Buffer} - */ + for (const input of this.inputs) { + if (input.witness.items.length > 0) + return true; + } -TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) { - if ((type & 0x1f) === hashType.SINGLE) { - // Bitcoind used to return 1 as an error code: - // it ended up being treated like a hash. - if (index >= this.outputs.length) - return Buffer.from(encoding.ONE_HASH); + return false; } - // Remove all code separators. - prev = prev.removeSeparators(); + /** + * Get the signature hash of the transaction for signing verifying. + * @param {Number} index - Index of input being signed/verified. + * @param {Script} prev - Previous output script or redeem script + * (in the case of witnesspubkeyhash, this should be the generated + * p2pkh script). + * @param {Amount} value - Previous output value. + * @param {SighashType} type - Sighash type. + * @param {Number} version - Sighash version (0=legacy, 1=segwit). + * @param {Boolean} [includeFloData=true] - Should floData be included in the signature Hash + * @returns {Buffer} Signature hash. + */ + + signatureHash(index, prev, value, type, version, includeFloData = true) { + assert(index >= 0 && index < this.inputs.length); + assert(prev instanceof Script); + assert(typeof value === 'number'); + assert(typeof type === 'number'); + + // Traditional sighashing + if (version === 0) + return this.signatureHashV0(index, prev, type, includeFloData); + + // Segwit sighashing + if (version === 1) + return this.signatureHashV1(index, prev, value, type, includeFloData); + + throw new Error('Unknown sighash version.'); + } + + /** + * Legacy sighashing -- O(n^2). + * @private + * @param {Number} index + * @param {Script} prev + * @param {SighashType} type + * @param {Boolean} [includeFloData=true] - Should floData be included in the signature Hash + * @returns {Buffer} + */ + + signatureHashV0(index, prev, type, includeFloData = true) { + if ((type & 0x1f) === hashType.SINGLE) { + // Bitcoind used to return 1 as an error code: + // it ended up being treated like a hash. + if (index >= this.outputs.length) { + const hash = Buffer.alloc(32, 0x00); + hash[0] = 0x01; + return hash; + } + } - // Calculate buffer size. - const size = this.hashSize(index, prev, type); - const bw = StaticWriter.pool(size); + // Remove all code separators. + prev = prev.removeSeparators(); - bw.writeU32(this.version); + // Calculate buffer size. + const size = this.hashSize(index, prev, type, includeFloData); + const bw = bio.pool(size); - // Serialize inputs. - if (type & hashType.ANYONECANPAY) { - // Serialize only the current - // input if ANYONECANPAY. - const input = this.inputs[index]; + bw.writeU32(this.version); - // Count. - bw.writeVarint(1); + // Serialize inputs. + if (type & hashType.ANYONECANPAY) { + // Serialize only the current + // input if ANYONECANPAY. + const input = this.inputs[index]; - // Outpoint. - input.prevout.toWriter(bw); - - // Replace script with previous - // output script if current index. - bw.writeVarBytes(prev.toRaw()); - bw.writeU32(input.sequence); - } else { - bw.writeVarint(this.inputs.length); - for (let i = 0; i < this.inputs.length; i++) { - const input = this.inputs[i]; + // Count. + bw.writeVarint(1); // Outpoint. input.prevout.toWriter(bw); // Replace script with previous // output script if current index. - if (i === index) { - bw.writeVarBytes(prev.toRaw()); - bw.writeU32(input.sequence); - continue; - } + bw.writeVarBytes(prev.toRaw()); + bw.writeU32(input.sequence); + } else { + bw.writeVarint(this.inputs.length); + for (let i = 0; i < this.inputs.length; i++) { + const input = this.inputs[i]; - // Script is null. - bw.writeVarint(0); + // Outpoint. + input.prevout.toWriter(bw); - // Sequences are 0 if NONE or SINGLE. - switch (type & 0x1f) { - case hashType.NONE: - case hashType.SINGLE: - bw.writeU32(0); - break; - default: + // Replace script with previous + // output script if current index. + if (i === index) { + bw.writeVarBytes(prev.toRaw()); bw.writeU32(input.sequence); - break; - } - } - } + continue; + } - // Serialize outputs. - switch (type & 0x1f) { - case hashType.NONE: { - // No outputs if NONE. - bw.writeVarint(0); - break; - } - case hashType.SINGLE: { - const output = this.outputs[index]; + // Script is null. + bw.writeVarint(0); - // Drop all outputs after the - // current input index if SINGLE. - bw.writeVarint(index + 1); + // Sequences are 0 if NONE or SINGLE. + switch (type & 0x1f) { + case hashType.NONE: + case hashType.SINGLE: + bw.writeU32(0); + break; + default: + bw.writeU32(input.sequence); + break; + } + } + } - for (let i = 0; i < index; i++) { - // Null all outputs not at - // current input index. - bw.writeI64(-1); + // Serialize outputs. + switch (type & 0x1f) { + case hashType.NONE: { + // No outputs if NONE. bw.writeVarint(0); + break; } + case hashType.SINGLE: { + const output = this.outputs[index]; + + // Drop all outputs after the + // current input index if SINGLE. + bw.writeVarint(index + 1); + + for (let i = 0; i < index; i++) { + // Null all outputs not at + // current input index. + bw.writeI64(-1); + bw.writeVarint(0); + } - // Regular serialization - // at current input index. - output.toWriter(bw); - - break; - } - default: { - // Regular output serialization if ALL. - bw.writeVarint(this.outputs.length); - for (const output of this.outputs) + // Regular serialization + // at current input index. output.toWriter(bw); - break; - } - } - - bw.writeU32(this.locktime); - var fOmitTxComment = !!(type & hashType.OMIT_TX_COMMENT); - - if (this.version >= 2 && !fOmitTxComment) { - bw.writeVarBytes(Buffer.from(this.strFloData)); - } + break; + } + default: { + // Regular output serialization if ALL. + bw.writeVarint(this.outputs.length); + for (const output of this.outputs) + output.toWriter(bw); + break; + } + } - type &= ~hashType.OMIT_TX_COMMENT; + bw.writeU32(this.locktime); - // Append the hash type. - bw.writeU32(type); + if (this.version >= 2 && includeFloData) { + bw.writeVarBytes(Buffer.from(this.strFloData)); + } - return digest.hash256(bw.render()); -}; + // Append the hash type. + bw.writeU32(type); -/** - * Calculate sighash size. - * @private - * @param {Number} index - * @param {Script} prev - * @param {Number} type - * @returns {Number} - */ + return hash256.digest(bw.render()); + } -TX.prototype.hashSize = function hashSize(index, prev, type) { - let size = 0; + /** + * Calculate sighash size. + * @private + * @param {Number} index + * @param {Script} prev + * @param {Number} type + * @returns {Number} + */ - size += 4; + hashSize(index, prev, type, includeFloData = true) { + let size = 0; - if (type & hashType.ANYONECANPAY) { - size += 1; - size += 36; - size += prev.getVarSize(); size += 4; - } else { - size += encoding.sizeVarint(this.inputs.length); - size += 41 * (this.inputs.length - 1); - size += 36; - size += prev.getVarSize(); - size += 4; - } - switch (type & 0x1f) { - case hashType.NONE: + if (type & hashType.ANYONECANPAY) { size += 1; - break; - case hashType.SINGLE: - size += encoding.sizeVarint(index + 1); - size += 9 * index; - size += this.outputs[index].getSize(); - break; - default: - size += encoding.sizeVarint(this.outputs.length); - for (const output of this.outputs) - size += output.getSize(); - break; - } + size += 36; + size += prev.getVarSize(); + size += 4; + } else { + size += encoding.sizeVarint(this.inputs.length); + size += 41 * (this.inputs.length - 1); + size += 36; + size += prev.getVarSize(); + size += 4; + } - var fOmitTxComment = !!(type & hashType.OMIT_TX_COMMENT); + switch (type & 0x1f) { + case hashType.NONE: + size += 1; + break; + case hashType.SINGLE: + size += encoding.sizeVarint(index + 1); + size += 9 * index; + size += this.outputs[index].getSize(); + break; + default: + size += encoding.sizeVarint(this.outputs.length); + for (const output of this.outputs) + size += output.getSize(); + break; + } - if (!fOmitTxComment){ - let bufferLength = Buffer.from(this.strFloData).length; + if (includeFloData){ + let bufferLength = Buffer.from(this.strFloData).length; - if (this.strFloData.length > 0){ size += encoding.sizeVarint(bufferLength); size += bufferLength - } else { - size += encoding.sizeVarint(0); } - } - size += 8; + size += 8; - return size; -}; + return size; + } -/** - * Witness sighashing -- O(n). - * @private - * @param {Number} index - * @param {Script} prev - * @param {Amount} value - * @param {SighashType} type - * @returns {Buffer} - */ + /** + * Witness sighashing -- O(n). + * @private + * @param {Number} index + * @param {Script} prev + * @param {Amount} value + * @param {SighashType} type + * @param {Boolean} [includeFloData=true] - Should floData be included in the signature Hash + * @returns {Buffer} + */ -TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, value, type) { - const input = this.inputs[index]; - let prevouts = encoding.ZERO_HASH; - let sequences = encoding.ZERO_HASH; - let outputs = encoding.ZERO_HASH; + signatureHashV1(index, prev, value, type, includeFloData = true) { + const input = this.inputs[index]; + let prevouts = consensus.ZERO_HASH; + let sequences = consensus.ZERO_HASH; + let outputs = consensus.ZERO_HASH; - if (!(type & hashType.ANYONECANPAY)) { - if (this._hashPrevouts) { - prevouts = this._hashPrevouts; - } else { - const bw = StaticWriter.pool(this.inputs.length * 36); + if (!(type & hashType.ANYONECANPAY)) { + if (this._hashPrevouts) { + prevouts = this._hashPrevouts; + } else { + const bw = bio.pool(this.inputs.length * 36); - for (const input of this.inputs) - input.prevout.toWriter(bw); + for (const input of this.inputs) + input.prevout.toWriter(bw); - prevouts = digest.hash256(bw.render()); + prevouts = hash256.digest(bw.render()); - if (!this.mutable) - this._hashPrevouts = prevouts; + if (!this.mutable) + this._hashPrevouts = prevouts; + } } - } - if (!(type & hashType.ANYONECANPAY) - && (type & 0x1f) !== hashType.SINGLE - && (type & 0x1f) !== hashType.NONE) { - if (this._hashSequence) { - sequences = this._hashSequence; - } else { - const bw = StaticWriter.pool(this.inputs.length * 4); + if (!(type & hashType.ANYONECANPAY) + && (type & 0x1f) !== hashType.SINGLE + && (type & 0x1f) !== hashType.NONE) { + if (this._hashSequence) { + sequences = this._hashSequence; + } else { + const bw = bio.pool(this.inputs.length * 4); - for (const input of this.inputs) - bw.writeU32(input.sequence); + for (const input of this.inputs) + bw.writeU32(input.sequence); - sequences = digest.hash256(bw.render()); + sequences = hash256.digest(bw.render()); - if (!this.mutable) - this._hashSequence = sequences; + if (!this.mutable) + this._hashSequence = sequences; + } } - } - if ((type & 0x1f) !== hashType.SINGLE - && (type & 0x1f) !== hashType.NONE) { - if (this._hashOutputs) { - outputs = this._hashOutputs; - } else { - let size = 0; + if ((type & 0x1f) !== hashType.SINGLE + && (type & 0x1f) !== hashType.NONE) { + if (this._hashOutputs) { + outputs = this._hashOutputs; + } else { + let size = 0; - for (const output of this.outputs) - size += output.getSize(); + for (const output of this.outputs) + size += output.getSize(); - const bw = StaticWriter.pool(size); + const bw = bio.pool(size); - for (const output of this.outputs) - output.toWriter(bw); + for (const output of this.outputs) + output.toWriter(bw); - outputs = digest.hash256(bw.render()); + outputs = hash256.digest(bw.render()); - if (!this.mutable) - this._hashOutputs = outputs; - } - } else if ((type & 0x1f) === hashType.SINGLE) { - if (index < this.outputs.length) { - const output = this.outputs[index]; - outputs = digest.hash256(output.toRaw()); + if (!this.mutable) + this._hashOutputs = outputs; + } + } else if ((type & 0x1f) === hashType.SINGLE) { + if (index < this.outputs.length) { + const output = this.outputs[index]; + outputs = hash256.digest(output.toRaw()); + } } - } - - // Calculate the size of the transaction including the Flo Data - let fOmitTxComment = !!(type & hashType.OMIT_TX_COMMENT); - let floDataSize = 0 - if (!fOmitTxComment){ - let bufferLength = Buffer.from(this.strFloData).length; - if (this.strFloData.length > 0){ - floDataSize += encoding.sizeVarint(bufferLength); - floDataSize += bufferLength - } else { - floDataSize += encoding.sizeVarint(0); - } - } + const size = 156 + prev.getVarSize(); + const bw = bio.pool(size); - const size = 156 + prev.getVarSize() + floDataSize; + bw.writeU32(this.version); + bw.writeBytes(prevouts); + bw.writeBytes(sequences); + bw.writeHash(input.prevout.hash); + bw.writeU32(input.prevout.index); + bw.writeVarBytes(prev.toRaw()); + bw.writeI64(value); + bw.writeU32(input.sequence); + bw.writeBytes(outputs); + bw.writeU32(this.locktime); - const bw = StaticWriter.pool(size); + // Add the FloData to the transaction + if (this.version >= 2 && includeFloData) { + bw.writeVarBytes(Buffer.from(this.strFloData)); + } - bw.writeU32(this.version); - bw.writeBytes(prevouts); - bw.writeBytes(sequences); - bw.writeHash(input.prevout.hash); - bw.writeU32(input.prevout.index); - bw.writeVarBytes(prev.toRaw()); - bw.writeI64(value); - bw.writeU32(input.sequence); - bw.writeBytes(outputs); - bw.writeU32(this.locktime); + bw.writeU32(type); - // Add the FloData to the transaction - if (this.version >= 2 && !fOmitTxComment) { - bw.writeVarBytes(Buffer.from(this.strFloData)); + return hash256.digest(bw.render()); } - // Remove the OMIT_TX_COMMENT flag if included - type &= ~hashType.OMIT_TX_COMMENT; - - bw.writeU32(type); + /** + * Verify signature. + * @param {Number} index + * @param {Script} prev + * @param {Amount} value + * @param {Buffer} sig + * @param {Buffer} key + * @param {Number} version + * @returns {Boolean} + */ - return digest.hash256(bw.render()); -}; - -/** - * Verify signature. - * @param {Number} index - * @param {Script} prev - * @param {Amount} value - * @param {Buffer} sig - * @param {Buffer} key - * @param {Number} version - * @returns {Boolean} - */ - -TX.prototype.checksig = function checksig(index, prev, value, sig, key, version) { - if (sig.length === 0) - return false; - - let type = sig[sig.length - 1]; - let signature = sig.slice(0, -1); - let hash = this.signatureHash(index, prev, value, type, version); - - if (secp256k1.verify(hash, signature, key)){ - return true; - } else { - type ^= hashType.OMIT_TX_COMMENT; + checksig(index, prev, value, sig, key, version) { + if (sig.length === 0) + return false; - hash = this.signatureHash(index, prev, value, type, version); + let type = sig[sig.length - 1]; + let signature = sig.slice(0, -1); + let hash = this.signatureHash(index, prev, value, type, version, true); - return secp256k1.verify(hash, signature, key); + if (secp256k1.verifyDER(hash, signature, key)){ + return true; + } else { + hash = this.signatureHash(index, prev, value, type, version, false); + return secp256k1.verifyDER(hash, signature, key); + } } -}; -/** - * Create a signature suitable for inserting into scriptSigs/witnesses. - * @param {Number} index - Index of input being signed. - * @param {Script} prev - Previous output script or redeem script - * (in the case of witnesspubkeyhash, this should be the generated - * p2pkh script). - * @param {Amount} value - Previous output value. - * @param {Buffer} key - * @param {SighashType} type - * @param {Number} version - Sighash version (0=legacy, 1=segwit). - * @returns {Buffer} Signature in DER format. - */ + /** + * Create a signature suitable for inserting into scriptSigs/witnesses. + * @param {Number} index - Index of input being signed. + * @param {Script} prev - Previous output script or redeem script + * (in the case of witnesspubkeyhash, this should be the generated + * p2pkh script). + * @param {Amount} value - Previous output value. + * @param {Buffer} key + * @param {SighashType} type + * @param {Number} version - Sighash version (0=legacy, 1=segwit). + * @returns {Buffer} Signature in DER format. + */ -TX.prototype.signature = function signature(index, prev, value, key, type, version) { - if (type == null) - type = hashType.ALL; + signature(index, prev, value, key, type, version) { + if (type == null) + type = hashType.ALL; - if (version == null) - version = 0; + if (version == null) + version = 0; - const hash = this.signatureHash(index, prev, value, type, version); - const sig = secp256k1.sign(hash, key); - const bw = new StaticWriter(sig.length + 1); + const hash = this.signatureHash(index, prev, value, type, version, true); + const sig = secp256k1.signDER(hash, key); + const bw = bio.write(sig.length + 1); - bw.writeBytes(sig); - bw.writeU8(type); + bw.writeBytes(sig); + bw.writeU8(type); - return bw.render(); -}; + return bw.render(); + } -/** - * Verify all transaction inputs. - * @param {CoinView} view - * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] - * @throws {ScriptError} on invalid inputs - */ + /** + * Verify all transaction inputs. + * @param {CoinView} view + * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] + * @throws {ScriptError} on invalid inputs + */ -TX.prototype.check = function check(view, flags) { - if (this.inputs.length === 0) - throw new ScriptError('UNKNOWN_ERROR', 'No inputs.'); + check(view, flags) { + if (this.inputs.length === 0) + throw new ScriptError('UNKNOWN_ERROR', 'No inputs.'); - if (this.isCoinbase()) - return; + if (this.isCoinbase()) + return; - for (let i = 0; i < this.inputs.length; i++) { - const {prevout} = this.inputs[i]; - const coin = view.getOutput(prevout); + for (let i = 0; i < this.inputs.length; i++) { + const {prevout} = this.inputs[i]; + const coin = view.getOutput(prevout); - if (!coin) - throw new ScriptError('UNKNOWN_ERROR', 'No coin available.'); + if (!coin) + throw new ScriptError('UNKNOWN_ERROR', 'No coin available.'); - this.checkInput(i, coin, flags); + this.checkInput(i, coin, flags); + } } -}; - -/** - * Verify a transaction input. - * @param {Number} index - Index of output being - * verified. - * @param {Coin|Output} coin - Previous output. - * @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS] - * @throws {ScriptError} on invalid input - */ - -TX.prototype.checkInput = function checkInput(index, coin, flags) { - const input = this.inputs[index]; - - assert(input, 'Input does not exist.'); - assert(coin, 'No coin passed.'); - Script.verify( - input.script, - input.witness, - coin.script, - this, - index, - coin.value, - flags - ); -}; + /** + * Verify a transaction input. + * @param {Number} index - Index of output being + * verified. + * @param {Coin|Output} coin - Previous output. + * @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS] + * @throws {ScriptError} on invalid input + */ -/** - * Verify the transaction inputs on the worker pool - * (if workers are enabled). - * @param {CoinView} view - * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] - * @param {WorkerPool?} pool - * @returns {Promise} - */ - -TX.prototype.checkAsync = async function checkAsync(view, flags, pool) { - if (this.inputs.length === 0) - throw new ScriptError('UNKNOWN_ERROR', 'No inputs.'); + checkInput(index, coin, flags) { + const input = this.inputs[index]; - if (this.isCoinbase()) - return; + assert(input, 'Input does not exist.'); + assert(coin, 'No coin passed.'); + + Script.verify( + input.script, + input.witness, + coin.script, + this, + index, + coin.value, + flags + ); + } + + /** + * Verify the transaction inputs on the worker pool + * (if workers are enabled). + * @param {CoinView} view + * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] + * @param {WorkerPool?} pool + * @returns {Promise} + */ + + async checkAsync(view, flags, pool) { + if (this.inputs.length === 0) + throw new ScriptError('UNKNOWN_ERROR', 'No inputs.'); + + if (this.isCoinbase()) + return; + + if (!pool) { + this.check(view, flags); + return; + } - if (!pool) { - this.check(view, flags); - return; + await pool.check(this, view, flags); } - await pool.check(this, view, flags); -}; + /** + * Verify a transaction input asynchronously. + * @param {Number} index - Index of output being + * verified. + * @param {Coin|Output} coin - Previous output. + * @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS] + * @param {WorkerPool?} pool + * @returns {Promise} + */ -/** - * Verify a transaction input asynchronously. - * @param {Number} index - Index of output being - * verified. - * @param {Coin|Output} coin - Previous output. - * @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS] - * @param {WorkerPool?} pool - * @returns {Promise} - */ + async checkInputAsync(index, coin, flags, pool) { + const input = this.inputs[index]; -TX.prototype.checkInputAsync = async function checkInputAsync(index, coin, flags, pool) { - const input = this.inputs[index]; + assert(input, 'Input does not exist.'); + assert(coin, 'No coin passed.'); - assert(input, 'Input does not exist.'); - assert(coin, 'No coin passed.'); + if (!pool) { + this.checkInput(index, coin, flags); + return; + } - if (!pool) { - this.checkInput(index, coin, flags); - return; + await pool.checkInput(this, index, coin, flags); } - await pool.checkInput(this, index, coin, flags); -}; - -/** - * Verify all transaction inputs. - * @param {CoinView} view - * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] - * @returns {Boolean} Whether the inputs are valid. - */ + /** + * Verify all transaction inputs. + * @param {CoinView} view + * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] + * @returns {Boolean} Whether the inputs are valid. + */ -TX.prototype.verify = function verify(view, flags) { - try { - this.check(view, flags); - } catch (e) { - if (e.type === 'ScriptError') - return false; - throw e; + verify(view, flags) { + try { + this.check(view, flags); + } catch (e) { + if (e.type === 'ScriptError') + return false; + throw e; + } + return true; } - return true; -}; - -/** - * Verify a transaction input. - * @param {Number} index - Index of output being - * verified. - * @param {Coin|Output} coin - Previous output. - * @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS] - * @returns {Boolean} Whether the input is valid. - */ -TX.prototype.verifyInput = function verifyInput(index, coin, flags) { - try { - this.checkInput(index, coin, flags); - } catch (e) { - if (e.type === 'ScriptError') - return false; - throw e; + /** + * Verify a transaction input. + * @param {Number} index - Index of output being + * verified. + * @param {Coin|Output} coin - Previous output. + * @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS] + * @returns {Boolean} Whether the input is valid. + */ + + verifyInput(index, coin, flags) { + try { + this.checkInput(index, coin, flags); + } catch (e) { + if (e.type === 'ScriptError') + return false; + throw e; + } + return true; } - return true; -}; -/** - * Verify the transaction inputs on the worker pool - * (if workers are enabled). - * @param {CoinView} view - * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] - * @param {WorkerPool?} pool - * @returns {Promise} - */ + /** + * Verify the transaction inputs on the worker pool + * (if workers are enabled). + * @param {CoinView} view + * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] + * @param {WorkerPool?} pool + * @returns {Promise} + */ + + async verifyAsync(view, flags, pool) { + try { + await this.checkAsync(view, flags, pool); + } catch (e) { + if (e.type === 'ScriptError') + return false; + throw e; + } + return true; + } -TX.prototype.verifyAsync = async function verifyAsync(view, flags, pool) { - try { - await this.checkAsync(view, flags, pool); - } catch (e) { - if (e.type === 'ScriptError') - return false; - throw e; + /** + * Verify a transaction input asynchronously. + * @param {Number} index - Index of output being + * verified. + * @param {Coin|Output} coin - Previous output. + * @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS] + * @param {WorkerPool?} pool + * @returns {Promise} + */ + + async verifyInputAsync(index, coin, flags, pool) { + try { + await this.checkInput(index, coin, flags, pool); + } catch (e) { + if (e.type === 'ScriptError') + return false; + throw e; + } + return true; } - return true; -}; -/** - * Verify a transaction input asynchronously. - * @param {Number} index - Index of output being - * verified. - * @param {Coin|Output} coin - Previous output. - * @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS] - * @param {WorkerPool?} pool - * @returns {Promise} - */ + /** + * Test whether the transaction is a coinbase + * by examining the inputs. + * @returns {Boolean} + */ -TX.prototype.verifyInputAsync = async function verifyInputAsync(index, coin, flags, pool) { - try { - await this.checkInput(index, coin, flags, pool); - } catch (e) { - if (e.type === 'ScriptError') - return false; - throw e; + isCoinbase() { + return this.inputs.length === 1 && this.inputs[0].prevout.isNull(); } - return true; -}; -/** - * Test whether the transaction is a coinbase - * by examining the inputs. - * @returns {Boolean} - */ + /** + * Test whether the transaction is replaceable. + * @returns {Boolean} + */ -TX.prototype.isCoinbase = function isCoinbase() { - return this.inputs.length === 1 && this.inputs[0].prevout.isNull(); -}; + isRBF() { + // Core doesn't do this, but it should: + if (this.version === 2) + return false; -/** - * Test whether the transaction is replaceable. - * @returns {Boolean} - */ + for (const input of this.inputs) { + if (input.isRBF()) + return true; + } -TX.prototype.isRBF = function isRBF() { - // Core doesn't do this, but it should: - if (this.version === 2) return false; - - for (const input of this.inputs) { - if (input.isRBF()) - return true; } - return false; -}; - -/** - * Calculate the fee for the transaction. - * @param {CoinView} view - * @returns {Amount} fee (zero if not all coins are available). - */ - -TX.prototype.getFee = function getFee(view) { - if (!this.hasCoins(view)) - return 0; - - return this.getInputValue(view) - this.getOutputValue(); -}; + /** + * Calculate the fee for the transaction. + * @param {CoinView} view + * @returns {Amount} fee (zero if not all coins are available). + */ -/** - * Calculate the total input value. - * @param {CoinView} view - * @returns {Amount} value - */ - -TX.prototype.getInputValue = function getInputValue(view) { - let total = 0; - - for (const {prevout} of this.inputs) { - const coin = view.getOutput(prevout); - - if (!coin) + getFee(view) { + if (!this.hasCoins(view)) return 0; - total += coin.value; + return this.getInputValue(view) - this.getOutputValue(); } - return total; -}; + /** + * Calculate the total input value. + * @param {CoinView} view + * @returns {Amount} value + */ -/** - * Calculate the total output value. - * @returns {Amount} value - */ + getInputValue(view) { + let total = 0; -TX.prototype.getOutputValue = function getOutputValue() { - let total = 0; + for (const {prevout} of this.inputs) { + const coin = view.getOutput(prevout); - for (const output of this.outputs) - total += output.value; + if (!coin) + return 0; - return total; -}; - -/** - * Get all input addresses. - * @private - * @param {CoinView} view - * @returns {Array} [addrs, table] - */ - -TX.prototype._getInputAddresses = function _getInputAddresses(view) { - const table = Object.create(null); - const addrs = []; + total += coin.value; + } - if (this.isCoinbase()) - return [addrs, table]; + return total; + } - for (const input of this.inputs) { - const coin = view ? view.getOutputFor(input) : null; - const addr = input.getAddress(coin); + /** + * Calculate the total output value. + * @returns {Amount} value + */ - if (!addr) - continue; + getOutputValue() { + let total = 0; - const hash = addr.getHash('hex'); + for (const output of this.outputs) + total += output.value; - if (!table[hash]) { - table[hash] = true; - addrs.push(addr); - } + return total; } - return [addrs, table]; -}; + /** + * Get all input addresses. + * @private + * @param {CoinView} view + * @returns {Array} [addrs, table] + */ -/** - * Get all output addresses. - * @private - * @returns {Array} [addrs, table] - */ + _getInputAddresses(view) { + const table = new BufferSet(); + const addrs = []; -TX.prototype._getOutputAddresses = function _getOutputAddresses() { - const table = Object.create(null); - const addrs = []; + if (this.isCoinbase()) + return [addrs, table]; - for (const output of this.outputs) { - const addr = output.getAddress(); + for (const input of this.inputs) { + const coin = view ? view.getOutputFor(input) : null; + const addr = input.getAddress(coin); - if (!addr) - continue; + if (!addr) + continue; - const hash = addr.getHash('hex'); + const hash = addr.getHash(); - if (!table[hash]) { - table[hash] = true; - addrs.push(addr); + if (!table.has(hash)) { + table.add(hash); + addrs.push(addr); + } } - } - - return [addrs, table]; -}; - -/** - * Get all addresses. - * @private - * @param {CoinView} view - * @returns {Array} [addrs, table] - */ - -TX.prototype._getAddresses = function _getAddresses(view) { - const [addrs, table] = this._getInputAddresses(view); - const output = this.getOutputAddresses(); - for (const addr of output) { - const hash = addr.getHash('hex'); - - if (!table[hash]) { - table[hash] = true; - addrs.push(addr); - } + return [addrs, table]; } - return [addrs, table]; -}; + /** + * Get all output addresses. + * @private + * @returns {Array} [addrs, table] + */ -/** - * Get all input addresses. - * @param {CoinView|null} view - * @returns {Address[]} addresses - */ + _getOutputAddresses() { + const table = new BufferSet(); + const addrs = []; -TX.prototype.getInputAddresses = function getInputAddresses(view) { - const [addrs] = this._getInputAddresses(view); - return addrs; -}; + for (const output of this.outputs) { + const addr = output.getAddress(); -/** - * Get all output addresses. - * @returns {Address[]} addresses - */ - -TX.prototype.getOutputAddresses = function getOutputAddresses() { - const [addrs] = this._getOutputAddresses(); - return addrs; -}; - -/** - * Get all addresses. - * @param {CoinView|null} view - * @returns {Address[]} addresses - */ + if (!addr) + continue; -TX.prototype.getAddresses = function getAddresses(view) { - const [addrs] = this._getAddresses(view); - return addrs; -}; + const hash = addr.getHash(); -/** - * Get all input address hashes. - * @param {CoinView|null} view - * @returns {Hash[]} hashes - */ + if (!table.has(hash)) { + table.add(hash); + addrs.push(addr); + } + } -TX.prototype.getInputHashes = function getInputHashes(view, enc) { - if (enc === 'hex') { - const [, table] = this._getInputAddresses(view); - return Object.keys(table); + return [addrs, table]; } - const addrs = this.getInputAddresses(view); - const hashes = []; + /** + * Get all addresses. + * @private + * @param {CoinView} view + * @returns {Array} [addrs, table] + */ - for (const addr of addrs) - hashes.push(addr.getHash()); + _getAddresses(view) { + const [addrs, table] = this._getInputAddresses(view); + const output = this.getOutputAddresses(); - return hashes; -}; + for (const addr of output) { + const hash = addr.getHash(); -/** - * Get all output address hashes. - * @returns {Hash[]} hashes - */ + if (!table.has(hash)) { + table.add(hash); + addrs.push(addr); + } + } -TX.prototype.getOutputHashes = function getOutputHashes(enc) { - if (enc === 'hex') { - const [, table] = this._getOutputAddresses(); - return Object.keys(table); + return [addrs, table]; } - const addrs = this.getOutputAddresses(); - const hashes = []; + /** + * Get all input addresses. + * @param {CoinView|null} view + * @returns {Address[]} addresses + */ - for (const addr of addrs) - hashes.push(addr.getHash()); + getInputAddresses(view) { + const [addrs] = this._getInputAddresses(view); + return addrs; + } - return hashes; -}; + /** + * Get all output addresses. + * @returns {Address[]} addresses + */ -/** - * Get all address hashes. - * @param {CoinView|null} view - * @returns {Hash[]} hashes - */ - -TX.prototype.getHashes = function getHashes(view, enc) { - if (enc === 'hex') { - const [, table] = this._getAddresses(view); - return Object.keys(table); + getOutputAddresses() { + const [addrs] = this._getOutputAddresses(); + return addrs; } - const addrs = this.getAddresses(view); - const hashes = []; + /** + * Get all addresses. + * @param {CoinView|null} view + * @returns {Address[]} addresses + */ - for (const addr of addrs) - hashes.push(addr.getHash()); + getAddresses(view) { + const [addrs] = this._getAddresses(view); + return addrs; + } - return hashes; -}; + /** + * Get all input address hashes. + * @param {CoinView|null} view + * @returns {Hash[]} hashes + */ -/** - * Test whether the transaction has - * all coins available. - * @param {CoinView} view - * @returns {Boolean} - */ + getInputHashes(view, enc) { + const [, table] = this._getInputAddresses(view); -TX.prototype.hasCoins = function hasCoins(view) { - if (this.inputs.length === 0) - return false; + if (enc !== 'hex') + return table.toArray(); - for (const {prevout} of this.inputs) { - if (!view.hasEntry(prevout)) - return false; + return table.toArray().map(h => h.toString('hex')); } - return true; -}; + /** + * Get all output address hashes. + * @returns {Hash[]} hashes + */ -/** - * Check finality of transaction by examining - * nLocktime and nSequence values. - * @example - * tx.isFinal(chain.height + 1, network.now()); - * @param {Number} height - Height at which to test. This - * is usually the chain height, or the chain height + 1 - * when the transaction entered the mempool. - * @param {Number} time - Time at which to test. This is - * usually the chain tip's parent's median time, or the - * time at which the transaction entered the mempool. If - * MEDIAN_TIME_PAST is enabled this will be the median - * time of the chain tip's previous entry's median time. - * @returns {Boolean} - */ - -TX.prototype.isFinal = function isFinal(height, time) { - const THRESHOLD = consensus.LOCKTIME_THRESHOLD; - - if (this.locktime === 0) - return true; + getOutputHashes(enc) { + const [, table] = this._getOutputAddresses(); - if (this.locktime < (this.locktime < THRESHOLD ? height : time)) - return true; + if (enc !== 'hex') + return table.toArray(); - for (const input of this.inputs) { - if (input.sequence !== 0xffffffff) - return false; + return table.toArray().map(h => h.toString('hex')); } - return true; -}; + /** + * Get all address hashes. + * @param {CoinView|null} view + * @returns {Hash[]} hashes + */ -/** - * Verify the absolute locktime of a transaction. - * Called by OP_CHECKLOCKTIMEVERIFY. - * @param {Number} index - Index of input being verified. - * @param {Number} predicate - Locktime to verify against. - * @returns {Boolean} - */ + getHashes(view, enc) { + const [, table] = this._getAddresses(view); -TX.prototype.verifyLocktime = function verifyLocktime(index, predicate) { - const THRESHOLD = consensus.LOCKTIME_THRESHOLD; - const input = this.inputs[index]; + if (enc !== 'hex') + return table.toArray(); - assert(input, 'Input does not exist.'); - assert(predicate >= 0, 'Locktime must be non-negative.'); + return table.toArray().map(h => h.toString('hex')); + } - // Locktimes must be of the same type (blocks or seconds). - if ((this.locktime < THRESHOLD) !== (predicate < THRESHOLD)) - return false; + /** + * Test whether the transaction has + * all coins available. + * @param {CoinView} view + * @returns {Boolean} + */ - if (predicate > this.locktime) - return false; + hasCoins(view) { + if (this.inputs.length === 0) + return false; - if (input.sequence === 0xffffffff) - return false; + for (const {prevout} of this.inputs) { + if (!view.hasEntry(prevout)) + return false; + } - return true; -}; + return true; + } -/** - * Verify the relative locktime of an input. - * Called by OP_CHECKSEQUENCEVERIFY. - * @param {Number} index - Index of input being verified. - * @param {Number} predicate - Relative locktime to verify against. - * @returns {Boolean} - */ + /** + * Check finality of transaction by examining + * nLocktime and nSequence values. + * @example + * tx.isFinal(chain.height + 1, network.now()); + * @param {Number} height - Height at which to test. This + * is usually the chain height, or the chain height + 1 + * when the transaction entered the mempool. + * @param {Number} time - Time at which to test. This is + * usually the chain tip's parent's median time, or the + * time at which the transaction entered the mempool. If + * MEDIAN_TIME_PAST is enabled this will be the median + * time of the chain tip's previous entry's median time. + * @returns {Boolean} + */ + + isFinal(height, time) { + const THRESHOLD = consensus.LOCKTIME_THRESHOLD; + + if (this.locktime === 0) + return true; -TX.prototype.verifySequence = function verifySequence(index, predicate) { - const DISABLE_FLAG = consensus.SEQUENCE_DISABLE_FLAG; - const TYPE_FLAG = consensus.SEQUENCE_TYPE_FLAG; - const MASK = consensus.SEQUENCE_MASK; - const input = this.inputs[index]; + if (this.locktime < (this.locktime < THRESHOLD ? height : time)) + return true; - assert(input, 'Input does not exist.'); - assert(predicate >= 0, 'Locktime must be non-negative.'); + for (const input of this.inputs) { + if (input.sequence !== 0xffffffff) + return false; + } - // For future softfork capability. - if (predicate & DISABLE_FLAG) return true; + } - // Version must be >=2. - if (this.version < 2) - return false; - - // Cannot use the disable flag without - // the predicate also having the disable - // flag (for future softfork capability). - if (input.sequence & DISABLE_FLAG) - return false; - - // Locktimes must be of the same type (blocks or seconds). - if ((input.sequence & TYPE_FLAG) !== (predicate & TYPE_FLAG)) - return false; - - if ((predicate & MASK) > (input.sequence & MASK)) - return false; + /** + * Verify the absolute locktime of a transaction. + * Called by OP_CHECKLOCKTIMEVERIFY. + * @param {Number} index - Index of input being verified. + * @param {Number} predicate - Locktime to verify against. + * @returns {Boolean} + */ - return true; -}; + verifyLocktime(index, predicate) { + const THRESHOLD = consensus.LOCKTIME_THRESHOLD; + const input = this.inputs[index]; -/** - * Calculate legacy (inaccurate) sigop count. - * @returns {Number} sigop count - */ + assert(input, 'Input does not exist.'); + assert(predicate >= 0, 'Locktime must be non-negative.'); -TX.prototype.getLegacySigops = function getLegacySigops() { - if (this._sigops !== -1) - return this._sigops; + // Locktimes must be of the same type (blocks or seconds). + if ((this.locktime < THRESHOLD) !== (predicate < THRESHOLD)) + return false; - let total = 0; + if (predicate > this.locktime) + return false; - for (const input of this.inputs) - total += input.script.getSigops(false); + if (input.sequence === 0xffffffff) + return false; - for (const output of this.outputs) - total += output.script.getSigops(false); + return true; + } - if (!this.mutable) - this._sigops = total; + /** + * Verify the relative locktime of an input. + * Called by OP_CHECKSEQUENCEVERIFY. + * @param {Number} index - Index of input being verified. + * @param {Number} predicate - Relative locktime to verify against. + * @returns {Boolean} + */ - return total; -}; + verifySequence(index, predicate) { + const DISABLE_FLAG = consensus.SEQUENCE_DISABLE_FLAG; + const TYPE_FLAG = consensus.SEQUENCE_TYPE_FLAG; + const MASK = consensus.SEQUENCE_MASK; + const input = this.inputs[index]; -/** - * Calculate accurate sigop count, taking into account redeem scripts. - * @param {CoinView} view - * @returns {Number} sigop count - */ + assert(input, 'Input does not exist.'); + assert(predicate >= 0, 'Locktime must be non-negative.'); -TX.prototype.getScripthashSigops = function getScripthashSigops(view) { - if (this.isCoinbase()) - return 0; + // For future softfork capability. + if (predicate & DISABLE_FLAG) + return true; - let total = 0; + // Version must be >=2. + if (this.version < 2) + return false; - for (const input of this.inputs) { - const coin = view.getOutputFor(input); + // Cannot use the disable flag without + // the predicate also having the disable + // flag (for future softfork capability). + if (input.sequence & DISABLE_FLAG) + return false; - if (!coin) - continue; + // Locktimes must be of the same type (blocks or seconds). + if ((input.sequence & TYPE_FLAG) !== (predicate & TYPE_FLAG)) + return false; - if (!coin.script.isScripthash()) - continue; + if ((predicate & MASK) > (input.sequence & MASK)) + return false; - total += coin.script.getScripthashSigops(input.script); + return true; } - return total; -}; + /** + * Calculate legacy (inaccurate) sigop count. + * @returns {Number} sigop count + */ -/** - * Calculate accurate sigop count, taking into account redeem scripts. - * @param {CoinView} view - * @returns {Number} sigop count - */ + getLegacySigops() { + if (this._sigops !== -1) + return this._sigops; -TX.prototype.getWitnessSigops = function getWitnessSigops(view) { - if (this.isCoinbase()) - return 0; + let total = 0; - let total = 0; + for (const input of this.inputs) + total += input.script.getSigops(false); - for (const input of this.inputs) { - const coin = view.getOutputFor(input); + for (const output of this.outputs) + total += output.script.getSigops(false); - if (!coin) - continue; + if (!this.mutable) + this._sigops = total; - total += coin.script.getWitnessSigops(input.script, input.witness); + return total; } - return total; -}; + /** + * Calculate accurate sigop count, taking into account redeem scripts. + * @param {CoinView} view + * @returns {Number} sigop count + */ -/** - * Calculate sigops cost, taking into account witness programs. - * @param {CoinView} view - * @param {VerifyFlags?} flags - * @returns {Number} sigop weight - */ + getScripthashSigops(view) { + if (this.isCoinbase()) + return 0; -TX.prototype.getSigopsCost = function getSigopsCost(view, flags) { - if (flags == null) - flags = Script.flags.STANDARD_VERIFY_FLAGS; + let total = 0; - const scale = consensus.WITNESS_SCALE_FACTOR; + for (const input of this.inputs) { + const coin = view.getOutputFor(input); - let cost = this.getLegacySigops() * scale; + if (!coin) + continue; - if (flags & Script.flags.VERIFY_P2SH) - cost += this.getScripthashSigops(view) * scale; + if (!coin.script.isScripthash()) + continue; - if (flags & Script.flags.VERIFY_WITNESS) - cost += this.getWitnessSigops(view); + total += coin.script.getScripthashSigops(input.script); + } - return cost; -}; + return total; + } -/** - * Calculate virtual sigop count. - * @param {CoinView} view - * @param {VerifyFlags?} flags - * @returns {Number} sigop count - */ + /** + * Calculate accurate sigop count, taking into account redeem scripts. + * @param {CoinView} view + * @returns {Number} sigop count + */ -TX.prototype.getSigops = function getSigops(view, flags) { - const scale = consensus.WITNESS_SCALE_FACTOR; - return (this.getSigopsCost(view, flags) + scale - 1) / scale | 0; -}; + getWitnessSigops(view) { + if (this.isCoinbase()) + return 0; -/** - * Non-contextual sanity checks for the transaction. - * Will mostly verify coin and output values. - * @see CheckTransaction() - * @returns {Array} [result, reason, score] - */ + let total = 0; -TX.prototype.isSane = function isSane() { - const [valid] = this.checkSanity(); - return valid; -}; + for (const input of this.inputs) { + const coin = view.getOutputFor(input); -/** - * Non-contextual sanity checks for the transaction. - * Will mostly verify coin and output values. - * @see CheckTransaction() - * @returns {Array} [valid, reason, score] - */ + if (!coin) + continue; -TX.prototype.checkSanity = function checkSanity() { - if (this.inputs.length === 0) - return [false, 'bad-txns-vin-empty', 100]; + total += coin.script.getWitnessSigops(input.script, input.witness); + } - if (this.outputs.length === 0) - return [false, 'bad-txns-vout-empty', 100]; + return total; + } - if (this.getBaseSize() > consensus.MAX_BLOCK_SIZE) - return [false, 'bad-txns-oversize', 100]; + /** + * Calculate sigops cost, taking into account witness programs. + * @param {CoinView} view + * @param {VerifyFlags?} flags + * @returns {Number} sigop weight + */ - let total = 0; + getSigopsCost(view, flags) { + if (flags == null) + flags = Script.flags.STANDARD_VERIFY_FLAGS; - for (const output of this.outputs) { - if (output.value < 0) - return [false, 'bad-txns-vout-negative', 100]; + const scale = consensus.WITNESS_SCALE_FACTOR; - if (output.value > consensus.MAX_MONEY) - return [false, 'bad-txns-vout-toolarge', 100]; + let cost = this.getLegacySigops() * scale; - if (!util.isSafeAddition(total, output.value)) { - return [false, 'bad-txns-txouttotal-toolarge', 100]; - } + if (flags & Script.flags.VERIFY_P2SH) + cost += this.getScripthashSigops(view) * scale; - total += output.value; + if (flags & Script.flags.VERIFY_WITNESS) + cost += this.getWitnessSigops(view); - if (total < 0 || total > consensus.MAX_MONEY) - return [false, 'bad-txns-txouttotal-toolarge', 100]; + return cost; } - const prevout = new Set(); + /** + * Calculate virtual sigop count. + * @param {CoinView} view + * @param {VerifyFlags?} flags + * @returns {Number} sigop count + */ - for (const input of this.inputs) { - const key = input.prevout.toKey(); + getSigops(view, flags) { + const scale = consensus.WITNESS_SCALE_FACTOR; + return (this.getSigopsCost(view, flags) + scale - 1) / scale | 0; + } - if (prevout.has(key)) - return [false, 'bad-txns-inputs-duplicate', 100]; + /** + * Non-contextual sanity checks for the transaction. + * Will mostly verify coin and output values. + * @see CheckTransaction() + * @returns {Array} [result, reason, score] + */ - prevout.add(key); + isSane() { + const [valid] = this.checkSanity(); + return valid; } - if (this.isCoinbase()) { - const size = this.inputs[0].script.getSize(); - if (size < 2 || size > 100) - return [false, 'bad-cb-length', 100]; - } else { - for (const input of this.inputs) { - if (input.prevout.isNull()) - return [false, 'bad-txns-prevout-null', 10]; - } - } + /** + * Non-contextual sanity checks for the transaction. + * Will mostly verify coin and output values. + * @see CheckTransaction() + * @returns {Array} [valid, reason, score] + */ - return [true, 'valid', 0]; -}; + checkSanity() { + if (this.inputs.length === 0) + return [false, 'bad-txns-vin-empty', 100]; -/** - * Non-contextual checks to determine whether the - * transaction has all standard output script - * types and standard input script size with only - * pushdatas in the code. - * Will mostly verify coin and output values. - * @see IsStandardTx() - * @returns {Array} [valid, reason, score] - */ + if (this.outputs.length === 0) + return [false, 'bad-txns-vout-empty', 100]; -TX.prototype.isStandard = function isStandard() { - const [valid] = this.checkStandard(); - return valid; -}; + if (this.getBaseSize() > consensus.MAX_BLOCK_SIZE) + return [false, 'bad-txns-oversize', 100]; -/** - * Non-contextual checks to determine whether the - * transaction has all standard output script - * types and standard input script size with only - * pushdatas in the code. - * Will mostly verify coin and output values. - * @see IsStandardTx() - * @returns {Array} [valid, reason, score] - */ + let total = 0; -TX.prototype.checkStandard = function checkStandard() { - if (this.version < 1 || this.version > policy.MAX_TX_VERSION) - return [false, 'version', 0]; + for (const output of this.outputs) { + if (output.value < 0) + return [false, 'bad-txns-vout-negative', 100]; - if (this.getWeight() >= policy.MAX_TX_WEIGHT) - return [false, 'tx-size', 0]; + if (output.value > consensus.MAX_MONEY) + return [false, 'bad-txns-vout-toolarge', 100]; - for (const input of this.inputs) { - if (input.script.getSize() > 1650) - return [false, 'scriptsig-size', 0]; + total += output.value; - if (!input.script.isPushOnly()) - return [false, 'scriptsig-not-pushonly', 0]; - } + if (total < 0 || total > consensus.MAX_MONEY) + return [false, 'bad-txns-txouttotal-toolarge', 100]; + } + + const prevout = new BufferSet(); - let nulldata = 0; + for (const input of this.inputs) { + const key = input.prevout.toKey(); - for (const output of this.outputs) { - if (!output.script.isStandard()) - return [false, 'scriptpubkey', 0]; + if (prevout.has(key)) + return [false, 'bad-txns-inputs-duplicate', 100]; - if (output.script.isNulldata()) { - nulldata++; - continue; + prevout.add(key); } - if (output.script.isMultisig() && !policy.BARE_MULTISIG) - return [false, 'bare-multisig', 0]; + if (this.isCoinbase()) { + const size = this.inputs[0].script.getSize(); + if (size < 2 || size > 100) + return [false, 'bad-cb-length', 100]; + } else { + for (const input of this.inputs) { + if (input.prevout.isNull()) + return [false, 'bad-txns-prevout-null', 10]; + } + } - if (output.isDust(policy.MIN_RELAY)) - return [false, 'dust', 0]; + return [true, 'valid', 0]; } - if (nulldata > 1) - return [false, 'multi-op-return', 0]; + /** + * Non-contextual checks to determine whether the + * transaction has all standard output script + * types and standard input script size with only + * pushdatas in the code. + * Will mostly verify coin and output values. + * @see IsStandardTx() + * @returns {Array} [valid, reason, score] + */ - return [true, 'valid', 0]; -}; + isStandard() { + const [valid] = this.checkStandard(); + return valid; + } -/** - * Perform contextual checks to verify coin and input - * script standardness (including the redeem script). - * @see AreInputsStandard() - * @param {CoinView} view - * @param {VerifyFlags?} flags - * @returns {Boolean} - */ + /** + * Non-contextual checks to determine whether the + * transaction has all standard output script + * types and standard input script size with only + * pushdatas in the code. + * Will mostly verify coin and output values. + * @see IsStandardTx() + * @returns {Array} [valid, reason, score] + */ -TX.prototype.hasStandardInputs = function hasStandardInputs(view) { - if (this.isCoinbase()) - return true; + checkStandard() { + if (this.version < 1 || this.version > policy.MAX_TX_VERSION) + return [false, 'version', 0]; - for (const input of this.inputs) { - const coin = view.getOutputFor(input); + if (this.getWeight() >= policy.MAX_TX_WEIGHT) + return [false, 'tx-size', 0]; - if (!coin) - return false; + for (const input of this.inputs) { + if (input.script.getSize() > 1650) + return [false, 'scriptsig-size', 0]; - if (coin.script.isPubkeyhash()) - continue; + if (!input.script.isPushOnly()) + return [false, 'scriptsig-not-pushonly', 0]; + } - if (coin.script.isScripthash()) { - const redeem = input.script.getRedeem(); + let nulldata = 0; - if (!redeem) - return false; + for (const output of this.outputs) { + if (!output.script.isStandard()) + return [false, 'scriptpubkey', 0]; - if (redeem.getSigops(true) > policy.MAX_P2SH_SIGOPS) - return false; + if (output.script.isNulldata()) { + nulldata++; + continue; + } - continue; - } + if (output.script.isMultisig() && !policy.BARE_MULTISIG) + return [false, 'bare-multisig', 0]; - if (coin.script.isUnknown()) - return false; - } + if (output.isDust(policy.MIN_RELAY)) + return [false, 'dust', 0]; + } - return true; -}; + if (nulldata > 1) + return [false, 'multi-op-return', 0]; -/** - * Perform contextual checks to verify coin and witness standardness. - * @see IsBadWitness() - * @param {CoinView} view - * @returns {Boolean} - */ + return [true, 'valid', 0]; + } -TX.prototype.hasStandardWitness = function hasStandardWitness(view) { - if (this.isCoinbase()) - return true; + /** + * Perform contextual checks to verify coin and input + * script standardness (including the redeem script). + * @see AreInputsStandard() + * @param {CoinView} view + * @param {VerifyFlags?} flags + * @returns {Boolean} + */ - for (const input of this.inputs) { - const witness = input.witness; - const coin = view.getOutputFor(input); + hasStandardInputs(view) { + if (this.isCoinbase()) + return true; - if (!coin) - continue; + for (const input of this.inputs) { + const coin = view.getOutputFor(input); - if (witness.items.length === 0) - continue; + if (!coin) + return false; - let prev = coin.script; + if (coin.script.isPubkeyhash()) + continue; - if (prev.isScripthash()) { - prev = input.script.getRedeem(); - if (!prev) - return false; - } + if (coin.script.isScripthash()) { + const redeem = input.script.getRedeem(); - if (!prev.isProgram()) - return false; + if (!redeem) + return false; - if (prev.isWitnessPubkeyhash()) { - if (witness.items.length !== 2) - return false; + if (redeem.getSigops(true) > policy.MAX_P2SH_SIGOPS) + return false; - if (witness.items[0].length > 73) - return false; + continue; + } - if (witness.items[1].length > 65) + if (coin.script.isUnknown()) return false; - - continue; } - if (prev.isWitnessScripthash()) { - if (witness.items.length - 1 > policy.MAX_P2WSH_STACK) - return false; + return true; + } - for (let i = 0; i < witness.items.length - 1; i++) { - const item = witness.items[i]; - if (item.length > policy.MAX_P2WSH_PUSH) - return false; - } + /** + * Perform contextual checks to verify coin and witness standardness. + * @see IsBadWitness() + * @param {CoinView} view + * @returns {Boolean} + */ - const raw = witness.items[witness.items.length - 1]; + hasStandardWitness(view) { + if (this.isCoinbase()) + return true; - if (raw.length > policy.MAX_P2WSH_SIZE) - return false; + for (const input of this.inputs) { + const witness = input.witness; + const coin = view.getOutputFor(input); - const redeem = Script.fromRaw(raw); + if (!coin) + continue; - if (redeem.isPubkey()) { - if (witness.items.length - 1 !== 1) - return false; + if (witness.items.length === 0) + continue; - if (witness.items[0].length > 73) - return false; + let prev = coin.script; - continue; + if (prev.isScripthash()) { + prev = input.script.getRedeem(); + if (!prev) + return false; } - if (redeem.isPubkeyhash()) { - if (input.witness.items.length - 1 !== 2) + if (!prev.isProgram()) + return false; + + if (prev.isWitnessPubkeyhash()) { + if (witness.items.length !== 2) return false; if (witness.items[0].length > 73) @@ -1724,916 +1662,967 @@ TX.prototype.hasStandardWitness = function hasStandardWitness(view) { continue; } - const [m] = redeem.getMultisig(); - - if (m !== -1) { - if (witness.items.length - 1 !== m + 1) - return false; - - if (witness.items[0].length !== 0) + if (prev.isWitnessScripthash()) { + if (witness.items.length - 1 > policy.MAX_P2WSH_STACK) return false; - for (let i = 1; i < witness.items.length - 1; i++) { + for (let i = 0; i < witness.items.length - 1; i++) { const item = witness.items[i]; - if (item.length > 73) + if (item.length > policy.MAX_P2WSH_PUSH) return false; } - } - continue; - } + const raw = witness.items[witness.items.length - 1]; - if (witness.items.length > policy.MAX_P2WSH_STACK) - return false; + if (raw.length > policy.MAX_P2WSH_SIZE) + return false; - for (const item of witness.items) { - if (item.length > policy.MAX_P2WSH_PUSH) - return false; - } - } + const redeem = Script.fromRaw(raw); - return true; -}; + if (redeem.isPubkey()) { + if (witness.items.length - 1 !== 1) + return false; -/** - * Perform contextual checks to verify input, output, - * and fee values, as well as coinbase spend maturity - * (coinbases can only be spent 100 blocks or more - * after they're created). Note that this function is - * consensus critical. - * @param {CoinView} view - * @param {Number} height - Height at which the - * transaction is being spent. In the mempool this is - * the chain height plus one at the time it entered the pool. - * @returns {Boolean} - */ + if (witness.items[0].length > 73) + return false; -TX.prototype.verifyInputs = function verifyInputs(view, height) { - const [fee] = this.checkInputs(view, height); - return fee !== -1; -}; + continue; + } -/** - * Perform contextual checks to verify input, output, - * and fee values, as well as coinbase spend maturity - * (coinbases can only be spent 100 blocks or more - * after they're created). Note that this function is - * consensus critical. - * @param {CoinView} view - * @param {Number} height - Height at which the - * transaction is being spent. In the mempool this is - * the chain height plus one at the time it entered the pool. - * @returns {Array} [fee, reason, score] - */ + if (redeem.isPubkeyhash()) { + if (input.witness.items.length - 1 !== 2) + return false; -TX.prototype.checkInputs = function checkInputs(view, height) { - assert(typeof height === 'number'); + if (witness.items[0].length > 73) + return false; - let total = 0; + if (witness.items[1].length > 65) + return false; - for (const {prevout} of this.inputs) { - const entry = view.getEntry(prevout); + continue; + } - if (!entry) - return [-1, 'bad-txns-inputs-missingorspent', 0]; + const [m] = redeem.getMultisig(); - if (entry.coinbase) { - if (height - entry.height < consensus.COINBASE_MATURITY) - return [-1, 'bad-txns-premature-spend-of-coinbase', 0]; - } + if (m !== -1) { + if (witness.items.length - 1 !== m + 1) + return false; - const coin = view.getOutput(prevout); - assert(coin); + if (witness.items[0].length !== 0) + return false; - if (coin.value < 0 || coin.value > consensus.MAX_MONEY) - return [-1, 'bad-txns-inputvalues-outofrange', 100]; + for (let i = 1; i < witness.items.length - 1; i++) { + const item = witness.items[i]; + if (item.length > 73) + return false; + } + } - if (!util.isSafeAddition(total, coin.value)) { - return [-1, 'bad-txns-inputvalues-outofrange', 100]; - } + continue; + } + + if (witness.items.length > policy.MAX_P2WSH_STACK) + return false; - total += coin.value; + for (const item of witness.items) { + if (item.length > policy.MAX_P2WSH_PUSH) + return false; + } + } - if (total < 0 || total > consensus.MAX_MONEY) - return [-1, 'bad-txns-inputvalues-outofrange', 100]; + return true; } - // Overflows already checked in `isSane()`. - const value = this.getOutputValue(); + /** + * Perform contextual checks to verify input, output, + * and fee values, as well as coinbase spend maturity + * (coinbases can only be spent 100 blocks or more + * after they're created). Note that this function is + * consensus critical. + * @param {CoinView} view + * @param {Number} height - Height at which the + * transaction is being spent. In the mempool this is + * the chain height plus one at the time it entered the pool. + * @returns {Boolean} + */ + + verifyInputs(view, height) { + const [fee] = this.checkInputs(view, height); + return fee !== -1; + } + + /** + * Perform contextual checks to verify input, output, + * and fee values, as well as coinbase spend maturity + * (coinbases can only be spent 100 blocks or more + * after they're created). Note that this function is + * consensus critical. + * @param {CoinView} view + * @param {Number} height - Height at which the + * transaction is being spent. In the mempool this is + * the chain height plus one at the time it entered the pool. + * @returns {Array} [fee, reason, score] + */ + + checkInputs(view, height) { + assert(typeof height === 'number'); + + let total = 0; + + for (const {prevout} of this.inputs) { + const entry = view.getEntry(prevout); + + if (!entry) + return [-1, 'bad-txns-inputs-missingorspent', 0]; + + if (entry.coinbase) { + if (height - entry.height < consensus.COINBASE_MATURITY) + return [-1, 'bad-txns-premature-spend-of-coinbase', 0]; + } - if (total < value) - return [-1, 'bad-txns-in-belowout', 100]; + const coin = view.getOutput(prevout); + assert(coin); - const fee = total - value; + if (coin.value < 0 || coin.value > consensus.MAX_MONEY) + return [-1, 'bad-txns-inputvalues-outofrange', 100]; - if (fee < 0) - return [-1, 'bad-txns-fee-negative', 100]; + total += coin.value; - if (fee > consensus.MAX_MONEY) - return [-1, 'bad-txns-fee-outofrange', 100]; + if (total < 0 || total > consensus.MAX_MONEY) + return [-1, 'bad-txns-inputvalues-outofrange', 100]; + } - return [fee, 'valid', 0]; -}; + // Overflows already checked in `isSane()`. + const value = this.getOutputValue(); -/** - * Calculate the modified size of the transaction. This - * is used in the mempool for calculating priority. - * @param {Number?} size - The size to modify. If not present, - * virtual size will be used. - * @returns {Number} Modified size. - */ + if (total < value) + return [-1, 'bad-txns-in-belowout', 100]; + + const fee = total - value; -TX.prototype.getModifiedSize = function getModifiedSize(size) { - if (size == null) - size = this.getVirtualSize(); + if (fee < 0) + return [-1, 'bad-txns-fee-negative', 100]; - for (const input of this.inputs) { - const offset = 41 + Math.min(110, input.script.getSize()); - if (size > offset) - size -= offset; + if (fee > consensus.MAX_MONEY) + return [-1, 'bad-txns-fee-outofrange', 100]; + + return [fee, 'valid', 0]; } - return size; -}; + /** + * Calculate the modified size of the transaction. This + * is used in the mempool for calculating priority. + * @param {Number?} size - The size to modify. If not present, + * virtual size will be used. + * @returns {Number} Modified size. + */ -/** - * Calculate the transaction priority. - * @param {CoinView} view - * @param {Number} height - * @param {Number?} size - Size to calculate priority - * based on. If not present, virtual size will be used. - * @returns {Number} - */ + getModifiedSize(size) { + if (size == null) + size = this.getVirtualSize(); + + for (const input of this.inputs) { + const offset = 41 + Math.min(110, input.script.getSize()); + if (size > offset) + size -= offset; + } -TX.prototype.getPriority = function getPriority(view, height, size) { - assert(typeof height === 'number', 'Must pass in height.'); + return size; + } + + /** + * Calculate the transaction priority. + * @param {CoinView} view + * @param {Number} height + * @param {Number?} size - Size to calculate priority + * based on. If not present, virtual size will be used. + * @returns {Number} + */ - if (this.isCoinbase()) - return 0; + getPriority(view, height, size) { + assert(typeof height === 'number', 'Must pass in height.'); + + if (this.isCoinbase()) + return 0; - if (size == null) - size = this.getVirtualSize(); + if (size == null) + size = this.getVirtualSize(); - let sum = 0; + let sum = 0; - for (const {prevout} of this.inputs) { - const coin = view.getOutput(prevout); + for (const {prevout} of this.inputs) { + const coin = view.getOutput(prevout); - if (!coin) - continue; + if (!coin) + continue; - const coinHeight = view.getHeight(prevout); + const coinHeight = view.getHeight(prevout); - if (coinHeight === -1) - continue; + if (coinHeight === -1) + continue; - if (coinHeight <= height) { - const age = height - coinHeight; - sum += coin.value * age; + if (coinHeight <= height) { + const age = height - coinHeight; + sum += coin.value * age; + } } + + return Math.floor(sum / size); } - return Math.floor(sum / size); -}; + /** + * Calculate the transaction's on-chain value. + * @param {CoinView} view + * @returns {Number} + */ -/** - * Calculate the transaction's on-chain value. - * @param {CoinView} view - * @returns {Number} - */ + getChainValue(view) { + if (this.isCoinbase()) + return 0; -TX.prototype.getChainValue = function getChainValue(view) { - if (this.isCoinbase()) - return 0; + let value = 0; - let value = 0; + for (const {prevout} of this.inputs) { + const coin = view.getOutput(prevout); - for (const {prevout} of this.inputs) { - const coin = view.getOutput(prevout); + if (!coin) + continue; - if (!coin) - continue; + const height = view.getHeight(prevout); - const height = view.getHeight(prevout); + if (height === -1) + continue; - if (height === -1) - continue; + value += coin.value; + } - value += coin.value; + return value; } - return value; -}; + /** + * Determine whether the transaction is above the + * free threshold in priority. A transaction which + * passed this test is most likely relayable + * without a fee. + * @param {CoinView} view + * @param {Number?} height - If not present, tx + * height or network height will be used. + * @param {Number?} size - If not present, modified + * size will be calculated and used. + * @returns {Boolean} + */ -/** - * Determine whether the transaction is above the - * free threshold in priority. A transaction which - * passed this test is most likely relayable - * without a fee. - * @param {CoinView} view - * @param {Number?} height - If not present, tx - * height or network height will be used. - * @param {Number?} size - If not present, modified - * size will be calculated and used. - * @returns {Boolean} - */ + isFree(view, height, size) { + const priority = this.getPriority(view, height, size); + return priority > policy.FREE_THRESHOLD; + } -TX.prototype.isFree = function isFree(view, height, size) { - const priority = this.getPriority(view, height, size); - return priority > policy.FREE_THRESHOLD; -}; + /** + * Calculate minimum fee in order for the transaction + * to be relayable (not the constant min relay fee). + * @param {Number?} size - If not present, max size + * estimation will be calculated and used. + * @param {Rate?} rate - Rate of satoshi per kB. + * @returns {Amount} fee + */ -/** - * Calculate minimum fee in order for the transaction - * to be relayable (not the constant min relay fee). - * @param {Number?} size - If not present, max size - * estimation will be calculated and used. - * @param {Rate?} rate - Rate of satoshi per kB. - * @returns {Amount} fee - */ + getMinFee(size, rate) { + if (size == null) + size = this.getVirtualSize(); -TX.prototype.getMinFee = function getMinFee(size, rate) { - if (size == null) - size = this.getVirtualSize(); + return policy.getMinFee(size, rate); + } - return policy.getMinFee(size, rate); -}; + /** + * Calculate the minimum fee in order for the transaction + * to be relayable, but _round to the nearest kilobyte + * when taking into account size. + * @param {Number?} size - If not present, max size + * estimation will be calculated and used. + * @param {Rate?} rate - Rate of satoshi per kB. + * @returns {Amount} fee + */ -/** - * Calculate the minimum fee in order for the transaction - * to be relayable, but _round to the nearest kilobyte - * when taking into account size. - * @param {Number?} size - If not present, max size - * estimation will be calculated and used. - * @param {Rate?} rate - Rate of satoshi per kB. - * @returns {Amount} fee - */ + getRoundFee(size, rate) { + if (size == null) + size = this.getVirtualSize(); -TX.prototype.getRoundFee = function getRoundFee(size, rate) { - if (size == null) - size = this.getVirtualSize(); + return policy.getRoundFee(size, rate); + } - return policy.getRoundFee(size, rate); -}; + /** + * Calculate the transaction's rate based on size + * and fees. Size will be calculated if not present. + * @param {CoinView} view + * @param {Number?} size + * @returns {Rate} + */ -/** - * Calculate the transaction's rate based on size - * and fees. Size will be calculated if not present. - * @param {CoinView} view - * @param {Number?} size - * @returns {Rate} - */ + getRate(view, size) { + const fee = this.getFee(view); -TX.prototype.getRate = function getRate(view, size) { - const fee = this.getFee(view); + if (fee < 0) + return 0; - if (fee < 0) - return 0; + if (size == null) + size = this.getVirtualSize(); - if (size == null) - size = this.getVirtualSize(); + return policy.getRate(size, fee); + } - return policy.getRate(size, fee); -}; + /** + * Get all unique outpoint hashes. + * @returns {Hash[]} Outpoint hashes. + */ -/** - * Get all unique outpoint hashes. - * @returns {Hash[]} Outpoint hashes. - */ + getPrevout() { + if (this.isCoinbase()) + return []; -TX.prototype.getPrevout = function getPrevout() { - if (this.isCoinbase()) - return []; + const prevout = new BufferSet(); - const prevout = Object.create(null); + for (const input of this.inputs) + prevout.add(input.prevout.hash); - for (const input of this.inputs) - prevout[input.prevout.hash] = true; + return prevout.toArray(); + } - return Object.keys(prevout); -}; + /** + * Test a transaction against a bloom filter using + * the BIP37 matching algorithm. Note that this may + * update the filter depending on what the `update` + * value is. + * @see "Filter matching algorithm": + * @see https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki + * @param {BloomFilter} filter + * @returns {Boolean} True if the transaction matched. + */ -/** - * Test a transaction against a bloom filter using - * the BIP37 matching algorithm. Note that this may - * update the filter depending on what the `update` - * value is. - * @see "Filter matching algorithm": - * @see https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki - * @param {Bloom} filter - * @returns {Boolean} True if the transaction matched. - */ + isWatched(filter) { + let found = false; -TX.prototype.isWatched = function isWatched(filter) { - let found = false; - - // 1. Test the tx hash - if (filter.test(this.hash())) - found = true; - - // 2. Test data elements in output scripts - // (may need to update filter on match) - for (let i = 0; i < this.outputs.length; i++) { - const output = this.outputs[i]; - // Test the output script - if (output.script.test(filter)) { - if (filter.update === Bloom.flags.ALL) { - const prevout = Outpoint.fromTX(this, i); - filter.add(prevout.toRaw()); - } else if (filter.update === Bloom.flags.PUBKEY_ONLY) { - if (output.script.isPubkey() || output.script.isMultisig()) { + // 1. Test the tx hash + if (filter.test(this.hash())) + found = true; + + // 2. Test data elements in output scripts + // (may need to update filter on match) + for (let i = 0; i < this.outputs.length; i++) { + const output = this.outputs[i]; + // Test the output script + if (output.script.test(filter)) { + if (filter.update === 1 /* ALL */) { const prevout = Outpoint.fromTX(this, i); filter.add(prevout.toRaw()); + } else if (filter.update === 2 /* PUBKEY_ONLY */) { + if (output.script.isPubkey() || output.script.isMultisig()) { + const prevout = Outpoint.fromTX(this, i); + filter.add(prevout.toRaw()); + } } + found = true; } - found = true; } + + if (found) + return found; + + // 3. Test prev_out structure + // 4. Test data elements in input scripts + for (const input of this.inputs) { + const prevout = input.prevout; + + // Test the COutPoint structure + if (filter.test(prevout.toRaw())) + return true; + + // Test the input script + if (input.script.test(filter)) + return true; + } + + // 5. No match + return false; } - if (found) - return found; + /** + * Get little-endian tx hash. + * @returns {Hash} + */ - // 3. Test prev_out structure - // 4. Test data elements in input scripts - for (const input of this.inputs) { - const prevout = input.prevout; + rhash() { + return util.revHex(this.hash()); + } - // Test the COutPoint structure - if (filter.test(prevout.toRaw())) - return true; + /** + * Get little-endian wtx hash. + * @returns {Hash} + */ - // Test the input script - if (input.script.test(filter)) - return true; + rwhash() { + return util.revHex(this.witnessHash()); } - // 5. No match - return false; -}; + /** + * Get little-endian tx hash. + * @returns {Hash} + */ -/** - * Get little-endian tx hash. - * @returns {Hash} - */ + txid() { + return this.rhash(); + } -TX.prototype.rhash = function rhash() { - return util.revHex(this.hash('hex')); -}; + /** + * Get little-endian wtx hash. + * @returns {Hash} + */ -/** - * Get little-endian wtx hash. - * @returns {Hash} - */ + wtxid() { + return this.rwhash(); + } -TX.prototype.rwhash = function rwhash() { - return util.revHex(this.witnessHash('hex')); -}; + /** + * Convert the tx to an inv item. + * @returns {InvItem} + */ -/** - * Get little-endian tx hash. - * @returns {Hash} - */ + toInv() { + return new InvItem(InvItem.types.TX, this.hash()); + } -TX.prototype.txid = function txid() { - return this.rhash(); -}; + /** + * Inspect the transaction and return a more + * user-friendly representation of the data. + * @returns {Object} + */ -/** - * Get little-endian wtx hash. - * @returns {Hash} - */ + [inspectSymbol]() { + return this.format(); + } -TX.prototype.wtxid = function wtxid() { - return this.rwhash(); -}; + /** + * Inspect the transaction and return a more + * user-friendly representation of the data. + * @param {CoinView} view + * @param {ChainEntry} entry + * @param {Number} index + * @returns {Object} + */ -/** - * Convert the tx to an inv item. - * @returns {InvItem} - */ + format(view, entry, index) { + let rate = 0; + let fee = 0; + let height = -1; + let block = null; + let time = 0; + let date = null; -TX.prototype.toInv = function toInv() { - return new InvItem(InvItem.types.TX, this.hash('hex')); -}; + if (view) { + fee = this.getFee(view); + rate = this.getRate(view); -/** - * Inspect the transaction and return a more - * user-friendly representation of the data. - * @returns {Object} - */ + // Rate can exceed 53 bits in testing. + if (!Number.isSafeInteger(rate)) + rate = 0; + } -TX.prototype.inspect = function inspect() { - return this.format(); -}; + if (entry) { + height = entry.height; + block = util.revHex(entry.hash); + time = entry.time; + date = util.date(time); + } -/** - * Inspect the transaction and return a more - * user-friendly representation of the data. - * @param {CoinView} view - * @param {ChainEntry} entry - * @param {Number} index - * @returns {Object} - */ + if (index == null) + index = -1; + + return { + hash: this.txid(), + witnessHash: this.wtxid(), + size: this.getSize(), + virtualSize: this.getVirtualSize(), + value: Amount.btc(this.getOutputValue()), + fee: Amount.btc(fee), + rate: Amount.btc(rate), + minFee: Amount.btc(this.getMinFee()), + height: height, + block: block, + time: time, + date: date, + index: index, + version: this.version, + inputs: this.inputs.map((input) => { + const coin = view ? view.getOutputFor(input) : null; + return input.format(coin); + }), + outputs: this.outputs, + locktime: this.locktime, + floData: this.strFloData + }; + } + + /** + * Convert the transaction to an object suitable + * for JSON serialization. + * @returns {Object} + */ + + toJSON() { + return this.getJSON(); + } + + /** + * Convert the transaction to an object suitable + * for JSON serialization. Note that the hashes + * will be reversed to abide by bitcoind's legacy + * of little-endian uint256s. + * @param {Network} network + * @param {CoinView} view + * @param {ChainEntry} entry + * @param {Number} index + * @returns {Object} + */ + + getJSON(network, view, entry, index) { + let rate, fee, height, block, time, date; + + if (view) { + fee = this.getFee(view); + rate = this.getRate(view); + + // Rate can exceed 53 bits in testing. + if (!Number.isSafeInteger(rate)) + rate = 0; + } -TX.prototype.format = function format(view, entry, index) { - let rate = 0; - let fee = 0; - let height = -1; - let block = null; - let time = 0; - let date = null; - - if (view) { - fee = this.getFee(view); - rate = this.getRate(view); - - // Rate can exceed 53 bits in testing. - if (!Number.isSafeInteger(rate)) - rate = 0; - } - - if (entry) { - height = entry.height; - block = util.revHex(entry.hash); - time = entry.time; - date = util.date(time); - } - - if (index == null) - index = -1; - - return { - hash: this.txid(), - witnessHash: this.wtxid(), - size: this.getSize(), - virtualSize: this.getVirtualSize(), - value: Amount.btc(this.getOutputValue()), - fee: Amount.btc(fee), - rate: Amount.btc(rate), - minFee: Amount.btc(this.getMinFee()), - height: height, - block: block, - time: time, - date: date, - index: index, - version: this.version, - inputs: this.inputs.map((input) => { - const coin = view ? view.getOutputFor(input) : null; - return input.format(coin); - }), - outputs: this.outputs, - locktime: this.locktime, - floData: this.strFloData - }; -}; + if (entry) { + height = entry.height; + block = util.revHex(entry.hash); + time = entry.time; + date = util.date(time); + } -/** - * Convert the transaction to an object suitable - * for JSON serialization. - * @returns {Object} - */ + network = Network.get(network); -TX.prototype.toJSON = function toJSON() { - return this.getJSON(); -}; + return { + hash: this.txid(), + witnessHash: this.wtxid(), + fee: fee, + rate: rate, + mtime: util.now(), + height: height, + block: block, + time: time, + date: date, + index: index, + version: this.version, + inputs: this.inputs.map((input) => { + const coin = view ? view.getCoinFor(input) : null; + return input.getJSON(network, coin); + }), + outputs: this.outputs.map((output) => { + return output.getJSON(network); + }), + locktime: this.locktime, + hex: this.toRaw().toString('hex'), + floData: this.strFloData + }; + } -/** - * Convert the transaction to an object suitable - * for JSON serialization. Note that the hashes - * will be reversed to abide by bitcoind's legacy - * of little-endian uint256s. - * @param {Network} network - * @param {CoinView} view - * @param {ChainEntry} entry - * @param {Number} index - * @returns {Object} - */ + /** + * Inject properties from a json object. + * @private + * @param {Object} json + */ -TX.prototype.getJSON = function getJSON(network, view, entry, index) { - let rate, fee, height, block, time, date; - - if (view) { - fee = this.getFee(view); - rate = this.getRate(view); - - // Rate can exceed 53 bits in testing. - if (!Number.isSafeInteger(rate)) - rate = 0; - } - - if (entry) { - height = entry.height; - block = util.revHex(entry.hash); - time = entry.time; - date = util.date(time); - } - - network = Network.get(network); - - return { - hash: this.txid(), - witnessHash: this.wtxid(), - fee: fee, - rate: rate, - mtime: util.now(), - height: height, - block: block, - time: time, - date: date, - index: index, - version: this.version, - inputs: this.inputs.map((input) => { - const coin = view ? view.getCoinFor(input) : null; - return input.getJSON(network, coin); - }), - outputs: this.outputs.map((output) => { - return output.getJSON(network); - }), - locktime: this.locktime, - hex: this.toRaw().toString('hex'), - floData: this.strFloData - }; -}; + fromJSON(json) { + assert(json, 'TX data is required.'); + assert((json.version >>> 0) === json.version, 'Version must be a uint32.'); + assert(Array.isArray(json.inputs), 'Inputs must be an array.'); + assert(Array.isArray(json.outputs), 'Outputs must be an array.'); + assert((json.locktime >>> 0) === json.locktime, + 'Locktime must be a uint32.'); -/** - * Inject properties from a json object. - * @private - * @param {Object} json - */ + this.version = json.version; -TX.prototype.fromJSON = function fromJSON(json) { - assert(json, 'TX data is required.'); - assert(util.isU32(json.version), 'Version must be a uint32.'); - assert(Array.isArray(json.inputs), 'Inputs must be an array.'); - assert(Array.isArray(json.outputs), 'Outputs must be an array.'); - assert(util.isU32(json.locktime), 'Locktime must be a uint32.'); + for (const input of json.inputs) + this.inputs.push(Input.fromJSON(input)); - this.version = json.version; + for (const output of json.outputs) + this.outputs.push(Output.fromJSON(output)); - for (const input of json.inputs) - this.inputs.push(Input.fromJSON(input)); + this.strFloData = json.floData - for (const output of json.outputs) - this.outputs.push(Output.fromJSON(output)); + this.locktime = json.locktime; - this.strFloData = json.floData + return this; + } - this.locktime = json.locktime; + /** + * Instantiate a transaction from a + * jsonified transaction object. + * @param {Object} json - The jsonified transaction object. + * @returns {TX} + */ - return this; -}; + static fromJSON(json) { + return new this().fromJSON(json); + } -/** - * Instantiate a transaction from a - * jsonified transaction object. - * @param {Object} json - The jsonified transaction object. - * @returns {TX} - */ + /** + * Instantiate a transaction from a serialized Buffer. + * @param {Buffer} data + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {TX} + */ -TX.fromJSON = function fromJSON(json) { - return new TX().fromJSON(json); -}; + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } -/** - * Instantiate a transaction from a serialized Buffer. - * @param {Buffer} data - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {TX} - */ + /** + * Instantiate a transaction from a buffer reader. + * @param {BufferReader} br + * @param {Boolean} block + * @returns {TX} + */ -TX.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new TX().fromRaw(data); -}; + static fromReader(br, block) { + return new this().fromReader(br, block); + } -/** - * Instantiate a transaction from a buffer reader. - * @param {BufferReader} br - * @returns {TX} - */ + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -TX.fromReader = function fromReader(br) { - return new TX().fromReader(br); -}; + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + * @param {Boolean} block + */ -TX.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + fromReader(br, block) { + if (hasWitnessBytes(br)) + return this.fromWitnessReader(br, block); -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + const start = br.start(); -TX.prototype.fromReader = function fromReader(br) { - if (hasWitnessBytes(br)) - return this.fromWitnessReader(br); + this.version = br.readU32(); - br.start(); + const inCount = br.readVarint(); - this.version = br.readU32(); + for (let i = 0; i < inCount; i++) + this.inputs.push(Input.fromReader(br)); - const inCount = br.readVarint(); + const outCount = br.readVarint(); - for (let i = 0; i < inCount; i++) - this.inputs.push(Input.fromReader(br)); + for (let i = 0; i < outCount; i++) + this.outputs.push(Output.fromReader(br)); - const outCount = br.readVarint(); + this.locktime = br.readU32(); - for (let i = 0; i < outCount; i++) - this.outputs.push(Output.fromReader(br)); + if (block) { + this._offset = start; + this._block = true; + } - this.locktime = br.readU32(); + if (this.version >= 2){ + let floDataBuffer = br.readVarBytes(); + this.strFloData = Buffer.from(floDataBuffer).toString(); + } - if (this.version >= 2){ - var floDataBuffer = br.readVarBytes(); - this.strFloData = Buffer.from(floDataBuffer).toString(); - } + if (!this.mutable) { + this._raw = br.endData(); + this._size = this._raw.length; + this._witness = 0; + } else { + br.end(); + } - if (!this.mutable) { - this._raw = br.endData(); - this._size = this._raw.length; - this._witness = 0; - } else { - br.end(); + return this; } - return this; -}; + /** + * Inject properties from serialized + * buffer reader (witness serialization). + * @private + * @param {BufferReader} br + * @param {Boolean} block + */ -/** - * Inject properties from serialized - * buffer reader (witness serialization). - * @private - * @param {BufferReader} br - */ + fromWitnessReader(br, block) { + const start = br.start(); -TX.prototype.fromWitnessReader = function fromWitnessReader(br) { - br.start(); + this.version = br.readU32(); - this.version = br.readU32(); + assert(br.readU8() === 0, 'Non-zero marker.'); - assert(br.readU8() === 0, 'Non-zero marker.'); + let flags = br.readU8(); - let flags = br.readU8(); + assert(flags !== 0, 'Flags byte is zero.'); - assert(flags !== 0, 'Flags byte is zero.'); + const inCount = br.readVarint(); - const inCount = br.readVarint(); + for (let i = 0; i < inCount; i++) + this.inputs.push(Input.fromReader(br)); - for (let i = 0; i < inCount; i++) - this.inputs.push(Input.fromReader(br)); + const outCount = br.readVarint(); - const outCount = br.readVarint(); + for (let i = 0; i < outCount; i++) + this.outputs.push(Output.fromReader(br)); - for (let i = 0; i < outCount; i++) - this.outputs.push(Output.fromReader(br)); + let witness = 0; + let hasWitness = false; - let witness = 0; - let hasWitness = false; + if (flags & 1) { + flags ^= 1; - if (flags & 1) { - flags ^= 1; + witness = br.offset; - witness = br.offset; + for (const input of this.inputs) { + input.witness.fromReader(br); + if (input.witness.items.length > 0) + hasWitness = true; + } - for (const input of this.inputs) { - input.witness.fromReader(br); - if (input.witness.items.length > 0) - hasWitness = true; + witness = (br.offset - witness) + 2; } - witness = (br.offset - witness) + 2; - } + if (flags !== 0) + throw new Error('Unknown witness flag.'); - if (flags !== 0) - throw new Error('Unknown witness flag.'); + // We'll never be able to reserialize + // this to get the regular txid, and + // there's no way it's valid anyway. + if (this.inputs.length === 0 && this.outputs.length !== 0) + throw new Error('Zero input witness tx.'); - // We'll never be able to reserialize - // this to get the regular txid, and - // there's no way it's valid anyway. - if (this.inputs.length === 0 && this.outputs.length !== 0) - throw new Error('Zero input witness tx.'); + this.locktime = br.readU32(); + if (this.version >= 2){ + var floDataLength = br.readVarint(); + var floDataBuffer = br.readBytes(floDataLength); + this.strFloData = Buffer.from(floDataBuffer).toString(); + } - this.locktime = br.readU32(); - if (this.version >= 2){ - var floDataLength = br.readVarint(); - var floDataBuffer = br.readBytes(floDataLength); - this.strFloData = Buffer.from(floDataBuffer).toString(); - } + if (block) { + this._offset = start; + this._block = true; + } - if (!this.mutable && hasWitness) { - this._raw = br.endData(); - this._size = this._raw.length; - this._witness = witness; - } else { - br.end(); - } + if (!this.mutable && hasWitness) { + this._raw = br.endData(); + this._size = this._raw.length; + this._witness = witness; + } else { + br.end(); + } - return this; -}; + return this; + } -/** - * Serialize transaction without witness. - * @private - * @returns {RawTX} - */ + /** + * Serialize transaction without witness. + * @private + * @returns {RawTX} + */ -TX.prototype.frameNormal = function frameNormal() { - const raw = this.getNormalSizes(); - const bw = new StaticWriter(raw.size); - this.writeNormal(bw); - raw.data = bw.render(); - return raw; -}; + frameNormal() { + const raw = this.getNormalSizes(); + const bw = bio.write(raw.size); + this.writeNormal(bw); + raw.data = bw.render(); + return raw; + } -/** - * Serialize transaction with witness. Calculates the witness - * size as it is framing (exposed on return value as `witness`). - * @private - * @returns {RawTX} - */ + /** + * Serialize transaction with witness. Calculates the witness + * size as it is framing (exposed on return value as `witness`). + * @private + * @returns {RawTX} + */ -TX.prototype.frameWitness = function frameWitness() { - const raw = this.getWitnessSizes(); - const bw = new StaticWriter(raw.size); - this.writeWitness(bw); - raw.data = bw.render(); - return raw; -}; + frameWitness() { + const raw = this.getWitnessSizes(); + const bw = bio.write(raw.size); + this.writeWitness(bw); + raw.data = bw.render(); + return raw; + } -/** - * Serialize transaction without witness. - * @private - * @param {BufferWriter} bw - * @returns {RawTX} - */ + /** + * Serialize transaction without witness. + * @private + * @param {BufferWriter} bw + * @returns {RawTX} + */ -TX.prototype.writeNormal = function writeNormal(bw) { - if (this.inputs.length === 0 && this.outputs.length !== 0) - throw new Error('Cannot serialize zero-input tx.'); + writeNormal(bw) { + if (this.inputs.length === 0 && this.outputs.length !== 0) + throw new Error('Cannot serialize zero-input tx.'); - bw.writeU32(this.version); + bw.writeU32(this.version); - bw.writeVarint(this.inputs.length); + bw.writeVarint(this.inputs.length); - for (const input of this.inputs) - input.toWriter(bw); + for (const input of this.inputs) + input.toWriter(bw); - bw.writeVarint(this.outputs.length); + bw.writeVarint(this.outputs.length); - for (const output of this.outputs) - output.toWriter(bw); + for (const output of this.outputs) + output.toWriter(bw); - bw.writeU32(this.locktime); + bw.writeU32(this.locktime); - if (this.version >= 2){ - bw.writeVarBytes(Buffer.from(this.strFloData)); - } + if (this.version >= 2){ + bw.writeVarBytes(Buffer.from(this.strFloData)); + } - return bw; -}; + return bw; + }; -/** - * Serialize transaction with witness. Calculates the witness - * size as it is framing (exposed on return value as `witness`). - * @private - * @param {BufferWriter} bw - * @returns {RawTX} - */ + /** + * Serialize transaction with witness. Calculates the witness + * size as it is framing (exposed on return value as `witness`). + * @private + * @param {BufferWriter} bw + * @returns {RawTX} + */ -TX.prototype.writeWitness = function writeWitness(bw) { - if (this.inputs.length === 0 && this.outputs.length !== 0) - throw new Error('Cannot serialize zero-input tx.'); + writeWitness(bw) { + if (this.inputs.length === 0 && this.outputs.length !== 0) + throw new Error('Cannot serialize zero-input tx.'); - bw.writeU32(this.version); - bw.writeU8(0); - bw.writeU8(1); + bw.writeU32(this.version); + bw.writeU8(0); + bw.writeU8(1); - bw.writeVarint(this.inputs.length); + bw.writeVarint(this.inputs.length); - for (const input of this.inputs) - input.toWriter(bw); + for (const input of this.inputs) + input.toWriter(bw); - bw.writeVarint(this.outputs.length); + bw.writeVarint(this.outputs.length); - for (const output of this.outputs) - output.toWriter(bw); + for (const output of this.outputs) + output.toWriter(bw); - const start = bw.offset; + const start = bw.offset; - for (const input of this.inputs) - input.witness.toWriter(bw); + for (const input of this.inputs) + input.witness.toWriter(bw); - const witness = bw.offset - start; + const witness = bw.offset - start; - bw.writeU32(this.locktime); + bw.writeU32(this.locktime); - if (this.version >= 2){ - bw.writeVarBytes(Buffer.from(this.strFloData)); - } + if (this.version >= 2){ + bw.writeVarBytes(Buffer.from(this.strFloData)); + } - if (witness === this.inputs.length) - throw new Error('Cannot serialize empty-witness tx.'); + if (witness === this.inputs.length) + throw new Error('Cannot serialize empty-witness tx.'); - return bw; -}; + return bw; + } -/** - * Calculate the real size of the transaction - * without the witness vector. - * @returns {RawTX} - */ + /** + * Calculate the real size of the transaction + * without the witness vector. + * @returns {RawTX} + */ -TX.prototype.getNormalSizes = function getNormalSizes() { - let base = 0; + getNormalSizes() { + let base = 0; - base += 4; + base += 4; - base += encoding.sizeVarint(this.inputs.length); + base += encoding.sizeVarint(this.inputs.length); - for (const input of this.inputs) - base += input.getSize(); + for (const input of this.inputs) + base += input.getSize(); - base += encoding.sizeVarint(this.outputs.length); + base += encoding.sizeVarint(this.outputs.length); - for (const output of this.outputs) - base += output.getSize(); + for (const output of this.outputs) + base += output.getSize(); - if (this.version >= 2){ - let bufferLength = Buffer.from(this.strFloData).length; + if (this.version >= 2){ + let bufferLength = Buffer.from(this.strFloData).length; - if (this.strFloData.length > 0){ - base += encoding.sizeVarint(bufferLength); - base += bufferLength - } else { - base += encoding.sizeVarint(0); + if (this.strFloData.length > 0){ + base += encoding.sizeVarint(bufferLength); + base += bufferLength + } else { + base += encoding.sizeVarint(0); + } } - } - base += 4; + base += 4; - return new RawTX(base, 0); -}; + return new RawTX(base, 0); + } -/** - * Calculate the real size of the transaction - * with the witness included. - * @returns {RawTX} - */ + /** + * Calculate the real size of the transaction + * with the witness included. + * @returns {RawTX} + */ -TX.prototype.getWitnessSizes = function getWitnessSizes() { - let base = 0; - let witness = 0; + getWitnessSizes() { + let base = 0; + let witness = 0; - base += 4; - witness += 2; + base += 4; + witness += 2; - base += encoding.sizeVarint(this.inputs.length); + base += encoding.sizeVarint(this.inputs.length); - for (const input of this.inputs) { - base += input.getSize(); - witness += input.witness.getVarSize(); - } + for (const input of this.inputs) { + base += input.getSize(); + witness += input.witness.getVarSize(); + } - base += encoding.sizeVarint(this.outputs.length); + base += encoding.sizeVarint(this.outputs.length); - for (const output of this.outputs) - base += output.getSize(); + for (const output of this.outputs) + base += output.getSize(); - if (this.version >= 2){ - let bufferLength = Buffer.from(this.strFloData).length; + if (this.version >= 2){ + let bufferLength = Buffer.from(this.strFloData).length; - if (this.strFloData.length > 0){ - base += encoding.sizeVarint(bufferLength); - base += bufferLength - } else { - base += encoding.sizeVarint(0); + if (this.strFloData.length > 0){ + base += encoding.sizeVarint(bufferLength); + base += bufferLength + } else { + base += encoding.sizeVarint(0); + } } - } - base += 4; + base += 4; - return new RawTX(base + witness, witness); -}; + return new RawTX(base + witness, witness); + } -/** - * Test whether an object is a TX. - * @param {Object} obj - * @returns {Boolean} - */ + /** + * Test whether an object is a TX. + * @param {Object} obj + * @returns {Boolean} + */ -TX.isTX = function isTX(obj) { - return obj instanceof TX; -}; + static isTX(obj) { + return obj instanceof TX; + } +} /* * Helpers @@ -2647,10 +2636,12 @@ function hasWitnessBytes(br) { && br.data[br.offset + 5] !== 0; } -function RawTX(size, witness) { - this.data = null; - this.size = size; - this.witness = witness; +class RawTX { + constructor(size, witness) { + this.data = null; + this.size = size; + this.witness = witness; + } } /* diff --git a/lib/primitives/txmeta.js b/lib/primitives/txmeta.js index 10ccc0864..9b5402ed8 100644 --- a/lib/primitives/txmeta.js +++ b/lib/primitives/txmeta.js @@ -6,299 +6,303 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); +const bio = require('bufio'); const util = require('../utils/util'); const TX = require('./tx'); -const StaticWriter = require('../utils/staticwriter'); -const BufferReader = require('../utils/reader'); +const {inspectSymbol} = require('../utils'); /** + * TXMeta * An extended transaction object. * @alias module:primitives.TXMeta - * @constructor - * @param {Object} options */ -function TXMeta(options) { - if (!(this instanceof TXMeta)) - return new TXMeta(options); - - this.tx = new TX(); - this.mtime = util.now(); - this.height = -1; - this.block = null; - this.time = 0; - this.index = -1; - - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -TXMeta.prototype.fromOptions = function fromOptions(options) { - if (options.tx) { - assert(options.tx instanceof TX); - this.tx = options.tx; - } - - if (options.mtime != null) { - assert(util.isU32(options.mtime)); - this.mtime = options.mtime; +class TXMeta { + /** + * Create an extended transaction. + * @constructor + * @param {Object?} options + */ + + constructor(options) { + this.tx = new TX(); + this.mtime = util.now(); + this.height = -1; + this.block = null; + this.time = 0; + this.index = -1; + + if (options) + this.fromOptions(options); } - if (options.height != null) { - assert(util.isInt(options.height)); - this.height = options.height; + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + if (options.tx) { + assert(options.tx instanceof TX); + this.tx = options.tx; + } + + if (options.mtime != null) { + assert((options.mtime >>> 0) === options.mtime); + this.mtime = options.mtime; + } + + if (options.height != null) { + assert(Number.isSafeInteger(options.height)); + this.height = options.height; + } + + if (options.block !== undefined) { + assert(options.block == null || Buffer.isBuffer(options.block)); + this.block = options.block; + } + + if (options.time != null) { + assert((options.time >>> 0) === options.time); + this.time = options.time; + } + + if (options.index != null) { + assert(Number.isSafeInteger(options.index)); + this.index = options.index; + } + + return this; } - if (options.block !== undefined) { - assert(options.block === null || typeof options.block === 'string'); - this.block = options.block; - } + /** + * Instantiate TXMeta from options. + * @param {Object} options + * @returns {TXMeta} + */ - if (options.time != null) { - assert(util.isU32(options.time)); - this.time = options.time; + static fromOptions(options) { + return new this().fromOptions(options); } - if (options.index != null) { - assert(util.isInt(options.index)); - this.index = options.index; + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromTX(tx, entry, index) { + this.tx = tx; + if (entry) { + this.height = entry.height; + this.block = entry.hash; + this.time = entry.time; + this.index = index; + } + return this; } - return this; -}; - -/** - * Instantiate TXMeta from options. - * @param {Object} options - * @returns {TXMeta} - */ - -TXMeta.fromOptions = function fromOptions(options) { - return new TXMeta().fromOptions(options); -}; + /** + * Instantiate TXMeta from options. + * @param {Object} options + * @returns {TXMeta} + */ -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -TXMeta.prototype.fromTX = function fromTX(tx, entry, index) { - this.tx = tx; - if (entry) { - this.height = entry.height; - this.block = entry.hash; - this.time = entry.time; - this.index = index; + static fromTX(tx, entry, index) { + return new this().fromTX(tx, entry, index); } - return this; -}; - -/** - * Instantiate TXMeta from options. - * @param {Object} options - * @returns {TXMeta} - */ - -TXMeta.fromTX = function fromTX(tx, entry, index) { - return new TXMeta().fromTX(tx, entry, index); -}; - -/** - * Inspect the transaction. - * @returns {Object} - */ - -TXMeta.prototype.inspect = function inspect() { - return this.format(); -}; - -/** - * Inspect the transaction. - * @returns {Object} - */ - -TXMeta.prototype.format = function format(view) { - const data = this.tx.format(view, null, this.index); - data.mtime = this.mtime; - data.height = this.height; - data.block = this.block ? util.revHex(this.block) : null; - data.time = this.time; - return data; -}; - -/** - * Convert transaction to JSON. - * @returns {Object} - */ - -TXMeta.prototype.toJSON = function toJSON() { - return this.getJSON(); -}; - -/** - * Convert the transaction to an object suitable - * for JSON serialization. - * @param {Network} network - * @param {CoinView} view - * @returns {Object} - */ - -TXMeta.prototype.getJSON = function getJSON(network, view, chainHeight) { - const json = this.tx.getJSON(network, view, null, this.index); - json.mtime = this.mtime; - json.height = this.height; - json.block = this.block ? util.revHex(this.block) : null; - json.time = this.time; - json.confirmations = 0; - if (chainHeight != null) - json.confirmations = chainHeight - this.height + 1; + /** + * Inspect the transaction. + * @returns {Object} + */ - return json; -}; + [inspectSymbol]() { + return this.format(); + } -/** - * Inject properties from a json object. - * @private - * @param {Object} json - */ + /** + * Inspect the transaction. + * @returns {Object} + */ + + format(view) { + const data = this.tx.format(view, null, this.index); + data.mtime = this.mtime; + data.height = this.height; + data.block = this.block ? util.revHex(this.block) : null; + data.time = this.time; + return data; + } -TXMeta.prototype.fromJSON = function fromJSON(json) { - this.tx.fromJSON(json); + /** + * Convert transaction to JSON. + * @returns {Object} + */ - assert(util.isU32(json.mtime)); - assert(util.isInt(json.height)); - assert(!json.block || typeof json.block === 'string'); - assert(util.isU32(json.time)); - assert(util.isInt(json.index)); + toJSON() { + return this.getJSON(); + } - this.mtime = json.mtime; - this.height = json.height; - this.block = util.revHex(json.block); - this.index = json.index; + /** + * Convert the transaction to an object suitable + * for JSON serialization. + * @param {Network} network + * @param {CoinView} view + * @returns {Object} + */ + + getJSON(network, view, chainHeight) { + const json = this.tx.getJSON(network, view, null, this.index); + json.mtime = this.mtime; + json.height = this.height; + json.block = this.block ? util.revHex(this.block) : null; + json.time = this.time; + json.confirmations = 0; + + if (chainHeight != null && this.height !== -1) + json.confirmations = chainHeight - this.height + 1; + + return json; + } - return this; -}; + /** + * Inject properties from a json object. + * @private + * @param {Object} json + */ -/** - * Instantiate a transaction from a - * jsonified transaction object. - * @param {Object} json - The jsonified transaction object. - * @returns {TX} - */ + fromJSON(json) { + this.tx.fromJSON(json); -TXMeta.fromJSON = function fromJSON(json) { - return new TXMeta().fromJSON(JSON); -}; + assert((json.mtime >>> 0) === json.mtime); + assert(Number.isSafeInteger(json.height)); + assert(!json.block || typeof json.block === 'string'); + assert((json.time >>> 0) === json.time); + assert(Number.isSafeInteger(json.index)); -/** - * Calculate serialization size. - * @returns {Number} - */ + this.mtime = json.mtime; + this.height = json.height; + this.block = util.fromRev(json.block); + this.index = json.index; -TXMeta.prototype.getSize = function getSize() { - let size = 0; + return this; + } - size += this.tx.getSize(); - size += 4; + /** + * Instantiate a transaction from a + * jsonified transaction object. + * @param {Object} json - The jsonified transaction object. + * @returns {TX} + */ - if (this.block) { - size += 1; - size += 32; - size += 4 * 3; - } else { - size += 1; + static fromJSON(json) { + return new this().fromJSON(json); } - return size; -}; + /** + * Calculate serialization size. + * @returns {Number} + */ -/** - * Serialize a transaction to "extended format". - * This is the serialization format bcoin uses internally - * to store transactions in the database. The extended - * serialization includes the height, block hash, index, - * timestamp, and pending-since time. - * @returns {Buffer} - */ + getSize() { + let size = 0; -TXMeta.prototype.toRaw = function toRaw() { - const size = this.getSize(); - const bw = new StaticWriter(size); + size += this.tx.getSize(); + size += 4; - this.tx.toWriter(bw); + if (this.block) { + size += 1; + size += 32; + size += 4 * 3; + } else { + size += 1; + } - bw.writeU32(this.mtime); + return size; + } - if (this.block) { - bw.writeU8(1); - bw.writeHash(this.block); - bw.writeU32(this.height); - bw.writeU32(this.time); - bw.writeU32(this.index); - } else { - bw.writeU8(0); + /** + * Serialize a transaction to "extended format". + * This is the serialization format bcoin uses internally + * to store transactions in the database. The extended + * serialization includes the height, block hash, index, + * timestamp, and pending-since time. + * @returns {Buffer} + */ + + toRaw() { + const size = this.getSize(); + const bw = bio.write(size); + + this.tx.toWriter(bw); + + bw.writeU32(this.mtime); + + if (this.block) { + bw.writeU8(1); + bw.writeHash(this.block); + bw.writeU32(this.height); + bw.writeU32(this.time); + bw.writeU32(this.index); + } else { + bw.writeU8(0); + } + + return bw.render(); } - return bw.render(); -}; + /** + * Inject properties from "extended" serialization format. + * @private + * @param {Buffer} data + */ -/** - * Inject properties from "extended" serialization format. - * @private - * @param {Buffer} data - */ + fromRaw(data) { + const br = bio.read(data); -TXMeta.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); + this.tx.fromReader(br); - this.tx.fromReader(br); + this.mtime = br.readU32(); - this.mtime = br.readU32(); + if (br.readU8() === 1) { + this.block = br.readHash(); + this.height = br.readU32(); + this.time = br.readU32(); + this.index = br.readU32(); + if (this.index === 0x7fffffff) + this.index = -1; + } - if (br.readU8() === 1) { - this.block = br.readHash('hex'); - this.height = br.readU32(); - this.time = br.readU32(); - this.index = br.readU32(); - if (this.index === 0x7fffffff) - this.index = -1; + return this; } - return this; -}; - -/** - * Instantiate a transaction from a Buffer - * in "extended" serialization format. - * @param {Buffer} data - * @param {String?} enc - One of `"hex"` or `null`. - * @returns {TX} - */ + /** + * Instantiate a transaction from a Buffer + * in "extended" serialization format. + * @param {Buffer} data + * @param {String?} enc - One of `"hex"` or `null`. + * @returns {TX} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } -TXMeta.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new TXMeta().fromRaw(data); -}; + /** + * Test whether an object is an TXMeta. + * @param {Object} obj + * @returns {Boolean} + */ -/** - * Test whether an object is an TXMeta. - * @param {Object} obj - * @returns {Boolean} - */ - -TXMeta.isTXMeta = function isTXMeta(obj) { - return obj instanceof TXMeta; -}; + static isTXMeta(obj) { + return obj instanceof TXMeta; + } +} /* * Expose diff --git a/lib/protocol/consensus.js b/lib/protocol/consensus.js index a1f3061c2..b8b8b4900 100644 --- a/lib/protocol/consensus.js +++ b/lib/protocol/consensus.js @@ -11,8 +11,8 @@ * @module protocol/consensus */ -const assert = require('assert'); -const BN = require('../crypto/bn'); +const assert = require('bsert'); +const BN = require('bcrypto/lib/bn.js'); /** * One bitcoin in satoshis. @@ -93,6 +93,14 @@ exports.MAX_BLOCK_SIGOPS = 1000000 / 50; exports.MAX_BLOCK_SIGOPS_COST = 80000; +/** + * Size of set to pick median time from. + * @const {Number} + * @default + */ + +exports.MEDIAN_TIMESPAN = 11; + /** * What bits to set in version * for versionbits blocks. @@ -218,6 +226,14 @@ exports.MAX_MULTISIG_PUBKEYS = 20; exports.BIP16_TIME = 1349049600; +/** + * A hash of all zeroes. + * @const {Buffer} + * @default + */ + +exports.ZERO_HASH = Buffer.alloc(32, 0x00); + /** * Convert a compact number to a big number. * Used for `block.bits` -> `target` conversion. @@ -298,6 +314,9 @@ exports.verifyPOW = function verifyPOW(hash, bits) { if (target.isNeg() || target.isZero()) return false; + if (target.bitLength() > 256) + return false; + const num = new BN(hash, 'le'); if (num.gt(target)) diff --git a/lib/protocol/errors.js b/lib/protocol/errors.js index 765e43563..919b07cfc 100644 --- a/lib/protocol/errors.js +++ b/lib/protocol/errors.js @@ -11,14 +11,14 @@ * @module protocol/errors */ -const assert = require('assert'); +const assert = require('bsert'); /** + * Verify Error * An error thrown during verification. Can be either * a mempool transaction validation error or a blockchain * block verification error. Ultimately used to send * `reject` packets to peers. - * @constructor * @extends Error * @param {Block|TX} msg * @param {String} code - Reject packet code. @@ -26,37 +26,42 @@ const assert = require('assert'); * @param {Number} score - Ban score increase * (can be -1 for no reject packet). * @param {Boolean} malleated - * @property {String} code - * @property {Buffer} hash - * @property {Number} height (will be the coinbase height if not present). - * @property {Number} score - * @property {String} message - * @property {Boolean} malleated */ -function VerifyError(msg, code, reason, score, malleated) { - Error.call(this); +class VerifyError extends Error { + /** + * Create a verify error. + * @constructor + * @param {Block|TX} msg + * @param {String} code - Reject packet code. + * @param {String} reason - Reject packet reason. + * @param {Number} score - Ban score increase + * (can be -1 for no reject packet). + * @param {Boolean} malleated + */ - assert(typeof code === 'string'); - assert(typeof reason === 'string'); - assert(score >= 0); + constructor(msg, code, reason, score, malleated) { + super(); - this.type = 'VerifyError'; - this.message = ''; - this.code = code; - this.reason = reason; - this.score = score; - this.hash = msg.hash('hex'); - this.malleated = malleated || false; + assert(typeof code === 'string'); + assert(typeof reason === 'string'); + assert(score >= 0); - this.message = `Verification failure: ${reason}` - + ` (code=${code} score=${score} hash=${msg.rhash()})`; + this.type = 'VerifyError'; + this.message = ''; + this.code = code; + this.reason = reason; + this.score = score; + this.hash = msg.hash(); + this.malleated = malleated || false; - if (Error.captureStackTrace) - Error.captureStackTrace(this, VerifyError); -} + this.message = `Verification failure: ${reason}` + + ` (code=${code} score=${score} hash=${msg.rhash()})`; -Object.setPrototypeOf(VerifyError.prototype, Error.prototype); + if (Error.captureStackTrace) + Error.captureStackTrace(this, VerifyError); + } +} /* * Expose diff --git a/lib/protocol/network.js b/lib/protocol/network.js index 439641c36..414481537 100644 --- a/lib/protocol/network.js +++ b/lib/protocol/network.js @@ -7,367 +7,374 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); +const assert = require('bsert'); +const binary = require('../utils/binary'); const networks = require('./networks'); const consensus = require('./consensus'); const TimeData = require('./timedata'); +const {inspectSymbol} = require('../utils'); /** + * Network * Represents a network. * @alias module:protocol.Network - * @constructor - * @param {Object|NetworkType} options - See {@link module:network}. */ -function Network(options) { - if (!(this instanceof Network)) - return new Network(options); - - assert(!Network[options.type], 'Cannot create two networks.'); - - this.type = options.type; - this.seeds = options.seeds; - this.magic = options.magic; - this.port = options.port; - this.checkpointMap = options.checkpointMap; - this.lastCheckpoint = options.lastCheckpoint; - this.checkpoints = []; - this.halvingInterval = options.halvingInterval; - this.genesis = options.genesis; - this.genesisBlock = options.genesisBlock; - this.pow = options.pow; - this.block = options.block; - this.bip30 = options.bip30; - this.activationThreshold = options.activationThreshold; - this.minerWindow = options.minerWindow; - this.deployments = options.deployments; - this.deploys = options.deploys; - this.unknownBits = ~consensus.VERSION_TOP_MASK; - this.keyPrefix = options.keyPrefix; - this.addressPrefix = options.addressPrefix; - this.requireStandard = options.requireStandard; - this.rpcPort = options.rpcPort; - this.minRelay = options.minRelay; - this.feeRate = options.feeRate; - this.maxFeeRate = options.maxFeeRate; - this.selfConnect = options.selfConnect; - this.requestMempool = options.requestMempool; - this.time = new TimeData(); - - this._init(); -} - -/** - * Default network. - * @type {Network} - */ +class Network { + /** + * Create a network. + * @constructor + * @param {Object} options + */ + + constructor(options) { + assert(!Network[options.type], 'Cannot create two networks.'); + + this.type = options.type; + this.seeds = options.seeds; + this.magic = options.magic; + this.port = options.port; + this.checkpointMap = options.checkpointMap; + this.lastCheckpoint = options.lastCheckpoint; + this.checkpoints = []; + this.halvingInterval = options.halvingInterval; + this.genesis = options.genesis; + this.genesisBlock = options.genesisBlock; + this.pow = options.pow; + this.block = options.block; + this.bip30 = options.bip30; + this.activationThreshold = options.activationThreshold; + this.minerWindow = options.minerWindow; + this.deployments = options.deployments; + this.deploys = options.deploys; + this.unknownBits = ~consensus.VERSION_TOP_MASK; + this.keyPrefix = options.keyPrefix; + this.addressPrefix = options.addressPrefix; + this.requireStandard = options.requireStandard; + this.rpcPort = options.rpcPort; + this.walletPort = options.walletPort; + this.minRelay = options.minRelay; + this.feeRate = options.feeRate; + this.maxFeeRate = options.maxFeeRate; + this.selfConnect = options.selfConnect; + this.requestMempool = options.requestMempool; + this.time = new TimeData(); + + this.init(); + } -Network.primary = null; + /** + * Get a deployment by bit index. + * @param {Number} bit + * @returns {Object} + */ -/** - * Default network type. - * @type {String} - */ + init() { + let bits = 0; -Network.type = null; + for (const deployment of this.deploys) + bits |= 1 << deployment.bit; -/* - * Networks (to avoid hash table mode). - */ + bits |= consensus.VERSION_TOP_MASK; -Network.main = null; -Network.testnet = null; -Network.regtest = null; -Network.simnet = null; + this.unknownBits = ~bits >>> 0; -/** - * Get a deployment by bit index. - * @param {Number} bit - * @returns {Object} - */ + for (const key of Object.keys(this.checkpointMap)) { + const hash = this.checkpointMap[key]; + const height = Number(key); -Network.prototype._init = function _init() { - let bits = 0; + this.checkpoints.push({ hash, height }); + } - for (const deployment of this.deploys) - bits |= 1 << deployment.bit; + this.checkpoints.sort(cmpNode); + } - bits |= consensus.VERSION_TOP_MASK; + /** + * Get a deployment by bit index. + * @param {Number} bit + * @returns {Object} + */ - this.unknownBits = ~bits; + byBit(bit) { + const index = binary.search(this.deploys, bit, cmpBit); - for (const key of Object.keys(this.checkpointMap)) { - const hash = this.checkpointMap[key]; - const height = Number(key); + if (index === -1) + return null; - this.checkpoints.push({ hash: hash, height: height }); + return this.deploys[index]; } - this.checkpoints.sort(cmpNode); -}; + /** + * Get network adjusted time. + * @returns {Number} + */ -/** - * Get a deployment by bit index. - * @param {Number} bit - * @returns {Object} - */ - -Network.prototype.byBit = function byBit(bit) { - const index = util.binarySearch(this.deploys, bit, cmpBit); - - if (index === -1) - return null; - - return this.deploys[index]; -}; + now() { + return this.time.now(); + } -/** - * Get network adjusted time. - * @returns {Number} - */ + /** + * Get network adjusted time in milliseconds. + * @returns {Number} + */ -Network.prototype.now = function now() { - return this.time.now(); -}; + ms() { + return this.time.ms(); + } -/** - * Get network adjusted time in milliseconds. - * @returns {Number} - */ + /** + * Create a network. Get existing network if possible. + * @param {NetworkType|Object} options + * @returns {Network} + */ -Network.prototype.ms = function ms() { - return this.time.ms(); -}; + static create(options) { + if (typeof options === 'string') + options = networks[options]; -/** - * Create a network. Get existing network if possible. - * @param {NetworkType|Object} options - * @returns {Network} - */ + assert(options, 'Unknown network.'); -Network.create = function create(options) { - if (typeof options === 'string') - options = networks[options]; + if (Network[options.type]) + return Network[options.type]; - assert(options, 'Unknown network.'); + const network = new Network(options); - if (Network[options.type]) - return Network[options.type]; + Network[network.type] = network; - const network = new Network(options); + if (!Network.primary) + Network.primary = network; - Network[network.type] = network; + return network; + } - if (!Network.primary) - Network.primary = network; + /** + * Set the default network. This network will be used + * if nothing is passed as the `network` option for + * certain objects. + * @param {NetworkType} type - Network type. + * @returns {Network} + */ + + static set(type) { + assert(typeof type === 'string', 'Bad network.'); + Network.primary = Network.get(type); + Network.type = type; + return Network.primary; + } - return network; -}; + /** + * Get a network with a string or a Network object. + * @param {NetworkType|Network} type - Network type. + * @returns {Network} + */ -/** - * Set the default network. This network will be used - * if nothing is passed as the `network` option for - * certain objects. - * @param {NetworkType} type - Network type. - * @returns {Network} - */ + static get(type) { + if (!type) { + assert(Network.primary, 'No default network.'); + return Network.primary; + } -Network.set = function set(type) { - assert(typeof type === 'string', 'Bad network.'); - Network.primary = Network.get(type); - Network.type = type; - return Network.primary; -}; + if (type instanceof Network) + return type; -/** - * Get a network with a string or a Network object. - * @param {NetworkType|Network} type - Network type. - * @returns {Network} - */ + if (typeof type === 'string') + return Network.create(type); -Network.get = function get(type) { - if (!type) { - assert(Network.primary, 'No default network.'); - return Network.primary; + throw new Error('Unknown network.'); } - if (type instanceof Network) - return type; + /** + * Get a network with a string or a Network object. + * @param {NetworkType|Network} type - Network type. + * @returns {Network} + */ - if (typeof type === 'string') - return Network.create(type); + static ensure(type) { + if (!type) { + assert(Network.primary, 'No default network.'); + return Network.primary; + } - throw new Error('Unknown network.'); -}; + if (type instanceof Network) + return type; -/** - * Get a network with a string or a Network object. - * @param {NetworkType|Network} type - Network type. - * @returns {Network} - */ + if (typeof type === 'string') { + if (networks[type]) + return Network.create(type); + } -Network.ensure = function ensure(type) { - if (!type) { assert(Network.primary, 'No default network.'); + return Network.primary; } - if (type instanceof Network) - return type; - - if (typeof type === 'string') { - if (networks[type]) - return Network.create(type); + /** + * Get a network by an associated comparator. + * @private + * @param {Object} value + * @param {Function} compare + * @param {Network|null} network + * @param {String} name + * @returns {Network} + */ + + static by(value, compare, network, name) { + if (network) { + network = Network.get(network); + if (compare(network, value)) + return network; + throw new Error(`Network mismatch for ${name}.`); + } + + for (const type of networks.types) { + network = networks[type]; + if (compare(network, value)) + return Network.get(type); + } + + throw new Error(`Network not found for ${name}.`); } - assert(Network.primary, 'No default network.'); + /** + * Get a network by its magic number. + * @param {Number} value + * @param {Network?} network + * @returns {Network} + */ - return Network.primary; -}; + static fromMagic(value, network) { + return Network.by(value, cmpMagic, network, 'magic number'); + } -/** - * Get a network by an associated comparator. - * @private - * @param {Object} value - * @param {Function} compare - * @param {Network|null} network - * @param {String} name - * @returns {Network} - */ + /** + * Get a network by its WIF prefix. + * @param {Number} value + * @param {Network?} network + * @returns {Network} + */ -Network.by = function by(value, compare, network, name) { - if (network) { - network = Network.get(network); - if (compare(network, value)) - return network; - throw new Error(`Network mismatch for ${name}.`); + static fromWIF(prefix, network) { + return Network.by(prefix, cmpWIF, network, 'WIF'); } - for (const type of networks.types) { - network = networks[type]; - if (compare(network, value)) - return Network.get(type); + /** + * Get a network by its xpubkey prefix. + * @param {Number} value + * @param {Network?} network + * @returns {Network} + */ + + static fromPublic(prefix, network) { + return Network.by(prefix, cmpPub, network, 'xpubkey'); } - throw new Error(`Network not found for ${name}.`); -}; + /** + * Get a network by its xprivkey prefix. + * @param {Number} value + * @param {Network?} network + * @returns {Network} + */ -/** - * Get a network by its magic number. - * @param {Number} value - * @param {Network?} network - * @returns {Network} - */ + static fromPrivate(prefix, network) { + return Network.by(prefix, cmpPriv, network, 'xprivkey'); + } -Network.fromMagic = function fromMagic(value, network) { - return Network.by(value, cmpMagic, network, 'magic number'); -}; + /** + * Get a network by its xpubkey base58 prefix. + * @param {String} prefix + * @param {Network?} network + * @returns {Network} + */ -/** - * Get a network by its WIF prefix. - * @param {Number} value - * @param {Network?} network - * @returns {Network} - */ + static fromPublic58(prefix, network) { + return Network.by(prefix, cmpPub58, network, 'xpubkey'); + } -Network.fromWIF = function fromWIF(prefix, network) { - return Network.by(prefix, cmpWIF, network, 'WIF'); -}; + /** + * Get a network by its xprivkey base58 prefix. + * @param {String} prefix + * @param {Network?} network + * @returns {Network} + */ -/** - * Get a network by its xpubkey prefix. - * @param {Number} value - * @param {Network?} network - * @returns {Network} - */ + static fromPrivate58(prefix, network) { + return Network.by(prefix, cmpPriv58, network, 'xprivkey'); + } -Network.fromPublic = function fromPublic(prefix, network) { - return Network.by(prefix, cmpPub, network, 'xpubkey'); -}; + /** + * Get a network by its base58 address prefix. + * @param {Number} value + * @param {Network?} network + * @returns {Network} + */ -/** - * Get a network by its xprivkey prefix. - * @param {Number} value - * @param {Network?} network - * @returns {Network} - */ + static fromAddress(prefix, network) { + return Network.by(prefix, cmpAddress, network, 'base58 address'); + } -Network.fromPrivate = function fromPrivate(prefix, network) { - return Network.by(prefix, cmpPriv, network, 'xprivkey'); -}; + /** + * Get a network by its bech32 address prefix. + * @param {String} hrp + * @param {Network?} network + * @returns {Network} + */ -/** - * Get a network by its xpubkey base58 prefix. - * @param {String} prefix - * @param {Network?} network - * @returns {Network} - */ + static fromBech32(hrp, network) { + return Network.by(hrp, cmpBech32, network, 'bech32 address'); + } -Network.fromPublic58 = function fromPublic58(prefix, network) { - return Network.by(prefix, cmpPub58, network, 'xpubkey'); -}; + /** + * Convert the network to a string. + * @returns {String} + */ -/** - * Get a network by its xprivkey base58 prefix. - * @param {String} prefix - * @param {Network?} network - * @returns {Network} - */ - -Network.fromPrivate58 = function fromPrivate58(prefix, network) { - return Network.by(prefix, cmpPriv58, network, 'xprivkey'); -}; + toString() { + return this.type; + } -/** - * Get a network by its base58 address prefix. - * @param {Number} value - * @param {Network?} network - * @returns {Network} - */ + /** + * Inspect the network. + * @returns {String} + */ -Network.fromAddress = function fromAddress(prefix, network) { - return Network.by(prefix, cmpAddress, network, 'base58 address'); -}; + [inspectSymbol]() { + return ``; + } -/** - * Get a network by its bech32 address prefix. - * @param {String} hrp - * @param {Network?} network - * @returns {Network} - */ + /** + * Test an object to see if it is a Network. + * @param {Object} obj + * @returns {Boolean} + */ -Network.fromBech32 = function fromBech32(hrp, network) { - return Network.by(hrp, cmpBech32, network, 'bech32 address'); -}; + static isNetwork(obj) { + return obj instanceof Network; + } +} /** - * Convert the network to a string. - * @returns {String} + * Default network. + * @type {Network} */ -Network.prototype.toString = function toString() { - return this.type; -}; +Network.primary = null; /** - * Inspect the network. - * @returns {String} + * Default network type. + * @type {String} */ -Network.prototype.inspect = function inspect() { - return ``; -}; +Network.type = null; -/** - * Test an object to see if it is a Network. - * @param {Object} obj - * @returns {Boolean} +/* + * Networks (to avoid hash table mode). */ -Network.isNetwork = function isNetwork(obj) { - return obj instanceof Network; -}; +Network.main = null; +Network.testnet = null; +Network.regtest = null; +Network.segnet4 = null; +Network.simnet = null; /* * Set initial network. diff --git a/lib/protocol/networks.js b/lib/protocol/networks.js index 2eaa229d3..4dab95419 100644 --- a/lib/protocol/networks.js +++ b/lib/protocol/networks.js @@ -11,10 +11,18 @@ * @module protocol/networks */ -const BN = require('../crypto/bn'); +const BN = require('bcrypto/lib/bn.js'); const network = exports; +/* + * Helpers + */ + +function b(hash) { + return Buffer.from(hash, 'hex'); +} + /** * Network type list. * @memberof module:protocol/networks @@ -77,59 +85,65 @@ main.port = 7312; */ main.checkpointMap = { - 0: '09c7781c9df90708e278c35d38ea5c9041d7ecfcdd1c56ba67274b7cff3e1cea', - 8002: '34927375cbedc5fb2ede80423021bb733b0cf832956c397f79bf9bd9163bbc73', - 18001: '821503f14c4ec624b69ce762678c1a994dfae919832fea3deec74faa21487a5a', - 38002: '401e79b77fd0abf129137e51f12a12f820f28fd30ce4c1440f450a6d7c436249', - 100000: 'fd49a7753e312efb6934b087081bc795bbfd9922d1cf2a1a87e6966b2c4efbc0', - 160002: '8f0f6ad2e82c8b3e9bbafb3e575b3e4dfcffa434b95fa0c3148629921c388d47', - 200000: 'ec3014dc8b6bbf9c80771667d3ad33b47ecd1efc9f3a830f43d7a9f2c2b539a9', - 208001: '4c6da540dc63cd2174aa78bfee895d3ad72be48d5d9fafa0ef1a08d5b2f8b32b', - 270001: '6008f82de8ee151033d9e4072b50025285fcba713fe68659bc6bae79318a9874', - 290036: 'a32d3c456604c850b6d66b2f793808673ba0e9acc1ad74560e4f5e1e38945914', - 300000: 'fc4090120859e2ec8f344fccafe39a7e208b3cba03a158cd1a355e08c79aec77', - 344665: 'adc1b951a715cd45330e5a067b8ced379a98b2991d6b9b52aa57c3ded836fe40', - 400000: '672c0ebc6ea69304f8c8eb1ccdc4c42e3dcfd2195286216136f98e3f5307ce7e', - 400236: '38efd4e7f4a5d12dd05fc4ffede76d96e8de281cf1f35fe43905411de2b8a4f9', - 415000: 'a8c041eac4438f92f70f441c2f9ddada310ec0bdd555579a0300738ab98aef16', - 420937: '44849bb5c21c135d18a1e1a497e13319b650de391403a2ddc01e0287465ea748', - 425606: '5ea87477099b6cb9afd836d78d6f8dc9b148dc04d7deffda6f9fa4b111d8c862', - 500000: '0c1bba2676f22972b8d4a48310a60447ab1942ddbb28eb2f84d322e99596c4fe', - 508694: '6ada39770e22aa56fdce69d505e60d8cafcf6fdcdcdcc464518e11e997e1cd65', - 600000: 'd24f5b7ea65db9edb001e79d6b9713472ded67813c99ce079cd267adf6f69265', - 696454: '76196ffe698f548038b15df2378e7506521e1fc12e52698f2fe205446875fb8c', - 700000: '9a4560c3872d9143745230113e740cbbb4a9fa2e43b63cbb84415ab22b9610ca', - 800000: '07930c98a9cb4a5ba3791ec805e64fd10c98d680c5958c0d7d87ba711eb38caf', - 900000: 'af434a0c78d826b61c9835264a5f965bb9b617996b99aade4f202e9c61c9eb8d', - 955000: '26751e49ab4b2e30d3cd01932b67f6f3babc6232eeb40aeb59afe6ae507a51b5', - 1000000: 'fd9409dfc4457d690b92c153ad4c0ea9ac8005c9ed26a61bdf4a37b42dd68bb6', - 1100000: '16edf57b45442f7eaabe515f3ac4cef9e59f819b843c68a82c389a5b331dd5a6', - 1200000: '088f39b49e7db714d4278d1793cd178d765b3ddab4b8bb06f36aaba50d1c16f2', - 1300000: '912b163388fbe6369e1a22d2ca921a63bfdbcbf3cd9f6e1ac6915fa316cc5137', - 1400000: '5f56ca1fad1c79e9cb58f5f1e3e10accccb3cc7ee3b21faceca9c06b4ea05a70', - 1500000: 'f25105ee2249aaf94f93b49dd068c0ab7bb7d66987c2587d6a0bcd451a1f9630', - 1505017: 'a7bfc5d33e06509bc70de2959e354ebb91437d74df0084c9a526bb5068308bd3', - 1600000: 'caee977ec6e6d856eda84909b1f305345674c9219cc873aacd0b242298100d5b', - 1678879: 'fc7666e28fc8a3255d3831faef97569f0ef7dc026c0e3f55b3dfe852284e871e', - 1678909: '8a05c5eddc23cfb5f7df90c334fc5121d76b34c8047971e642a537e340105a4c', - 1679162: '0fc11f48af54fed1b5f00ec027cdd7e12906b13d88bee1f36f19a480fb642cb3', - 1700000: 'feba3d283205d7f2b66ce3ac5cde7e4db44cc3e702ce41f98e7f4c53f8184868', - 1796633: '45d4272fb6600e5b945a255586d7ba1e973d5fc4c6a02ae00d2c7f6a938bdac2', - 1800000: '90039841a5c44ff8c89c948a06cd69eece7e82ff4fd3c17cd515fff35c0b56cc', - 1900000: '6d5bc7b28d2387a52ea14e8e5b0a8062583e65d683a60507e1aed76c3b2e9160', - 2000000: '21d2e9dea1c735c68624e515955b7c3fb9ff3991facc70c2927c7b39cab58c4a', - 2094558: '813fe41c83461eb89465b5f9e111866fa8876d456858c1fa2bf38682c8166694', - 2100000: '015fce6c79ec955cf87e2c948d957096f28ab684bd61d16b0985936ab4ddb4ce', - 2200000: '587578080522585935d42996b1b12c33fc29b916f115da38024b1b28b09d99ae', - 2300000: '3808a15146976c4832025c405514e90787fa10d9f312731d231f96d50e73aca0', - 2400000: 'd2e19b323d539a5edd85de0d13fe44c054aae33546079529d0db2874769bfb2d', - 2500000: '2e51213b29f09bf7dffe106c7286e5b0ff7437634fa9f27d35d43b968a768b47', - 2532181: '791c9359312f180f1204613541e925050b2141a797b91dae8810edaa4951cdca', - 2600000: '77bad12e7940c74f1514d66b2b8f455ac0992babee390236fabeea6b670151a3', - 2700000: '4384f467a9af8b7fa3efac8b36691be6bd4fca289935ce06a5a69a191b0e9f9e', - 2800000: 'bdc1f13f345c7db604cf94ef4264f0d47f564fd98d5777741781bd53551ebcfc', - 2900000: '0593d51fa8fda98fd4a77fc1fe41eba19b841cc403f6987cf5245f9baca7304e', - 3000000: '7f8e94ae9f83221fa6230c530aa895e53ea04e38117417f081c6b1e302a3d35a' + 0: b('09c7781c9df90708e278c35d38ea5c9041d7ecfcdd1c56ba67274b7cff3e1cea'), + 8002: b('34927375cbedc5fb2ede80423021bb733b0cf832956c397f79bf9bd9163bbc73'), + 18001: b('821503f14c4ec624b69ce762678c1a994dfae919832fea3deec74faa21487a5a'), + 38002: b('401e79b77fd0abf129137e51f12a12f820f28fd30ce4c1440f450a6d7c436249'), + 100000: b('fd49a7753e312efb6934b087081bc795bbfd9922d1cf2a1a87e6966b2c4efbc0'), + 160002: b('8f0f6ad2e82c8b3e9bbafb3e575b3e4dfcffa434b95fa0c3148629921c388d47'), + 200000: b('ec3014dc8b6bbf9c80771667d3ad33b47ecd1efc9f3a830f43d7a9f2c2b539a9'), + 208001: b('4c6da540dc63cd2174aa78bfee895d3ad72be48d5d9fafa0ef1a08d5b2f8b32b'), + 270001: b('6008f82de8ee151033d9e4072b50025285fcba713fe68659bc6bae79318a9874'), + 290036: b('a32d3c456604c850b6d66b2f793808673ba0e9acc1ad74560e4f5e1e38945914'), + 300000: b('fc4090120859e2ec8f344fccafe39a7e208b3cba03a158cd1a355e08c79aec77'), + 344665: b('adc1b951a715cd45330e5a067b8ced379a98b2991d6b9b52aa57c3ded836fe40'), + 400000: b('672c0ebc6ea69304f8c8eb1ccdc4c42e3dcfd2195286216136f98e3f5307ce7e'), + 400236: b('38efd4e7f4a5d12dd05fc4ffede76d96e8de281cf1f35fe43905411de2b8a4f9'), + 415000: b('a8c041eac4438f92f70f441c2f9ddada310ec0bdd555579a0300738ab98aef16'), + 420937: b('44849bb5c21c135d18a1e1a497e13319b650de391403a2ddc01e0287465ea748'), + 425606: b('5ea87477099b6cb9afd836d78d6f8dc9b148dc04d7deffda6f9fa4b111d8c862'), + 500000: b('0c1bba2676f22972b8d4a48310a60447ab1942ddbb28eb2f84d322e99596c4fe'), + 508694: b('6ada39770e22aa56fdce69d505e60d8cafcf6fdcdcdcc464518e11e997e1cd65'), + 600000: b('d24f5b7ea65db9edb001e79d6b9713472ded67813c99ce079cd267adf6f69265'), + 696454: b('76196ffe698f548038b15df2378e7506521e1fc12e52698f2fe205446875fb8c'), + 700000: b('9a4560c3872d9143745230113e740cbbb4a9fa2e43b63cbb84415ab22b9610ca'), + 800000: b('07930c98a9cb4a5ba3791ec805e64fd10c98d680c5958c0d7d87ba711eb38caf'), + 900000: b('af434a0c78d826b61c9835264a5f965bb9b617996b99aade4f202e9c61c9eb8d'), + 955000: b('26751e49ab4b2e30d3cd01932b67f6f3babc6232eeb40aeb59afe6ae507a51b5'), + 1000000: b('fd9409dfc4457d690b92c153ad4c0ea9ac8005c9ed26a61bdf4a37b42dd68bb6'), + 1100000: b('16edf57b45442f7eaabe515f3ac4cef9e59f819b843c68a82c389a5b331dd5a6'), + 1200000: b('088f39b49e7db714d4278d1793cd178d765b3ddab4b8bb06f36aaba50d1c16f2'), + 1300000: b('912b163388fbe6369e1a22d2ca921a63bfdbcbf3cd9f6e1ac6915fa316cc5137'), + 1400000: b('5f56ca1fad1c79e9cb58f5f1e3e10accccb3cc7ee3b21faceca9c06b4ea05a70'), + 1500000: b('f25105ee2249aaf94f93b49dd068c0ab7bb7d66987c2587d6a0bcd451a1f9630'), + 1505017: b('a7bfc5d33e06509bc70de2959e354ebb91437d74df0084c9a526bb5068308bd3'), + 1600000: b('caee977ec6e6d856eda84909b1f305345674c9219cc873aacd0b242298100d5b'), + 1678879: b('fc7666e28fc8a3255d3831faef97569f0ef7dc026c0e3f55b3dfe852284e871e'), + 1678909: b('8a05c5eddc23cfb5f7df90c334fc5121d76b34c8047971e642a537e340105a4c'), + 1679162: b('0fc11f48af54fed1b5f00ec027cdd7e12906b13d88bee1f36f19a480fb642cb3'), + 1700000: b('feba3d283205d7f2b66ce3ac5cde7e4db44cc3e702ce41f98e7f4c53f8184868'), + 1796633: b('45d4272fb6600e5b945a255586d7ba1e973d5fc4c6a02ae00d2c7f6a938bdac2'), + 1800000: b('90039841a5c44ff8c89c948a06cd69eece7e82ff4fd3c17cd515fff35c0b56cc'), + 1900000: b('6d5bc7b28d2387a52ea14e8e5b0a8062583e65d683a60507e1aed76c3b2e9160'), + 2000000: b('21d2e9dea1c735c68624e515955b7c3fb9ff3991facc70c2927c7b39cab58c4a'), + 2094558: b('813fe41c83461eb89465b5f9e111866fa8876d456858c1fa2bf38682c8166694'), + 2100000: b('015fce6c79ec955cf87e2c948d957096f28ab684bd61d16b0985936ab4ddb4ce'), + 2200000: b('587578080522585935d42996b1b12c33fc29b916f115da38024b1b28b09d99ae'), + 2300000: b('3808a15146976c4832025c405514e90787fa10d9f312731d231f96d50e73aca0'), + 2400000: b('d2e19b323d539a5edd85de0d13fe44c054aae33546079529d0db2874769bfb2d'), + 2500000: b('2e51213b29f09bf7dffe106c7286e5b0ff7437634fa9f27d35d43b968a768b47'), + 2532181: b('791c9359312f180f1204613541e925050b2141a797b91dae8810edaa4951cdca'), + 2600000: b('77bad12e7940c74f1514d66b2b8f455ac0992babee390236fabeea6b670151a3'), + 2700000: b('4384f467a9af8b7fa3efac8b36691be6bd4fca289935ce06a5a69a191b0e9f9e'), + 2800000: b('bdc1f13f345c7db604cf94ef4264f0d47f564fd98d5777741781bd53551ebcfc'), + 2900000: b('0593d51fa8fda98fd4a77fc1fe41eba19b841cc403f6987cf5245f9baca7304e'), + 3000000: b('7f8e94ae9f83221fa6230c530aa895e53ea04e38117417f081c6b1e302a3d35a'), + 3100000: b('fd07bdd12ba2e7a6e2b8924db588e51378542f10e7e860c192abc4658dac81db'), + 3200000: b('e9a37a7a543a495225eda8275f7a41088e64b12cb1e29f667a7584c55dbeb28c'), + 3300000: b('3df2ba30f5f857abc82efb18246c1c6e63dfa56238c67f1cc0326bf7292dcd96'), + 3400000: b('3e530c19dcf0449fc68d955392a9ae2e1ebb2948eb0c5ca03640804c4a26eddd'), + 3500000: b('014467023220f107225267c45eb06a44c6fcedb22c661fd8f72350524511aa25'), + 3550000: b('247c96b3531608b39f8725dad00463bd0a4d45e4428255294aa304cb968ba5e2') }; /** @@ -138,7 +152,8 @@ main.checkpointMap = { * @default */ -main.lastCheckpoint = 3000000; +// If updating this, please also update main.block.slowHeight to prevent logs being spammy +main.lastCheckpoint = 3550000; /** * @const {Number} @@ -149,14 +164,14 @@ main.halvingInterval = 800000; /** * Genesis block header. - * @const {NakedBlock} + * @const {Object} */ main.genesis = { version: 1, - hash: '09c7781c9df90708e278c35d38ea5c9041d7ecfcdd1c56ba67274b7cff3e1cea', - prevBlock: '0000000000000000000000000000000000000000000000000000000000000000', - merkleRoot: '730f0c8ddc5a592d5512566890e2a73e45feaa6748b24b849d1c29a7ab2b2300', + hash: b('09c7781c9df90708e278c35d38ea5c9041d7ecfcdd1c56ba67274b7cff3e1cea'), + prevBlock: b('0000000000000000000000000000000000000000000000000000000000000000'), + merkleRoot: b('730f0c8ddc5a592d5512566890e2a73e45feaa6748b24b849d1c29a7ab2b2300'), time: 1371488396, bits: 504365040, // 0x1e0ffff0 nonce: 1000112548, @@ -215,20 +230,22 @@ main.pow = { ), /** - * Desired retarget period in seconds. + * Average block time. * @const {Number} * @default */ - targetTimespan: 6 * 40, + targetSpacing: 40, /** - * Average block time. + * Desired retarget period in seconds. * @const {Number} * @default */ - targetSpacing: 40, + targetTimespan_Version1: 60 * 60, + targetTimespan_Version2: 15 * 40, + targetTimespan_Version3: 6 * 40, /** * Retarget interval in blocks. @@ -236,7 +253,9 @@ main.pow = { * @default */ - retargetInterval: 1, + retargetInterval_Version1: (60 * 60) / 40, + retargetInterval_Version2: 15, + retargetInterval_Version3: 1, /** * Average retarget interval in blocks. @@ -244,7 +263,9 @@ main.pow = { * @default */ - averagingInterval: 6, + averagingInterval_Version1: (60 * 60) / 40, + averagingInterval_Version2: 15, + averagingInterval_Version3: 6, /** * Average retarget interval in blocks. @@ -252,7 +273,9 @@ main.pow = { * @default */ - averagingIntervalTimespan: 6 * 40, + averagingIntervalTimespan_Version1: (60 * 60) / 40, + averagingIntervalTimespan_Version2: 15, + averagingIntervalTimespan_Version3: 6, /** * Adjust Target Timespan Max. @@ -260,7 +283,9 @@ main.pow = { * @default */ - adjustUp: 2, + adjustUp_Version1: 75, + adjustUp_Version2: 75, + adjustUp_Version3: 2, /** * Adjust Target Timespan Min. @@ -268,31 +293,17 @@ main.pow = { * @default */ - adjustDown: 3, - - targetTimespan_version1: 60 * 60, - targetSpacing_version1: 40, - retargetInterval_version1: (60 * 60) / 40, - averagingInterval_version1: (60 * 60) / 40, - averagingIntervalTimespan_version1: ((60 * 60) / 40) * 40, - adjustUp_version1: 75, - adjustDown_version1: 300, - blockHeight_version2: 208440, - targetTimespan_version2: 15 * 40, - targetSpacing_version2: 40, - retargetInterval_version2: 15, - averagingInterval_version2: 15, - averagingIntervalTimespan_version2: 15 * 40, - adjustUp_version2: 75, - adjustDown_version2: 300, - blockHeight_version3: 426000, - targetTimespan_version3: 6 * 40, - targetSpacing_version3: 40, - retargetInterval_version3: 1, - averagingInterval_version3: 6, - averagingIntervalTimespan_version3: 6 * 40, - adjustUp_version3: 2, - adjustDown_version3: 3, + adjustDown_Version1: 300, + adjustDown_Version2: 300, + adjustDown_Version3: 3, + + /** + * Block Heights that each difficulty algorithm should be used + * @const {Number} + * @default + */ + blockHeight_Version2: 208440, + blockHeight_Version3: 426000, /** * Whether to reset target if a block @@ -330,7 +341,7 @@ main.block = { * Hash of the block that activated bip34. */ - bip34hash: '490a10507efe42b89104408787088b7c43310cc230310201b5f57dac6f513b8b', + bip34hash: b('490a10507efe42b89104408787088b7c43310cc230310201b5f57dac6f513b8b'), /** * Height at which bip65 was activated. @@ -342,7 +353,7 @@ main.block = { * Hash of the block that activated bip65. */ - bip65hash: '490a10507efe42b89104408787088b7c43310cc230310201b5f57dac6f513b8b', + bip65hash: b('490a10507efe42b89104408787088b7c43310cc230310201b5f57dac6f513b8b'), /** * Height at which bip66 was activated. @@ -354,7 +365,7 @@ main.block = { * Hash of the block that activated bip66. */ - bip66hash: '490a10507efe42b89104408787088b7c43310cc230310201b5f57dac6f513b8b', + bip66hash: b('490a10507efe42b89104408787088b7c43310cc230310201b5f57dac6f513b8b'), /** * Safe height to start pruning. @@ -381,7 +392,7 @@ main.block = { * logs without spamming. */ - slowHeight: 900000 + slowHeight: 3550000 }; /** @@ -506,6 +517,14 @@ main.requireStandard = true; main.rpcPort = 7313; +/** + * Default wallet port. + * @const {Number} + * @default + */ + +main.walletPort = 7315; + /** * Default min relay rate. * @const {Rate} @@ -562,26 +581,32 @@ testnet.magic = 0xf25ac0fd; testnet.port = 17312; testnet.checkpointMap = { - 2056: '484137382926862f600e0fe5f9c6e8a5a1fec22406f151a6ea1b7371b04d33d3', - 10000: '7938fd4a183b4d06234983cb0c1aace5e153ef37e335f80652ecd12c5f606890', - 50000: '070cf3309161205b8c3c99df1e556b1e86a0315ad811a7c9e4d12f693f1c9a62', - 75000: '5ab6b417eb612d6ec10cec0acd5d23a2b9d3ab78e3af06a438123bc193bb6da2', - 100000: '8813cf76021b3e6fc09d16f5d0d00fd9af8cf51c34064f22fe55e6061b755b91', - 150000: 'c5e1afd50798f00c58ab7d9f785bb17cd3368fd7d704590af12f965ac0e79a2d', - 200000: '9fb320c2131c870496a8dcf946b682be9bbef9e78a421adde9b891ee0d9d56ac', - 225000: '12711469c9c5c06df598afad3fe173535bdd529ab08f907532162004c9e93527', - 245000: '159cb9befa6b3560db71d6fcfd5d75b11d986629723c9cf0485998653dc5bfd3' + 2056: b('484137382926862f600e0fe5f9c6e8a5a1fec22406f151a6ea1b7371b04d33d3'), + 10000: b('7938fd4a183b4d06234983cb0c1aace5e153ef37e335f80652ecd12c5f606890'), + 50000: b('070cf3309161205b8c3c99df1e556b1e86a0315ad811a7c9e4d12f693f1c9a62'), + 75000: b('5ab6b417eb612d6ec10cec0acd5d23a2b9d3ab78e3af06a438123bc193bb6da2'), + 100000: b('8813cf76021b3e6fc09d16f5d0d00fd9af8cf51c34064f22fe55e6061b755b91'), + 150000: b('c5e1afd50798f00c58ab7d9f785bb17cd3368fd7d704590af12f965ac0e79a2d'), + 200000: b('9fb320c2131c870496a8dcf946b682be9bbef9e78a421adde9b891ee0d9d56ac'), + 225000: b('12711469c9c5c06df598afad3fe173535bdd529ab08f907532162004c9e93527'), + 245000: b('159cb9befa6b3560db71d6fcfd5d75b11d986629723c9cf0485998653dc5bfd3'), + 300000: b('f602525e8eb7cd98d061b444025a816cd7f32fac4be21d9a89c621b04b7e4d24'), + 400000: b('5714fcff34cdf83f567db616bbc0d108ca548ffcfab7530114ca7fa94fbd0507'), + 500000: b('8987ddc3b37c0ab5d08d2f686d6986d13132c590619080f1264c74bce9fbe2ff'), + 600000: b('3e35f526bce485df26e15dda9c329d19205249b7849f5ceb7cadb50e642a001d'), + 686000: b('ba415faddfeef253c8c0e8fe3b6f2cb37d5b12416cb86491d62081abf179a79b') }; -testnet.lastCheckpoint = 245000; +// If updating this, please also update testnet.block.slowHeight to prevent logs being spammy +testnet.lastCheckpoint = 686000; testnet.halvingInterval = 800000; testnet.genesis = { version: 1, - hash: '9b7bc86236c34b5e3a39367c036b7fe8807a966c22a7a1f0da2a198a27e03731', - prevBlock: '0000000000000000000000000000000000000000000000000000000000000000', - merkleRoot: '730f0c8ddc5a592d5512566890e2a73e45feaa6748b24b849d1c29a7ab2b2300', + hash: b('9b7bc86236c34b5e3a39367c036b7fe8807a966c22a7a1f0da2a198a27e03731'), + prevBlock: b('0000000000000000000000000000000000000000000000000000000000000000'), + merkleRoot: b('730f0c8ddc5a592d5512566890e2a73e45feaa6748b24b849d1c29a7ab2b2300'), time: 1371387277, bits: 504365040, nonce: 1000580675, @@ -606,47 +631,94 @@ testnet.pow = { ), bits: 504365055, chainwork: new BN( - '000000000000000000000000000000000000000000000000000000083540886d', + '0000000000000000000000000000000000000000000000000000003dd47d3172', 'hex' ), - targetTimespan_version1: 60 * 60, - targetSpacing_version1: 40, - retargetInterval_version1: (60 * 60) / 40, - averagingInterval_version1: (60 * 60) / 40, - averagingIntervalTimespan_version1: ((60 * 60) / 40) * 40, - adjustUp_version1: 75, - adjustDown_version1: 300, - blockHeight_version2: 50000, - targetTimespan_version2: 15 * 40, - targetSpacing_version2: 40, - retargetInterval_version2: 15, - averagingInterval_version2: 15, - averagingIntervalTimespan_version2: 15 * 40, - adjustUp_version2: 75, - adjustDown_version2: 300, - blockHeight_version3: 60000, - targetTimespan_version3: 6 * 40, - targetSpacing_version3: 40, - retargetInterval_version3: 1, - averagingInterval_version3: 6, - averagingIntervalTimespan_version3: 6 * 40, - adjustUp_version3: 2, - adjustDown_version3: 3, + targetSpacing: 40, + + /** + * Desired retarget period in seconds. + * @const {Number} + * @default + */ + + targetTimespan_Version1: 60 * 60, + targetTimespan_Version2: 15 * 40, + targetTimespan_Version3: 6 * 40, + + /** + * Retarget interval in blocks. + * @const {Number} + * @default + */ + + retargetInterval_Version1: (60 * 60) / 40, + retargetInterval_Version2: 15, + retargetInterval_Version3: 1, + + /** + * Average retarget interval in blocks. + * @const {Number} + * @default + */ + + averagingInterval_Version1: (60 * 60) / 40, + averagingInterval_Version2: 15, + averagingInterval_Version3: 6, + + /** + * Average retarget interval in blocks. + * @const {Number} + * @default + */ + + averagingIntervalTimespan_Version1: (60 * 60) / 40, + averagingIntervalTimespan_Version2: 15, + averagingIntervalTimespan_Version3: 6, + + /** + * Adjust Target Timespan Max. + * @const {Number} + * @default + */ + + adjustUp_Version1: 75, + adjustUp_Version2: 75, + adjustUp_Version3: 2, + + /** + * Adjust Target Timespan Min. + * @const {Number} + * @default + */ + + adjustDown_Version1: 300, + adjustDown_Version2: 300, + adjustDown_Version3: 3, + + /** + * Block Heights that each difficulty algorithm should be used + * @const {Number} + * @default + */ + blockHeight_Version2: 50000, + blockHeight_Version3: 60000, + targetReset: true, noRetargeting: false }; testnet.block = { bip34height: 33600, - bip34hash: '4ac31d938531317c065405a9b23478c8c99204ff17fc294cb09821e2c2b42e65', + bip34hash: b('4ac31d938531317c065405a9b23478c8c99204ff17fc294cb09821e2c2b42e65'), bip65height: 33600, - bip65hash: '4ac31d938531317c065405a9b23478c8c99204ff17fc294cb09821e2c2b42e65', + bip65hash: b('4ac31d938531317c065405a9b23478c8c99204ff17fc294cb09821e2c2b42e65'), bip66height: 33600, - bip66hash: '4ac31d938531317c065405a9b23478c8c99204ff17fc294cb09821e2c2b42e65', + bip66hash: b('4ac31d938531317c065405a9b23478c8c99204ff17fc294cb09821e2c2b42e65'), pruneAfterHeight: 1000, keepBlocks: 10000, maxTipAge: 24 * 60 * 60, - slowHeight: 950000 + slowHeight: 686000 }; testnet.bip30 = {}; @@ -716,6 +788,8 @@ testnet.requireStandard = false; testnet.rpcPort = 17313; +testnet.walletPort = 17315; + testnet.minRelay = 1000; testnet.feeRate = 20000; @@ -734,9 +808,7 @@ const regtest = {}; regtest.type = 'regtest'; -regtest.seeds = [ - '127.0.0.1' -]; +regtest.seeds = []; regtest.magic = 0xfabfb5da; @@ -749,9 +821,9 @@ regtest.halvingInterval = 150; regtest.genesis = { version: 1, - hash: 'ec42fa26ca6dcb1103b59a1d24b161935ea4566f8d5736db8917d5b9a8dee0d7', - prevBlock: '0000000000000000000000000000000000000000000000000000000000000000', - merkleRoot: '730f0c8ddc5a592d5512566890e2a73e45feaa6748b24b849d1c29a7ab2b2300', + hash: b('ec42fa26ca6dcb1103b59a1d24b161935ea4566f8d5736db8917d5b9a8dee0d7'), + prevBlock: b('0000000000000000000000000000000000000000000000000000000000000000'), + merkleRoot: b('730f0c8ddc5a592d5512566890e2a73e45feaa6748b24b849d1c29a7ab2b2300'), time: 1371387277, bits: 545259519, nonce: 0, @@ -820,7 +892,7 @@ regtest.deployments = { segwit: { name: 'segwit', bit: 1, - startTime: 0, + startTime: -1, timeout: 0xffffffff, threshold: -1, window: -1, @@ -857,7 +929,6 @@ regtest.keyPrefix = { regtest.addressPrefix = { pubkeyhash: 0x6f, scripthash: 0xc4, - scripthash2: 0x3a, witnesspubkeyhash: 0x03, witnessscripthash: 0x28, bech32: 'rflo' @@ -865,7 +936,9 @@ regtest.addressPrefix = { regtest.requireStandard = false; -regtest.rpcPort = 19445; +regtest.rpcPort = 17413; + +regtest.walletPort = 17415; regtest.minRelay = 1000; @@ -901,10 +974,12 @@ simnet.halvingInterval = 210000; simnet.genesis = { version: 1, - hash: 'f67ad7695d9b662a72ff3d8edbbb2de0bfa67b13974bb9910d116d5cbd863e68', - prevBlock: '0000000000000000000000000000000000000000000000000000000000000000', + hash: + b('f67ad7695d9b662a72ff3d8edbbb2de0bfa67b13974bb9910d116d5cbd863e68'), + prevBlock: + b('0000000000000000000000000000000000000000000000000000000000000000'), merkleRoot: - '3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a', + b('3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a'), time: 1401292357, bits: 545259519, nonce: 2, @@ -942,11 +1017,14 @@ simnet.pow = { simnet.block = { bip34height: 0, - bip34hash: 'f67ad7695d9b662a72ff3d8edbbb2de0bfa67b13974bb9910d116d5cbd863e68', + bip34hash: + b('f67ad7695d9b662a72ff3d8edbbb2de0bfa67b13974bb9910d116d5cbd863e68'), bip65height: 0, - bip65hash: 'f67ad7695d9b662a72ff3d8edbbb2de0bfa67b13974bb9910d116d5cbd863e68', + bip65hash: + b('f67ad7695d9b662a72ff3d8edbbb2de0bfa67b13974bb9910d116d5cbd863e68'), bip66height: 0, - bip66hash: 'f67ad7695d9b662a72ff3d8edbbb2de0bfa67b13974bb9910d116d5cbd863e68', + bip66hash: + b('f67ad7695d9b662a72ff3d8edbbb2de0bfa67b13974bb9910d116d5cbd863e68'), pruneAfterHeight: 1000, keepBlocks: 10000, maxTipAge: 0xffffffff, @@ -1023,13 +1101,15 @@ simnet.addressPrefix = { scripthash: 0x7b, witnesspubkeyhash: 0x19, witnessscripthash: 0x28, - bech32: 'sc' + bech32: 'sb' }; simnet.requireStandard = false; simnet.rpcPort = 18556; +simnet.walletPort = 18558; + simnet.minRelay = 1000; simnet.feeRate = 20000; diff --git a/lib/protocol/policy.js b/lib/protocol/policy.js index 00dc8c327..c94bd08e0 100644 --- a/lib/protocol/policy.js +++ b/lib/protocol/policy.js @@ -11,7 +11,7 @@ * @module protocol/policy */ -const assert = require('assert'); +const assert = require('bsert'); const consensus = require('./consensus'); /** @@ -170,7 +170,7 @@ exports.MEMPOOL_EXPIRY_TIME = 72 * 60 * 60; * @default */ -exports.MEMPOOL_MAX_ORPHANS = 100; +exports.MEMPOOL_MAX_ORPHANS = 2500; /** * Minimum block size to create. Block will be diff --git a/lib/protocol/timedata.js b/lib/protocol/timedata.js index 310c090a3..cfff6fcb1 100644 --- a/lib/protocol/timedata.js +++ b/lib/protocol/timedata.js @@ -9,125 +9,129 @@ const EventEmitter = require('events'); const util = require('../utils/util'); +const binary = require('../utils/binary'); /** + * Time Data * An object which handles "adjusted time". This may not * look it, but this is actually a semi-consensus-critical * piece of code. It handles version packets from peers * and calculates what to offset our system clock's time by. * @alias module:protocol.TimeData - * @constructor - * @param {Number} [limit=200] + * @extends EventEmitter * @property {Array} samples * @property {Object} known * @property {Number} limit * @property {Number} offset */ -function TimeData(limit) { - if (!(this instanceof TimeData)) - return new TimeData(limit); +class TimeData extends EventEmitter { + /** + * Create time data. + * @constructor + * @param {Number} [limit=200] + */ - EventEmitter.call(this); + constructor(limit) { + super(); - if (limit == null) - limit = 200; + if (limit == null) + limit = 200; - this.samples = []; - this.known = new Map(); - this.limit = limit; - this.offset = 0; - this.checked = false; -} + this.samples = []; + this.known = new Map(); + this.limit = limit; + this.offset = 0; + this.checked = false; + } -Object.setPrototypeOf(TimeData.prototype, EventEmitter.prototype); + /** + * Add time data. + * @param {String} id + * @param {Number} time + */ -/** - * Add time data. - * @param {String} id - * @param {Number} time - */ + add(id, time) { + if (this.samples.length >= this.limit) + return; -TimeData.prototype.add = function add(id, time) { - if (this.samples.length >= this.limit) - return; + if (this.known.has(id)) + return; - if (this.known.has(id)) - return; + const sample = time - util.now(); - const sample = time - util.now(); + this.known.set(id, sample); - this.known.set(id, sample); + binary.insert(this.samples, sample, compare); - util.binaryInsert(this.samples, sample, compare); + this.emit('sample', sample, this.samples.length); - this.emit('sample', sample, this.samples.length); + if (this.samples.length >= 5 && this.samples.length % 2 === 1) { + let median = this.samples[this.samples.length >>> 1]; - if (this.samples.length >= 5 && this.samples.length % 2 === 1) { - let median = this.samples[this.samples.length >>> 1]; + if (Math.abs(median) >= 70 * 60) { + if (!this.checked) { + let match = false; - if (Math.abs(median) >= 70 * 60) { - if (!this.checked) { - let match = false; + for (const offset of this.samples) { + if (offset !== 0 && Math.abs(offset) < 5 * 60) { + match = true; + break; + } + } - for (const offset of this.samples) { - if (offset !== 0 && Math.abs(offset) < 5 * 60) { - match = true; - break; + if (!match) { + this.checked = true; + this.emit('mismatch'); } } - if (!match) { - this.checked = true; - this.emit('mismatch'); - } + median = 0; } - median = 0; + this.offset = median; + this.emit('offset', this.offset); } - - this.offset = median; - this.emit('offset', this.offset); } -}; -/** - * Get the current adjusted time. - * @returns {Number} Adjusted Time. - */ + /** + * Get the current adjusted time. + * @returns {Number} Adjusted Time. + */ -TimeData.prototype.now = function now() { - return util.now() + this.offset; -}; + now() { + return util.now() + this.offset; + } -/** - * Adjust a timestamp. - * @param {Number} time - * @returns {Number} Adjusted Time. - */ + /** + * Adjust a timestamp. + * @param {Number} time + * @returns {Number} Adjusted Time. + */ -TimeData.prototype.adjust = function adjust(time) { - return time + this.offset; -}; + adjust(time) { + return time + this.offset; + } -/** - * Unadjust a timestamp. - * @param {Number} time - * @returns {Number} Local Time. - */ + /** + * Unadjust a timestamp. + * @param {Number} time + * @returns {Number} Local Time. + */ -TimeData.prototype.local = function local(time) { - return time - this.offset; -}; + local(time) { + return time - this.offset; + } -/** - * Get the current adjusted time in milliseconds. - * @returns {Number} Adjusted Time. - */ + /** + * Get the current adjusted time in milliseconds. + * @returns {Number} Adjusted Time. + */ -TimeData.prototype.ms = function ms() { - return util.ms() + this.offset * 1000; -}; + ms() { + return Date.now() + this.offset * 1000; + } +} /* * Helpers diff --git a/lib/script/common.js b/lib/script/common.js index 02c0d8e07..520c9aa36 100644 --- a/lib/script/common.js +++ b/lib/script/common.js @@ -11,9 +11,8 @@ * @module script/common */ -const assert = require('assert'); -const util = require('../utils/util'); -const secp256k1 = require('../crypto/secp256k1'); +const assert = require('bsert'); +const secp256k1 = require('bcrypto/lib/secp256k1'); const ScriptNum = require('./scriptnum'); /** @@ -160,10 +159,144 @@ exports.opcodes = { /** * Opcodes by value. - * @const {RevMap} + * @const {Object} */ -exports.opcodesByVal = util.reverse(exports.opcodes); +exports.opcodesByVal = { + // Push + 0x00: 'OP_0', + + 0x4c: 'OP_PUSHDATA1', + 0x4d: 'OP_PUSHDATA2', + 0x4e: 'OP_PUSHDATA4', + + 0x4f: 'OP_1NEGATE', + + 0x50: 'OP_RESERVED', + + 0x51: 'OP_1', + 0x52: 'OP_2', + 0x53: 'OP_3', + 0x54: 'OP_4', + 0x55: 'OP_5', + 0x56: 'OP_6', + 0x57: 'OP_7', + 0x58: 'OP_8', + 0x59: 'OP_9', + 0x5a: 'OP_10', + 0x5b: 'OP_11', + 0x5c: 'OP_12', + 0x5d: 'OP_13', + 0x5e: 'OP_14', + 0x5f: 'OP_15', + 0x60: 'OP_16', + + // Control + 0x61: 'OP_NOP', + 0x62: 'OP_VER', + 0x63: 'OP_IF', + 0x64: 'OP_NOTIF', + 0x65: 'OP_VERIF', + 0x66: 'OP_VERNOTIF', + 0x67: 'OP_ELSE', + 0x68: 'OP_ENDIF', + 0x69: 'OP_VERIFY', + 0x6a: 'OP_RETURN', + + // Stack + 0x6b: 'OP_TOALTSTACK', + 0x6c: 'OP_FROMALTSTACK', + 0x6d: 'OP_2DROP', + 0x6e: 'OP_2DUP', + 0x6f: 'OP_3DUP', + 0x70: 'OP_2OVER', + 0x71: 'OP_2ROT', + 0x72: 'OP_2SWAP', + 0x73: 'OP_IFDUP', + 0x74: 'OP_DEPTH', + 0x75: 'OP_DROP', + 0x76: 'OP_DUP', + 0x77: 'OP_NIP', + 0x78: 'OP_OVER', + 0x79: 'OP_PICK', + 0x7a: 'OP_ROLL', + 0x7b: 'OP_ROT', + 0x7c: 'OP_SWAP', + 0x7d: 'OP_TUCK', + + // Splice + 0x7e: 'OP_CAT', + 0x7f: 'OP_SUBSTR', + 0x80: 'OP_LEFT', + 0x81: 'OP_RIGHT', + 0x82: 'OP_SIZE', + + // Bit + 0x83: 'OP_INVERT', + 0x84: 'OP_AND', + 0x85: 'OP_OR', + 0x86: 'OP_XOR', + 0x87: 'OP_EQUAL', + 0x88: 'OP_EQUALVERIFY', + 0x89: 'OP_RESERVED1', + 0x8a: 'OP_RESERVED2', + + // Numeric + 0x8b: 'OP_1ADD', + 0x8c: 'OP_1SUB', + 0x8d: 'OP_2MUL', + 0x8e: 'OP_2DIV', + 0x8f: 'OP_NEGATE', + 0x90: 'OP_ABS', + 0x91: 'OP_NOT', + 0x92: 'OP_0NOTEQUAL', + 0x93: 'OP_ADD', + 0x94: 'OP_SUB', + 0x95: 'OP_MUL', + 0x96: 'OP_DIV', + 0x97: 'OP_MOD', + 0x98: 'OP_LSHIFT', + 0x99: 'OP_RSHIFT', + 0x9a: 'OP_BOOLAND', + 0x9b: 'OP_BOOLOR', + 0x9c: 'OP_NUMEQUAL', + 0x9d: 'OP_NUMEQUALVERIFY', + 0x9e: 'OP_NUMNOTEQUAL', + 0x9f: 'OP_LESSTHAN', + 0xa0: 'OP_GREATERTHAN', + 0xa1: 'OP_LESSTHANOREQUAL', + 0xa2: 'OP_GREATERTHANOREQUAL', + 0xa3: 'OP_MIN', + 0xa4: 'OP_MAX', + 0xa5: 'OP_WITHIN', + + // Crypto + 0xa6: 'OP_RIPEMD160', + 0xa7: 'OP_SHA1', + 0xa8: 'OP_SHA256', + 0xa9: 'OP_HASH160', + 0xaa: 'OP_HASH256', + 0xab: 'OP_CODESEPARATOR', + 0xac: 'OP_CHECKSIG', + 0xad: 'OP_CHECKSIGVERIFY', + 0xae: 'OP_CHECKMULTISIG', + 0xaf: 'OP_CHECKMULTISIGVERIFY', + + // Expansion + 0xb0: 'OP_NOP1', + 0xb1: 'OP_CHECKLOCKTIMEVERIFY', + 0xb2: 'OP_CHECKSEQUENCEVERIFY', + 0xb3: 'OP_NOP4', + 0xb4: 'OP_NOP5', + 0xb5: 'OP_NOP6', + 0xb6: 'OP_NOP7', + 0xb7: 'OP_NOP8', + 0xb8: 'OP_NOP9', + 0xb9: 'OP_NOP10', + + // Custom + 0xff: 'OP_INVALIDOPCODE' +}; /** * Small ints (1 indexed, 1==0). @@ -214,7 +347,7 @@ exports.flags = { VERIFY_MINIMALIF: 1 << 13, VERIFY_NULLFAIL: 1 << 14, VERIFY_WITNESS_PUBKEYTYPE: 1 << 15, - VERIFY_MAST: 1 << 16 + VERIFY_CONST_SCRIPTCODE: 1 << 16 }; /** @@ -246,7 +379,8 @@ exports.flags.STANDARD_VERIFY_FLAGS = 0 | exports.flags.VERIFY_LOW_S | exports.flags.VERIFY_WITNESS | exports.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM - | exports.flags.VERIFY_WITNESS_PUBKEYTYPE; + | exports.flags.VERIFY_WITNESS_PUBKEYTYPE + | exports.flags.VERIFY_CONST_SCRIPTCODE; /** * Standard flags without mandatory bits. @@ -297,10 +431,15 @@ exports.hashType = { /** * Sighash types by value. - * @const {RevMap} + * @const {Object} */ -exports.hashTypeByVal = util.reverse(exports.hashType); +exports.hashTypeByVal = { + 1: 'ALL', + 2: 'NONE', + 3: 'SINGLE', + 0x80: 'ANYONECANPAY' +}; /** * Output script types. @@ -314,18 +453,27 @@ exports.types = { SCRIPTHASH: 3, MULTISIG: 4, NULLDATA: 5, - WITNESSMALFORMED: 0x80 | 0, - WITNESSSCRIPTHASH: 0x80 | 1, - WITNESSPUBKEYHASH: 0x80 | 2, - WITNESSMASTHASH: 0x80 | 3 + WITNESSMALFORMED: 0x80, + WITNESSSCRIPTHASH: 0x81, + WITNESSPUBKEYHASH: 0x82 }; /** * Output script types by value. - * @const {RevMap} + * @const {Object} */ -exports.typesByVal = util.reverse(exports.types); +exports.typesByVal = { + 0: 'NONSTANDARD', + 1: 'PUBKEY', + 2: 'PUBKEYHASH', + 3: 'SCRIPTHASH', + 4: 'MULTISIG', + 5: 'NULLDATA', + 0x80: 'WITNESSMALFORMED', + 0x81: 'WITNESSSCRIPTHASH', + 0x82: 'WITNESSPUBKEYHASH' +}; /** * Test a signature to see whether it contains a valid sighash type. @@ -341,7 +489,7 @@ exports.isHashType = function isHashType(sig) { const type = sig[sig.length - 1] & ~exports.hashType.ANYONECANPAY; - if (!(type >= exports.hashType.ALL && type <= exports.hashType.SINGLE)) + if (type < exports.hashType.ALL || type > exports.hashType.SINGLE) return false; return true; @@ -357,7 +505,7 @@ exports.isLowDER = function isLowDER(sig) { if (!exports.isSignatureEncoding(sig)) return false; - return secp256k1.isLowS(sig.slice(0, -1)); + return secp256k1.isLowDER(sig.slice(0, -1)); }; /** diff --git a/lib/script/opcode.js b/lib/script/opcode.js index a46366e1b..488ba6198 100644 --- a/lib/script/opcode.js +++ b/lib/script/opcode.js @@ -7,12 +7,10 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); +const bio = require('bufio'); const ScriptNum = require('./scriptnum'); -const util = require('../utils/util'); const common = require('./common'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); const opcodes = common.opcodes; const opCache = []; @@ -20,653 +18,667 @@ const opCache = []; let PARSE_ERROR = null; /** + * Opcode * A simple struct which contains * an opcode and pushdata buffer. - * Note: this should not be called directly. * @alias module:script.Opcode - * @constructor - * @param {Number} value - Opcode. - * @param {Buffer?} data - Pushdata buffer. * @property {Number} value * @property {Buffer|null} data */ -function Opcode(value, data) { - if (!(this instanceof Opcode)) - return new Opcode(value, data); +class Opcode { + /** + * Create an opcode. + * Note: this should not be called directly. + * @constructor + * @param {Number} value - Opcode. + * @param {Buffer?} data - Pushdata buffer. + */ - this.value = value || 0; - this.data = data || null; -} + constructor(value, data) { + this.value = value || 0; + this.data = data || null; + } -/** - * Test whether a pushdata abides by minimaldata. - * @returns {Boolean} - */ + /** + * Test whether a pushdata abides by minimaldata. + * @returns {Boolean} + */ -Opcode.prototype.isMinimal = function isMinimal() { - if (!this.data) - return true; - - if (this.data.length === 1) { - if (this.data[0] === 0x81) - return false; + isMinimal() { + if (!this.data) + return true; - if (this.data[0] >= 1 && this.data[0] <= 16) - return false; - } + if (this.data.length === 1) { + if (this.data[0] === 0x81) + return false; - if (this.data.length <= 0x4b) - return this.value === this.data.length; + if (this.data[0] >= 1 && this.data[0] <= 16) + return false; + } - if (this.data.length <= 0xff) - return this.value === opcodes.OP_PUSHDATA1; + if (this.data.length <= 0x4b) + return this.value === this.data.length; - if (this.data.length <= 0xffff) - return this.value === opcodes.OP_PUSHDATA2; + if (this.data.length <= 0xff) + return this.value === opcodes.OP_PUSHDATA1; - assert(this.value === opcodes.OP_PUSHDATA4); + if (this.data.length <= 0xffff) + return this.value === opcodes.OP_PUSHDATA2; - return true; -}; + assert(this.value === opcodes.OP_PUSHDATA4); -/** - * Test whether opcode is a disabled opcode. - * @returns {Boolean} - */ + return true; + } -Opcode.prototype.isDisabled = function isDisabled() { - switch (this.value) { - case opcodes.OP_CAT: - case opcodes.OP_SUBSTR: - case opcodes.OP_LEFT: - case opcodes.OP_RIGHT: - case opcodes.OP_INVERT: - case opcodes.OP_AND: - case opcodes.OP_OR: - case opcodes.OP_XOR: - case opcodes.OP_2MUL: - case opcodes.OP_2DIV: - case opcodes.OP_MUL: - case opcodes.OP_DIV: - case opcodes.OP_MOD: - case opcodes.OP_LSHIFT: - case opcodes.OP_RSHIFT: - return true; + /** + * Test whether opcode is a disabled opcode. + * @returns {Boolean} + */ + + isDisabled() { + switch (this.value) { + case opcodes.OP_CAT: + case opcodes.OP_SUBSTR: + case opcodes.OP_LEFT: + case opcodes.OP_RIGHT: + case opcodes.OP_INVERT: + case opcodes.OP_AND: + case opcodes.OP_OR: + case opcodes.OP_XOR: + case opcodes.OP_2MUL: + case opcodes.OP_2DIV: + case opcodes.OP_MUL: + case opcodes.OP_DIV: + case opcodes.OP_MOD: + case opcodes.OP_LSHIFT: + case opcodes.OP_RSHIFT: + return true; + } + return false; } - return false; -}; -/** - * Test whether opcode is a branch (if/else/endif). - * @returns {Boolean} - */ + /** + * Test whether opcode is a branch (if/else/endif). + * @returns {Boolean} + */ -Opcode.prototype.isBranch = function isBranch() { - return this.value >= opcodes.OP_IF && this.value <= opcodes.OP_ENDIF; -}; + isBranch() { + return this.value >= opcodes.OP_IF && this.value <= opcodes.OP_ENDIF; + } -/** - * Test opcode equality. - * @param {Opcode} op - * @returns {Boolean} - */ + /** + * Test opcode equality. + * @param {Opcode} op + * @returns {Boolean} + */ -Opcode.prototype.equals = function equals(op) { - assert(Opcode.isOpcode(op)); + equals(op) { + assert(Opcode.isOpcode(op)); - if (this.value !== op.value) - return false; + if (this.value !== op.value) + return false; - if (!this.data) { - assert(!op.data); - return true; - } + if (!this.data) { + assert(!op.data); + return true; + } - assert(op.data); + assert(op.data); - return this.data.equals(op.data); -}; + return this.data.equals(op.data); + } -/** - * Convert Opcode to opcode value. - * @returns {Number} - */ + /** + * Convert Opcode to opcode value. + * @returns {Number} + */ -Opcode.prototype.toOp = function toOp() { - return this.value; -}; + toOp() { + return this.value; + } -/** - * Covert opcode to data push. - * @returns {Buffer|null} - */ + /** + * Covert opcode to data push. + * @returns {Buffer|null} + */ -Opcode.prototype.toData = function toData() { - return this.data; -}; + toData() { + return this.data; + } -/** - * Covert opcode to data length. - * @returns {Number} - */ + /** + * Covert opcode to data length. + * @returns {Number} + */ -Opcode.prototype.toLength = function toLength() { - return this.data ? this.data.length : -1; -}; + toLength() { + return this.data ? this.data.length : -1; + } -/** - * Covert and _cast_ opcode to data push. - * @returns {Buffer|null} - */ + /** + * Covert and _cast_ opcode to data push. + * @returns {Buffer|null} + */ -Opcode.prototype.toPush = function toPush() { - if (this.value === opcodes.OP_0) - return common.small[0 + 1]; + toPush() { + if (this.value === opcodes.OP_0) + return common.small[0 + 1]; - if (this.value === opcodes.OP_1NEGATE) - return common.small[-1 + 1]; + if (this.value === opcodes.OP_1NEGATE) + return common.small[-1 + 1]; - if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16) - return common.small[this.value - 0x50 + 1]; + if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16) + return common.small[this.value - 0x50 + 1]; - return this.toData(); -}; + return this.toData(); + } -/** - * Get string for opcode. - * @param {String?} enc - * @returns {Buffer|null} - */ + /** + * Get string for opcode. + * @param {String?} enc + * @returns {Buffer|null} + */ -Opcode.prototype.toString = function toString(enc) { - const data = this.toPush(); + toString(enc) { + const data = this.toPush(); - if (!data) - return null; + if (!data) + return null; - return data.toString(enc || 'utf8'); -}; + return data.toString(enc || 'utf8'); + } -/** - * Convert opcode to small integer. - * @returns {Number} - */ + /** + * Convert opcode to small integer. + * @returns {Number} + */ -Opcode.prototype.toSmall = function toSmall() { - if (this.value === opcodes.OP_0) - return 0; + toSmall() { + if (this.value === opcodes.OP_0) + return 0; - if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16) - return this.value - 0x50; + if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16) + return this.value - 0x50; - return -1; -}; + return -1; + } -/** - * Convert opcode to script number. - * @param {Boolean?} minimal - * @param {Number?} limit - * @returns {ScriptNum|null} - */ + /** + * Convert opcode to script number. + * @param {Boolean?} minimal + * @param {Number?} limit + * @returns {ScriptNum|null} + */ -Opcode.prototype.toNum = function toNum(minimal, limit) { - if (this.value === opcodes.OP_0) - return ScriptNum.fromInt(0); + toNum(minimal, limit) { + if (this.value === opcodes.OP_0) + return ScriptNum.fromInt(0); - if (this.value === opcodes.OP_1NEGATE) - return ScriptNum.fromInt(-1); + if (this.value === opcodes.OP_1NEGATE) + return ScriptNum.fromInt(-1); - if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16) - return ScriptNum.fromInt(this.value - 0x50); + if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16) + return ScriptNum.fromInt(this.value - 0x50); - if (!this.data) - return null; + if (!this.data) + return null; - return ScriptNum.decode(this.data, minimal, limit); -}; + return ScriptNum.decode(this.data, minimal, limit); + } -/** - * Convert opcode to integer. - * @param {Boolean?} minimal - * @param {Number?} limit - * @returns {Number} - */ + /** + * Convert opcode to integer. + * @param {Boolean?} minimal + * @param {Number?} limit + * @returns {Number} + */ -Opcode.prototype.toInt = function toInt(minimal, limit) { - const num = this.toNum(minimal, limit); + toInt(minimal, limit) { + const num = this.toNum(minimal, limit); - if (!num) - return -1; + if (!num) + return -1; - return num.getInt(); -}; + return num.getInt(); + } -/** - * Convert opcode to boolean. - * @returns {Boolean} - */ + /** + * Convert opcode to boolean. + * @returns {Boolean} + */ -Opcode.prototype.toBool = function toBool() { - const smi = this.toSmall(); + toBool() { + const smi = this.toSmall(); - if (smi === -1) - return false; + if (smi === -1) + return false; - return smi === 1; -}; + return smi === 1; + } -/** - * Convert opcode to its symbolic representation. - * @returns {String} - */ + /** + * Convert opcode to its symbolic representation. + * @returns {String} + */ -Opcode.prototype.toSymbol = function toSymbol() { - if (this.value === -1) - return 'OP_INVALIDOPCODE'; + toSymbol() { + if (this.value === -1) + return 'OP_INVALIDOPCODE'; - const symbol = common.opcodesByVal[this.value]; + const symbol = common.opcodesByVal[this.value]; - if (!symbol) - return `0x${util.hex8(this.value)}`; + if (!symbol) + return `0x${hex8(this.value)}`; - return symbol; -}; + return symbol; + } -/** - * Calculate opcode size. - * @returns {Number} - */ + /** + * Calculate opcode size. + * @returns {Number} + */ + + getSize() { + if (!this.data) + return 1; + + switch (this.value) { + case opcodes.OP_PUSHDATA1: + return 2 + this.data.length; + case opcodes.OP_PUSHDATA2: + return 3 + this.data.length; + case opcodes.OP_PUSHDATA4: + return 5 + this.data.length; + default: + return 1 + this.data.length; + } + } -Opcode.prototype.getSize = function getSize() { - if (!this.data) - return 1; + /** + * Encode the opcode to a buffer writer. + * @param {BufferWriter} bw + */ - switch (this.value) { - case opcodes.OP_PUSHDATA1: - return 2 + this.data.length; - case opcodes.OP_PUSHDATA2: - return 3 + this.data.length; - case opcodes.OP_PUSHDATA4: - return 5 + this.data.length; - default: - return 1 + this.data.length; - } -}; + toWriter(bw) { + if (this.value === -1) + throw new Error('Cannot reserialize a parse error.'); -/** - * Encode the opcode to a buffer writer. - * @param {BufferWriter} bw - */ + if (!this.data) { + bw.writeU8(this.value); + return bw; + } -Opcode.prototype.toWriter = function toWriter(bw) { - if (this.value === -1) - throw new Error('Cannot reserialize a parse error.'); + switch (this.value) { + case opcodes.OP_PUSHDATA1: + bw.writeU8(this.value); + bw.writeU8(this.data.length); + bw.writeBytes(this.data); + break; + case opcodes.OP_PUSHDATA2: + bw.writeU8(this.value); + bw.writeU16(this.data.length); + bw.writeBytes(this.data); + break; + case opcodes.OP_PUSHDATA4: + bw.writeU8(this.value); + bw.writeU32(this.data.length); + bw.writeBytes(this.data); + break; + default: + assert(this.value === this.data.length); + bw.writeU8(this.value); + bw.writeBytes(this.data); + break; + } - if (!this.data) { - bw.writeU8(this.value); return bw; } - switch (this.value) { - case opcodes.OP_PUSHDATA1: - bw.writeU8(this.value); - bw.writeU8(this.data.length); - bw.writeBytes(this.data); - break; - case opcodes.OP_PUSHDATA2: - bw.writeU8(this.value); - bw.writeU16(this.data.length); - bw.writeBytes(this.data); - break; - case opcodes.OP_PUSHDATA4: - bw.writeU8(this.value); - bw.writeU32(this.data.length); - bw.writeBytes(this.data); - break; - default: - assert(this.value === this.data.length); - bw.writeU8(this.value); - bw.writeBytes(this.data); - break; + /** + * Encode the opcode. + * @returns {Buffer} + */ + + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); } - return bw; -}; + /** + * Convert the opcode to a bitcoind test string. + * @returns {String} Human-readable script code. + */ + + toFormat() { + if (this.value === -1) + return '0x01'; + + if (this.data) { + // Numbers + if (this.data.length <= 4) { + const num = this.toNum(); + if (this.equals(Opcode.fromNum(num))) + return num.toString(10); + } -/** - * Encode the opcode. - * @returns {Buffer} - */ + const symbol = common.opcodesByVal[this.value]; + const data = this.data.toString('hex'); -Opcode.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + // Direct push + if (!symbol) { + const size = hex8(this.value); + return `0x${size} 0x${data}`; + } -/** - * Convert the opcode to a bitcoind test string. - * @returns {String} Human-readable script code. - */ + // Pushdatas + let size = this.data.length.toString(16); -Opcode.prototype.toFormat = function toFormat() { - if (this.value === -1) - return '0x01'; + while (size.length % 2 !== 0) + size = '0' + size; - if (this.data) { - // Numbers - if (this.data.length <= 4) { - const num = this.toNum(); - if (this.equals(Opcode.fromNum(num))) - return num.toString(10); + return `${symbol} 0x${size} 0x${data}`; } + // Opcodes const symbol = common.opcodesByVal[this.value]; - const data = this.data.toString('hex'); + if (symbol) + return symbol; - // Direct push - if (!symbol) { - const size = util.hex8(this.value); - return `0x${size} 0x${data}`; - } + // Unknown opcodes + const value = hex8(this.value); - // Pushdatas - let size = this.data.length.toString(16); - - while (size.length % 2 !== 0) - size = '0' + size; - - return `${symbol} 0x${size} 0x${data}`; + return `0x${value}`; } - // Opcodes - const symbol = common.opcodesByVal[this.value]; - if (symbol) - return symbol; + /** + * Format the opcode as bitcoind asm. + * @param {Boolean?} decode - Attempt to decode hash types. + * @returns {String} Human-readable script. + */ - // Unknown opcodes - const value = util.hex8(this.value); + toASM(decode) { + if (this.value === -1) + return '[error]'; - return `0x${value}`; -}; + if (this.data) + return common.toASM(this.data, decode); -/** - * Format the opcode as bitcoind asm. - * @param {Boolean?} decode - Attempt to decode hash types. - * @returns {String} Human-readable script. - */ - -Opcode.prototype.toASM = function toASM(decode) { - if (this.value === -1) - return '[error]'; + return common.opcodesByVal[this.value] || 'OP_UNKNOWN'; + } - if (this.data) - return common.toASM(this.data, decode); + /** + * Instantiate an opcode from a number opcode. + * @param {Number} op + * @returns {Opcode} + */ - return common.opcodesByVal[this.value] || 'OP_UNKNOWN'; -}; + static fromOp(op) { + assert(typeof op === 'number'); -/** - * Instantiate an opcode from a number opcode. - * @param {Number} op - * @returns {Opcode} - */ + const cached = opCache[op]; -Opcode.fromOp = function fromOp(op) { - assert(typeof op === 'number'); + assert(cached, 'Bad opcode.'); - const cached = opCache[op]; - - assert(cached, 'Bad opcode.'); + return cached; + } - return cached; -}; + /** + * Instantiate a pushdata opcode from + * a buffer (will encode minimaldata). + * @param {Buffer} data + * @returns {Opcode} + */ -/** - * Instantiate a pushdata opcode from - * a buffer (will encode minimaldata). - * @param {Buffer} data - * @returns {Opcode} - */ + static fromData(data) { + assert(Buffer.isBuffer(data)); -Opcode.fromData = function fromData(data) { - assert(Buffer.isBuffer(data)); + if (data.length === 1) { + if (data[0] === 0x81) + return this.fromOp(opcodes.OP_1NEGATE); - if (data.length === 1) { - if (data[0] === 0x81) - return Opcode.fromOp(opcodes.OP_1NEGATE); + if (data[0] >= 1 && data[0] <= 16) + return this.fromOp(data[0] + 0x50); + } - if (data[0] >= 1 && data[0] <= 16) - return Opcode.fromOp(data[0] + 0x50); + return this.fromPush(data); } - return Opcode.fromPush(data); -}; + /** + * Instantiate a pushdata opcode from a + * buffer (this differs from fromData in + * that it will _always_ be a pushdata op). + * @param {Buffer} data + * @returns {Opcode} + */ -/** - * Instantiate a pushdata opcode from a - * buffer (this differs from fromData in - * that it will _always_ be a pushdata op). - * @param {Buffer} data - * @returns {Opcode} - */ + static fromPush(data) { + assert(Buffer.isBuffer(data)); -Opcode.fromPush = function fromPush(data) { - assert(Buffer.isBuffer(data)); + if (data.length === 0) + return this.fromOp(opcodes.OP_0); - if (data.length === 0) - return Opcode.fromOp(opcodes.OP_0); + if (data.length <= 0x4b) + return new this(data.length, data); - if (data.length <= 0x4b) - return new Opcode(data.length, data); + if (data.length <= 0xff) + return new this(opcodes.OP_PUSHDATA1, data); - if (data.length <= 0xff) - return new Opcode(opcodes.OP_PUSHDATA1, data); + if (data.length <= 0xffff) + return new this(opcodes.OP_PUSHDATA2, data); - if (data.length <= 0xffff) - return new Opcode(opcodes.OP_PUSHDATA2, data); + if (data.length <= 0xffffffff) + return new this(opcodes.OP_PUSHDATA4, data); - if (data.length <= 0xffffffff) - return new Opcode(opcodes.OP_PUSHDATA4, data); + throw new Error('Pushdata size too large.'); + } - throw new Error('Pushdata size too large.'); -}; + /** + * Instantiate a pushdata opcode from a string. + * @param {String} str + * @param {String} [enc=utf8] + * @returns {Opcode} + */ + + static fromString(str, enc) { + assert(typeof str === 'string'); + const data = Buffer.from(str, enc || 'utf8'); + return this.fromData(data); + } -/** - * Instantiate a pushdata opcode from a string. - * @param {String} str - * @param {String} [enc=utf8] - * @returns {Opcode} - */ + /** + * Instantiate an opcode from a small number. + * @param {Number} num + * @returns {Opcode} + */ -Opcode.fromString = function fromString(str, enc) { - assert(typeof str === 'string'); - const data = Buffer.from(str, enc || 'utf8'); - return Opcode.fromData(data); -}; + static fromSmall(num) { + assert((num & 0xff) === num && num >= 0 && num <= 16); + return this.fromOp(num === 0 ? 0 : num + 0x50); + } -/** - * Instantiate an opcode from a small number. - * @param {Number} num - * @returns {Opcode} - */ + /** + * Instantiate an opcode from a ScriptNum. + * @param {ScriptNumber} num + * @returns {Opcode} + */ -Opcode.fromSmall = function fromSmall(num) { - assert(util.isU8(num) && num >= 0 && num <= 16); - return Opcode.fromOp(num === 0 ? 0 : num + 0x50); -}; + static fromNum(num) { + assert(ScriptNum.isScriptNum(num)); + return this.fromData(num.encode()); + } -/** - * Instantiate an opcode from a ScriptNum. - * @param {ScriptNumber} num - * @returns {Opcode} - */ + /** + * Instantiate an opcode from a Number. + * @param {Number} num + * @returns {Opcode} + */ -Opcode.fromNum = function fromNum(num) { - assert(ScriptNum.isScriptNum(num)); - return Opcode.fromData(num.encode()); -}; + static fromInt(num) { + assert(Number.isSafeInteger(num)); -/** - * Instantiate an opcode from a Number. - * @param {Number} num - * @returns {Opcode} - */ + if (num === 0) + return this.fromOp(opcodes.OP_0); -Opcode.fromInt = function fromInt(num) { - assert(util.isInt(num)); + if (num === -1) + return this.fromOp(opcodes.OP_1NEGATE); - if (num === 0) - return Opcode.fromOp(opcodes.OP_0); + if (num >= 1 && num <= 16) + return this.fromOp(num + 0x50); - if (num === -1) - return Opcode.fromOp(opcodes.OP_1NEGATE); + return this.fromNum(ScriptNum.fromNumber(num)); + } - if (num >= 1 && num <= 16) - return Opcode.fromOp(num + 0x50); + /** + * Instantiate an opcode from a Number. + * @param {Boolean} value + * @returns {Opcode} + */ - return Opcode.fromNum(ScriptNum.fromNumber(num)); -}; + static fromBool(value) { + assert(typeof value === 'boolean'); + return this.fromSmall(value ? 1 : 0); + } -/** - * Instantiate an opcode from a Number. - * @param {Boolean} value - * @returns {Opcode} - */ + /** + * Instantiate a pushdata opcode from symbolic name. + * @example + * Opcode.fromSymbol('checksequenceverify') + * @param {String} name + * @returns {Opcode} + */ -Opcode.fromBool = function fromBool(value) { - assert(typeof value === 'boolean'); - return Opcode.fromSmall(value ? 1 : 0); -}; + static fromSymbol(name) { + assert(typeof name === 'string'); + assert(name.length > 0); -/** - * Instantiate a pushdata opcode from symbolic name. - * @example - * Opcode.fromSymbol('checksequenceverify') - * @param {String} name - * @returns {Opcode} - */ + if (name.charCodeAt(0) & 32) + name = name.toUpperCase(); -Opcode.fromSymbol = function fromSymbol(name) { - assert(typeof name === 'string'); - assert(name.length > 0); + if (!/^OP_/.test(name)) + name = `OP_${name}`; - if (!util.isUpperCase(name)) - name = name.toUpperCase(); + const op = common.opcodes[name]; - if (!util.startsWith(name, 'OP_')) - name = `OP_${name}`; + if (op != null) + return this.fromOp(op); - const op = common.opcodes[name]; + assert(/^OP_0X/.test(name), 'Unknown opcode.'); + assert(name.length === 7, 'Unknown opcode.'); - if (op != null) - return Opcode.fromOp(op); + const value = parseInt(name.substring(5), 16); - assert(util.startsWith(name, 'OP_0X'), 'Unknown opcode.'); - assert(name.length === 7, 'Unknown opcode.'); + assert((value & 0xff) === value, 'Unknown opcode.'); - const value = parseInt(name.substring(5), 16); + return this.fromOp(value); + } - assert(util.isU8(value), 'Unknown opcode.'); + /** + * Instantiate opcode from buffer reader. + * @param {BufferReader} br + * @returns {Opcode} + */ - return Opcode.fromOp(value); -}; + static fromReader(br) { + const value = br.readU8(); + const op = opCache[value]; -/** - * Instantiate opcode from buffer reader. - * @param {BufferReader} br - * @returns {Opcode} - */ + if (op) + return op; -Opcode.fromReader = function fromReader(br) { - const value = br.readU8(); - const op = opCache[value]; + switch (value) { + case opcodes.OP_PUSHDATA1: { + if (br.left() < 1) + return PARSE_ERROR; - if (op) - return op; + const size = br.readU8(); - switch (value) { - case opcodes.OP_PUSHDATA1: { - if (br.left() < 1) - return PARSE_ERROR; + if (br.left() < size) { + br.seek(br.left()); + return PARSE_ERROR; + } - const size = br.readU8(); + const data = br.readBytes(size); - if (br.left() < size) { - br.seek(br.left()); - return PARSE_ERROR; + return new this(value, data); } + case opcodes.OP_PUSHDATA2: { + if (br.left() < 2) { + br.seek(br.left()); + return PARSE_ERROR; + } - const data = br.readBytes(size); + const size = br.readU16(); - return new Opcode(value, data); - } - case opcodes.OP_PUSHDATA2: { - if (br.left() < 2) { - br.seek(br.left()); - return PARSE_ERROR; - } + if (br.left() < size) { + br.seek(br.left()); + return PARSE_ERROR; + } - const size = br.readU16(); + const data = br.readBytes(size); - if (br.left() < size) { - br.seek(br.left()); - return PARSE_ERROR; + return new this(value, data); } + case opcodes.OP_PUSHDATA4: { + if (br.left() < 4) { + br.seek(br.left()); + return PARSE_ERROR; + } - const data = br.readBytes(size); + const size = br.readU32(); - return new Opcode(value, data); - } - case opcodes.OP_PUSHDATA4: { - if (br.left() < 4) { - br.seek(br.left()); - return PARSE_ERROR; - } + if (br.left() < size) { + br.seek(br.left()); + return PARSE_ERROR; + } - const size = br.readU32(); + const data = br.readBytes(size); - if (br.left() < size) { - br.seek(br.left()); - return PARSE_ERROR; + return new this(value, data); } + default: { + if (br.left() < value) { + br.seek(br.left()); + return PARSE_ERROR; + } - const data = br.readBytes(size); + const data = br.readBytes(value); - return new Opcode(value, data); - } - default: { - if (br.left() < value) { - br.seek(br.left()); - return PARSE_ERROR; + return new this(value, data); } + } + } - const data = br.readBytes(value); + /** + * Instantiate opcode from serialized data. + * @param {Buffer} data + * @returns {Opcode} + */ - return new Opcode(value, data); - } + static fromRaw(data) { + return this.fromReader(bio.read(data)); } -}; -/** - * Instantiate opcode from serialized data. - * @param {Buffer} data - * @returns {Opcode} - */ + /** + * Test whether an object an Opcode. + * @param {Object} obj + * @returns {Boolean} + */ -Opcode.fromRaw = function fromRaw(data) { - return Opcode.fromReader(new BufferReader(data)); -}; + static isOpcode(obj) { + return obj instanceof Opcode; + } +} -/** - * Test whether an object an Opcode. - * @param {Object} obj - * @returns {Boolean} +/* + * Helpers */ -Opcode.isOpcode = function isOpcode(obj) { - return obj instanceof Opcode; -}; +function hex8(num) { + if (num <= 0x0f) + return '0' + num.toString(16); + return num.toString(16); +} /* * Fill Cache diff --git a/lib/script/program.js b/lib/script/program.js index fedee361a..a68baf908 100644 --- a/lib/script/program.js +++ b/lib/script/program.js @@ -7,97 +7,90 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); +const assert = require('bsert'); const common = require('./common'); const scriptTypes = common.types; +const {inspectSymbol} = require('../utils'); /** * Witness Program - * @constructor * @alias module:script.Program - * @param {Number} version - * @param {Buffer} data * @property {Number} version - Ranges from 0 to 16. - * @property {String|null} type - Null if malformed. `unknown` if unknown - * version (treated as anyone-can-spend). Otherwise one of `witnesspubkeyhash` - * or `witnessscripthash`. + * @property {String|null} type - Null if malformed. * @property {Buffer} data - The hash (for now). */ -function Program(version, data) { - if (!(this instanceof Program)) - return new Program(version, data); - - assert(util.isU8(version)); - assert(version >= 0 && version <= 16); - assert(Buffer.isBuffer(data)); - assert(data.length >= 2 && data.length <= 40); +class Program { + /** + * Create a witness program. + * @constructor + * @param {Number} version + * @param {Buffer} data + */ + + constructor(version, data) { + assert((version & 0xff) === version); + assert(version >= 0 && version <= 16); + assert(Buffer.isBuffer(data)); + assert(data.length >= 2 && data.length <= 40); + + this.version = version; + this.data = data; + } - this.version = version; - this.data = data; -} + /** + * Get the witness program type. + * @returns {ScriptType} + */ -/** - * Get the witness program type. - * @returns {ScriptType} - */ + getType() { + if (this.version === 0) { + if (this.data.length === 20) + return scriptTypes.WITNESSPUBKEYHASH; -Program.prototype.getType = function getType() { - if (this.version === 0) { - if (this.data.length === 20) - return scriptTypes.WITNESSPUBKEYHASH; + if (this.data.length === 32) + return scriptTypes.WITNESSSCRIPTHASH; - if (this.data.length === 32) - return scriptTypes.WITNESSSCRIPTHASH; + // Fail on bad version=0 + return scriptTypes.WITNESSMALFORMED; + } - // Fail on bad version=0 - return scriptTypes.WITNESSMALFORMED; + // No interpretation of script (anyone can spend) + return scriptTypes.NONSTANDARD; } - if (this.version === 1) { - if (this.data.length === 32) - return scriptTypes.WITNESSMASTHASH; + /** + * Test whether the program is either + * an unknown version or malformed. + * @returns {Boolean} + */ - // Fail on bad version=1 - return scriptTypes.WITNESSMALFORMED; + isUnknown() { + const type = this.getType(); + return type === scriptTypes.WITNESSMALFORMED + || type === scriptTypes.NONSTANDARD; } - // No interpretation of script (anyone can spend) - return scriptTypes.NONSTANDARD; -}; - -/** - * Test whether the program is either - * an unknown version or malformed. - * @returns {Boolean} - */ - -Program.prototype.isUnknown = function isUnknown() { - const type = this.getType(); - return type === scriptTypes.WITNESSMALFORMED - || type === scriptTypes.NONSTANDARD; -}; + /** + * Test whether the program is malformed. + * @returns {Boolean} + */ -/** - * Test whether the program is malformed. - * @returns {Boolean} - */ - -Program.prototype.isMalformed = function isMalformed() { - return this.getType() === scriptTypes.WITNESSMALFORMED; -}; + isMalformed() { + return this.getType() === scriptTypes.WITNESSMALFORMED; + } -/** - * Inspect the program. - * @returns {String} - */ + /** + * Inspect the program. + * @returns {String} + */ -Program.prototype.inspect = function inspect() { - const data = this.data.toString('hex'); - const type = common.typesByVal[this.getType()].toLowerCase(); - return ``; -}; + [inspectSymbol]() { + const data = this.data.toString('hex'); + const type = common.typesByVal[this.getType()].toLowerCase(); + return ``; + } +} /* * Expose diff --git a/lib/script/script.js b/lib/script/script.js index f96eeccf5..590463c66 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -7,3525 +7,3381 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); +const bio = require('bufio'); +const ripemd160 = require('bcrypto/lib/ripemd160'); +const sha1 = require('bcrypto/lib/sha1'); +const sha256 = require('bcrypto/lib/sha256'); +const hash160 = require('bcrypto/lib/hash160'); +const hash256 = require('bcrypto/lib/hash256'); +const secp256k1 = require('bcrypto/lib/secp256k1'); const consensus = require('../protocol/consensus'); const policy = require('../protocol/policy'); -const util = require('../utils/util'); -const digest = require('../crypto/digest'); -const merkle = require('../crypto/merkle'); -const BufferWriter = require('../utils/writer'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); const Program = require('./program'); const Opcode = require('./opcode'); const Stack = require('./stack'); const ScriptError = require('./scripterror'); const ScriptNum = require('./scriptnum'); const common = require('./common'); -const encoding = require('../utils/encoding'); -const secp256k1 = require('../crypto/secp256k1'); const Address = require('../primitives/address'); const opcodes = common.opcodes; const scriptTypes = common.types; +const {encoding} = bio; +const {inspectSymbol} = require('../utils'); + +/* + * Constants + */ + const EMPTY_BUFFER = Buffer.alloc(0); /** + * Script * Represents a input or output script. * @alias module:script.Script - * @constructor - * @param {Buffer|Array|Object|NakedScript} code - Array - * of script code or a serialized script Buffer. * @property {Array} code - Parsed script code. * @property {Buffer?} raw - Serialized script. * @property {Number} length - Number of parsed opcodes. */ -function Script(options) { - if (!(this instanceof Script)) - return new Script(options); +class Script { + /** + * Create a script. + * @constructor + * @param {Buffer|Array|Object} code + */ - this.raw = EMPTY_BUFFER; - this.code = []; + constructor(options) { + this.raw = EMPTY_BUFFER; + this.code = []; - if (options) - this.fromOptions(options); -} - -/** - * Script opcodes. - * @enum {Number} - * @default - */ + if (options) + this.fromOptions(options); + } -Script.opcodes = common.opcodes; + /** + * Get length. + * @returns {Number} + */ -/** - * Opcodes by value. - * @const {RevMap} - */ + get length() { + return this.code.length; + } -Script.opcodesByVal = common.opcodesByVal; + /** + * Set length. + * @param {Number} value + */ -/** - * Script and locktime flags. See {@link VerifyFlags}. - * @enum {Number} - */ + set length(value) { + this.code.length = value; + } -Script.flags = common.flags; + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ -/** - * Sighash Types. - * @enum {SighashType} - * @default - */ + fromOptions(options) { + assert(options, 'Script data is required.'); -Script.hashType = common.hashType; + if (Buffer.isBuffer(options)) + return this.fromRaw(options); -/** - * Sighash types by value. - * @const {RevMap} - */ + if (Array.isArray(options)) + return this.fromArray(options); -Script.hashTypeByVal = common.hashTypeByVal; + if (options.raw) { + if (!options.code) + return this.fromRaw(options.raw); + assert(Buffer.isBuffer(options.raw), 'Raw must be a Buffer.'); + this.raw = options.raw; + } -/** - * Output script types. - * @enum {Number} - */ + if (options.code) { + if (!options.raw) + return this.fromArray(options.code); + assert(Array.isArray(options.code), 'Code must be an array.'); + this.code = options.code; + } -Script.types = common.types; + return this; + } -/** - * Output script types by value. - * @const {RevMap} - */ + /** + * Insantiate script from options object. + * @param {Object} options + * @returns {Script} + */ -Script.typesByVal = common.typesByVal; + static fromOptions(options) { + return new this().fromOptions(options); + } -/* - * Expose length setter and getter. - */ + /** + * Instantiate a value-only iterator. + * @returns {ScriptIterator} + */ -Object.defineProperty(Script.prototype, 'length', { - get() { - return this.code.length; - }, - set(length) { - this.code.length = length; - return this.code.length; + values() { + return this.code.values(); } -}); -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ + /** + * Instantiate a key and value iterator. + * @returns {ScriptIterator} + */ -Script.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Script data is required.'); - - if (Buffer.isBuffer(options)) - return this.fromRaw(options); + entries() { + return this.code.entries(); + } - if (Array.isArray(options)) - return this.fromArray(options); + /** + * Instantiate a value-only iterator. + * @returns {ScriptIterator} + */ - if (options.raw) { - if (!options.code) - return this.fromRaw(options.raw); - assert(Buffer.isBuffer(options.raw), 'Raw must be a Buffer.'); - this.raw = options.raw; + [Symbol.iterator]() { + return this.code[Symbol.iterator](); } - if (options.code) { - if (!options.raw) - return this.fromArray(options.code); - assert(Array.isArray(options.code), 'Code must be an array.'); - this.code = options.code; + /** + * Convert the script to an array of + * Buffers (pushdatas) and Numbers + * (opcodes). + * @returns {Array} + */ + + toArray() { + return this.code.slice(); } - return this; -}; + /** + * Inject properties from an array of + * of buffers and numbers. + * @private + * @param {Array} code + * @returns {Script} + */ -/** - * Insantiate script from options object. - * @param {Object} options - * @returns {Script} - */ + fromArray(code) { + assert(Array.isArray(code)); -Script.fromOptions = function fromOptions(options) { - return new Script().fromOptions(options); -}; + this.clear(); -/** - * Instantiate a value-only iterator. - * @returns {ScriptIterator} - */ + for (const op of code) + this.push(op); -Script.prototype.values = function values() { - return this.code.values(); -}; + return this.compile(); + } -/** - * Instantiate a key and value iterator. - * @returns {ScriptIterator} - */ + /** + * Instantiate script from an array + * of buffers and numbers. + * @param {Array} code + * @returns {Script} + */ -Script.prototype.entries = function entries() { - return this.code.entries(); -}; + static fromArray(code) { + return new this().fromArray(code); + } -/** - * Instantiate a value-only iterator. - * @returns {ScriptIterator} - */ + /** + * Convert script to stack items. + * @returns {Buffer[]} + */ -Script.prototype[Symbol.iterator] = function() { - return this.code[Symbol.iterator](); -}; + toItems() { + const items = []; -/** - * Convert the script to an array of - * Buffers (pushdatas) and Numbers - * (opcodes). - * @returns {Array} - */ + for (const op of this.code) { + const data = op.toPush(); -Script.prototype.toArray = function toArray() { - return this.code.slice(); -}; + if (!data) + throw new Error('Non-push opcode in script.'); -/** - * Inject properties from an array of - * of buffers and numbers. - * @private - * @param {Array} code - * @returns {Script} - */ + items.push(data); + } -Script.prototype.fromArray = function fromArray(code) { - assert(Array.isArray(code)); + return items; + } - this.clear(); + /** + * Inject data from stack items. + * @private + * @param {Buffer[]} items + * @returns {Script} + */ - for (const op of code) - this.push(op); + fromItems(items) { + assert(Array.isArray(items)); - return this.compile(); -}; + this.clear(); -/** - * Instantiate script from an array - * of buffers and numbers. - * @param {Array} code - * @returns {Script} - */ + for (const item of items) + this.pushData(item); + + return this.compile(); + } -Script.fromArray = function fromArray(code) { - return new Script().fromArray(code); -}; + /** + * Instantiate script from stack items. + * @param {Buffer[]} items + * @returns {Script} + */ -/** - * Convert script to stack items. - * @returns {Buffer[]} - */ + static fromItems(items) { + return new this().fromItems(items); + } -Script.prototype.toItems = function toItems() { - const items = []; + /** + * Convert script to stack. + * @returns {Stack} + */ - for (const op of this.code) { - const data = op.toPush(); + toStack() { + return new Stack(this.toItems()); + } - if (!data) - throw new Error('Non-push opcode in script.'); + /** + * Inject data from stack. + * @private + * @param {Stack} stack + * @returns {Script} + */ - items.push(data); + fromStack(stack) { + return this.fromItems(stack.items); } - return items; -}; + /** + * Instantiate script from stack. + * @param {Stack} stack + * @returns {Script} + */ -/** - * Inject data from stack items. - * @private - * @param {Buffer[]} items - * @returns {Script} - */ + static fromStack(stack) { + return new this().fromStack(stack); + } -Script.prototype.fromItems = function fromItems(items) { - assert(Array.isArray(items)); + /** + * Clone the script. + * @returns {Script} Cloned script. + */ - this.clear(); + clone() { + return new this.constructor().inject(this); + } - for (const item of items) - this.pushData(item); + /** + * Inject properties from script. + * Used for cloning. + * @private + * @param {Script} script + * @returns {Script} + */ + + inject(script) { + this.raw = script.raw; + this.code = script.code.slice(); + return this; + } - return this.compile(); -}; + /** + * Test equality against script. + * @param {Script} script + * @returns {Boolean} + */ -/** - * Instantiate script from stack items. - * @param {Buffer[]} items - * @returns {Script} - */ + equals(script) { + assert(Script.isScript(script)); + return this.raw.equals(script.raw); + } -Script.fromItems = function fromItems(items) { - return new Script().fromItems(items); -}; + /** + * Compare against another script. + * @param {Script} script + * @returns {Number} + */ -/** - * Convert script to stack. - * @returns {Stack} - */ + compare(script) { + assert(Script.isScript(script)); + return this.raw.compare(script.raw); + } -Script.prototype.toStack = function toStack() { - return new Stack(this.toItems()); -}; + /** + * Clear the script. + * @returns {Script} + */ -/** - * Inject data from stack. - * @private - * @param {Stack} stack - * @returns {Script} - */ + clear() { + this.raw = EMPTY_BUFFER; + this.code.length = 0; + return this; + } -Script.prototype.fromStack = function fromStack(stack) { - return this.fromItems(stack.items); -}; + /** + * Inspect the script. + * @returns {String} Human-readable script code. + */ -/** - * Instantiate script from stack. - * @param {Stack} stack - * @returns {Script} - */ + [inspectSymbol]() { + return ``; + } -Script.fromStack = function fromStack(stack) { - return new Script().fromStack(stack); -}; + /** + * Convert the script to a bitcoind test string. + * @returns {String} Human-readable script code. + */ -/** - * Clone the script. - * @returns {Script} Cloned script. - */ + toString() { + const out = []; -Script.prototype.clone = function clone() { - return new Script().inject(this); -}; + for (const op of this.code) + out.push(op.toFormat()); -/** - * Inject properties from script. - * Used for cloning. - * @private - * @param {Script} script - * @returns {Script} - */ + return out.join(' '); + } -Script.prototype.inject = function inject(script) { - this.raw = script.raw; - this.code = script.code.slice(); - return this; -}; + /** + * Format the script as bitcoind asm. + * @param {Boolean?} decode - Attempt to decode hash types. + * @returns {String} Human-readable script. + */ -/** - * Test equality against script. - * @param {Script} script - * @returns {Boolean} - */ + toASM(decode) { + if (this.isNulldata()) + decode = false; -Script.prototype.equals = function equals(script) { - assert(Script.isScript(script)); - return this.raw.equals(script.raw); -}; + const out = []; -/** - * Compare against another script. - * @param {Script} script - * @returns {Number} - */ + for (const op of this.code) + out.push(op.toASM(decode)); -Script.prototype.compare = function compare(script) { - assert(Script.isScript(script)); - return this.raw.compare(script.raw); -}; + return out.join(' '); + } -/** - * Clear the script. - * @returns {Script} - */ + /** + * Re-encode the script internally. Useful if you + * changed something manually in the `code` array. + * @returns {Script} + */ -Script.prototype.clear = function clear() { - this.raw = EMPTY_BUFFER; - this.code.length = 0; - return this; -}; + compile() { + if (this.code.length === 0) + return this.clear(); -/** - * Inspect the script. - * @returns {String} Human-readable script code. - */ + let size = 0; -Script.prototype.inspect = function inspect() { - return ``; -}; + for (const op of this.code) + size += op.getSize(); -/** - * Convert the script to a bitcoind test string. - * @returns {String} Human-readable script code. - */ + const bw = bio.write(size); -Script.prototype.toString = function toString() { - const out = []; + for (const op of this.code) + op.toWriter(bw); - for (const op of this.code) - out.push(op.toFormat()); + this.raw = bw.render(); - return out.join(' '); -}; + return this; + } -/** - * Format the script as bitcoind asm. - * @param {Boolean?} decode - Attempt to decode hash types. - * @returns {String} Human-readable script. - */ + /** + * Write the script to a buffer writer. + * @param {BufferWriter} bw + */ -Script.prototype.toASM = function toASM(decode) { - if (this.isNulldata()) - decode = false; + toWriter(bw) { + bw.writeVarBytes(this.raw); + return bw; + } - const out = []; + /** + * Encode the script to a Buffer. See {@link Script#encode}. + * @param {String} enc - Encoding, either `'hex'` or `null`. + * @returns {Buffer|String} Serialized script. + */ - for (const op of this.code) - out.push(op.toASM(decode)); + toRaw() { + return this.raw; + } - return out.join(' '); -}; + /** + * Convert script to a hex string. + * @returns {String} + */ -/** - * Re-encode the script internally. Useful if you - * changed something manually in the `code` array. - * @returns {Script} - */ + toJSON() { + return this.toRaw().toString('hex'); + } -Script.prototype.compile = function compile() { - if (this.code.length === 0) - return this.clear(); + /** + * Inject properties from json object. + * @private + * @param {String} json + */ - let size = 0; + fromJSON(json) { + assert(typeof json === 'string', 'Code must be a string.'); + return this.fromRaw(Buffer.from(json, 'hex')); + } - for (const op of this.code) - size += op.getSize(); + /** + * Instantiate script from a hex string. + * @params {String} json + * @returns {Script} + */ - const bw = new StaticWriter(size); + static fromJSON(json) { + return new this().fromJSON(json); + } - for (const op of this.code) - op.toWriter(bw); + /** + * Get the script's "subscript" starting at a separator. + * @param {Number} index - The last separator to sign/verify beyond. + * @returns {Script} Subscript. + */ - this.raw = bw.render(); + getSubscript(index) { + if (index === 0) + return this.clone(); - return this; -}; + const script = new Script(); -/** - * Write the script to a buffer writer. - * @param {BufferWriter} bw - */ + for (let i = index; i < this.code.length; i++) { + const op = this.code[i]; -Script.prototype.toWriter = function toWriter(bw) { - bw.writeVarBytes(this.raw); - return bw; -}; + if (op.value === -1) + break; -/** - * Encode the script to a Buffer. See {@link Script#encode}. - * @param {String} enc - Encoding, either `'hex'` or `null`. - * @returns {Buffer|String} Serialized script. - */ + script.code.push(op); + } -Script.prototype.toRaw = function toRaw() { - return this.raw; -}; + return script.compile(); + } -/** - * Convert script to a hex string. - * @returns {String} - */ + /** + * Get the script's "subscript" starting at a separator. + * Remove all OP_CODESEPARATORs if present. This bizarre + * behavior is necessary for signing and verification when + * code separators are present. + * @returns {Script} Subscript. + */ + + removeSeparators() { + let found = false; + + // Optimizing for the common case: + // Check for any separators first. + for (const op of this.code) { + if (op.value === -1) + break; -Script.prototype.toJSON = function toJSON() { - return this.toRaw().toString('hex'); -}; + if (op.value === opcodes.OP_CODESEPARATOR) { + found = true; + break; + } + } -/** - * Inject properties from json object. - * @private - * @param {String} json - */ + if (!found) + return this; -Script.prototype.fromJSON = function fromJSON(json) { - assert(typeof json === 'string', 'Code must be a string.'); - return this.fromRaw(Buffer.from(json, 'hex')); -}; + // Uncommon case: someone actually + // has a code separator. Go through + // and remove them all. + const script = new Script(); -/** - * Instantiate script from a hex string. - * @params {String} json - * @returns {Script} - */ + for (const op of this.code) { + if (op.value === -1) + break; -Script.fromJSON = function fromJSON(json) { - return new Script().fromJSON(json); -}; + if (op.value !== opcodes.OP_CODESEPARATOR) + script.code.push(op); + } -/** - * Get the script's "subscript" starting at a separator. - * @param {Number} index - The last separator to sign/verify beyond. - * @returns {Script} Subscript. - */ + return script.compile(); + } -Script.prototype.getSubscript = function getSubscript(index) { - if (index === 0) - return this.clone(); + /** + * Execute and interpret the script. + * @param {Stack} stack - Script execution stack. + * @param {Number?} flags - Script standard flags. + * @param {TX?} tx - Transaction being verified. + * @param {Number?} index - Index of input being verified. + * @param {Amount?} value - Previous output value. + * @param {Number?} version - Signature hash version (0=legacy, 1=segwit). + * @throws {ScriptError} Will be thrown on VERIFY failures. + */ - const script = new Script(); + execute(stack, flags, tx, index, value, version) { + if (flags == null) + flags = Script.flags.STANDARD_VERIFY_FLAGS; - for (let i = index; i < this.code.length; i++) { - const op = this.code[i]; + if (version == null) + version = 0; - if (op.value === -1) - break; + if (this.raw.length > consensus.MAX_SCRIPT_SIZE) + throw new ScriptError('SCRIPT_SIZE'); - script.code.push(op); - } + const state = []; + const alt = []; - return script.compile(); -}; + let lastSep = 0; + let opCount = 0; + let negate = 0; + let minimal = false; -/** - * Get the script's "subscript" starting at a separator. - * Remove all OP_CODESEPARATORs if present. This bizarre - * behavior is necessary for signing and verification when - * code separators are present. - * @returns {Script} Subscript. - */ + if (flags & Script.flags.VERIFY_MINIMALDATA) + minimal = true; -Script.prototype.removeSeparators = function removeSeparators() { - let found = false; + for (let ip = 0; ip < this.code.length; ip++) { + const op = this.code[ip]; - // Optimizing for the common case: - // Check for any separators first. - for (const op of this.code) { - if (op.value === -1) - break; + if (op.value === -1) + throw new ScriptError('BAD_OPCODE', op, ip); - if (op.value === opcodes.OP_CODESEPARATOR) { - found = true; - break; - } - } + if (op.data && op.data.length > consensus.MAX_SCRIPT_PUSH) + throw new ScriptError('PUSH_SIZE', op, ip); - if (!found) - return this; + if (op.value > opcodes.OP_16 && ++opCount > consensus.MAX_SCRIPT_OPS) + throw new ScriptError('OP_COUNT', op, ip); - // Uncommon case: someone actually - // has a code separator. Go through - // and remove them all. - const script = new Script(); + if (op.isDisabled()) + throw new ScriptError('DISABLED_OPCODE', op, ip); - for (const op of this.code) { - if (op.value === -1) - break; + if (op.value === opcodes.OP_CODESEPARATOR && version === 0 && + (flags & Script.flags.VERIFY_CONST_SCRIPTCODE)) + throw new ScriptError('OP_CODESEPARATOR', op, ip); - if (op.value !== opcodes.OP_CODESEPARATOR) - script.code.push(op); - } + if (negate && !op.isBranch()) { + if (stack.length + alt.length > consensus.MAX_SCRIPT_STACK) + throw new ScriptError('STACK_SIZE', op, ip); + continue; + } - return script.compile(); -}; + if (op.data) { + if (minimal && !op.isMinimal()) + throw new ScriptError('MINIMALDATA', op, ip); -/** - * Execute and interpret the script. - * @param {Stack} stack - Script execution stack. - * @param {Number?} flags - Script standard flags. - * @param {TX?} tx - Transaction being verified. - * @param {Number?} index - Index of input being verified. - * @param {Amount?} value - Previous output value. - * @param {Number?} version - Signature hash version (0=legacy, 1=segwit). - * @throws {ScriptError} Will be thrown on VERIFY failures, among other things. - */ + stack.push(op.data); -Script.prototype.execute = function execute(stack, flags, tx, index, value, version) { - if (flags == null) - flags = Script.flags.STANDARD_VERIFY_FLAGS; + if (stack.length + alt.length > consensus.MAX_SCRIPT_STACK) + throw new ScriptError('STACK_SIZE', op, ip); - if (version == null) - version = 0; + continue; + } - if (this.raw.length > consensus.MAX_SCRIPT_SIZE) - throw new ScriptError('SCRIPT_SIZE'); + switch (op.value) { + case opcodes.OP_0: { + stack.pushInt(0); + break; + } + case opcodes.OP_1NEGATE: { + stack.pushInt(-1); + break; + } + case opcodes.OP_1: + case opcodes.OP_2: + case opcodes.OP_3: + case opcodes.OP_4: + case opcodes.OP_5: + case opcodes.OP_6: + case opcodes.OP_7: + case opcodes.OP_8: + case opcodes.OP_9: + case opcodes.OP_10: + case opcodes.OP_11: + case opcodes.OP_12: + case opcodes.OP_13: + case opcodes.OP_14: + case opcodes.OP_15: + case opcodes.OP_16: { + stack.pushInt(op.value - 0x50); + break; + } + case opcodes.OP_NOP: { + break; + } + case opcodes.OP_CHECKLOCKTIMEVERIFY: { + // OP_CHECKLOCKTIMEVERIFY = OP_NOP2 + if (!(flags & Script.flags.VERIFY_CHECKLOCKTIMEVERIFY)) + break; - const state = []; - const alt = []; + if (!tx) + throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); - let lastSep = 0; - let opCount = 0; - let negate = 0; - let minimal = false; + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - if (flags & Script.flags.VERIFY_MINIMALDATA) - minimal = true; + const num = stack.getNum(-1, minimal, 5); - for (let ip = 0; ip < this.code.length; ip++) { - const op = this.code[ip]; + if (num.isNeg()) + throw new ScriptError('NEGATIVE_LOCKTIME', op, ip); - if (op.value === -1) - throw new ScriptError('BAD_OPCODE', op, ip); + const locktime = num.toDouble(); - if (op.data && op.data.length > consensus.MAX_SCRIPT_PUSH) - throw new ScriptError('PUSH_SIZE', op, ip); + if (!tx.verifyLocktime(index, locktime)) + throw new ScriptError('UNSATISFIED_LOCKTIME', op, ip); - if (op.value > opcodes.OP_16 && ++opCount > consensus.MAX_SCRIPT_OPS) - throw new ScriptError('OP_COUNT', op, ip); + break; + } + case opcodes.OP_CHECKSEQUENCEVERIFY: { + // OP_CHECKSEQUENCEVERIFY = OP_NOP3 + if (!(flags & Script.flags.VERIFY_CHECKSEQUENCEVERIFY)) + break; - if (op.isDisabled()) - throw new ScriptError('DISABLED_OPCODE', op, ip); + if (!tx) + throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); - if (negate && !op.isBranch()) { - if (stack.length + alt.length > consensus.MAX_SCRIPT_STACK) - throw new ScriptError('STACK_SIZE', op, ip); - continue; - } + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - if (op.data) { - if (minimal && !op.isMinimal()) - throw new ScriptError('MINIMALDATA', op, ip); + const num = stack.getNum(-1, minimal, 5); - stack.push(op.data); + if (num.isNeg()) + throw new ScriptError('NEGATIVE_LOCKTIME', op, ip); - if (stack.length + alt.length > consensus.MAX_SCRIPT_STACK) - throw new ScriptError('STACK_SIZE', op, ip); + const locktime = num.toDouble(); - continue; - } + if (!tx.verifySequence(index, locktime)) + throw new ScriptError('UNSATISFIED_LOCKTIME', op, ip); - switch (op.value) { - case opcodes.OP_0: { - stack.pushInt(0); - break; - } - case opcodes.OP_1NEGATE: { - stack.pushInt(-1); - break; - } - case opcodes.OP_1: - case opcodes.OP_2: - case opcodes.OP_3: - case opcodes.OP_4: - case opcodes.OP_5: - case opcodes.OP_6: - case opcodes.OP_7: - case opcodes.OP_8: - case opcodes.OP_9: - case opcodes.OP_10: - case opcodes.OP_11: - case opcodes.OP_12: - case opcodes.OP_13: - case opcodes.OP_14: - case opcodes.OP_15: - case opcodes.OP_16: { - stack.pushInt(op.value - 0x50); - break; - } - case opcodes.OP_NOP: { - break; - } - case opcodes.OP_CHECKLOCKTIMEVERIFY: { - // OP_CHECKLOCKTIMEVERIFY = OP_NOP2 - if (!(flags & Script.flags.VERIFY_CHECKLOCKTIMEVERIFY)) { + break; + } + case opcodes.OP_NOP1: + case opcodes.OP_NOP4: + case opcodes.OP_NOP5: + case opcodes.OP_NOP6: + case opcodes.OP_NOP7: + case opcodes.OP_NOP8: + case opcodes.OP_NOP9: + case opcodes.OP_NOP10: { if (flags & Script.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) throw new ScriptError('DISCOURAGE_UPGRADABLE_NOPS', op, ip); break; } + case opcodes.OP_IF: + case opcodes.OP_NOTIF: { + let val = false; - if (!tx) - throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); + if (!negate) { + if (stack.length < 1) + throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + if (version === 1 && (flags & Script.flags.VERIFY_MINIMALIF)) { + const item = stack.get(-1); - const num = stack.getNum(-1, minimal, 5); + if (item.length > 1) + throw new ScriptError('MINIMALIF'); - if (num.isNeg()) - throw new ScriptError('NEGATIVE_LOCKTIME', op, ip); + if (item.length === 1 && item[0] !== 1) + throw new ScriptError('MINIMALIF'); + } - const locktime = num.toDouble(); + val = stack.getBool(-1); - if (!tx.verifyLocktime(index, locktime)) - throw new ScriptError('UNSATISFIED_LOCKTIME', op, ip); + if (op.value === opcodes.OP_NOTIF) + val = !val; - break; - } - case opcodes.OP_CHECKSEQUENCEVERIFY: { - // OP_CHECKSEQUENCEVERIFY = OP_NOP3 - if (!(flags & Script.flags.VERIFY_CHECKSEQUENCEVERIFY)) { - if (flags & Script.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) - throw new ScriptError('DISCOURAGE_UPGRADABLE_NOPS', op, ip); - break; - } + stack.pop(); + } - if (!tx) - throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); + state.push(val); - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + if (!val) + negate += 1; - const num = stack.getNum(-1, minimal, 5); + break; + } + case opcodes.OP_ELSE: { + if (state.length === 0) + throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); - if (num.isNeg()) - throw new ScriptError('NEGATIVE_LOCKTIME', op, ip); + state[state.length - 1] = !state[state.length - 1]; - const locktime = num.toDouble(); + if (!state[state.length - 1]) + negate += 1; + else + negate -= 1; - if (!tx.verifySequence(index, locktime)) - throw new ScriptError('UNSATISFIED_LOCKTIME', op, ip); + break; + } + case opcodes.OP_ENDIF: { + if (state.length === 0) + throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); - break; - } - case opcodes.OP_NOP1: - case opcodes.OP_NOP4: - case opcodes.OP_NOP5: - case opcodes.OP_NOP6: - case opcodes.OP_NOP7: - case opcodes.OP_NOP8: - case opcodes.OP_NOP9: - case opcodes.OP_NOP10: { - if (flags & Script.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) - throw new ScriptError('DISCOURAGE_UPGRADABLE_NOPS', op, ip); - break; - } - case opcodes.OP_IF: - case opcodes.OP_NOTIF: { - let val = false; + if (!state.pop()) + negate -= 1; - if (!negate) { - if (stack.length < 1) - throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); + break; + } + case opcodes.OP_VERIFY: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - if (version === 1 && (flags & Script.flags.VERIFY_MINIMALIF)) { - const item = stack.get(-1); + if (!stack.getBool(-1)) + throw new ScriptError('VERIFY', op, ip); - if (item.length > 1) - throw new ScriptError('MINIMALIF'); + stack.pop(); - if (item.length === 1 && item[0] !== 1) - throw new ScriptError('MINIMALIF'); - } + break; + } + case opcodes.OP_RETURN: { + throw new ScriptError('OP_RETURN', op, ip); + } + case opcodes.OP_TOALTSTACK: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - val = stack.getBool(-1); + alt.push(stack.pop()); + break; + } + case opcodes.OP_FROMALTSTACK: { + if (alt.length === 0) + throw new ScriptError('INVALID_ALTSTACK_OPERATION', op, ip); - if (op.value === opcodes.OP_NOTIF) - val = !val; + stack.push(alt.pop()); + break; + } + case opcodes.OP_2DROP: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); stack.pop(); + stack.pop(); + break; } + case opcodes.OP_2DUP: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - state.push(val); + const v1 = stack.get(-2); + const v2 = stack.get(-1); - if (!val) - negate += 1; + stack.push(v1); + stack.push(v2); + break; + } + case opcodes.OP_3DUP: { + if (stack.length < 3) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - break; - } - case opcodes.OP_ELSE: { - if (state.length === 0) - throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); + const v1 = stack.get(-3); + const v2 = stack.get(-2); + const v3 = stack.get(-1); - state[state.length - 1] = !state[state.length - 1]; + stack.push(v1); + stack.push(v2); + stack.push(v3); + break; + } + case opcodes.OP_2OVER: { + if (stack.length < 4) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - if (!state[state.length - 1]) - negate += 1; - else - negate -= 1; + const v1 = stack.get(-4); + const v2 = stack.get(-3); - break; - } - case opcodes.OP_ENDIF: { - if (state.length === 0) - throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); + stack.push(v1); + stack.push(v2); + break; + } + case opcodes.OP_2ROT: { + if (stack.length < 6) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - if (!state.pop()) - negate -= 1; + const v1 = stack.get(-6); + const v2 = stack.get(-5); - break; - } - case opcodes.OP_VERIFY: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.erase(-6, -4); + stack.push(v1); + stack.push(v2); + break; + } + case opcodes.OP_2SWAP: { + if (stack.length < 4) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - if (!stack.getBool(-1)) - throw new ScriptError('VERIFY', op, ip); + stack.swap(-4, -2); + stack.swap(-3, -1); + break; + } + case opcodes.OP_IFDUP: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - stack.pop(); + if (stack.getBool(-1)) { + const val = stack.get(-1); + stack.push(val); + } - break; - } - case opcodes.OP_RETURN: { - throw new ScriptError('OP_RETURN', op, ip); - } - case opcodes.OP_TOALTSTACK: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - alt.push(stack.pop()); - break; - } - case opcodes.OP_FROMALTSTACK: { - if (alt.length === 0) - throw new ScriptError('INVALID_ALTSTACK_OPERATION', op, ip); - - stack.push(alt.pop()); - break; - } - case opcodes.OP_2DROP: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.pop(); - stack.pop(); - break; - } - case opcodes.OP_2DUP: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const v1 = stack.get(-2); - const v2 = stack.get(-1); + break; + } + case opcodes.OP_DEPTH: { + stack.pushInt(stack.length); + break; + } + case opcodes.OP_DROP: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - stack.push(v1); - stack.push(v2); - break; - } - case opcodes.OP_3DUP: { - if (stack.length < 3) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.pop(); + break; + } + case opcodes.OP_DUP: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - const v1 = stack.get(-3); - const v2 = stack.get(-2); - const v3 = stack.get(-1); + stack.push(stack.get(-1)); + break; + } + case opcodes.OP_NIP: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - stack.push(v1); - stack.push(v2); - stack.push(v3); - break; - } - case opcodes.OP_2OVER: { - if (stack.length < 4) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.remove(-2); + break; + } + case opcodes.OP_OVER: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - const v1 = stack.get(-4); - const v2 = stack.get(-3); + stack.push(stack.get(-2)); + break; + } + case opcodes.OP_PICK: + case opcodes.OP_ROLL: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - stack.push(v1); - stack.push(v2); - break; - } - case opcodes.OP_2ROT: { - if (stack.length < 6) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + const num = stack.getInt(-1, minimal, 4); + stack.pop(); - const v1 = stack.get(-6); - const v2 = stack.get(-5); + if (num < 0 || num >= stack.length) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - stack.erase(-6, -4); - stack.push(v1); - stack.push(v2); - break; - } - case opcodes.OP_2SWAP: { - if (stack.length < 4) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + const val = stack.get(-num - 1); - stack.swap(-4, -2); - stack.swap(-3, -1); - break; - } - case opcodes.OP_IFDUP: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + if (op.value === opcodes.OP_ROLL) + stack.remove(-num - 1); - if (stack.getBool(-1)) { - const val = stack.get(-1); stack.push(val); + break; } + case opcodes.OP_ROT: { + if (stack.length < 3) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - break; - } - case opcodes.OP_DEPTH: { - stack.pushInt(stack.length); - break; - } - case opcodes.OP_DROP: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.pop(); - break; - } - case opcodes.OP_DUP: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.swap(-3, -2); + stack.swap(-2, -1); + break; + } + case opcodes.OP_SWAP: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - stack.push(stack.get(-1)); - break; - } - case opcodes.OP_NIP: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.swap(-2, -1); + break; + } + case opcodes.OP_TUCK: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - stack.remove(-2); - break; - } - case opcodes.OP_OVER: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.insert(-2, stack.get(-1)); + break; + } + case opcodes.OP_SIZE: { + if (stack.length < 1) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - stack.push(stack.get(-2)); - break; - } - case opcodes.OP_PICK: - case opcodes.OP_ROLL: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.pushInt(stack.get(-1).length); + break; + } + case opcodes.OP_EQUAL: + case opcodes.OP_EQUALVERIFY: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - const num = stack.getInt(-1, minimal, 4); - stack.pop(); + const v1 = stack.get(-2); + const v2 = stack.get(-1); - if (num < 0 || num >= stack.length) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + const res = v1.equals(v2); - const val = stack.get(-num - 1); + stack.pop(); + stack.pop(); - if (op.value === opcodes.OP_ROLL) - stack.remove(-num - 1); + stack.pushBool(res); - stack.push(val); - break; - } - case opcodes.OP_ROT: { - if (stack.length < 3) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + if (op.value === opcodes.OP_EQUALVERIFY) { + if (!res) + throw new ScriptError('EQUALVERIFY', op, ip); + stack.pop(); + } - stack.swap(-3, -2); - stack.swap(-2, -1); - break; - } - case opcodes.OP_SWAP: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + break; + } + case opcodes.OP_1ADD: + case opcodes.OP_1SUB: + case opcodes.OP_NEGATE: + case opcodes.OP_ABS: + case opcodes.OP_NOT: + case opcodes.OP_0NOTEQUAL: { + if (stack.length < 1) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + let num = stack.getNum(-1, minimal, 4); + let cmp; + + switch (op.value) { + case opcodes.OP_1ADD: + num.iaddn(1); + break; + case opcodes.OP_1SUB: + num.isubn(1); + break; + case opcodes.OP_NEGATE: + num.ineg(); + break; + case opcodes.OP_ABS: + num.iabs(); + break; + case opcodes.OP_NOT: + cmp = num.isZero(); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_0NOTEQUAL: + cmp = !num.isZero(); + num = ScriptNum.fromBool(cmp); + break; + default: + assert(false, 'Fatal script error.'); + break; + } - stack.swap(-2, -1); - break; - } - case opcodes.OP_TUCK: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.pop(); + stack.pushNum(num); - stack.insert(-2, stack.get(-1)); - break; - } - case opcodes.OP_SIZE: { - if (stack.length < 1) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + break; + } + case opcodes.OP_ADD: + case opcodes.OP_SUB: + case opcodes.OP_BOOLAND: + case opcodes.OP_BOOLOR: + case opcodes.OP_NUMEQUAL: + case opcodes.OP_NUMEQUALVERIFY: + case opcodes.OP_NUMNOTEQUAL: + case opcodes.OP_LESSTHAN: + case opcodes.OP_GREATERTHAN: + case opcodes.OP_LESSTHANOREQUAL: + case opcodes.OP_GREATERTHANOREQUAL: + case opcodes.OP_MIN: + case opcodes.OP_MAX: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + const n1 = stack.getNum(-2, minimal, 4); + const n2 = stack.getNum(-1, minimal, 4); + let num, cmp; + + switch (op.value) { + case opcodes.OP_ADD: + num = n1.iadd(n2); + break; + case opcodes.OP_SUB: + num = n1.isub(n2); + break; + case opcodes.OP_BOOLAND: + cmp = n1.toBool() && n2.toBool(); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_BOOLOR: + cmp = n1.toBool() || n2.toBool(); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_NUMEQUAL: + cmp = n1.eq(n2); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_NUMEQUALVERIFY: + cmp = n1.eq(n2); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_NUMNOTEQUAL: + cmp = !n1.eq(n2); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_LESSTHAN: + cmp = n1.lt(n2); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_GREATERTHAN: + cmp = n1.gt(n2); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_LESSTHANOREQUAL: + cmp = n1.lte(n2); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_GREATERTHANOREQUAL: + cmp = n1.gte(n2); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_MIN: + num = ScriptNum.min(n1, n2); + break; + case opcodes.OP_MAX: + num = ScriptNum.max(n1, n2); + break; + default: + assert(false, 'Fatal script error.'); + break; + } - stack.pushInt(stack.get(-1).length); - break; - } - case opcodes.OP_EQUAL: - case opcodes.OP_EQUALVERIFY: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.pop(); + stack.pop(); + stack.pushNum(num); - const v1 = stack.get(-2); - const v2 = stack.get(-1); + if (op.value === opcodes.OP_NUMEQUALVERIFY) { + if (!stack.getBool(-1)) + throw new ScriptError('NUMEQUALVERIFY', op, ip); + stack.pop(); + } - const res = v1.equals(v2); + break; + } + case opcodes.OP_WITHIN: { + if (stack.length < 3) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - stack.pop(); - stack.pop(); + const n1 = stack.getNum(-3, minimal, 4); + const n2 = stack.getNum(-2, minimal, 4); + const n3 = stack.getNum(-1, minimal, 4); - stack.pushBool(res); + const val = n2.lte(n1) && n1.lt(n3); - if (op.value === opcodes.OP_EQUALVERIFY) { - if (!res) - throw new ScriptError('EQUALVERIFY', op, ip); stack.pop(); - } + stack.pop(); + stack.pop(); - break; - } - case opcodes.OP_1ADD: - case opcodes.OP_1SUB: - case opcodes.OP_NEGATE: - case opcodes.OP_ABS: - case opcodes.OP_NOT: - case opcodes.OP_0NOTEQUAL: { - if (stack.length < 1) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - let num = stack.getNum(-1, minimal, 4); - let cmp; - - switch (op.value) { - case opcodes.OP_1ADD: - num.iaddn(1); - break; - case opcodes.OP_1SUB: - num.isubn(1); - break; - case opcodes.OP_NEGATE: - num.ineg(); - break; - case opcodes.OP_ABS: - num.iabs(); - break; - case opcodes.OP_NOT: - cmp = num.isZero(); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_0NOTEQUAL: - cmp = !num.isZero(); - num = ScriptNum.fromBool(cmp); - break; - default: - assert(false, 'Fatal script error.'); - break; + stack.pushBool(val); + break; } + case opcodes.OP_RIPEMD160: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - stack.pop(); - stack.pushNum(num); - - break; - } - case opcodes.OP_ADD: - case opcodes.OP_SUB: - case opcodes.OP_BOOLAND: - case opcodes.OP_BOOLOR: - case opcodes.OP_NUMEQUAL: - case opcodes.OP_NUMEQUALVERIFY: - case opcodes.OP_NUMNOTEQUAL: - case opcodes.OP_LESSTHAN: - case opcodes.OP_GREATERTHAN: - case opcodes.OP_LESSTHANOREQUAL: - case opcodes.OP_GREATERTHANOREQUAL: - case opcodes.OP_MIN: - case opcodes.OP_MAX: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const n1 = stack.getNum(-2, minimal, 4); - const n2 = stack.getNum(-1, minimal, 4); - let num, cmp; - - switch (op.value) { - case opcodes.OP_ADD: - num = n1.iadd(n2); - break; - case opcodes.OP_SUB: - num = n1.isub(n2); - break; - case opcodes.OP_BOOLAND: - cmp = n1.toBool() && n2.toBool(); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_BOOLOR: - cmp = n1.toBool() || n2.toBool(); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_NUMEQUAL: - cmp = n1.eq(n2); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_NUMEQUALVERIFY: - cmp = n1.eq(n2); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_NUMNOTEQUAL: - cmp = !n1.eq(n2); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_LESSTHAN: - cmp = n1.lt(n2); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_GREATERTHAN: - cmp = n1.gt(n2); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_LESSTHANOREQUAL: - cmp = n1.lte(n2); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_GREATERTHANOREQUAL: - cmp = n1.gte(n2); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_MIN: - num = ScriptNum.min(n1, n2); - break; - case opcodes.OP_MAX: - num = ScriptNum.max(n1, n2); - break; - default: - assert(false, 'Fatal script error.'); - break; + stack.push(ripemd160.digest(stack.pop())); + break; } + case opcodes.OP_SHA1: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - stack.pop(); - stack.pop(); - stack.pushNum(num); - - if (op.value === opcodes.OP_NUMEQUALVERIFY) { - if (!stack.getBool(-1)) - throw new ScriptError('NUMEQUALVERIFY', op, ip); - stack.pop(); + stack.push(sha1.digest(stack.pop())); + break; } + case opcodes.OP_SHA256: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - break; - } - case opcodes.OP_WITHIN: { - if (stack.length < 3) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const n1 = stack.getNum(-3, minimal, 4); - const n2 = stack.getNum(-2, minimal, 4); - const n3 = stack.getNum(-1, minimal, 4); - - const val = n2.lte(n1) && n1.lt(n3); - - stack.pop(); - stack.pop(); - stack.pop(); - - stack.pushBool(val); - break; - } - case opcodes.OP_RIPEMD160: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.push(digest.ripemd160(stack.pop())); - break; - } - case opcodes.OP_SHA1: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.push(digest.sha1(stack.pop())); - break; - } - case opcodes.OP_SHA256: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.push(digest.sha256(stack.pop())); - break; - } - case opcodes.OP_HASH160: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.push(digest.hash160(stack.pop())); - break; - } - case opcodes.OP_HASH256: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.push(sha256.digest(stack.pop())); + break; + } + case opcodes.OP_HASH160: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - stack.push(digest.hash256(stack.pop())); - break; - } - case opcodes.OP_CODESEPARATOR: { - lastSep = ip + 1; - break; - } - case opcodes.OP_CHECKSIG: - case opcodes.OP_CHECKSIGVERIFY: { - if (!tx) - throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); + stack.push(hash160.digest(stack.pop())); + break; + } + case opcodes.OP_HASH256: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.push(hash256.digest(stack.pop())); + break; + } + case opcodes.OP_CODESEPARATOR: { + lastSep = ip + 1; + break; + } + case opcodes.OP_CHECKSIG: + case opcodes.OP_CHECKSIGVERIFY: { + if (!tx) + throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); - const sig = stack.get(-2); - const key = stack.get(-1); + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - const subscript = this.getSubscript(lastSep); + const sig = stack.get(-2); + const key = stack.get(-1); - if (version === 0) - subscript.findAndDelete(sig); + const subscript = this.getSubscript(lastSep); - validateSignature(sig, flags); - validateKey(key, flags, version); + if (version === 0) { + const found = subscript.findAndDelete(sig); + if (found > 0 && (flags & Script.flags.VERIFY_CONST_SCRIPTCODE)) + throw new ScriptError('SIG_FINDANDDELETE', op, ip); + } - let res = false; + validateSignature(sig, flags); + validateKey(key, flags, version); - if (sig.length > 0) { - let type = sig[sig.length - 1]; - let hash; + let res = false; - try { - hash = tx.signatureHash(index, subscript, value, type, version); - res = checksig(hash, sig, key); - } catch (e) { - // Having issues with signatures? Log this error then! - } + if (sig.length > 0) { + let type = sig[sig.length - 1]; + let hash; - if (!res){ - type ^= Script.hashType.OMIT_TX_COMMENT; try { - hash = tx.signatureHash(index, subscript, value, type, version); + hash = tx.signatureHash(index, subscript, value, type, version, true); res = checksig(hash, sig, key); } catch (e) { // Having issues with signatures? Log this error then! - throw new ScriptError('CHECKSIGVERIFY_ERROR', e) + } + + if (!res){ + try { + hash = tx.signatureHash(index, subscript, value, type, version, false); + res = checksig(hash, sig, key); + } catch (e) { + throw new ScriptError('CHECKSIGVERIFY_ERROR', e) + } } } - } - if (!res && (flags & Script.flags.VERIFY_NULLFAIL)) { - if (sig.length !== 0) - throw new ScriptError('NULLFAIL', op, ip); - } + if (!res && (flags & Script.flags.VERIFY_NULLFAIL)) { + if (sig.length !== 0) + throw new ScriptError('NULLFAIL', op, ip); + } - stack.pop(); - stack.pop(); + stack.pop(); + stack.pop(); - stack.pushBool(res); + stack.pushBool(res); - if (op.value === opcodes.OP_CHECKSIGVERIFY) { - if (!res) - throw new ScriptError('CHECKSIGVERIFY', op, ip); - stack.pop(); + if (op.value === opcodes.OP_CHECKSIGVERIFY) { + if (!res) + throw new ScriptError('CHECKSIGVERIFY', op, ip); + stack.pop(); + } + + break; } + case opcodes.OP_CHECKMULTISIG: + case opcodes.OP_CHECKMULTISIGVERIFY: { + if (!tx) + throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); - break; - } - case opcodes.OP_CHECKMULTISIG: - case opcodes.OP_CHECKMULTISIGVERIFY: { - if (!tx) - throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); + let i = 1; + if (stack.length < i) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - let i = 1; - if (stack.length < i) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + let n = stack.getInt(-i, minimal, 4); + let okey = n + 2; + let ikey, isig; - let n = stack.getInt(-i, minimal, 4); - let okey = n + 2; - let ikey, isig; + if (n < 0 || n > consensus.MAX_MULTISIG_PUBKEYS) + throw new ScriptError('PUBKEY_COUNT', op, ip); - if (n < 0 || n > consensus.MAX_MULTISIG_PUBKEYS) - throw new ScriptError('PUBKEY_COUNT', op, ip); + opCount += n; - opCount += n; + if (opCount > consensus.MAX_SCRIPT_OPS) + throw new ScriptError('OP_COUNT', op, ip); - if (opCount > consensus.MAX_SCRIPT_OPS) - throw new ScriptError('OP_COUNT', op, ip); + i += 1; + ikey = i; + i += n; - i += 1; - ikey = i; - i += n; + if (stack.length < i) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - if (stack.length < i) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + let m = stack.getInt(-i, minimal, 4); - let m = stack.getInt(-i, minimal, 4); + if (m < 0 || m > n) + throw new ScriptError('SIG_COUNT', op, ip); - if (m < 0 || m > n) - throw new ScriptError('SIG_COUNT', op, ip); + i += 1; + isig = i; + i += m; - i += 1; - isig = i; - i += m; + if (stack.length < i) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - if (stack.length < i) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + const subscript = this.getSubscript(lastSep); - const subscript = this.getSubscript(lastSep); + for (let j = 0; j < m; j++) { + const sig = stack.get(-isig - j); + if (version === 0) { + const found = subscript.findAndDelete(sig); + if (found > 0 && (flags & Script.flags.VERIFY_CONST_SCRIPTCODE)) + throw new ScriptError('SIG_FINDANDDELETE', op, ip); + } + } - for (let j = 0; j < m; j++) { - const sig = stack.get(-isig - j); - if (version === 0) - subscript.findAndDelete(sig); - } + let res = true; + while (res && m > 0) { + const sig = stack.get(-isig); + const key = stack.get(-ikey); + + validateSignature(sig, flags); + validateKey(key, flags, version); + + if (sig.length > 0) { + const type = sig[sig.length - 1]; + const hash = tx.signatureHash( + index, + subscript, + value, + type, + version + ); + + if (checksig(hash, sig, key)) { + isig += 1; + m -= 1; + } + } - let res = true; - while (res && m > 0) { - const sig = stack.get(-isig); - const key = stack.get(-ikey); + ikey += 1; + n -= 1; - validateSignature(sig, flags); - validateKey(key, flags, version); + if (m > n) + res = false; + } - if (sig.length > 0) { - const type = sig[sig.length - 1]; - const hash = tx.signatureHash( - index, - subscript, - value, - type, - version - ); - - if (checksig(hash, sig, key)) { - isig += 1; - m -= 1; + while (i > 1) { + if (!res && (flags & Script.flags.VERIFY_NULLFAIL)) { + if (okey === 0 && stack.get(-1).length !== 0) + throw new ScriptError('NULLFAIL', op, ip); } - } - ikey += 1; - n -= 1; + if (okey > 0) + okey -= 1; - if (m > n) - res = false; - } + stack.pop(); - while (i > 1) { - if (!res && (flags & Script.flags.VERIFY_NULLFAIL)) { - if (okey === 0 && stack.get(-1).length !== 0) - throw new ScriptError('NULLFAIL', op, ip); + i -= 1; } - if (okey > 0) - okey -= 1; + if (stack.length < 1) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + if (flags & Script.flags.VERIFY_NULLDUMMY) { + if (stack.get(-1).length !== 0) + throw new ScriptError('SIG_NULLDUMMY', op, ip); + } stack.pop(); - i -= 1; - } + stack.pushBool(res); - if (stack.length < 1) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + if (op.value === opcodes.OP_CHECKMULTISIGVERIFY) { + if (!res) + throw new ScriptError('CHECKMULTISIGVERIFY', op, ip); + stack.pop(); + } - if (flags & Script.flags.VERIFY_NULLDUMMY) { - if (stack.get(-1).length !== 0) - throw new ScriptError('SIG_NULLDUMMY', op, ip); + break; + } + default: { + throw new ScriptError('BAD_OPCODE', op, ip); } + } - stack.pop(); + if (stack.length + alt.length > consensus.MAX_SCRIPT_STACK) + throw new ScriptError('STACK_SIZE', op, ip); + } - stack.pushBool(res); + if (state.length !== 0) + throw new ScriptError('UNBALANCED_CONDITIONAL'); + } - if (op.value === opcodes.OP_CHECKMULTISIGVERIFY) { - if (!res) - throw new ScriptError('CHECKMULTISIGVERIFY', op, ip); - stack.pop(); - } + /** + * Remove all matched data elements from + * a script's code (used to remove signatures + * before verification). Note that this + * compares and removes data on the _byte level_. + * It also reserializes the data to a single + * script with minimaldata encoding beforehand. + * A signature will _not_ be removed if it is + * not minimaldata. + * @see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2014-November/006878.html + * @see https://test.webbtc.com/tx/19aa42fee0fa57c45d3b16488198b27caaacc4ff5794510d0c17f173f05587ff + * @param {Buffer} data - Data element to match against. + * @returns {Number} Total. + */ + + findAndDelete(data) { + const target = Opcode.fromPush(data); + + if (this.raw.length < target.getSize()) + return 0; + + let found = false; + + for (const op of this.code) { + if (op.value === -1) + break; + if (op.equals(target)) { + found = true; break; } - default: { - throw new ScriptError('BAD_OPCODE', op, ip); - } } - if (stack.length + alt.length > consensus.MAX_SCRIPT_STACK) - throw new ScriptError('STACK_SIZE', op, ip); - } - - if (state.length !== 0) - throw new ScriptError('UNBALANCED_CONDITIONAL'); -}; - -/** - * Remove all matched data elements from - * a script's code (used to remove signatures - * before verification). Note that this - * compares and removes data on the _byte level_. - * It also reserializes the data to a single - * script with minimaldata encoding beforehand. - * A signature will _not_ be removed if it is - * not minimaldata. - * @see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2014-November/006878.html - * @see https://test.webbtc.com/tx/19aa42fee0fa57c45d3b16488198b27caaacc4ff5794510d0c17f173f05587ff - * @param {Buffer} data - Data element to match against. - * @returns {Number} Total. - */ + if (!found) + return 0; -Script.prototype.findAndDelete = function findAndDelete(data) { - const target = Opcode.fromPush(data); + const code = []; - if (this.raw.length < target.getSize()) - return 0; + let total = 0; - let found = false; + for (const op of this.code) { + if (op.value === -1) + break; - for (const op of this.code) { - if (op.value === -1) - break; + if (op.equals(target)) { + total += 1; + continue; + } - if (op.equals(target)) { - found = true; - break; + code.push(op); } - } - if (!found) - return 0; - - const code = []; - - let total = 0; - - for (const op of this.code) { - if (op.value === -1) - break; - - if (op.equals(target)) { - total += 1; - continue; - } + this.code = code; + this.compile(); - code.push(op); + return total; } - this.code = code; - this.compile(); + /** + * Find a data element in a script. + * @param {Buffer} data - Data element to match against. + * @returns {Number} Index (`-1` if not present). + */ - return total; -}; + indexOf(data) { + for (let i = 0; i < this.code.length; i++) { + const op = this.code[i]; -/** - * Find a data element in a script. - * @param {Buffer} data - Data element to match against. - * @returns {Number} Index (`-1` if not present). - */ - -Script.prototype.indexOf = function indexOf(data) { - for (let i = 0; i < this.code.length; i++) { - const op = this.code[i]; + if (op.value === -1) + break; - if (op.value === -1) - break; + if (!op.data) + continue; - if (!op.data) - continue; + if (op.data.equals(data)) + return i; + } - if (op.data.equals(data)) - return i; + return -1; } - return -1; -}; + /** + * Test a script to see if it is likely + * to be script code (no weird opcodes). + * @returns {Boolean} + */ -/** - * Test a script to see if it is likely - * to be script code (no weird opcodes). - * @returns {Boolean} - */ + isCode() { + for (const op of this.code) { + if (op.value === -1) + return false; -Script.prototype.isCode = function isCode() { - for (const op of this.code) { - if (op.value === -1) - return false; + if (op.isDisabled()) + return false; - if (op.isDisabled()) - return false; + switch (op.value) { + case opcodes.OP_RESERVED: + case opcodes.OP_NOP: + case opcodes.OP_VER: + case opcodes.OP_VERIF: + case opcodes.OP_VERNOTIF: + case opcodes.OP_RESERVED1: + case opcodes.OP_RESERVED2: + case opcodes.OP_NOP1: + return false; + } - switch (op.value) { - case opcodes.OP_RESERVED: - case opcodes.OP_NOP: - case opcodes.OP_VER: - case opcodes.OP_VERIF: - case opcodes.OP_VERNOTIF: - case opcodes.OP_RESERVED1: - case opcodes.OP_RESERVED2: - case opcodes.OP_NOP1: + if (op.value > opcodes.OP_CHECKSEQUENCEVERIFY) return false; } - if (op.value > opcodes.OP_CHECKSEQUENCEVERIFY) - return false; + return true; } - return true; -}; - -/** - * Inject properties from a pay-to-pubkey script. - * @private - * @param {Buffer} key - */ - -Script.prototype.fromPubkey = function fromPubkey(key) { - assert(Buffer.isBuffer(key) && (key.length === 33 || key.length === 65)); + /** + * Inject properties from a pay-to-pubkey script. + * @private + * @param {Buffer} key + */ - this.raw = Buffer.allocUnsafe(1 + key.length + 1); - this.raw[0] = key.length; - key.copy(this.raw, 1); - this.raw[1 + key.length] = opcodes.OP_CHECKSIG; + fromPubkey(key) { + assert(Buffer.isBuffer(key) && (key.length === 33 || key.length === 65)); - key = this.raw.slice(1, 1 + key.length); + this.raw = Buffer.allocUnsafe(1 + key.length + 1); + this.raw[0] = key.length; + key.copy(this.raw, 1); + this.raw[1 + key.length] = opcodes.OP_CHECKSIG; - this.code.length = 0; - this.code.push(Opcode.fromPush(key)); - this.code.push(Opcode.fromOp(opcodes.OP_CHECKSIG)); + key = this.raw.slice(1, 1 + key.length); - return this; -}; + this.code.length = 0; + this.code.push(Opcode.fromPush(key)); + this.code.push(Opcode.fromOp(opcodes.OP_CHECKSIG)); -/** - * Create a pay-to-pubkey script. - * @param {Buffer} key - * @returns {Script} - */ - -Script.fromPubkey = function fromPubkey(key) { - return new Script().fromPubkey(key); -}; - -/** - * Inject properties from a pay-to-pubkeyhash script. - * @private - * @param {Buffer} hash - */ - -Script.prototype.fromPubkeyhash = function fromPubkeyhash(hash) { - assert(Buffer.isBuffer(hash) && hash.length === 20); - - this.raw = Buffer.allocUnsafe(25); - this.raw[0] = opcodes.OP_DUP; - this.raw[1] = opcodes.OP_HASH160; - this.raw[2] = 0x14; - hash.copy(this.raw, 3); - this.raw[23] = opcodes.OP_EQUALVERIFY; - this.raw[24] = opcodes.OP_CHECKSIG; - - hash = this.raw.slice(3, 23); - - this.code.length = 0; - this.code.push(Opcode.fromOp(opcodes.OP_DUP)); - this.code.push(Opcode.fromOp(opcodes.OP_HASH160)); - this.code.push(Opcode.fromPush(hash)); - this.code.push(Opcode.fromOp(opcodes.OP_EQUALVERIFY)); - this.code.push(Opcode.fromOp(opcodes.OP_CHECKSIG)); - - return this; -}; - -/** - * Create a pay-to-pubkeyhash script. - * @param {Buffer} hash - * @returns {Script} - */ - -Script.fromPubkeyhash = function fromPubkeyhash(hash) { - return new Script().fromPubkeyhash(hash); -}; - -/** - * Inject properties from pay-to-multisig script. - * @private - * @param {Number} m - * @param {Number} n - * @param {Buffer[]} keys - */ - -Script.prototype.fromMultisig = function fromMultisig(m, n, keys) { - assert(util.isU8(m) && util.isU8(n)); - assert(Array.isArray(keys)); - assert(keys.length === n, '`n` keys are required for multisig.'); - assert(m >= 1 && m <= n); - assert(n >= 1 && n <= 15); - - this.clear(); - - this.pushSmall(m); - - for (const key of sortKeys(keys)) - this.pushData(key); - - this.pushSmall(n); - this.pushOp(opcodes.OP_CHECKMULTISIG); - - return this.compile(); -}; - -/** - * Create a pay-to-multisig script. - * @param {Number} m - * @param {Number} n - * @param {Buffer[]} keys - * @returns {Script} - */ - -Script.fromMultisig = function fromMultisig(m, n, keys) { - return new Script().fromMultisig(m, n, keys); -}; - -/** - * Inject properties from a pay-to-scripthash script. - * @private - * @param {Buffer} hash - */ - -Script.prototype.fromScripthash = function fromScripthash(hash) { - assert(Buffer.isBuffer(hash) && hash.length === 20); - - this.raw = Buffer.allocUnsafe(23); - this.raw[0] = opcodes.OP_HASH160; - this.raw[1] = 0x14; - hash.copy(this.raw, 2); - this.raw[22] = opcodes.OP_EQUAL; - - hash = this.raw.slice(2, 22); - - this.code.length = 0; - this.code.push(Opcode.fromOp(opcodes.OP_HASH160)); - this.code.push(Opcode.fromPush(hash)); - this.code.push(Opcode.fromOp(opcodes.OP_EQUAL)); + return this; + } - return this; -}; + /** + * Create a pay-to-pubkey script. + * @param {Buffer} key + * @returns {Script} + */ -/** - * Create a pay-to-scripthash script. - * @param {Buffer} hash - * @returns {Script} - */ + static fromPubkey(key) { + return new this().fromPubkey(key); + } -Script.fromScripthash = function fromScripthash(hash) { - return new Script().fromScripthash(hash); -}; + /** + * Inject properties from a pay-to-pubkeyhash script. + * @private + * @param {Buffer} hash + */ -/** - * Inject properties from a nulldata/opreturn script. - * @private - * @param {Buffer} flags - */ + fromPubkeyhash(hash) { + assert(Buffer.isBuffer(hash) && hash.length === 20); -Script.prototype.fromNulldata = function fromNulldata(flags) { - assert(Buffer.isBuffer(flags)); - assert(flags.length <= policy.MAX_OP_RETURN, 'Nulldata too large.'); + this.raw = Buffer.allocUnsafe(25); + this.raw[0] = opcodes.OP_DUP; + this.raw[1] = opcodes.OP_HASH160; + this.raw[2] = 0x14; + hash.copy(this.raw, 3); + this.raw[23] = opcodes.OP_EQUALVERIFY; + this.raw[24] = opcodes.OP_CHECKSIG; - this.clear(); - this.pushOp(opcodes.OP_RETURN); - this.pushData(flags); + hash = this.raw.slice(3, 23); - return this.compile(); -}; + this.code.length = 0; + this.code.push(Opcode.fromOp(opcodes.OP_DUP)); + this.code.push(Opcode.fromOp(opcodes.OP_HASH160)); + this.code.push(Opcode.fromPush(hash)); + this.code.push(Opcode.fromOp(opcodes.OP_EQUALVERIFY)); + this.code.push(Opcode.fromOp(opcodes.OP_CHECKSIG)); -/** - * Create a nulldata/opreturn script. - * @param {Buffer} flags - * @returns {Script} - */ - -Script.fromNulldata = function fromNulldata(flags) { - return new Script().fromNulldata(flags); -}; + return this; + } -/** - * Inject properties from a witness program. - * @private - * @param {Number} version - * @param {Buffer} data - */ + /** + * Create a pay-to-pubkeyhash script. + * @param {Buffer} hash + * @returns {Script} + */ -Script.prototype.fromProgram = function fromProgram(version, data) { - assert(util.isU8(version) && version >= 0 && version <= 16); - assert(Buffer.isBuffer(data) && data.length >= 2 && data.length <= 40); + static fromPubkeyhash(hash) { + return new this().fromPubkeyhash(hash); + } - this.raw = Buffer.allocUnsafe(2 + data.length); - this.raw[0] = version === 0 ? 0 : version + 0x50; - this.raw[1] = data.length; - data.copy(this.raw, 2); + /** + * Inject properties from pay-to-multisig script. + * @private + * @param {Number} m + * @param {Number} n + * @param {Buffer[]} keys + */ - data = this.raw.slice(2, 2 + data.length); + fromMultisig(m, n, keys) { + assert((m & 0xff) === m && (n & 0xff) === n); + assert(Array.isArray(keys)); + assert(keys.length === n, '`n` keys are required for multisig.'); + assert(m >= 1 && m <= n); + assert(n >= 1 && n <= 15); - this.code.length = 0; - this.code.push(Opcode.fromSmall(version)); - this.code.push(Opcode.fromPush(data)); + this.clear(); - return this; -}; + this.pushSmall(m); -/** - * Create a witness program. - * @param {Number} version - * @param {Buffer} data - * @returns {Script} - */ + for (const key of sortKeys(keys)) + this.pushData(key); -Script.fromProgram = function fromProgram(version, data) { - return new Script().fromProgram(version, data); -}; + this.pushSmall(n); + this.pushOp(opcodes.OP_CHECKMULTISIG); -/** - * Inject properties from an address. - * @private - * @param {Address|Base58Address} address - */ + return this.compile(); + } -Script.prototype.fromAddress = function fromAddress(address) { - if (typeof address === 'string') - address = Address.fromString(address); + /** + * Create a pay-to-multisig script. + * @param {Number} m + * @param {Number} n + * @param {Buffer[]} keys + * @returns {Script} + */ - assert(address instanceof Address, 'Not an address.'); + static fromMultisig(m, n, keys) { + return new this().fromMultisig(m, n, keys); + } - if (address.isPubkeyhash()) - return this.fromPubkeyhash(address.hash); + /** + * Inject properties from a pay-to-scripthash script. + * @private + * @param {Buffer} hash + */ - if (address.isScripthash()) - return this.fromScripthash(address.hash); + fromScripthash(hash) { + assert(Buffer.isBuffer(hash) && hash.length === 20); - if (address.isProgram()) - return this.fromProgram(address.version, address.hash); + this.raw = Buffer.allocUnsafe(23); + this.raw[0] = opcodes.OP_HASH160; + this.raw[1] = 0x14; + hash.copy(this.raw, 2); + this.raw[22] = opcodes.OP_EQUAL; - throw new Error('Unknown address type.'); -}; + hash = this.raw.slice(2, 22); -/** - * Create an output script from an address. - * @param {Address|Base58Address} address - * @returns {Script} - */ + this.code.length = 0; + this.code.push(Opcode.fromOp(opcodes.OP_HASH160)); + this.code.push(Opcode.fromPush(hash)); + this.code.push(Opcode.fromOp(opcodes.OP_EQUAL)); -Script.fromAddress = function fromAddress(address) { - return new Script().fromAddress(address); -}; + return this; + } -/** - * Inject properties from a witness block commitment. - * @private - * @param {Buffer} hash - * @param {String|Buffer} flags - */ + /** + * Create a pay-to-scripthash script. + * @param {Buffer} hash + * @returns {Script} + */ -Script.prototype.fromCommitment = function fromCommitment(hash, flags) { - const bw = new StaticWriter(36); + static fromScripthash(hash) { + return new this().fromScripthash(hash); + } - bw.writeU32BE(0xaa21a9ed); - bw.writeHash(hash); + /** + * Inject properties from a nulldata/opreturn script. + * @private + * @param {Buffer} flags + */ - this.clear(); - this.pushOp(opcodes.OP_RETURN); - this.pushData(bw.render()); + fromNulldata(flags) { + assert(Buffer.isBuffer(flags)); + assert(flags.length <= policy.MAX_OP_RETURN, 'Nulldata too large.'); - if (flags) + this.clear(); + this.pushOp(opcodes.OP_RETURN); this.pushData(flags); - return this.compile(); -}; + return this.compile(); + } -/** - * Create a witness block commitment. - * @param {Buffer} hash - * @param {String|Buffer} flags - * @returns {Script} - */ + /** + * Create a nulldata/opreturn script. + * @param {Buffer} flags + * @returns {Script} + */ -Script.fromCommitment = function fromCommitment(hash, flags) { - return new Script().fromCommitment(hash, flags); -}; + static fromNulldata(flags) { + return new this().fromNulldata(flags); + } -/** - * Grab and deserialize the redeem script. - * @returns {Script|null} Redeem script. - */ + /** + * Inject properties from a witness program. + * @private + * @param {Number} version + * @param {Buffer} data + */ -Script.prototype.getRedeem = function getRedeem() { - let data = null; + fromProgram(version, data) { + assert((version & 0xff) === version && version >= 0 && version <= 16); + assert(Buffer.isBuffer(data) && data.length >= 2 && data.length <= 40); - for (const op of this.code) { - if (op.value === -1) - return null; + this.raw = Buffer.allocUnsafe(2 + data.length); + this.raw[0] = version === 0 ? 0 : version + 0x50; + this.raw[1] = data.length; + data.copy(this.raw, 2); - if (op.value > opcodes.OP_16) - return null; + data = this.raw.slice(2, 2 + data.length); + + this.code.length = 0; + this.code.push(Opcode.fromSmall(version)); + this.code.push(Opcode.fromPush(data)); - data = op.data; + return this; } - if (!data) - return null; + /** + * Create a witness program. + * @param {Number} version + * @param {Buffer} data + * @returns {Script} + */ - return Script.fromRaw(data); -}; + static fromProgram(version, data) { + return new this().fromProgram(version, data); + } -/** - * Get the standard script type. - * @returns {ScriptType} - */ + /** + * Inject properties from an address. + * @private + * @param {Address|AddressString} address + */ -Script.prototype.getType = function getType() { - if (this.isPubkey()) - return scriptTypes.PUBKEY; + fromAddress(address) { + if (typeof address === 'string') + address = Address.fromString(address); - if (this.isPubkeyhash()) - return scriptTypes.PUBKEYHASH; + assert(address instanceof Address, 'Not an address.'); - if (this.isScripthash()) - return scriptTypes.SCRIPTHASH; + if (address.isPubkeyhash()) + return this.fromPubkeyhash(address.hash); - if (this.isWitnessPubkeyhash()) - return scriptTypes.WITNESSPUBKEYHASH; + if (address.isScripthash()) + return this.fromScripthash(address.hash); - if (this.isWitnessScripthash()) - return scriptTypes.WITNESSSCRIPTHASH; + if (address.isProgram()) + return this.fromProgram(address.version, address.hash); - if (this.isWitnessMasthash()) - return scriptTypes.WITNESSMASTHASH; + throw new Error('Unknown address type.'); + } - if (this.isMultisig()) - return scriptTypes.MULTISIG; + /** + * Create an output script from an address. + * @param {Address|AddressString} address + * @returns {Script} + */ - if (this.isNulldata()) - return scriptTypes.NULLDATA; + static fromAddress(address) { + return new this().fromAddress(address); + } - return scriptTypes.NONSTANDARD; -}; + /** + * Inject properties from a witness block commitment. + * @private + * @param {Buffer} hash + * @param {String|Buffer} flags + */ -/** - * Test whether a script is of an unknown/non-standard type. - * @returns {Boolean} - */ + fromCommitment(hash, flags) { + const bw = bio.write(36); -Script.prototype.isUnknown = function isUnknown() { - return this.getType() === scriptTypes.NONSTANDARD; -}; + bw.writeU32BE(0xaa21a9ed); + bw.writeHash(hash); -/** - * Test whether the script is standard by policy standards. - * @returns {Boolean} - */ + this.clear(); + this.pushOp(opcodes.OP_RETURN); + this.pushData(bw.render()); -Script.prototype.isStandard = function isStandard() { - const [m, n] = this.getMultisig(); + if (flags) + this.pushData(flags); - if (m !== -1) { - if (n < 1 || n > 3) - return false; + return this.compile(); + } - if (m < 1 || m > n) - return false; + /** + * Create a witness block commitment. + * @param {Buffer} hash + * @param {String|Buffer} flags + * @returns {Script} + */ - return true; + static fromCommitment(hash, flags) { + return new this().fromCommitment(hash, flags); } - if (this.isNulldata()) - return this.raw.length <= policy.MAX_OP_RETURN_BYTES; + /** + * Grab and deserialize the redeem script. + * @returns {Script|null} Redeem script. + */ - return this.getType() !== scriptTypes.NONSTANDARD; -}; + getRedeem() { + let data = null; -/** - * Calculate the size of the script - * excluding the varint size bytes. - * @returns {Number} - */ + for (const op of this.code) { + if (op.value === -1) + return null; -Script.prototype.getSize = function getSize() { - return this.raw.length; -}; + if (op.value > opcodes.OP_16) + return null; -/** - * Calculate the size of the script - * including the varint size bytes. - * @returns {Number} - */ + data = op.data; + } -Script.prototype.getVarSize = function getVarSize() { - return encoding.sizeVarBytes(this.raw); -}; + if (!data) + return null; -/** - * "Guess" the address of the input script. - * This method is not 100% reliable. - * @returns {Address|null} - */ + return Script.fromRaw(data); + } -Script.prototype.getInputAddress = function getInputAddress() { - return Address.fromInputScript(this); -}; + /** + * Get the standard script type. + * @returns {ScriptType} + */ -/** - * Get the address of the script if present. Note that - * pubkey and multisig scripts will be treated as though - * they are pubkeyhash and scripthashes respectively. - * @returns {Address|null} - */ + getType() { + if (this.isPubkey()) + return scriptTypes.PUBKEY; -Script.prototype.getAddress = function getAddress() { - return Address.fromScript(this); -}; + if (this.isPubkeyhash()) + return scriptTypes.PUBKEYHASH; -/** - * Get the hash160 of the raw script. - * @param {String?} enc - * @returns {Hash} - */ + if (this.isScripthash()) + return scriptTypes.SCRIPTHASH; -Script.prototype.hash160 = function hash160(enc) { - let hash = digest.hash160(this.toRaw()); - if (enc === 'hex') - hash = hash.toString('hex'); - return hash; -}; + if (this.isWitnessPubkeyhash()) + return scriptTypes.WITNESSPUBKEYHASH; -/** - * Get the sha256 of the raw script. - * @param {String?} enc - * @returns {Hash} - */ + if (this.isWitnessScripthash()) + return scriptTypes.WITNESSSCRIPTHASH; -Script.prototype.sha256 = function sha256(enc) { - let hash = digest.sha256(this.toRaw()); - if (enc === 'hex') - hash = hash.toString('hex'); - return hash; -}; + if (this.isMultisig()) + return scriptTypes.MULTISIG; -/** - * Test whether the output script is pay-to-pubkey. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Boolean} - */ + if (this.isNulldata()) + return scriptTypes.NULLDATA; -Script.prototype.isPubkey = function isPubkey(minimal) { - if (minimal) { - return this.raw.length >= 35 - && (this.raw[0] === 33 || this.raw[0] === 65) - && this.raw[0] + 2 === this.raw.length - && this.raw[this.raw.length - 1] === opcodes.OP_CHECKSIG; + return scriptTypes.NONSTANDARD; } - if (this.code.length !== 2) - return false; + /** + * Test whether a script is of an unknown/non-standard type. + * @returns {Boolean} + */ - const size = this.getLength(0); + isUnknown() { + return this.getType() === scriptTypes.NONSTANDARD; + } - return (size === 33 || size === 65) - && this.getOp(1) === opcodes.OP_CHECKSIG; -}; + /** + * Test whether the script is standard by policy standards. + * @returns {Boolean} + */ -/** - * Get P2PK key if present. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Buffer|null} - */ + isStandard() { + const [m, n] = this.getMultisig(); -Script.prototype.getPubkey = function getPubkey(minimal) { - if (!this.isPubkey(minimal)) - return null; + if (m !== -1) { + if (n < 1 || n > 3) + return false; - if (minimal) - return this.raw.slice(1, 1 + this.raw[0]); + if (m < 1 || m > n) + return false; - return this.getData(0); -}; + return true; + } -/** - * Test whether the output script is pay-to-pubkeyhash. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Boolean} - */ + if (this.isNulldata()) + return this.raw.length <= policy.MAX_OP_RETURN_BYTES; -Script.prototype.isPubkeyhash = function isPubkeyhash(minimal) { - if (minimal || this.raw.length === 25) { - return this.raw.length === 25 - && this.raw[0] === opcodes.OP_DUP - && this.raw[1] === opcodes.OP_HASH160 - && this.raw[2] === 0x14 - && this.raw[23] === opcodes.OP_EQUALVERIFY - && this.raw[24] === opcodes.OP_CHECKSIG; + return this.getType() !== scriptTypes.NONSTANDARD; } - if (this.code.length !== 5) - return false; - - return this.getOp(0) === opcodes.OP_DUP - && this.getOp(1) === opcodes.OP_HASH160 - && this.getLength(2) === 20 - && this.getOp(3) === opcodes.OP_EQUALVERIFY - && this.getOp(4) === opcodes.OP_CHECKSIG; -}; - -/** - * Get P2PKH hash if present. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Buffer|null} - */ - -Script.prototype.getPubkeyhash = function getPubkeyhash(minimal) { - if (!this.isPubkeyhash(minimal)) - return null; + /** + * Calculate the size of the script + * excluding the varint size bytes. + * @returns {Number} + */ - if (minimal) - return this.raw.slice(3, 23); - - return this.getData(2); -}; + getSize() { + return this.raw.length; + } -/** - * Test whether the output script is pay-to-multisig. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Boolean} - */ + /** + * Calculate the size of the script + * including the varint size bytes. + * @returns {Number} + */ -Script.prototype.isMultisig = function isMultisig(minimal) { - if (this.code.length < 4 || this.code.length > 19) - return false; + getVarSize() { + return encoding.sizeVarBytes(this.raw); + } - if (this.getOp(-1) !== opcodes.OP_CHECKMULTISIG) - return false; + /** + * "Guess" the address of the input script. + * This method is not 100% reliable. + * @returns {Address|null} + */ - const m = this.getSmall(0); + getInputAddress() { + return Address.fromInputScript(this); + } - if (m < 1) - return false; + /** + * Get the address of the script if present. Note that + * pubkey and multisig scripts will be treated as though + * they are pubkeyhash and scripthashes respectively. + * @returns {Address|null} + */ - const n = this.getSmall(-2); + getAddress() { + return Address.fromScript(this); + } - if (n < 1 || m > n) - return false; + /** + * Get the hash160 of the raw script. + * @param {String?} enc + * @returns {Hash} + */ + + hash160(enc) { + let hash = hash160.digest(this.toRaw()); + if (enc === 'hex') + hash = hash.toString('hex'); + return hash; + } - if (this.code.length !== n + 3) - return false; + /** + * Get the sha256 of the raw script. + * @param {String?} enc + * @returns {Hash} + */ + + sha256(enc) { + let hash = sha256.digest(this.toRaw()); + if (enc === 'hex') + hash = hash.toString('hex'); + return hash; + } - for (let i = 1; i < n + 1; i++) { - const op = this.code[i]; - const size = op.toLength(); + /** + * Test whether the output script is pay-to-pubkey. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Boolean} + */ + + isPubkey(minimal) { + if (minimal) { + return this.raw.length >= 35 + && (this.raw[0] === 33 || this.raw[0] === 65) + && this.raw[0] + 2 === this.raw.length + && this.raw[this.raw.length - 1] === opcodes.OP_CHECKSIG; + } - if (size !== 33 && size !== 65) + if (this.code.length !== 2) return false; - if (minimal && !op.isMinimal()) - return false; + const size = this.getLength(0); + + return (size === 33 || size === 65) + && this.getOp(1) === opcodes.OP_CHECKSIG; } - return true; -}; + /** + * Get P2PK key if present. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Buffer|null} + */ -/** - * Get multisig m and n values if present. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Array} [m, n] - */ + getPubkey(minimal) { + if (!this.isPubkey(minimal)) + return null; -Script.prototype.getMultisig = function getMultisig(minimal) { - if (!this.isMultisig(minimal)) - return [-1, -1]; + if (minimal) + return this.raw.slice(1, 1 + this.raw[0]); - return [this.getSmall(0), this.getSmall(-2)]; -}; + return this.getData(0); + } -/** - * Test whether the output script is pay-to-scripthash. Note that - * bitcoin itself requires scripthashes to be in strict minimaldata - * encoding. Using `OP_HASH160 OP_PUSHDATA1 [hash] OP_EQUAL` will - * _not_ be recognized as a scripthash. - * @returns {Boolean} - */ + /** + * Test whether the output script is pay-to-pubkeyhash. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Boolean} + */ + + isPubkeyhash(minimal) { + if (minimal || this.raw.length === 25) { + return this.raw.length === 25 + && this.raw[0] === opcodes.OP_DUP + && this.raw[1] === opcodes.OP_HASH160 + && this.raw[2] === 0x14 + && this.raw[23] === opcodes.OP_EQUALVERIFY + && this.raw[24] === opcodes.OP_CHECKSIG; + } -Script.prototype.isScripthash = function isScripthash() { - return this.raw.length === 23 - && this.raw[0] === opcodes.OP_HASH160 - && this.raw[1] === 0x14 - && this.raw[22] === opcodes.OP_EQUAL; -}; + if (this.code.length !== 5) + return false; -/** - * Get P2SH hash if present. - * @returns {Buffer|null} - */ + return this.getOp(0) === opcodes.OP_DUP + && this.getOp(1) === opcodes.OP_HASH160 + && this.getLength(2) === 20 + && this.getOp(3) === opcodes.OP_EQUALVERIFY + && this.getOp(4) === opcodes.OP_CHECKSIG; + } -Script.prototype.getScripthash = function getScripthash() { - if (!this.isScripthash()) - return null; + /** + * Get P2PKH hash if present. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Buffer|null} + */ - return this.getData(1); -}; + getPubkeyhash(minimal) { + if (!this.isPubkeyhash(minimal)) + return null; -/** - * Test whether the output script is nulldata/opreturn. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Boolean} - */ + if (minimal) + return this.raw.slice(3, 23); -Script.prototype.isNulldata = function isNulldata(minimal) { - if (this.code.length === 0) - return false; + return this.getData(2); + } - if (this.getOp(0) !== opcodes.OP_RETURN) - return false; + /** + * Test whether the output script is pay-to-multisig. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Boolean} + */ - if (this.code.length === 1) - return true; + isMultisig(minimal) { + if (this.code.length < 4 || this.code.length > 19) + return false; - if (minimal) { - if (this.raw.length > policy.MAX_OP_RETURN_BYTES) + if (this.getOp(-1) !== opcodes.OP_CHECKMULTISIG) return false; - } - for (let i = 1; i < this.code.length; i++) { - const op = this.code[i]; + const m = this.getSmall(0); - if (op.value === -1) + if (m < 1) return false; - if (op.value > opcodes.OP_16) + const n = this.getSmall(-2); + + if (n < 1 || m > n) return false; - if (minimal && !op.isMinimal()) + if (this.code.length !== n + 3) return false; - } - return true; -}; + for (let i = 1; i < n + 1; i++) { + const op = this.code[i]; + const size = op.toLength(); -/** - * Get OP_RETURN data if present. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Buffer|null} - */ + if (size !== 33 && size !== 65) + return false; -Script.prototype.getNulldata = function getNulldata(minimal) { - if (!this.isNulldata(minimal)) - return null; + if (minimal && !op.isMinimal()) + return false; + } - for (let i = 1; i < this.code.length; i++) { - const op = this.code[i]; - const data = op.toPush(); - if (data) - return data; + return true; } - return EMPTY_BUFFER; -}; - -/** - * Test whether the output script is a segregated witness - * commitment. - * @returns {Boolean} - */ - -Script.prototype.isCommitment = function isCommitment() { - return this.raw.length >= 38 - && this.raw[0] === opcodes.OP_RETURN - && this.raw[1] === 0x24 - && this.raw.readUInt32BE(2, true) === 0xaa21a9ed; -}; + /** + * Get multisig m and n values if present. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Array} [m, n] + */ -/** - * Get the commitment hash if present. - * @returns {Buffer|null} - */ + getMultisig(minimal) { + if (!this.isMultisig(minimal)) + return [-1, -1]; -Script.prototype.getCommitment = function getCommitment() { - if (!this.isCommitment()) - return null; + return [this.getSmall(0), this.getSmall(-2)]; + } - return this.raw.slice(6, 38); -}; + /** + * Test whether the output script is pay-to-scripthash. Note that + * bitcoin itself requires scripthashes to be in strict minimaldata + * encoding. Using `OP_HASH160 OP_PUSHDATA1 [hash] OP_EQUAL` will + * _not_ be recognized as a scripthash. + * @returns {Boolean} + */ + + isScripthash() { + return this.raw.length === 23 + && this.raw[0] === opcodes.OP_HASH160 + && this.raw[1] === 0x14 + && this.raw[22] === opcodes.OP_EQUAL; + } -/** - * Test whether the output script is a witness program. - * Note that this will return true even for malformed - * witness v0 programs. - * @return {Boolean} - */ + /** + * Get P2SH hash if present. + * @returns {Buffer|null} + */ -Script.prototype.isProgram = function isProgram() { - if (this.raw.length < 4 || this.raw.length > 42) - return false; + getScripthash() { + if (!this.isScripthash()) + return null; - if (this.raw[0] !== opcodes.OP_0 - && (this.raw[0] < opcodes.OP_1 || this.raw[0] > opcodes.OP_16)) { - return false; + return this.getData(1); } - if (this.raw[1] + 2 !== this.raw.length) - return false; - - return true; -}; - -/** - * Get the witness program if present. - * @returns {Program|null} - */ + /** + * Test whether the output script is nulldata/opreturn. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Boolean} + */ -Script.prototype.getProgram = function getProgram() { - if (!this.isProgram()) - return null; + isNulldata(minimal) { + if (this.code.length === 0) + return false; - const version = this.getSmall(0); - const data = this.getData(1); + if (this.getOp(0) !== opcodes.OP_RETURN) + return false; - return new Program(version, data); -}; + if (this.code.length === 1) + return true; -/** - * Get the script to the equivalent witness - * program (mimics bitcoind's scriptForWitness). - * @returns {Script|null} - */ + if (minimal) { + if (this.raw.length > policy.MAX_OP_RETURN_BYTES) + return false; + } -Script.prototype.forWitness = function forWitness() { - if (this.isProgram()) - return this.clone(); + for (let i = 1; i < this.code.length; i++) { + const op = this.code[i]; - const pk = this.getPubkey(); - if (pk) { - const hash = digest.hash160(pk); - return Script.fromProgram(0, hash); - } + if (op.value === -1) + return false; - const pkh = this.getPubkeyhash(); - if (pkh) - return Script.fromProgram(0, pkh); + if (op.value > opcodes.OP_16) + return false; - return Script.fromProgram(0, this.sha256()); -}; + if (minimal && !op.isMinimal()) + return false; + } -/** - * Test whether the output script is - * a pay-to-witness-pubkeyhash program. - * @returns {Boolean} - */ + return true; + } -Script.prototype.isWitnessPubkeyhash = function isWitnessPubkeyhash() { - return this.raw.length === 22 - && this.raw[0] === opcodes.OP_0 - && this.raw[1] === 0x14; -}; + /** + * Get OP_RETURN data if present. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Buffer|null} + */ -/** - * Get P2WPKH hash if present. - * @returns {Buffer|null} - */ + getNulldata(minimal) { + if (!this.isNulldata(minimal)) + return null; -Script.prototype.getWitnessPubkeyhash = function getWitnessPubkeyhash() { - if (!this.isWitnessPubkeyhash()) - return null; + for (let i = 1; i < this.code.length; i++) { + const op = this.code[i]; + const data = op.toPush(); + if (data) + return data; + } - return this.getData(1); -}; + return EMPTY_BUFFER; + } -/** - * Test whether the output script is - * a pay-to-witness-scripthash program. - * @returns {Boolean} - */ + /** + * Test whether the output script is a segregated witness + * commitment. + * @returns {Boolean} + */ + + isCommitment() { + return this.raw.length >= 38 + && this.raw[0] === opcodes.OP_RETURN + && this.raw[1] === 0x24 + && this.raw.readUInt32BE(2, true) === 0xaa21a9ed; + } -Script.prototype.isWitnessScripthash = function isWitnessScripthash() { - return this.raw.length === 34 - && this.raw[0] === opcodes.OP_0 - && this.raw[1] === 0x20; -}; + /** + * Get the commitment hash if present. + * @returns {Buffer|null} + */ -/** - * Get P2WSH hash if present. - * @returns {Buffer|null} - */ + getCommitment() { + if (!this.isCommitment()) + return null; -Script.prototype.getWitnessScripthash = function getWitnessScripthash() { - if (!this.isWitnessScripthash()) - return null; + return this.raw.slice(6, 38); + } - return this.getData(1); -}; + /** + * Test whether the output script is a witness program. + * Note that this will return true even for malformed + * witness v0 programs. + * @return {Boolean} + */ -/** - * Test whether the output script - * is a pay-to-mast program. - * @returns {Boolean} - */ + isProgram() { + if (this.raw.length < 4 || this.raw.length > 42) + return false; -Script.prototype.isWitnessMasthash = function isWitnessMasthash() { - return this.raw.length === 34 - && this.raw[0] === opcodes.OP_1 - && this.raw[1] === 0x20; -}; + if (this.raw[0] !== opcodes.OP_0 + && (this.raw[0] < opcodes.OP_1 || this.raw[0] > opcodes.OP_16)) { + return false; + } -/** - * Get P2WMH hash if present. - * @returns {Buffer|null} - */ + if (this.raw[1] + 2 !== this.raw.length) + return false; -Script.prototype.getWitnessMasthash = function getWitnessMasthash() { - if (!this.isWitnessMasthash()) - return null; + return true; + } - return this.getData(1); -}; + /** + * Get the witness program if present. + * @returns {Program|null} + */ -/** - * Test whether the output script is unspendable. - * @returns {Boolean} - */ + getProgram() { + if (!this.isProgram()) + return null; -Script.prototype.isUnspendable = function isUnspendable() { - if (this.raw.length > consensus.MAX_SCRIPT_SIZE) - return true; + const version = this.getSmall(0); + const data = this.getData(1); - return this.raw.length > 0 && this.raw[0] === opcodes.OP_RETURN; -}; + return new Program(version, data); + } -/** - * "Guess" the type of the input script. - * This method is not 100% reliable. - * @returns {ScriptType} - */ + /** + * Get the script to the equivalent witness + * program (mimics bitcoind's scriptForWitness). + * @returns {Script|null} + */ -Script.prototype.getInputType = function getInputType() { - if (this.isPubkeyInput()) - return scriptTypes.PUBKEY; + forWitness() { + if (this.isProgram()) + return this.clone(); - if (this.isPubkeyhashInput()) - return scriptTypes.PUBKEYHASH; + const pk = this.getPubkey(); + if (pk) { + const hash = hash160.digest(pk); + return Script.fromProgram(0, hash); + } - if (this.isScripthashInput()) - return scriptTypes.SCRIPTHASH; + const pkh = this.getPubkeyhash(); + if (pkh) + return Script.fromProgram(0, pkh); - if (this.isMultisigInput()) - return scriptTypes.MULTISIG; + return Script.fromProgram(0, this.sha256()); + } - return scriptTypes.NONSTANDARD; -}; + /** + * Test whether the output script is + * a pay-to-witness-pubkeyhash program. + * @returns {Boolean} + */ -/** - * "Guess" whether the input script is an unknown/non-standard type. - * This method is not 100% reliable. - * @returns {Boolean} - */ + isWitnessPubkeyhash() { + return this.raw.length === 22 + && this.raw[0] === opcodes.OP_0 + && this.raw[1] === 0x14; + } -Script.prototype.isUnknownInput = function isUnknownInput() { - return this.getInputType() === scriptTypes.NONSTANDARD; -}; + /** + * Get P2WPKH hash if present. + * @returns {Buffer|null} + */ -/** - * "Guess" whether the input script is pay-to-pubkey. - * This method is not 100% reliable. - * @returns {Boolean} - */ + getWitnessPubkeyhash() { + if (!this.isWitnessPubkeyhash()) + return null; -Script.prototype.isPubkeyInput = function isPubkeyInput() { - if (this.code.length !== 1) - return false; + return this.getData(1); + } - const size = this.getLength(0); + /** + * Test whether the output script is + * a pay-to-witness-scripthash program. + * @returns {Boolean} + */ - return size >= 9 && size <= 73; -}; + isWitnessScripthash() { + return this.raw.length === 34 + && this.raw[0] === opcodes.OP_0 + && this.raw[1] === 0x20; + } -/** - * Get P2PK signature if present. - * @returns {Buffer|null} - */ + /** + * Get P2WSH hash if present. + * @returns {Buffer|null} + */ -Script.prototype.getPubkeyInput = function getPubkeyInput() { - if (!this.isPubkeyInput()) - return null; + getWitnessScripthash() { + if (!this.isWitnessScripthash()) + return null; - return this.getData(0); -}; + return this.getData(1); + } -/** - * "Guess" whether the input script is pay-to-pubkeyhash. - * This method is not 100% reliable. - * @returns {Boolean} - */ + /** + * Test whether the output script is unspendable. + * @returns {Boolean} + */ -Script.prototype.isPubkeyhashInput = function isPubkeyhashInput() { - if (this.code.length !== 2) - return false; + isUnspendable() { + if (this.raw.length > consensus.MAX_SCRIPT_SIZE) + return true; - const sig = this.getLength(0); - const key = this.getLength(1); + return this.raw.length > 0 && this.raw[0] === opcodes.OP_RETURN; + } - return sig >= 9 && sig <= 73 - && (key === 33 || key === 65); -}; + /** + * "Guess" the type of the input script. + * This method is not 100% reliable. + * @returns {ScriptType} + */ -/** - * Get P2PKH signature and key if present. - * @returns {Array} [sig, key] - */ + getInputType() { + if (this.isPubkeyInput()) + return scriptTypes.PUBKEY; -Script.prototype.getPubkeyhashInput = function getPubkeyhashInput() { - if (!this.isPubkeyhashInput()) - return [null, null]; + if (this.isPubkeyhashInput()) + return scriptTypes.PUBKEYHASH; - return [this.getData(0), this.getData(1)]; -}; + if (this.isScripthashInput()) + return scriptTypes.SCRIPTHASH; -/** - * "Guess" whether the input script is pay-to-multisig. - * This method is not 100% reliable. - * @returns {Boolean} - */ + if (this.isMultisigInput()) + return scriptTypes.MULTISIG; -Script.prototype.isMultisigInput = function isMultisigInput() { - if (this.code.length < 2) - return false; + return scriptTypes.NONSTANDARD; + } - if (this.getOp(0) !== opcodes.OP_0) - return false; + /** + * "Guess" whether the input script is an unknown/non-standard type. + * This method is not 100% reliable. + * @returns {Boolean} + */ - if (this.getOp(1) > opcodes.OP_PUSHDATA4) - return false; + isUnknownInput() { + return this.getInputType() === scriptTypes.NONSTANDARD; + } - // We need to rule out scripthash - // because it may look like multisig. - if (this.isScripthashInput()) - return false; + /** + * "Guess" whether the input script is pay-to-pubkey. + * This method is not 100% reliable. + * @returns {Boolean} + */ - for (let i = 1; i < this.code.length; i++) { - const size = this.getLength(i); - if (size < 9 || size > 73) + isPubkeyInput() { + if (this.code.length !== 1) return false; - } - return true; -}; + const size = this.getLength(0); -/** - * Get multisig signatures if present. - * @returns {Buffer[]|null} - */ + return size >= 9 && size <= 73; + } -Script.prototype.getMultisigInput = function getMultisigInput() { - if (!this.isMultisigInput()) - return null; + /** + * Get P2PK signature if present. + * @returns {Buffer|null} + */ - const sigs = []; + getPubkeyInput() { + if (!this.isPubkeyInput()) + return null; - for (let i = 1; i < this.code.length; i++) - sigs.push(this.getData(i)); + return this.getData(0); + } - return sigs; -}; + /** + * "Guess" whether the input script is pay-to-pubkeyhash. + * This method is not 100% reliable. + * @returns {Boolean} + */ -/** - * "Guess" whether the input script is pay-to-scripthash. - * This method is not 100% reliable. - * @returns {Boolean} - */ + isPubkeyhashInput() { + if (this.code.length !== 2) + return false; -Script.prototype.isScripthashInput = function isScripthashInput() { - if (this.code.length < 2) - return false; + const sig = this.getLength(0); + const key = this.getLength(1); - // Grab the raw redeem script. - const raw = this.getData(-1); + return sig >= 9 && sig <= 73 + && (key === 33 || key === 65); + } - // Last data element should be an array - // for the redeem script. - if (!raw) - return false; + /** + * Get P2PKH signature and key if present. + * @returns {Array} [sig, key] + */ - // Testing for scripthash inputs requires - // some evil magic to work. We do it by - // ruling things _out_. This test will not - // be correct 100% of the time. We rule - // out that the last data element is: a - // null dummy, a valid signature, a valid - // key, and we ensure that it is at least - // a script that does not use undefined - // opcodes. - if (raw.length === 0) - return false; + getPubkeyhashInput() { + if (!this.isPubkeyhashInput()) + return [null, null]; - if (common.isSignatureEncoding(raw)) - return false; + return [this.getData(0), this.getData(1)]; + } - if (common.isKeyEncoding(raw)) - return false; + /** + * "Guess" whether the input script is pay-to-multisig. + * This method is not 100% reliable. + * @returns {Boolean} + */ - const redeem = Script.fromRaw(raw); + isMultisigInput() { + if (this.code.length < 2) + return false; - if (!redeem.isCode()) - return false; + if (this.getOp(0) !== opcodes.OP_0) + return false; - if (redeem.isUnspendable()) - return false; + if (this.getOp(1) > opcodes.OP_PUSHDATA4) + return false; - if (!this.isPushOnly()) - return false; + // We need to rule out scripthash + // because it may look like multisig. + if (this.isScripthashInput()) + return false; - return true; -}; + for (let i = 1; i < this.code.length; i++) { + const size = this.getLength(i); + if (size < 9 || size > 73) + return false; + } -/** - * Get P2SH redeem script if present. - * @returns {Buffer|null} - */ + return true; + } -Script.prototype.getScripthashInput = function getScripthashInput() { - if (!this.isScripthashInput()) - return null; + /** + * Get multisig signatures if present. + * @returns {Buffer[]|null} + */ - return this.getData(-1); -}; + getMultisigInput() { + if (!this.isMultisigInput()) + return null; -/** - * Get coinbase height. - * @returns {Number} `-1` if not present. - */ + const sigs = []; -Script.prototype.getCoinbaseHeight = function getCoinbaseHeight() { - return Script.getCoinbaseHeight(this.raw); -}; + for (let i = 1; i < this.code.length; i++) + sigs.push(this.getData(i)); -/** - * Get coinbase height. - * @param {Buffer} raw - Raw script. - * @returns {Number} `-1` if not present. - */ + return sigs; + } -Script.getCoinbaseHeight = function getCoinbaseHeight(raw) { - if (raw.length === 0) - return -1; + /** + * "Guess" whether the input script is pay-to-scripthash. + * This method is not 100% reliable. + * @returns {Boolean} + */ - if (raw[0] >= opcodes.OP_1 && raw[0] <= opcodes.OP_16) - return raw[0] - 0x50; + isScripthashInput() { + if (this.code.length < 1) + return false; - if (raw[0] > 0x06) - return -1; + // Grab the raw redeem script. + const raw = this.getData(-1); - const op = Opcode.fromRaw(raw); - const num = op.toNum(); + // Last data element should be an array + // for the redeem script. + if (!raw) + return false; - if (!num) - return 1; + // Testing for scripthash inputs requires + // some evil magic to work. We do it by + // ruling things _out_. This test will not + // be correct 100% of the time. We rule + // out that the last data element is: a + // null dummy, a valid signature, a valid + // key, and we ensure that it is at least + // a script that does not use undefined + // opcodes. + if (raw.length === 0) + return false; - if (num.isNeg()) - return -1; + if (common.isSignatureEncoding(raw)) + return false; - if (!op.equals(Opcode.fromNum(num))) - return -1; + if (common.isKeyEncoding(raw)) + return false; - return num.toDouble(); -}; + const redeem = Script.fromRaw(raw); -/** - * Test the script against a bloom filter. - * @param {Bloom} filter - * @returns {Boolean} - */ + if (!redeem.isCode()) + return false; -Script.prototype.test = function test(filter) { - for (const op of this.code) { - if (op.value === -1) - break; + if (redeem.isUnspendable()) + return false; - if (!op.data || op.data.length === 0) - continue; + if (!this.isPushOnly()) + return false; - if (filter.test(op.data)) - return true; + return true; } - return false; -}; - -/** - * Test the script to see if it contains only push ops. - * Push ops are: OP_1NEGATE, OP_0-OP_16 and all PUSHDATAs. - * @returns {Boolean} - */ + /** + * Get P2SH redeem script if present. + * @returns {Buffer|null} + */ -Script.prototype.isPushOnly = function isPushOnly() { - for (const op of this.code) { - if (op.value === -1) - return false; + getScripthashInput() { + if (!this.isScripthashInput()) + return null; - if (op.value > opcodes.OP_16) - return false; + return this.getData(-1); } - return true; -}; + /** + * Get coinbase height. + * @returns {Number} `-1` if not present. + */ -/** - * Count the sigops in the script. - * @param {Boolean} accurate - Whether to enable accurate counting. This will - * take into account the `n` value for OP_CHECKMULTISIG(VERIFY). - * @returns {Number} sigop count - */ + getCoinbaseHeight() { + return Script.getCoinbaseHeight(this.raw); + } -Script.prototype.getSigops = function getSigops(accurate) { - let total = 0; - let lastOp = -1; + /** + * Get coinbase height. + * @param {Buffer} raw - Raw script. + * @returns {Number} `-1` if not present. + */ - for (const op of this.code) { - if (op.value === -1) - break; + static getCoinbaseHeight(raw) { + if (raw.length === 0) + return -1; - switch (op.value) { - case opcodes.OP_CHECKSIG: - case opcodes.OP_CHECKSIGVERIFY: - total += 1; - break; - case opcodes.OP_CHECKMULTISIG: - case opcodes.OP_CHECKMULTISIGVERIFY: - if (accurate && lastOp >= opcodes.OP_1 && lastOp <= opcodes.OP_16) - total += lastOp - 0x50; - else - total += consensus.MAX_MULTISIG_PUBKEYS; - break; - } + if (raw[0] >= opcodes.OP_1 && raw[0] <= opcodes.OP_16) + return raw[0] - 0x50; - lastOp = op.value; - } + if (raw[0] > 0x06) + return -1; - return total; -}; + const op = Opcode.fromRaw(raw); + const num = op.toNum(); -/** - * Count the sigops in the script, taking into account redeem scripts. - * @param {Script} input - Input script, needed for access to redeem script. - * @returns {Number} sigop count - */ + if (!num) + return 1; -Script.prototype.getScripthashSigops = function getScripthashSigops(input) { - if (!this.isScripthash()) - return this.getSigops(true); + if (num.isNeg()) + return -1; - const redeem = input.getRedeem(); + if (!op.equals(Opcode.fromNum(num))) + return -1; - if (!redeem) - return 0; + return num.toDouble(); + } - return redeem.getSigops(true); -}; + /** + * Test the script against a bloom filter. + * @param {Bloom} filter + * @returns {Boolean} + */ -/** - * Count the sigops in a script, taking into account witness programs. - * @param {Script} input - * @param {Witness} witness - * @returns {Number} sigop count - */ + test(filter) { + for (const op of this.code) { + if (op.value === -1) + break; -Script.prototype.getWitnessSigops = function getWitnessSigops(input, witness) { - let program = this.getProgram(); + if (!op.data || op.data.length === 0) + continue; - if (!program) { - if (this.isScripthash()) { - const redeem = input.getRedeem(); - if (redeem) - program = redeem.getProgram(); + if (filter.test(op.data)) + return true; } + + return false; } - if (!program) - return 0; + /** + * Test the script to see if it contains only push ops. + * Push ops are: OP_1NEGATE, OP_0-OP_16 and all PUSHDATAs. + * @returns {Boolean} + */ - if (program.version === 0) { - if (program.data.length === 20) - return 1; + isPushOnly() { + for (const op of this.code) { + if (op.value === -1) + return false; - if (program.data.length === 32 && witness.items.length > 0) { - const redeem = witness.getRedeem(); - return redeem.getSigops(true); + if (op.value > opcodes.OP_16) + return false; } + + return true; } - return 0; -}; + /** + * Count the sigops in the script. + * @param {Boolean} accurate - Whether to enable accurate counting. This will + * take into account the `n` value for OP_CHECKMULTISIG(VERIFY). + * @returns {Number} sigop count + */ -/* - * Mutation - */ + getSigops(accurate) { + let total = 0; + let lastOp = -1; -Script.prototype.get = function get(index) { - if (index < 0) - index += this.code.length; + for (const op of this.code) { + if (op.value === -1) + break; - if (index < 0 || index >= this.code.length) - return null; + switch (op.value) { + case opcodes.OP_CHECKSIG: + case opcodes.OP_CHECKSIGVERIFY: + total += 1; + break; + case opcodes.OP_CHECKMULTISIG: + case opcodes.OP_CHECKMULTISIGVERIFY: + if (accurate && lastOp >= opcodes.OP_1 && lastOp <= opcodes.OP_16) + total += lastOp - 0x50; + else + total += consensus.MAX_MULTISIG_PUBKEYS; + break; + } - return this.code[index]; -}; + lastOp = op.value; + } -Script.prototype.pop = function pop() { - const op = this.code.pop(); - return op || null; -}; + return total; + } -Script.prototype.shift = function shift() { - const op = this.code.shift(); - return op || null; -}; + /** + * Count the sigops in the script, taking into account redeem scripts. + * @param {Script} input - Input script, needed for access to redeem script. + * @returns {Number} sigop count + */ -Script.prototype.remove = function remove(index) { - if (index < 0) - index += this.code.length; + getScripthashSigops(input) { + if (!this.isScripthash()) + return this.getSigops(true); - if (index < 0 || index >= this.code.length) - return null; + const redeem = input.getRedeem(); - const items = this.code.splice(index, 1); + if (!redeem) + return 0; - if (items.length === 0) - return null; + return redeem.getSigops(true); + } - return items[0]; -}; + /** + * Count the sigops in a script, taking into account witness programs. + * @param {Script} input + * @param {Witness} witness + * @returns {Number} sigop count + */ + + getWitnessSigops(input, witness) { + let program = this.getProgram(); + + if (!program) { + if (this.isScripthash()) { + const redeem = input.getRedeem(); + if (redeem) + program = redeem.getProgram(); + } + } -Script.prototype.set = function set(index, op) { - if (index < 0) - index += this.code.length; + if (!program) + return 0; - assert(Opcode.isOpcode(op)); - assert(index >= 0 && index <= this.code.length); + if (program.version === 0) { + if (program.data.length === 20) + return 1; - this.code[index] = op; + if (program.data.length === 32 && witness.items.length > 0) { + const redeem = witness.getRedeem(); + return redeem.getSigops(true); + } + } - return this; -}; + return 0; + } -Script.prototype.push = function push(op) { - assert(Opcode.isOpcode(op)); - this.code.push(op); - return this; -}; + /* + * Mutation + */ -Script.prototype.unshift = function unshift(op) { - assert(Opcode.isOpcode(op)); - this.code.unshift(op); - return this; -}; + get(index) { + if (index < 0) + index += this.code.length; -Script.prototype.insert = function insert(index, op) { - if (index < 0) - index += this.code.length; + if (index < 0 || index >= this.code.length) + return null; - assert(Opcode.isOpcode(op)); - assert(index >= 0 && index <= this.code.length); + return this.code[index]; + } - this.code.splice(index, 0, op); + pop() { + const op = this.code.pop(); + return op || null; + } - return this; -}; + shift() { + const op = this.code.shift(); + return op || null; + } -/* - * Op - */ + remove(index) { + if (index < 0) + index += this.code.length; -Script.prototype.getOp = function getOp(index) { - const op = this.get(index); - return op ? op.value : -1; -}; + if (index < 0 || index >= this.code.length) + return null; -Script.prototype.popOp = function popOp() { - const op = this.pop(); - return op ? op.value : -1; -}; + const items = this.code.splice(index, 1); -Script.prototype.shiftOp = function shiftOp() { - const op = this.shift(); - return op ? op.value : -1; -}; + if (items.length === 0) + return null; -Script.prototype.removeOp = function removeOp(index) { - const op = this.remove(index); - return op ? op.value : -1; -}; + return items[0]; + } -Script.prototype.setOp = function setOp(index, value) { - return this.set(index, Opcode.fromOp(value)); -}; + set(index, op) { + if (index < 0) + index += this.code.length; -Script.prototype.pushOp = function pushOp(value) { - return this.push(Opcode.fromOp(value)); -}; + assert(Opcode.isOpcode(op)); + assert(index >= 0 && index <= this.code.length); -Script.prototype.unshiftOp = function unshiftOp(value) { - return this.unshift(Opcode.fromOp(value)); -}; + this.code[index] = op; -Script.prototype.insertOp = function insertOp(index, value) { - return this.insert(index, Opcode.fromOp(value)); -}; + return this; + } -/* - * Data - */ + push(op) { + assert(Opcode.isOpcode(op)); + this.code.push(op); + return this; + } -Script.prototype.getData = function getData(index) { - const op = this.get(index); - return op ? op.data : null; -}; + unshift(op) { + assert(Opcode.isOpcode(op)); + this.code.unshift(op); + return this; + } -Script.prototype.popData = function popData() { - const op = this.pop(); - return op ? op.data : null; -}; + insert(index, op) { + if (index < 0) + index += this.code.length; -Script.prototype.shiftData = function shiftData() { - const op = this.shift(); - return op ? op.data : null; -}; + assert(Opcode.isOpcode(op)); + assert(index >= 0 && index <= this.code.length); -Script.prototype.removeData = function removeData(index) { - const op = this.remove(index); - return op ? op.data : null; -}; + this.code.splice(index, 0, op); -Script.prototype.setData = function setData(index, data) { - return this.set(index, Opcode.fromData(data)); -}; + return this; + } -Script.prototype.pushData = function pushData(data) { - return this.push(Opcode.fromData(data)); -}; + /* + * Op + */ -Script.prototype.unshiftData = function unshiftData(data) { - return this.unshift(Opcode.fromData(data)); -}; + getOp(index) { + const op = this.get(index); + return op ? op.value : -1; + } -Script.prototype.insertData = function insertData(index, data) { - return this.insert(index, Opcode.fromData(data)); -}; + popOp() { + const op = this.pop(); + return op ? op.value : -1; + } -/* - * Length - */ + shiftOp() { + const op = this.shift(); + return op ? op.value : -1; + } -Script.prototype.getLength = function getLength(index) { - const op = this.get(index); - return op ? op.toLength() : -1; -}; + removeOp(index) { + const op = this.remove(index); + return op ? op.value : -1; + } -/* - * Push - */ + setOp(index, value) { + return this.set(index, Opcode.fromOp(value)); + } -Script.prototype.getPush = function getPush(index) { - const op = this.get(index); - return op ? op.toPush() : null; -}; + pushOp(value) { + return this.push(Opcode.fromOp(value)); + } -Script.prototype.popPush = function popPush() { - const op = this.pop(); - return op ? op.toPush() : null; -}; + unshiftOp(value) { + return this.unshift(Opcode.fromOp(value)); + } -Script.prototype.shiftPush = function shiftPush() { - const op = this.shift(); - return op ? op.toPush() : null; -}; + insertOp(index, value) { + return this.insert(index, Opcode.fromOp(value)); + } -Script.prototype.removePush = function removePush(index) { - const op = this.remove(index); - return op ? op.toPush() : null; -}; + /* + * Data + */ -Script.prototype.setPush = function setPush(index, data) { - return this.set(index, Opcode.fromPush(data)); -}; + getData(index) { + const op = this.get(index); + return op ? op.data : null; + } -Script.prototype.pushPush = function pushPush(data) { - return this.push(Opcode.fromPush(data)); -}; + popData() { + const op = this.pop(); + return op ? op.data : null; + } -Script.prototype.unshiftPush = function unshiftPush(data) { - return this.unshift(Opcode.fromPush(data)); -}; + shiftData() { + const op = this.shift(); + return op ? op.data : null; + } -Script.prototype.insertPush = function insertPush(index, data) { - return this.insert(index, Opcode.fromPush(data)); -}; + removeData(index) { + const op = this.remove(index); + return op ? op.data : null; + } -/* - * String - */ + setData(index, data) { + return this.set(index, Opcode.fromData(data)); + } -Script.prototype.getString = function getString(index, enc) { - const op = this.get(index); - return op ? op.toString(enc) : null; -}; + pushData(data) { + return this.push(Opcode.fromData(data)); + } -Script.prototype.popString = function popString(enc) { - const op = this.pop(); - return op ? op.toString(enc) : null; -}; + unshiftData(data) { + return this.unshift(Opcode.fromData(data)); + } -Script.prototype.shiftString = function shiftString(enc) { - const op = this.shift(); - return op ? op.toString(enc) : null; -}; + insertData(index, data) { + return this.insert(index, Opcode.fromData(data)); + } -Script.prototype.removeString = function removeString(index, enc) { - const op = this.remove(index); - return op ? op.toString(enc) : null; -}; + /* + * Length + */ -Script.prototype.setString = function setString(index, str, enc) { - return this.set(index, Opcode.fromString(str, enc)); -}; + getLength(index) { + const op = this.get(index); + return op ? op.toLength() : -1; + } -Script.prototype.pushString = function pushString(str, enc) { - return this.push(Opcode.fromString(str, enc)); -}; + /* + * Push + */ -Script.prototype.unshiftString = function unshiftString(str, enc) { - return this.unshift(Opcode.fromString(str, enc)); -}; + getPush(index) { + const op = this.get(index); + return op ? op.toPush() : null; + } -Script.prototype.insertString = function insertString(index, str, enc) { - return this.insert(index, Opcode.fromString(str, enc)); -}; + popPush() { + const op = this.pop(); + return op ? op.toPush() : null; + } -/* - * Small - */ + shiftPush() { + const op = this.shift(); + return op ? op.toPush() : null; + } -Script.prototype.getSmall = function getSmall(index) { - const op = this.get(index); - return op ? op.toSmall() : -1; -}; + removePush(index) { + const op = this.remove(index); + return op ? op.toPush() : null; + } -Script.prototype.popSmall = function popSmall() { - const op = this.pop(); - return op ? op.toSmall() : -1; -}; + setPush(index, data) { + return this.set(index, Opcode.fromPush(data)); + } -Script.prototype.shiftSmall = function shiftSmall() { - const op = this.shift(); - return op ? op.toSmall() : -1; -}; + pushPush(data) { + return this.push(Opcode.fromPush(data)); + } -Script.prototype.removeSmall = function removeSmall(index) { - const op = this.remove(index); - return op ? op.toSmall() : -1; -}; + unshiftPush(data) { + return this.unshift(Opcode.fromPush(data)); + } -Script.prototype.setSmall = function setSmall(index, num) { - return this.set(index, Opcode.fromSmall(num)); -}; + insertPush(index, data) { + return this.insert(index, Opcode.fromPush(data)); + } -Script.prototype.pushSmall = function pushSmall(num) { - return this.push(Opcode.fromSmall(num)); -}; + /* + * String + */ -Script.prototype.unshiftSmall = function unshiftSmall(num) { - return this.unshift(Opcode.fromSmall(num)); -}; + getString(index, enc) { + const op = this.get(index); + return op ? op.toString(enc) : null; + } -Script.prototype.insertSmall = function insertSmall(index, num) { - return this.insert(index, Opcode.fromSmall(num)); -}; + popString(enc) { + const op = this.pop(); + return op ? op.toString(enc) : null; + } -/* - * Num - */ + shiftString(enc) { + const op = this.shift(); + return op ? op.toString(enc) : null; + } -Script.prototype.getNum = function getNum(index, minimal, limit) { - const op = this.get(index); - return op ? op.toNum(minimal, limit) : null; -}; + removeString(index, enc) { + const op = this.remove(index); + return op ? op.toString(enc) : null; + } -Script.prototype.popNum = function popNum(minimal, limit) { - const op = this.pop(); - return op ? op.toNum(minimal, limit) : null; -}; + setString(index, str, enc) { + return this.set(index, Opcode.fromString(str, enc)); + } -Script.prototype.shiftNum = function shiftNum(minimal, limit) { - const op = this.shift(); - return op ? op.toNum(minimal, limit) : null; -}; + pushString(str, enc) { + return this.push(Opcode.fromString(str, enc)); + } -Script.prototype.removeNum = function removeNum(index, minimal, limit) { - const op = this.remove(index); - return op ? op.toNum(minimal, limit) : null; -}; + unshiftString(str, enc) { + return this.unshift(Opcode.fromString(str, enc)); + } -Script.prototype.setNum = function setNum(index, num) { - return this.set(index, Opcode.fromNum(num)); -}; + insertString(index, str, enc) { + return this.insert(index, Opcode.fromString(str, enc)); + } -Script.prototype.pushNum = function pushNum(num) { - return this.push(Opcode.fromNum(num)); -}; + /* + * Small + */ -Script.prototype.unshiftNum = function unshiftNum(num) { - return this.unshift(Opcode.fromNum(num)); -}; + getSmall(index) { + const op = this.get(index); + return op ? op.toSmall() : -1; + } -Script.prototype.insertNum = function insertNum(index, num) { - return this.insert(index, Opcode.fromNum(num)); -}; + popSmall() { + const op = this.pop(); + return op ? op.toSmall() : -1; + } -/* - * Int - */ + shiftSmall() { + const op = this.shift(); + return op ? op.toSmall() : -1; + } -Script.prototype.getInt = function getInt(index, minimal, limit) { - const op = this.get(index); - return op ? op.toInt(minimal, limit) : -1; -}; + removeSmall(index) { + const op = this.remove(index); + return op ? op.toSmall() : -1; + } -Script.prototype.popInt = function popInt(minimal, limit) { - const op = this.pop(); - return op ? op.toInt(minimal, limit) : -1; -}; + setSmall(index, num) { + return this.set(index, Opcode.fromSmall(num)); + } -Script.prototype.shiftInt = function shiftInt(minimal, limit) { - const op = this.shift(); - return op ? op.toInt(minimal, limit) : -1; -}; + pushSmall(num) { + return this.push(Opcode.fromSmall(num)); + } -Script.prototype.removeInt = function removeInt(index, minimal, limit) { - const op = this.remove(index); - return op ? op.toInt(minimal, limit) : -1; -}; + unshiftSmall(num) { + return this.unshift(Opcode.fromSmall(num)); + } -Script.prototype.setInt = function setInt(index, num) { - return this.set(index, Opcode.fromInt(num)); -}; + insertSmall(index, num) { + return this.insert(index, Opcode.fromSmall(num)); + } -Script.prototype.pushInt = function pushInt(num) { - return this.push(Opcode.fromInt(num)); -}; + /* + * Num + */ -Script.prototype.unshiftInt = function unshiftInt(num) { - return this.unshift(Opcode.fromInt(num)); -}; + getNum(index, minimal, limit) { + const op = this.get(index); + return op ? op.toNum(minimal, limit) : null; + } -Script.prototype.insertInt = function insertInt(index, num) { - return this.insert(index, Opcode.fromInt(num)); -}; + popNum(minimal, limit) { + const op = this.pop(); + return op ? op.toNum(minimal, limit) : null; + } -/* - * Bool - */ + shiftNum(minimal, limit) { + const op = this.shift(); + return op ? op.toNum(minimal, limit) : null; + } -Script.prototype.getBool = function getBool(index) { - const op = this.get(index); - return op ? op.toBool() : false; -}; + removeNum(index, minimal, limit) { + const op = this.remove(index); + return op ? op.toNum(minimal, limit) : null; + } -Script.prototype.popBool = function popBool() { - const op = this.pop(); - return op ? op.toBool() : false; -}; + setNum(index, num) { + return this.set(index, Opcode.fromNum(num)); + } -Script.prototype.shiftBool = function shiftBool() { - const op = this.shift(); - return op ? op.toBool() : false; -}; + pushNum(num) { + return this.push(Opcode.fromNum(num)); + } -Script.prototype.removeBool = function removeBool(index) { - const op = this.remove(index); - return op ? op.toBool() : false; -}; + unshiftNum(num) { + return this.unshift(Opcode.fromNum(num)); + } -Script.prototype.setBool = function setBool(index, value) { - return this.set(index, Opcode.fromBool(value)); -}; + insertNum(index, num) { + return this.insert(index, Opcode.fromNum(num)); + } -Script.prototype.pushBool = function pushBool(value) { - return this.push(Opcode.fromBool(value)); -}; + /* + * Int + */ -Script.prototype.unshiftBool = function unshiftBool(value) { - return this.unshift(Opcode.fromBool(value)); -}; + getInt(index, minimal, limit) { + const op = this.get(index); + return op ? op.toInt(minimal, limit) : -1; + } -Script.prototype.insertBool = function insertBool(index, value) { - return this.insert(index, Opcode.fromBool(value)); -}; + popInt(minimal, limit) { + const op = this.pop(); + return op ? op.toInt(minimal, limit) : -1; + } -/* - * Symbol - */ + shiftInt(minimal, limit) { + const op = this.shift(); + return op ? op.toInt(minimal, limit) : -1; + } -Script.prototype.getSym = function getSym(index) { - const op = this.get(index); - return op ? op.toSymbol() : null; -}; + removeInt(index, minimal, limit) { + const op = this.remove(index); + return op ? op.toInt(minimal, limit) : -1; + } -Script.prototype.popSym = function popSym() { - const op = this.pop(); - return op ? op.toSymbol() : null; -}; + setInt(index, num) { + return this.set(index, Opcode.fromInt(num)); + } -Script.prototype.shiftSym = function shiftSym() { - const op = this.shift(); - return op ? op.toSymbol() : null; -}; + pushInt(num) { + return this.push(Opcode.fromInt(num)); + } -Script.prototype.removeSym = function removeSym(index) { - const op = this.remove(index); - return op ? op.toSymbol() : null; -}; + unshiftInt(num) { + return this.unshift(Opcode.fromInt(num)); + } -Script.prototype.setSym = function setSym(index, symbol) { - return this.set(index, Opcode.fromSymbol(symbol)); -}; + insertInt(index, num) { + return this.insert(index, Opcode.fromInt(num)); + } -Script.prototype.pushSym = function pushSym(symbol) { - return this.push(Opcode.fromSymbol(symbol)); -}; + /* + * Bool + */ -Script.prototype.unshiftSym = function unshiftSym(symbol) { - return this.unshift(Opcode.fromSymbol(symbol)); -}; + getBool(index) { + const op = this.get(index); + return op ? op.toBool() : false; + } -Script.prototype.insertSym = function insertSym(index, symbol) { - return this.insert(index, Opcode.fromSymbol(symbol)); -}; + popBool() { + const op = this.pop(); + return op ? op.toBool() : false; + } -/** - * Inject properties from bitcoind test string. - * @private - * @param {String} items - Script string. - * @throws Parse error. - */ + shiftBool() { + const op = this.shift(); + return op ? op.toBool() : false; + } -Script.prototype.fromString = function fromString(code) { - assert(typeof code === 'string'); + removeBool(index) { + const op = this.remove(index); + return op ? op.toBool() : false; + } - code = code.trim(); + setBool(index, value) { + return this.set(index, Opcode.fromBool(value)); + } - if (code.length === 0) - return this; + pushBool(value) { + return this.push(Opcode.fromBool(value)); + } - const items = code.split(/\s+/); - const bw = new BufferWriter(); + unshiftBool(value) { + return this.unshift(Opcode.fromBool(value)); + } - for (const item of items) { - let symbol = item; + insertBool(index, value) { + return this.insert(index, Opcode.fromBool(value)); + } - if (!util.isUpperCase(symbol)) - symbol = symbol.toUpperCase(); + /* + * Symbol + */ - if (!util.startsWith(symbol, 'OP_')) - symbol = `OP_${symbol}`; + getSym(index) { + const op = this.get(index); + return op ? op.toSymbol() : null; + } - const value = opcodes[symbol]; + popSym() { + const op = this.pop(); + return op ? op.toSymbol() : null; + } - if (value == null) { - if (item[0] === '\'') { - assert(item[item.length - 1] === '\'', 'Invalid string.'); - const str = item.slice(1, -1); - const op = Opcode.fromString(str); - bw.writeBytes(op.toRaw()); - continue; - } + shiftSym() { + const op = this.shift(); + return op ? op.toSymbol() : null; + } - if (/^-?\d+$/.test(item)) { - const num = ScriptNum.fromString(item, 10); - const op = Opcode.fromNum(num); - bw.writeBytes(op.toRaw()); - continue; - } + removeSym(index) { + const op = this.remove(index); + return op ? op.toSymbol() : null; + } - assert(item.indexOf('0x') === 0, 'Unknown opcode.'); + setSym(index, symbol) { + return this.set(index, Opcode.fromSymbol(symbol)); + } - const hex = item.substring(2); - const data = Buffer.from(hex, 'hex'); + pushSym(symbol) { + return this.push(Opcode.fromSymbol(symbol)); + } - assert(data.length === hex.length / 2, 'Invalid hex string.'); + unshiftSym(symbol) { + return this.unshift(Opcode.fromSymbol(symbol)); + } - bw.writeBytes(data); + insertSym(index, symbol) { + return this.insert(index, Opcode.fromSymbol(symbol)); + } - continue; - } + /** + * Inject properties from bitcoind test string. + * @private + * @param {String} items - Script string. + * @throws Parse error. + */ - bw.writeU8(value); - } + fromString(code) { + assert(typeof code === 'string'); - return this.fromRaw(bw.render()); -}; + code = code.trim(); -/** - * Parse a bitcoind test script - * string into a script object. - * @param {String} items - Script string. - * @returns {Script} - * @throws Parse error. - */ + if (code.length === 0) + return this; -Script.fromString = function fromString(code) { - return new Script().fromString(code); -}; + const items = code.split(/\s+/); + const bw = bio.write(); -/** - * Verify an input and output script, and a witness if present. - * @param {Script} input - * @param {Witness} witness - * @param {Script} output - * @param {TX} tx - * @param {Number} index - * @param {Amount} value - * @param {VerifyFlags} flags - * @throws {ScriptError} - */ + for (const item of items) { + let symbol = item; -Script.verify = function verify(input, witness, output, tx, index, value, flags) { - if (flags == null) - flags = Script.flags.STANDARD_VERIFY_FLAGS; + if (symbol.charCodeAt(0) & 32) + symbol = symbol.toUpperCase(); - if (flags & Script.flags.VERIFY_SIGPUSHONLY) { - if (!input.isPushOnly()) - throw new ScriptError('SIG_PUSHONLY'); - } + if (!/^OP_/.test(symbol)) + symbol = `OP_${symbol}`; - // Setup a stack. - let stack = new Stack(); + const value = opcodes[symbol]; - // Execute the input script - input.execute(stack, flags, tx, index, value, 0); + if (value == null) { + if (item[0] === '\'') { + assert(item[item.length - 1] === '\'', 'Invalid string.'); + const str = item.slice(1, -1); + const op = Opcode.fromString(str); + bw.writeBytes(op.toRaw()); + continue; + } - // Copy the stack for P2SH - let copy; - if (flags & Script.flags.VERIFY_P2SH) - copy = stack.clone(); + if (/^-?\d+$/.test(item)) { + const num = ScriptNum.fromString(item, 10); + const op = Opcode.fromNum(num); + bw.writeBytes(op.toRaw()); + continue; + } - // Execute the previous output script. - output.execute(stack, flags, tx, index, value, 0); + assert(item.indexOf('0x') === 0, 'Unknown opcode.'); - // Verify the stack values. - if (stack.length === 0 || !stack.getBool(-1)) - throw new ScriptError('EVAL_FALSE'); + const hex = item.substring(2); + const data = Buffer.from(hex, 'hex'); - let hadWitness = false; + assert(data.length === hex.length / 2, 'Invalid hex string.'); - if ((flags & Script.flags.VERIFY_WITNESS) && output.isProgram()) { - hadWitness = true; + bw.writeBytes(data); - // Input script must be empty. - if (input.raw.length !== 0) - throw new ScriptError('WITNESS_MALLEATED'); + continue; + } - // Verify the program in the output script. - Script.verifyProgram(witness, output, flags, tx, index, value); + bw.writeU8(value); + } - // Force a cleanstack - stack.length = 1; + return this.fromRaw(bw.render()); } - // If the script is P2SH, execute the real output script - if ((flags & Script.flags.VERIFY_P2SH) && output.isScripthash()) { - // P2SH can only have push ops in the scriptSig - if (!input.isPushOnly()) - throw new ScriptError('SIG_PUSHONLY'); + /** + * Parse a bitcoind test script + * string into a script object. + * @param {String} items - Script string. + * @returns {Script} + * @throws Parse error. + */ + + static fromString(code) { + return new this().fromString(code); + } - // Reset the stack - stack = copy; + /** + * Verify an input and output script, and a witness if present. + * @param {Script} input + * @param {Witness} witness + * @param {Script} output + * @param {TX} tx + * @param {Number} index + * @param {Amount} value + * @param {VerifyFlags} flags + * @throws {ScriptError} + */ + + static verify(input, witness, output, tx, index, value, flags) { + if (flags == null) + flags = Script.flags.STANDARD_VERIFY_FLAGS; + + if (flags & Script.flags.VERIFY_SIGPUSHONLY) { + if (!input.isPushOnly()) + throw new ScriptError('SIG_PUSHONLY'); + } - // Stack should not be empty at this point - if (stack.length === 0) - throw new ScriptError('EVAL_FALSE'); + // Setup a stack. + let stack = new Stack(); - // Grab the real redeem script - const raw = stack.pop(); - const redeem = Script.fromRaw(raw); + // Execute the input script + input.execute(stack, flags, tx, index, value, 0); - // Execute the redeem script. - redeem.execute(stack, flags, tx, index, value, 0); + // Copy the stack for P2SH + let copy; + if (flags & Script.flags.VERIFY_P2SH) + copy = stack.clone(); - // Verify the the stack values. + // Execute the previous output script. + output.execute(stack, flags, tx, index, value, 0); + + // Verify the stack values. if (stack.length === 0 || !stack.getBool(-1)) throw new ScriptError('EVAL_FALSE'); - if ((flags & Script.flags.VERIFY_WITNESS) && redeem.isProgram()) { + let hadWitness = false; + + if ((flags & Script.flags.VERIFY_WITNESS) && output.isProgram()) { hadWitness = true; - // Input script must be exactly one push of the redeem script. - if (!input.raw.equals(Opcode.fromPush(raw).toRaw())) - throw new ScriptError('WITNESS_MALLEATED_P2SH'); + // Input script must be empty. + if (input.raw.length !== 0) + throw new ScriptError('WITNESS_MALLEATED'); - // Verify the program in the redeem script. - Script.verifyProgram(witness, redeem, flags, tx, index, value); + // Verify the program in the output script. + Script.verifyProgram(witness, output, flags, tx, index, value); - // Force a cleanstack. + // Force a cleanstack stack.length = 1; } - } - - // Ensure there is nothing left on the stack. - if (flags & Script.flags.VERIFY_CLEANSTACK) { - assert((flags & Script.flags.VERIFY_P2SH) !== 0); - if (stack.length !== 1) - throw new ScriptError('CLEANSTACK'); - } - // If we had a witness but no witness program, fail. - if (flags & Script.flags.VERIFY_WITNESS) { - assert((flags & Script.flags.VERIFY_P2SH) !== 0); - if (!hadWitness && witness.items.length > 0) - throw new ScriptError('WITNESS_UNEXPECTED'); - } -}; + // If the script is P2SH, execute the real output script + if ((flags & Script.flags.VERIFY_P2SH) && output.isScripthash()) { + // P2SH can only have push ops in the scriptSig + if (!input.isPushOnly()) + throw new ScriptError('SIG_PUSHONLY'); -/** - * Verify a witness program. This runs after regular script - * execution if a witness program is present. It will convert - * the witness to a stack and execute the program. - * @param {Witness} witness - * @param {Script} output - * @param {VerifyFlags} flags - * @param {TX} tx - * @param {Number} index - * @param {Amount} value - * @throws {ScriptError} - */ + // Reset the stack + stack = copy; -Script.verifyProgram = function verifyProgram(witness, output, flags, tx, index, value) { - const program = output.getProgram(); + // Stack should not be empty at this point + if (stack.length === 0) + throw new ScriptError('EVAL_FALSE'); - assert(program, 'verifyProgram called on non-witness-program.'); - assert((flags & Script.flags.VERIFY_WITNESS) !== 0); + // Grab the real redeem script + const raw = stack.pop(); + const redeem = Script.fromRaw(raw); - const stack = witness.toStack(); - let redeem; + // Execute the redeem script. + redeem.execute(stack, flags, tx, index, value, 0); - if (program.version === 0) { - if (program.data.length === 32) { - if (stack.length === 0) - throw new ScriptError('WITNESS_PROGRAM_WITNESS_EMPTY'); + // Verify the the stack values. + if (stack.length === 0 || !stack.getBool(-1)) + throw new ScriptError('EVAL_FALSE'); - const witnessScript = stack.pop(); + if ((flags & Script.flags.VERIFY_WITNESS) && redeem.isProgram()) { + hadWitness = true; - if (!digest.sha256(witnessScript).equals(program.data)) - throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); + // Input script must be exactly one push of the redeem script. + if (!input.raw.equals(Opcode.fromPush(raw).toRaw())) + throw new ScriptError('WITNESS_MALLEATED_P2SH'); - redeem = Script.fromRaw(witnessScript); - } else if (program.data.length === 20) { - if (stack.length !== 2) - throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); + // Verify the program in the redeem script. + Script.verifyProgram(witness, redeem, flags, tx, index, value); - redeem = Script.fromPubkeyhash(program.data); - } else { - // Failure on version=0 (bad program data length). - throw new ScriptError('WITNESS_PROGRAM_WRONG_LENGTH'); + // Force a cleanstack. + stack.length = 1; + } } - } else if ((flags & Script.flags.VERIFY_MAST) && program.version === 1) { - Script.verifyMast(program, stack, output, flags, tx, index); - return; - } else { - // Anyone can spend (we can return true here - // if we want to always relay these transactions). - // Otherwise, if we want to act like an "old" - // implementation and only accept them in blocks, - // we can use the regular output script which will - // succeed in a block, but fail in the mempool - // due to VERIFY_CLEANSTACK. - if (flags & Script.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) - throw new ScriptError('DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM'); - return; - } - - // Witnesses still have push limits. - for (let j = 0; j < stack.length; j++) { - if (stack.get(j).length > consensus.MAX_SCRIPT_PUSH) - throw new ScriptError('PUSH_SIZE'); - } - - // Verify the redeem script. - redeem.execute(stack, flags, tx, index, value, 1); - - // Verify the stack values. - if (stack.length !== 1 || !stack.getBool(-1)) - throw new ScriptError('EVAL_FALSE'); -}; -/** - * Verify a MAST witness program. - * @param {Program} program - * @param {Stack} stack - * @param {Script} output - * @param {VerifyFlags} flags - * @param {TX} tx - * @param {Number} index - * @param {Amount} value - * @throws {ScriptError} - */ + // Ensure there is nothing left on the stack. + if (flags & Script.flags.VERIFY_CLEANSTACK) { + assert((flags & Script.flags.VERIFY_P2SH) !== 0); + if (stack.length !== 1) + throw new ScriptError('CLEANSTACK'); + } -Script.verifyMast = function verifyMast(program, stack, output, flags, tx, index, value) { - assert(program.version === 1); - assert((flags & Script.flags.VERIFY_MAST) !== 0); + // If we had a witness but no witness program, fail. + if (flags & Script.flags.VERIFY_WITNESS) { + assert((flags & Script.flags.VERIFY_P2SH) !== 0); + if (!hadWitness && witness.items.length > 0) + throw new ScriptError('WITNESS_UNEXPECTED'); + } + } - if (stack.length < 4) - throw new ScriptError('INVALID_MAST_STACK'); + /** + * Verify a witness program. This runs after regular script + * execution if a witness program is present. It will convert + * the witness to a stack and execute the program. + * @param {Witness} witness + * @param {Script} output + * @param {VerifyFlags} flags + * @param {TX} tx + * @param {Number} index + * @param {Amount} value + * @throws {ScriptError} + */ + + static verifyProgram(witness, output, flags, tx, index, value) { + const program = output.getProgram(); + + assert(program, 'verifyProgram called on non-witness-program.'); + assert((flags & Script.flags.VERIFY_WITNESS) !== 0); + + const stack = witness.toStack(); + let redeem; + + if (program.version === 0) { + if (program.data.length === 32) { + if (stack.length === 0) + throw new ScriptError('WITNESS_PROGRAM_WITNESS_EMPTY'); - const metadata = stack.get(-1); - if (metadata.length < 1 || metadata.length > 5) - throw new ScriptError('INVALID_MAST_STACK'); + const witnessScript = stack.pop(); - const subscripts = metadata[0]; - if (subscripts === 0 || stack.length < subscripts + 3) - throw new ScriptError('INVALID_MAST_STACK'); + if (!sha256.digest(witnessScript).equals(program.data)) + throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); - let ops = subscripts; - let scriptRoot = new BufferWriter(); - scriptRoot.writeU8(subscripts); + redeem = Script.fromRaw(witnessScript); + } else if (program.data.length === 20) { + if (stack.length !== 2) + throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); - if (metadata[metadata.length - 1] === 0x00) - throw new ScriptError('INVALID_MAST_STACK'); + redeem = Script.fromPubkeyhash(program.data); + } else { + // Failure on version=0 (bad program data length). + throw new ScriptError('WITNESS_PROGRAM_WRONG_LENGTH'); + } + } else { + // Anyone can spend (we can return true here + // if we want to always relay these transactions). + // Otherwise, if we want to act like an "old" + // implementation and only accept them in blocks, + // we can use the regular output script which will + // succeed in a block, but fail in the mempool + // due to VERIFY_CLEANSTACK. + if (flags & Script.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) + throw new ScriptError('DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM'); + return; + } - let version = 0; + // Witnesses still have push limits. + for (let j = 0; j < stack.length; j++) { + if (stack.get(j).length > consensus.MAX_SCRIPT_PUSH) + throw new ScriptError('PUSH_SIZE'); + } - for (let j = 1; j < metadata.length; j++) - version |= metadata[j] << 8 * (j - 1); + // Verify the redeem script. + redeem.execute(stack, flags, tx, index, value, 1); - if (version < 0) - version += 0x100000000; + // Verify the stack values. + if (stack.length !== 1) + throw new ScriptError('CLEANSTACK'); - if (version > 0) { - if (flags & Script.flags.DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) - throw new ScriptError('DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM'); + if (!stack.getBool(-1)) + throw new ScriptError('EVAL_FALSE'); } - let mastRoot = new BufferWriter(); - mastRoot.writeU32(version); + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ - const pathdata = stack.get(-2); - - if (pathdata.length & 0x1f) - throw new ScriptError('INVALID_MAST_STACK'); - - const depth = pathdata.length >>> 5; - - if (depth > 32) - throw new ScriptError('INVALID_MAST_STACK'); - - ops += depth; - if (version === 0) { - if (ops > consensus.MAX_SCRIPT_OPS) - throw new ScriptError('OP_COUNT'); + fromReader(br) { + return this.fromRaw(br.readVarBytes()); } - const path = []; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} + */ - for (let j = 0; j < depth; j++) - path.push(pathdata.slice(j * 32, j * 32 + 32)); + fromRaw(data) { + const br = bio.read(data); - const posdata = stack.get(-3); + this.raw = data; - if (posdata.length > 4) - throw new ScriptError('INVALID_MAST_STACK'); + while (br.left()) + this.code.push(Opcode.fromReader(br)); - let pos = 0; - if (posdata.length > 0) { - if (posdata[posdata.length - 1] === 0x00) - throw new ScriptError('INVALID_MAST_STACK'); + return this; + } - for (let j = 0; j < posdata.length; j++) - pos |= posdata[j] << 8 * j; + /** + * Create a script from buffer reader. + * @param {BufferReader} br + * @param {String?} enc - Either `"hex"` or `null`. + * @returns {Script} + */ - if (pos < 0) - pos += 0x100000000; + static fromReader(br) { + return new this().fromReader(br); } - if (depth < 32) { - if (pos >= ((1 << depth) >>> 0)) - throw new ScriptError('INVALID_MAST_STACK'); + /** + * Create a script from a serialized buffer. + * @param {Buffer|String} data - Serialized script. + * @param {String?} enc - Either `"hex"` or `null`. + * @returns {Script} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); } - let scripts = new BufferWriter(); - scripts.writeBytes(output.raw); + /** + * Test whether an object a Script. + * @param {Object} obj + * @returns {Boolean} + */ - for (let j = 0; j < subscripts; j++) { - const script = stack.get(-(4 + j)); - if (version === 0) { - if ((scripts.offset + script.length) > consensus.MAX_SCRIPT_SIZE) - throw new ScriptError('SCRIPT_SIZE'); - } - scriptRoot.writeBytes(digest.hash256(script)); - scripts.writeBytes(script); + static isScript(obj) { + return obj instanceof Script; } +} - scriptRoot = digest.hash256(scriptRoot.render()); - scriptRoot = merkle.verifyBranch(scriptRoot, path, pos); - - mastRoot.writeBytes(scriptRoot); - mastRoot = digest.hash256(mastRoot.render()); - - if (!mastRoot.equals(program.data)) - throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); - - if (version === 0) { - stack.length -= 3 + subscripts; - - for (let j = 0; j < stack.length; j++) { - if (stack.get(j).length > consensus.MAX_SCRIPT_PUSH) - throw new ScriptError('PUSH_SIZE'); - } - - scripts = scripts.render(); - output = Script.fromRaw(scripts); - output.execute(stack, flags, tx, index, value, 1); +/** + * Script opcodes. + * @enum {Number} + * @default + */ - if (stack.length !== 0) - throw new ScriptError('EVAL_FALSE'); - } -}; +Script.opcodes = common.opcodes; /** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br + * Opcodes by value. + * @const {RevMap} */ -Script.prototype.fromReader = function fromReader(br) { - return this.fromRaw(br.readVarBytes()); -}; +Script.opcodesByVal = common.opcodesByVal; /** - * Inject properties from serialized data. - * @private - * @param {Buffer} + * Script and locktime flags. See {@link VerifyFlags}. + * @enum {Number} */ -Script.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); - - this.raw = data; +Script.flags = common.flags; - while (br.left()) - this.code.push(Opcode.fromReader(br)); +/** + * Sighash Types. + * @enum {SighashType} + * @default + */ - return this; -}; +Script.hashType = common.hashType; /** - * Create a script from buffer reader. - * @param {BufferReader} br - * @param {String?} enc - Either `"hex"` or `null`. - * @returns {Script} + * Sighash types by value. + * @const {RevMap} */ -Script.fromReader = function fromReader(br) { - return new Script().fromReader(br); -}; +Script.hashTypeByVal = common.hashTypeByVal; /** - * Create a script from a serialized buffer. - * @param {Buffer|String} data - Serialized script. - * @param {String?} enc - Either `"hex"` or `null`. - * @returns {Script} + * Output script types. + * @enum {Number} */ -Script.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new Script().fromRaw(data); -}; +Script.types = common.types; /** - * Test whether an object a Script. - * @param {Object} obj - * @returns {Boolean} + * Output script types by value. + * @const {RevMap} */ -Script.isScript = function isScript(obj) { - return obj instanceof Script; -}; +Script.typesByVal = common.typesByVal; /* * Helpers @@ -3614,7 +3470,7 @@ function validateSignature(sig, flags) { */ function checksig(msg, sig, key) { - return secp256k1.verify(msg, sig.slice(0, -1), key); + return secp256k1.verifyDER(msg, sig.slice(0, -1), key); } /* diff --git a/lib/script/scripterror.js b/lib/script/scripterror.js index d910c5241..b024533c3 100644 --- a/lib/script/scripterror.js +++ b/lib/script/scripterror.js @@ -7,44 +7,50 @@ 'use strict'; /** + * Script Error * An error thrown from the scripting system, * potentially pertaining to Script execution. * @alias module:script.ScriptError - * @constructor * @extends Error - * @param {String} code - Error code. - * @param {Opcode} op - Opcode. - * @param {Number?} ip - Instruction pointer. * @property {String} message - Error message. * @property {String} code - Original code passed in. * @property {Number} op - Opcode. * @property {Number} ip - Instruction pointer. */ -function ScriptError(code, op, ip) { - if (!(this instanceof ScriptError)) - return new ScriptError(code, op, ip); - - Error.call(this); - - this.type = 'ScriptError'; - this.code = code; - this.message = code; - this.op = -1; - this.ip = -1; - - if (typeof op === 'string') { - this.message = op; - } else if (op) { - this.message = `${code} (op=${op.toSymbol()}, ip=${ip})`; - this.op = op.value; - this.ip = ip; +class ScriptError extends Error { + /** + * Create an error. + * @constructor + * @param {String} code - Error code. + * @param {Opcode} op - Opcode. + * @param {Number?} ip - Instruction pointer. + */ + + constructor(code, op, ip) { + super(); + + this.type = 'ScriptError'; + this.code = code; + this.message = code; + this.op = -1; + this.ip = -1; + + if (typeof op === 'string') { + this.message = op; + } else if (op) { + this.message = `${code} (op=${op.toSymbol()}, ip=${ip})`; + this.op = op.value; + this.ip = ip; + } + + if (Error.captureStackTrace) + Error.captureStackTrace(this, ScriptError); } +} - if (Error.captureStackTrace) - Error.captureStackTrace(this, ScriptError); -}; - -Object.setPrototypeOf(ScriptError.prototype, Error.prototype); +/* + * Expose + */ module.exports = ScriptError; diff --git a/lib/script/scriptnum.js b/lib/script/scriptnum.js index bd2cd0d7b..0631fe06a 100644 --- a/lib/script/scriptnum.js +++ b/lib/script/scriptnum.js @@ -6,9 +6,10 @@ 'use strict'; -const assert = require('assert'); -const {I64} = require('../utils/int64'); +const assert = require('bsert'); +const {I64} = require('n64'); const ScriptError = require('./scripterror'); +const {inspectSymbol} = require('../utils'); /* * Constants @@ -20,237 +21,237 @@ const EMPTY_ARRAY = Buffer.alloc(0); * Script Number * @see https://github.com/chjj/n64 * @alias module:script.ScriptNum - * @constructor - * @param {(Number|String|Buffer|Object)?} num - * @param {(String|Number)?} base * @property {Number} hi * @property {Number} lo * @property {Number} sign */ -function ScriptNum(num, base) { - if (!(this instanceof ScriptNum)) - return new ScriptNum(num, base); +class ScriptNum extends I64 { + /** + * Create a script number. + * @constructor + * @param {(Number|String|Buffer|Object)?} num + * @param {(String|Number)?} base + */ - I64.call(this, num, base); -} - -Object.setPrototypeOf(ScriptNum, I64); -Object.setPrototypeOf(ScriptNum.prototype, I64.prototype); - -/** - * Cast to int32. - * @returns {Number} - */ - -ScriptNum.prototype.getInt = function getInt() { - if (this.lt(I64.INT32_MIN)) - return I64.LONG_MIN; - - if (this.gt(I64.INT32_MAX)) - return I64.LONG_MAX; - - return this.toInt(); -}; - -/** - * Serialize script number. - * @returns {Buffer} - */ + constructor(num, base) { + super(num, base); + } -ScriptNum.prototype.toRaw = function toRaw() { - let num = this; + /** + * Cast to int32. + * @returns {Number} + */ - // Zeroes are always empty arrays. - if (num.isZero()) - return EMPTY_ARRAY; + getInt() { + if (this.lt(I64.INT32_MIN)) + return I64.LONG_MIN; - // Need to append sign bit. - let neg = false; - if (num.isNeg()) { - num = num.neg(); - neg = true; - } + if (this.gt(I64.INT32_MAX)) + return I64.LONG_MAX; - // Calculate size. - const size = num.byteLength(); - - let offset = 0; - - if (num.testn((size * 8) - 1)) - offset = 1; - - // Write number. - const data = Buffer.allocUnsafe(size + offset); - - switch (size) { - case 8: - data[7] = (num.hi >>> 24) & 0xff; - case 7: - data[6] = (num.hi >> 16) & 0xff; - case 6: - data[5] = (num.hi >> 8) & 0xff; - case 5: - data[4] = num.hi & 0xff; - case 4: - data[3] = (num.lo >>> 24) & 0xff; - case 3: - data[2] = (num.lo >> 16) & 0xff; - case 2: - data[1] = (num.lo >> 8) & 0xff; - case 1: - data[0] = num.lo & 0xff; + return this.toInt(); } - // Append sign bit. - if (data[size - 1] & 0x80) { - assert(offset === 1); - assert(data.length === size + offset); - data[size] = neg ? 0x80 : 0; - } else if (neg) { - assert(offset === 0); - assert(data.length === size); - data[size - 1] |= 0x80; - } else { - assert(offset === 0); - assert(data.length === size); + /** + * Serialize script number. + * @returns {Buffer} + */ + + toRaw() { + let num = this; + + // Zeroes are always empty arrays. + if (num.isZero()) + return EMPTY_ARRAY; + + // Need to append sign bit. + let neg = false; + if (num.isNeg()) { + num = num.neg(); + neg = true; + } + + // Calculate size. + const size = num.byteLength(); + + let offset = 0; + + if (num.testn((size * 8) - 1)) + offset = 1; + + // Write number. + const data = Buffer.allocUnsafe(size + offset); + + switch (size) { + case 8: + data[7] = (num.hi >>> 24) & 0xff; + case 7: + data[6] = (num.hi >> 16) & 0xff; + case 6: + data[5] = (num.hi >> 8) & 0xff; + case 5: + data[4] = num.hi & 0xff; + case 4: + data[3] = (num.lo >>> 24) & 0xff; + case 3: + data[2] = (num.lo >> 16) & 0xff; + case 2: + data[1] = (num.lo >> 8) & 0xff; + case 1: + data[0] = num.lo & 0xff; + } + + // Append sign bit. + if (data[size - 1] & 0x80) { + assert(offset === 1); + assert(data.length === size + offset); + data[size] = neg ? 0x80 : 0; + } else if (neg) { + assert(offset === 0); + assert(data.length === size); + data[size - 1] |= 0x80; + } else { + assert(offset === 0); + assert(data.length === size); + } + + return data; } - return data; -}; - -/** - * Instantiate script number from serialized data. - * @private - * @param {Buffer} data - * @returns {ScriptNum} - */ - -ScriptNum.prototype.fromRaw = function fromRaw(data) { - assert(Buffer.isBuffer(data)); + /** + * Instantiate script number from serialized data. + * @private + * @param {Buffer} data + * @returns {ScriptNum} + */ + + fromRaw(data) { + assert(Buffer.isBuffer(data)); + + // Empty arrays are always zero. + if (data.length === 0) + return this; + + // Read number (9 bytes max). + switch (data.length) { + case 8: + this.hi |= data[7] << 24; + case 7: + this.hi |= data[6] << 16; + case 6: + this.hi |= data[5] << 8; + case 5: + this.hi |= data[4]; + case 4: + this.lo |= data[3] << 24; + case 3: + this.lo |= data[2] << 16; + case 2: + this.lo |= data[1] << 8; + case 1: + this.lo |= data[0]; + break; + default: + for (let i = 0; i < data.length; i++) + this.orb(i, data[i]); + break; + } + + // Remove high bit and flip sign. + if (data[data.length - 1] & 0x80) { + this.setn((data.length * 8) - 1, 0); + this.ineg(); + } - // Empty arrays are always zero. - if (data.length === 0) return this; - - // Read number (9 bytes max). - switch (data.length) { - case 8: - this.hi |= data[7] << 24; - case 7: - this.hi |= data[6] << 16; - case 6: - this.hi |= data[5] << 8; - case 5: - this.hi |= data[4]; - case 4: - this.lo |= data[3] << 24; - case 3: - this.lo |= data[2] << 16; - case 2: - this.lo |= data[1] << 8; - case 1: - this.lo |= data[0]; - break; - default: - for (let i = 0; i < data.length; i++) - this.orb(i, data[i]); - break; } - // Remove high bit and flip sign. - if (data[data.length - 1] & 0x80) { - this.setn((data.length * 8) - 1, 0); - this.ineg(); + /** + * Serialize script number. + * @returns {Buffer} + */ + + encode() { + return this.toRaw(); } - return this; -}; + /** + * Decode and verify script number. + * @private + * @param {Buffer} data + * @param {Boolean?} minimal - Require minimal encoding. + * @param {Number?} limit - Size limit. + * @returns {ScriptNum} + */ -/** - * Serialize script number. - * @returns {Buffer} - */ + decode(data, minimal, limit) { + assert(Buffer.isBuffer(data)); -ScriptNum.prototype.encode = function encode() { - return this.toRaw(); -}; + if (limit != null && data.length > limit) + throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.'); -/** - * Decode and verify script number. - * @private - * @param {Buffer} data - * @param {Boolean?} minimal - Require minimal encoding. - * @param {Number?} limit - Size limit. - * @returns {ScriptNum} - */ + if (minimal && !ScriptNum.isMinimal(data)) + throw new ScriptError('UNKNOWN_ERROR', 'Non-minimal script number.'); -ScriptNum.prototype.decode = function decode(data, minimal, limit) { - assert(Buffer.isBuffer(data)); + return this.fromRaw(data); + } - if (limit != null && data.length > limit) - throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.'); + /** + * Inspect script number. + * @returns {String} + */ - if (minimal && !ScriptNum.isMinimal(data)) - throw new ScriptError('UNKNOWN_ERROR', 'Non-minimal script number.'); + [inspectSymbol]() { + return ``; + } - return this.fromRaw(data); -}; + /** + * Test wether a serialized script + * number is in its most minimal form. + * @param {Buffer} data + * @returns {Boolean} + */ -/** - * Inspect script number. - * @returns {String} - */ + static isMinimal(data) { + assert(Buffer.isBuffer(data)); -ScriptNum.prototype.inspect = function inspect() { - return ``; -}; + if (data.length === 0) + return true; -/** - * Test wether a serialized script - * number is in its most minimal form. - * @param {Buffer} data - * @returns {Boolean} - */ + if ((data[data.length - 1] & 0x7f) === 0) { + if (data.length === 1) + return false; -ScriptNum.isMinimal = function isMinimal(data) { - assert(Buffer.isBuffer(data)); + if ((data[data.length - 2] & 0x80) === 0) + return false; + } - if (data.length === 0) return true; - - if ((data[data.length - 1] & 0x7f) === 0) { - if (data.length === 1) - return false; - - if ((data[data.length - 2] & 0x80) === 0) - return false; } - return true; -}; + /** + * Decode and verify script number. + * @param {Buffer} data + * @param {Boolean?} minimal - Require minimal encoding. + * @param {Number?} limit - Size limit. + * @returns {ScriptNum} + */ -/** - * Decode and verify script number. - * @param {Buffer} data - * @param {Boolean?} minimal - Require minimal encoding. - * @param {Number?} limit - Size limit. - * @returns {ScriptNum} - */ + static decode(data, minimal, limit) { + return new this().decode(data, minimal, limit); + } -ScriptNum.decode = function decode(data, minimal, limit) { - return new ScriptNum().decode(data, minimal, limit); -}; + /** + * Test whether object is a script number. + * @param {Object} obj + * @returns {Boolean} + */ -/** - * Test whether object is a script number. - * @param {Object} obj - * @returns {Boolean} - */ - -ScriptNum.isScriptNum = function isScriptNum(obj) { - return obj instanceof ScriptNum; -}; + static isScriptNum(obj) { + return obj instanceof ScriptNum; + } +} /* * Expose diff --git a/lib/script/sigcache.js b/lib/script/sigcache.js index 378613bd9..d2b20a0b6 100644 --- a/lib/script/sigcache.js +++ b/lib/script/sigcache.js @@ -6,141 +6,148 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); -const secp256k1 = require('../crypto/secp256k1'); +const assert = require('bsert'); +const {BufferMap} = require('buffer-map'); +const secp256k1 = require('bcrypto/lib/secp256k1'); /** * Signature cache. * @alias module:script.SigCache - * @constructor - * @param {Number} [size=10000] * @property {Number} size * @property {Hash[]} keys * @property {Object} valid */ -function SigCache(size) { - if (!(this instanceof SigCache)) - return new SigCache(size); +class SigCache { + /** + * Create a signature cache. + * @constructor + * @param {Number} [size=10000] + */ - if (size == null) - size = 10000; + constructor(size) { + if (size == null) + size = 10000; - assert(util.isU32(size)); + assert((size >>> 0) === size); - this.size = size; - this.keys = []; - this.valid = new Map(); -} - -/** - * Resize the sigcache. - * @param {Number} size - */ - -SigCache.prototype.resize = function resize(size) { - assert(util.isU32(size)); - - this.size = size; - this.keys.length = 0; - this.valid.clear(); -}; - -/** - * Add item to the sigcache. - * Potentially evict a random member. - * @param {Hash} hash - Sig hash. - * @param {Buffer} sig - * @param {Buffer} key - */ + this.size = size; + this.keys = []; + this.valid = new BufferMap(); + } -SigCache.prototype.add = function add(hash, sig, key) { - if (this.size === 0) - return; + /** + * Resize the sigcache. + * @param {Number} size + */ - this.valid.set(hash, new SigCacheEntry(sig, key)); + resize(size) { + assert((size >>> 0) === size); - if (this.keys.length >= this.size) { - const i = Math.floor(Math.random() * this.keys.length); - const k = this.keys[i]; - this.valid.delete(k); - this.keys[i] = hash; - } else { - this.keys.push(hash); + this.size = size; + this.keys.length = 0; + this.valid.clear(); } -}; -/** - * Test whether the sig exists. - * @param {Hash} hash - Sig hash. - * @param {Buffer} sig - * @param {Buffer} key - * @returns {Boolean} - */ + /** + * Add item to the sigcache. + * Potentially evict a random member. + * @param {Hash} msg - Sig hash. + * @param {Buffer} sig + * @param {Buffer} key + */ + + add(msg, sig, key) { + if (this.size === 0) + return; + + this.valid.set(msg, new SigCacheEntry(sig, key)); + + if (this.keys.length >= this.size) { + const i = Math.floor(Math.random() * this.keys.length); + const k = this.keys[i]; + this.valid.delete(k); + this.keys[i] = msg; + } else { + this.keys.push(msg); + } + } -SigCache.prototype.has = function has(hash, sig, key) { - const entry = this.valid.get(hash); + /** + * Test whether the sig exists. + * @param {Hash} msg - Sig hash. + * @param {Buffer} sig + * @param {Buffer} key + * @returns {Boolean} + */ - if (!entry) - return false; + has(msg, sig, key) { + const entry = this.valid.get(msg); - return entry.equals(sig, key); -}; + if (!entry) + return false; -/** - * Verify a signature, testing - * it against the cache first. - * @param {Buffer} msg - * @param {Buffer} sig - * @param {Buffer} key - * @returns {Boolean} - */ + return entry.equals(sig, key); + } -SigCache.prototype.verify = function verify(msg, sig, key) { - if (this.size === 0) - return secp256k1.verify(msg, sig, key); + /** + * Verify a signature, testing + * it against the cache first. + * @param {Buffer} msg + * @param {Buffer} sig + * @param {Buffer} key + * @returns {Boolean} + */ - const hash = msg.toString('hex'); + verify(msg, sig, key) { + if (this.size === 0) + return secp256k1.verifyDER(msg, sig, key); - if (this.has(hash, sig, key)) - return true; + if (this.has(msg, sig, key)) + return true; - const result = secp256k1.verify(msg, sig, key); + const result = secp256k1.verifyDER(msg, sig, key); - if (!result) - return false; + if (!result) + return false; - this.add(hash, sig, key); + this.add(msg, sig, key); - return true; -}; + return true; + } +} /** - * Signature cache entry. - * @constructor + * Signature Cache Entry * @ignore - * @param {Buffer} sig - * @param {Buffer} key * @property {Buffer} sig * @property {Buffer} key */ -function SigCacheEntry(sig, key) { - this.sig = Buffer.from(sig); - this.key = Buffer.from(key); -} +class SigCacheEntry { + /** + * Create a cache entry. + * @constructor + * @param {Buffer} sig + * @param {Buffer} key + */ + + constructor(sig, key) { + this.sig = Buffer.from(sig); + this.key = Buffer.from(key); + } -/** - * Compare an entry to a sig and key. - * @param {Buffer} sig - * @param {Buffer} key - * @returns {Boolean} - */ + /** + * Compare an entry to a sig and key. + * @param {Buffer} sig + * @param {Buffer} key + * @returns {Boolean} + */ -SigCacheEntry.prototype.equals = function equals(sig, key) { - return this.sig.equals(sig) && this.key.equals(key); -}; + equals(sig, key) { + return this.sig.equals(sig) && this.key.equals(key); + } +} /* * Expose diff --git a/lib/script/stack.js b/lib/script/stack.js index 18e62f9c0..4ec14e82d 100644 --- a/lib/script/stack.js +++ b/lib/script/stack.js @@ -7,557 +7,566 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const common = require('./common'); const ScriptNum = require('./scriptnum'); +const {inspectSymbol} = require('../utils'); /** + * Stack * Represents the stack of a Script during execution. * @alias module:script.Stack - * @constructor - * @param {Buffer[]?} items - Stack items. * @property {Buffer[]} items - Stack items. * @property {Number} length - Size of stack. */ -function Stack(items) { - if (!(this instanceof Stack)) - return new Stack(items); +class Stack { + /** + * Create a stack. + * @constructor + * @param {Buffer[]?} items - Stack items. + */ - this.items = items || []; -} + constructor(items) { + this.items = items || []; + } -/* - * Expose length setter and getter. - */ + /** + * Get length. + * @returns {Number} + */ -Object.defineProperty(Stack.prototype, 'length', { - get() { - return this.items.length; - }, - set(length) { - this.items.length = length; + get length() { return this.items.length; } -}); -/** - * Instantiate a key and value iterator. - * @returns {StackIterator} - */ + /** + * Set length. + * @param {Number} value + */ -Stack.prototype[Symbol.iterator] = function iterator() { - return this.items[Symbol.iterator](); -}; + set length(value) { + this.items.length = value; + } -/** - * Instantiate a value-only iterator. - * @returns {StackIterator} - */ + /** + * Instantiate a value-only iterator. + * @returns {StackIterator} + */ -Stack.prototype.values = function values() { - return this.items.values(); -}; + [Symbol.iterator]() { + return this.items[Symbol.iterator](); + } -/** - * Instantiate a key and value iterator. - * @returns {StackIterator} - */ + /** + * Instantiate a value-only iterator. + * @returns {StackIterator} + */ -Stack.prototype.entries = function entries() { - return this.items.entries(); -}; + values() { + return this.items.values(); + } -/** - * Inspect the stack. - * @returns {String} Human-readable stack. - */ + /** + * Instantiate a key and value iterator. + * @returns {StackIterator} + */ -Stack.prototype.inspect = function inspect() { - return ``; -}; + entries() { + return this.items.entries(); + } -/** - * Convert the stack to a string. - * @returns {String} Human-readable stack. - */ + /** + * Inspect the stack. + * @returns {String} Human-readable stack. + */ -Stack.prototype.toString = function toString() { - const out = []; + [inspectSymbol]() { + return ``; + } - for (const item of this.items) - out.push(item.toString('hex')); + /** + * Convert the stack to a string. + * @returns {String} Human-readable stack. + */ - return out.join(' '); -}; + toString() { + const out = []; -/** - * Format the stack as bitcoind asm. - * @param {Boolean?} decode - Attempt to decode hash types. - * @returns {String} Human-readable script. - */ + for (const item of this.items) + out.push(item.toString('hex')); -Stack.prototype.toASM = function toASM(decode) { - const out = []; + return out.join(' '); + } - for (const item of this.items) - out.push(common.toASM(item, decode)); + /** + * Format the stack as bitcoind asm. + * @param {Boolean?} decode - Attempt to decode hash types. + * @returns {String} Human-readable script. + */ - return out.join(' '); -}; + toASM(decode) { + const out = []; -/** - * Clone the stack. - * @returns {Stack} Cloned stack. - */ + for (const item of this.items) + out.push(common.toASM(item, decode)); -Stack.prototype.clone = function clone() { - return new Stack(this.items.slice()); -}; + return out.join(' '); + } -/** - * Clear the stack. - * @returns {Stack} - */ + /** + * Clone the stack. + * @returns {Stack} Cloned stack. + */ -Stack.prototype.clear = function clear() { - this.items.length = 0; - return this; -}; + clone() { + return new this.constructor(this.items.slice()); + } -/** - * Get a stack item by index. - * @param {Number} index - * @returns {Buffer|null} - */ + /** + * Clear the stack. + * @returns {Stack} + */ -Stack.prototype.get = function get(index) { - if (index < 0) - index += this.items.length; + clear() { + this.items.length = 0; + return this; + } - if (index < 0 || index >= this.items.length) - return null; + /** + * Get a stack item by index. + * @param {Number} index + * @returns {Buffer|null} + */ - return this.items[index]; -}; + get(index) { + if (index < 0) + index += this.items.length; -/** - * Pop a stack item. - * @see Array#pop - * @returns {Buffer|null} - */ + if (index < 0 || index >= this.items.length) + return null; -Stack.prototype.pop = function pop() { - const item = this.items.pop(); - return item || null; -}; + return this.items[index]; + } -/** - * Shift a stack item. - * @see Array#shift - * @returns {Buffer|null} - */ + /** + * Pop a stack item. + * @see Array#pop + * @returns {Buffer|null} + */ -Stack.prototype.shift = function shift() { - const item = this.items.shift(); - return item || null; -}; + pop() { + const item = this.items.pop(); + return item || null; + } -/** - * Remove an item. - * @param {Number} index - * @returns {Buffer} - */ + /** + * Shift a stack item. + * @see Array#shift + * @returns {Buffer|null} + */ -Stack.prototype.remove = function remove(index) { - if (index < 0) - index += this.items.length; + shift() { + const item = this.items.shift(); + return item || null; + } - if (index < 0 || index >= this.items.length) - return null; + /** + * Remove an item. + * @param {Number} index + * @returns {Buffer} + */ - const items = this.items.splice(index, 1); + remove(index) { + if (index < 0) + index += this.items.length; - if (items.length === 0) - return null; + if (index < 0 || index >= this.items.length) + return null; - return items[0]; -}; + const items = this.items.splice(index, 1); -/** - * Set stack item at index. - * @param {Number} index - * @param {Buffer} value - * @returns {Buffer} - */ + if (items.length === 0) + return null; -Stack.prototype.set = function set(index, item) { - if (index < 0) - index += this.items.length; + return items[0]; + } - assert(Buffer.isBuffer(item)); - assert(index >= 0 && index <= this.items.length); + /** + * Set stack item at index. + * @param {Number} index + * @param {Buffer} value + * @returns {Buffer} + */ - this.items[index] = item; + set(index, item) { + if (index < 0) + index += this.items.length; - return this; -}; + assert(Buffer.isBuffer(item)); + assert(index >= 0 && index <= this.items.length); -/** - * Push item onto stack. - * @see Array#push - * @param {Buffer} item - * @returns {Number} Stack size. - */ + this.items[index] = item; -Stack.prototype.push = function push(item) { - assert(Buffer.isBuffer(item)); - this.items.push(item); - return this; -}; + return this; + } -/** - * Unshift item from stack. - * @see Array#unshift - * @param {Buffer} item - * @returns {Number} - */ + /** + * Push item onto stack. + * @see Array#push + * @param {Buffer} item + * @returns {Number} Stack size. + */ + + push(item) { + assert(Buffer.isBuffer(item)); + this.items.push(item); + return this; + } -Stack.prototype.unshift = function unshift(item) { - assert(Buffer.isBuffer(item)); - this.items.unshift(item); - return this; -}; + /** + * Unshift item from stack. + * @see Array#unshift + * @param {Buffer} item + * @returns {Number} + */ + + unshift(item) { + assert(Buffer.isBuffer(item)); + this.items.unshift(item); + return this; + } -/** - * Insert an item. - * @param {Number} index - * @param {Buffer} item - * @returns {Buffer} - */ + /** + * Insert an item. + * @param {Number} index + * @param {Buffer} item + * @returns {Buffer} + */ -Stack.prototype.insert = function insert(index, item) { - if (index < 0) - index += this.items.length; + insert(index, item) { + if (index < 0) + index += this.items.length; - assert(Buffer.isBuffer(item)); - assert(index >= 0 && index <= this.items.length); + assert(Buffer.isBuffer(item)); + assert(index >= 0 && index <= this.items.length); - this.items.splice(index, 0, item); + this.items.splice(index, 0, item); - return this; -}; + return this; + } -/** - * Erase stack items. - * @param {Number} start - * @param {Number} end - * @returns {Buffer[]} - */ + /** + * Erase stack items. + * @param {Number} start + * @param {Number} end + * @returns {Buffer[]} + */ -Stack.prototype.erase = function erase(start, end) { - if (start < 0) - start = this.items.length + start; + erase(start, end) { + if (start < 0) + start = this.items.length + start; - if (end < 0) - end = this.items.length + end; + if (end < 0) + end = this.items.length + end; - this.items.splice(start, end - start); -}; + this.items.splice(start, end - start); + } -/** - * Swap stack values. - * @param {Number} i1 - Index 1. - * @param {Number} i2 - Index 2. - */ + /** + * Swap stack values. + * @param {Number} i1 - Index 1. + * @param {Number} i2 - Index 2. + */ -Stack.prototype.swap = function swap(i1, i2) { - if (i1 < 0) - i1 = this.items.length + i1; + swap(i1, i2) { + if (i1 < 0) + i1 = this.items.length + i1; - if (i2 < 0) - i2 = this.items.length + i2; + if (i2 < 0) + i2 = this.items.length + i2; - const v1 = this.items[i1]; - const v2 = this.items[i2]; + const v1 = this.items[i1]; + const v2 = this.items[i2]; - this.items[i1] = v2; - this.items[i2] = v1; -}; + this.items[i1] = v2; + this.items[i2] = v1; + } -/* - * Data - */ + /* + * Data + */ -Stack.prototype.getData = function getData(index) { - return this.get(index); -}; + getData(index) { + return this.get(index); + } -Stack.prototype.popData = function popData() { - return this.pop(); -}; + popData() { + return this.pop(); + } -Stack.prototype.shiftData = function shiftData() { - return this.shift(); -}; + shiftData() { + return this.shift(); + } -Stack.prototype.removeData = function removeData(index) { - return this.remove(index); -}; + removeData(index) { + return this.remove(index); + } -Stack.prototype.setData = function setData(index, data) { - return this.set(index, data); -}; + setData(index, data) { + return this.set(index, data); + } -Stack.prototype.pushData = function pushData(data) { - return this.push(data); -}; + pushData(data) { + return this.push(data); + } -Stack.prototype.unshiftData = function unshiftData(data) { - return this.unshift(data); -}; + unshiftData(data) { + return this.unshift(data); + } -Stack.prototype.insertData = function insertData(index, data) { - return this.insert(index, data); -}; + insertData(index, data) { + return this.insert(index, data); + } -/* - * Length - */ + /* + * Length + */ -Stack.prototype.getLength = function getLength(index) { - const item = this.get(index); - return item ? item.length : -1; -}; + getLength(index) { + const item = this.get(index); + return item ? item.length : -1; + } -/* - * String - */ + /* + * String + */ -Stack.prototype.getString = function getString(index, enc) { - const item = this.get(index); - return item ? Stack.toString(item, enc) : null; -}; + getString(index, enc) { + const item = this.get(index); + return item ? Stack.toString(item, enc) : null; + } -Stack.prototype.popString = function popString(enc) { - const item = this.pop(); - return item ? Stack.toString(item, enc) : null; -}; + popString(enc) { + const item = this.pop(); + return item ? Stack.toString(item, enc) : null; + } -Stack.prototype.shiftString = function shiftString(enc) { - const item = this.shift(); - return item ? Stack.toString(item, enc) : null; -}; + shiftString(enc) { + const item = this.shift(); + return item ? Stack.toString(item, enc) : null; + } -Stack.prototype.removeString = function removeString(index, enc) { - const item = this.remove(index); - return item ? Stack.toString(item, enc) : null; -}; + removeString(index, enc) { + const item = this.remove(index); + return item ? Stack.toString(item, enc) : null; + } -Stack.prototype.setString = function setString(index, str, enc) { - return this.set(index, Stack.fromString(str, enc)); -}; + setString(index, str, enc) { + return this.set(index, Stack.fromString(str, enc)); + } -Stack.prototype.pushString = function pushString(str, enc) { - return this.push(Stack.fromString(str, enc)); -}; + pushString(str, enc) { + return this.push(Stack.fromString(str, enc)); + } -Stack.prototype.unshiftString = function unshiftString(str, enc) { - return this.unshift(Stack.fromString(str, enc)); -}; + unshiftString(str, enc) { + return this.unshift(Stack.fromString(str, enc)); + } -Stack.prototype.insertString = function insertString(index, str, enc) { - return this.insert(index, Stack.fromString(str, enc)); -}; + insertString(index, str, enc) { + return this.insert(index, Stack.fromString(str, enc)); + } -/* - * Num - */ + /* + * Num + */ -Stack.prototype.getNum = function getNum(index, minimal, limit) { - const item = this.get(index); - return item ? Stack.toNum(item, minimal, limit) : null; -}; + getNum(index, minimal, limit) { + const item = this.get(index); + return item ? Stack.toNum(item, minimal, limit) : null; + } -Stack.prototype.popNum = function popNum(minimal, limit) { - const item = this.pop(); - return item ? Stack.toNum(item, minimal, limit) : null; -}; + popNum(minimal, limit) { + const item = this.pop(); + return item ? Stack.toNum(item, minimal, limit) : null; + } -Stack.prototype.shiftNum = function shiftNum(minimal, limit) { - const item = this.shift(); - return item ? Stack.toNum(item, minimal, limit) : null; -}; + shiftNum(minimal, limit) { + const item = this.shift(); + return item ? Stack.toNum(item, minimal, limit) : null; + } -Stack.prototype.removeNum = function removeNum(index, minimal, limit) { - const item = this.remove(index); - return item ? Stack.toNum(item, minimal, limit) : null; -}; + removeNum(index, minimal, limit) { + const item = this.remove(index); + return item ? Stack.toNum(item, minimal, limit) : null; + } -Stack.prototype.setNum = function setNum(index, num) { - return this.set(index, Stack.fromNum(num)); -}; + setNum(index, num) { + return this.set(index, Stack.fromNum(num)); + } -Stack.prototype.pushNum = function pushNum(num) { - return this.push(Stack.fromNum(num)); -}; + pushNum(num) { + return this.push(Stack.fromNum(num)); + } -Stack.prototype.unshiftNum = function unshiftNum(num) { - return this.unshift(Stack.fromNum(num)); -}; + unshiftNum(num) { + return this.unshift(Stack.fromNum(num)); + } -Stack.prototype.insertNum = function insertNum(index, num) { - return this.insert(index, Stack.fromNum(num)); -}; + insertNum(index, num) { + return this.insert(index, Stack.fromNum(num)); + } -/* - * Int - */ + /* + * Int + */ -Stack.prototype.getInt = function getInt(index, minimal, limit) { - const item = this.get(index); - return item ? Stack.toInt(item, minimal, limit) : -1; -}; + getInt(index, minimal, limit) { + const item = this.get(index); + return item ? Stack.toInt(item, minimal, limit) : -1; + } -Stack.prototype.popInt = function popInt(minimal, limit) { - const item = this.pop(); - return item ? Stack.toInt(item, minimal, limit) : -1; -}; + popInt(minimal, limit) { + const item = this.pop(); + return item ? Stack.toInt(item, minimal, limit) : -1; + } -Stack.prototype.shiftInt = function shiftInt(minimal, limit) { - const item = this.shift(); - return item ? Stack.toInt(item, minimal, limit) : -1; -}; + shiftInt(minimal, limit) { + const item = this.shift(); + return item ? Stack.toInt(item, minimal, limit) : -1; + } -Stack.prototype.removeInt = function removeInt(index, minimal, limit) { - const item = this.remove(index); - return item ? Stack.toInt(item, minimal, limit) : -1; -}; + removeInt(index, minimal, limit) { + const item = this.remove(index); + return item ? Stack.toInt(item, minimal, limit) : -1; + } -Stack.prototype.setInt = function setInt(index, num) { - return this.set(index, Stack.fromInt(num)); -}; + setInt(index, num) { + return this.set(index, Stack.fromInt(num)); + } -Stack.prototype.pushInt = function pushInt(num) { - return this.push(Stack.fromInt(num)); -}; + pushInt(num) { + return this.push(Stack.fromInt(num)); + } -Stack.prototype.unshiftInt = function unshiftInt(num) { - return this.unshift(Stack.fromInt(num)); -}; + unshiftInt(num) { + return this.unshift(Stack.fromInt(num)); + } -Stack.prototype.insertInt = function insertInt(index, num) { - return this.insert(index, Stack.fromInt(num)); -}; + insertInt(index, num) { + return this.insert(index, Stack.fromInt(num)); + } -/* - * Bool - */ + /* + * Bool + */ -Stack.prototype.getBool = function getBool(index) { - const item = this.get(index); - return item ? Stack.toBool(item) : false; -}; + getBool(index) { + const item = this.get(index); + return item ? Stack.toBool(item) : false; + } -Stack.prototype.popBool = function popBool() { - const item = this.pop(); - return item ? Stack.toBool(item) : false; -}; + popBool() { + const item = this.pop(); + return item ? Stack.toBool(item) : false; + } -Stack.prototype.shiftBool = function shiftBool() { - const item = this.shift(); - return item ? Stack.toBool(item) : false; -}; + shiftBool() { + const item = this.shift(); + return item ? Stack.toBool(item) : false; + } -Stack.prototype.removeBool = function removeBool(index) { - const item = this.remove(index); - return item ? Stack.toBool(item) : false; -}; + removeBool(index) { + const item = this.remove(index); + return item ? Stack.toBool(item) : false; + } -Stack.prototype.setBool = function setBool(index, value) { - return this.set(index, Stack.fromBool(value)); -}; + setBool(index, value) { + return this.set(index, Stack.fromBool(value)); + } -Stack.prototype.pushBool = function pushBool(value) { - return this.push(Stack.fromBool(value)); -}; + pushBool(value) { + return this.push(Stack.fromBool(value)); + } -Stack.prototype.unshiftBool = function unshiftBool(value) { - return this.unshift(Stack.fromBool(value)); -}; + unshiftBool(value) { + return this.unshift(Stack.fromBool(value)); + } -Stack.prototype.insertBool = function insertBool(index, value) { - return this.insert(index, Stack.fromBool(value)); -}; + insertBool(index, value) { + return this.insert(index, Stack.fromBool(value)); + } -/** - * Test an object to see if it is a Stack. - * @param {Object} obj - * @returns {Boolean} - */ + /** + * Test an object to see if it is a Stack. + * @param {Object} obj + * @returns {Boolean} + */ -Stack.isStack = function isStack(obj) { - return obj instanceof Stack; -}; + static isStack(obj) { + return obj instanceof Stack; + } -/* - * Encoding - */ + /* + * Encoding + */ -Stack.toString = function toString(item, enc) { - assert(Buffer.isBuffer(item)); - return item.toString(enc || 'utf8'); -}; + static toString(item, enc) { + assert(Buffer.isBuffer(item)); + return item.toString(enc || 'utf8'); + } -Stack.fromString = function fromString(str, enc) { - assert(typeof str === 'string'); - return Buffer.from(str, enc || 'utf8'); -}; + static fromString(str, enc) { + assert(typeof str === 'string'); + return Buffer.from(str, enc || 'utf8'); + } -Stack.toNum = function toNum(item, minimal, limit) { - return ScriptNum.decode(item, minimal, limit); -}; + static toNum(item, minimal, limit) { + return ScriptNum.decode(item, minimal, limit); + } -Stack.fromNum = function fromNum(num) { - assert(ScriptNum.isScriptNum(num)); - return num.encode(); -}; + static fromNum(num) { + assert(ScriptNum.isScriptNum(num)); + return num.encode(); + } -Stack.toInt = function toInt(item, minimal, limit) { - const num = Stack.toNum(item, minimal, limit); - return num.getInt(); -}; + static toInt(item, minimal, limit) { + const num = Stack.toNum(item, minimal, limit); + return num.getInt(); + } -Stack.fromInt = function fromInt(int) { - assert(typeof int === 'number'); + static fromInt(int) { + assert(typeof int === 'number'); - if (int >= -1 && int <= 16) - return common.small[int + 1]; + if (int >= -1 && int <= 16) + return common.small[int + 1]; - const num = ScriptNum.fromNumber(int); + const num = ScriptNum.fromNumber(int); - return Stack.fromNum(num); -}; + return Stack.fromNum(num); + } -Stack.toBool = function toBool(item) { - assert(Buffer.isBuffer(item)); + static toBool(item) { + assert(Buffer.isBuffer(item)); - for (let i = 0; i < item.length; i++) { - if (item[i] !== 0) { - // Cannot be negative zero - if (i === item.length - 1 && item[i] === 0x80) - return false; - return true; + for (let i = 0; i < item.length; i++) { + if (item[i] !== 0) { + // Cannot be negative zero + if (i === item.length - 1 && item[i] === 0x80) + return false; + return true; + } } - } - return false; -}; + return false; + } -Stack.fromBool = function fromBool(value) { - assert(typeof value === 'boolean'); - return Stack.fromInt(value ? 1 : 0); -}; + static fromBool(value) { + assert(typeof value === 'boolean'); + return Stack.fromInt(value ? 1 : 0); + } +} /* * Expose diff --git a/lib/script/witness.js b/lib/script/witness.js index 001f9ef34..27d8bc3cd 100644 --- a/lib/script/witness.js +++ b/lib/script/witness.js @@ -7,538 +7,550 @@ 'use strict'; -const assert = require('assert'); -const util = require('../utils/util'); +const assert = require('bsert'); +const bio = require('bufio'); const Script = require('./script'); const common = require('./common'); -const encoding = require('../utils/encoding'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); const Address = require('../primitives/address'); const Stack = require('./stack'); +const {encoding} = bio; const scriptTypes = common.types; +const {inspectSymbol} = require('../utils'); /** - * Refers to the witness field of segregated witness transactions. + * Witness + * Refers to the witness vector of + * segregated witness transactions. * @alias module:script.Witness - * @constructor - * @param {Buffer[]|NakedWitness} items - Array of - * stack items. + * @extends Stack * @property {Buffer[]} items * @property {Script?} redeem * @property {Number} length */ -function Witness(options) { - if (!(this instanceof Witness)) - return new Witness(options); +class Witness extends Stack { + /** + * Create a witness. + * @alias module:script.Witness + * @constructor + * @param {Buffer[]|Object} items - Array of + * stack items. + * @property {Buffer[]} items + * @property {Script?} redeem + * @property {Number} length + */ - Stack.call(this, []); + constructor(options) { + super(); - if (options) - this.fromOptions(options); -} + if (options) + this.fromOptions(options); + } -Object.setPrototypeOf(Witness.prototype, Stack.prototype); + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ + fromOptions(options) { + assert(options, 'Witness data is required.'); -Witness.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Witness data is required.'); + if (Array.isArray(options)) + return this.fromArray(options); - if (Array.isArray(options)) - return this.fromArray(options); + if (options.items) + return this.fromArray(options.items); - if (options.items) - return this.fromArray(options.items); + return this; + } - return this; -}; + /** + * Instantiate witness from options. + * @param {Object} options + * @returns {Witness} + */ -/** - * Instantiate witness from options. - * @param {Object} options - * @returns {Witness} - */ + static fromOptions(options) { + return new this().fromOptions(options); + } -Witness.fromOptions = function fromOptions(options) { - return new Witness().fromOptions(options); -}; + /** + * Convert witness to an array of buffers. + * @returns {Buffer[]} + */ -/** - * Convert witness to an array of buffers. - * @returns {Buffer[]} - */ + toArray() { + return this.items.slice(); + } -Witness.prototype.toArray = function toArray() { - return this.items.slice(); -}; + /** + * Inject properties from an array of buffers. + * @private + * @param {Buffer[]} items + */ -/** - * Inject properties from an array of buffers. - * @private - * @param {Buffer[]} items - */ + fromArray(items) { + assert(Array.isArray(items)); + this.items = items; + return this; + } -Witness.prototype.fromArray = function fromArray(items) { - assert(Array.isArray(items)); - this.items = items; - return this; -}; + /** + * Insantiate witness from an array of buffers. + * @param {Buffer[]} items + * @returns {Witness} + */ -/** - * Insantiate witness from an array of buffers. - * @param {Buffer[]} items - * @returns {Witness} - */ + static fromArray(items) { + return new this().fromArray(items); + } -Witness.fromArray = function fromArray(items) { - return new Witness().fromArray(items); -}; + /** + * Convert witness to an array of buffers. + * @returns {Buffer[]} + */ -/** - * Convert witness to an array of buffers. - * @returns {Buffer[]} - */ + toItems() { + return this.items.slice(); + } -Witness.prototype.toItems = function toItems() { - return this.items.slice(); -}; + /** + * Inject properties from an array of buffers. + * @private + * @param {Buffer[]} items + */ -/** - * Inject properties from an array of buffers. - * @private - * @param {Buffer[]} items - */ + fromItems(items) { + assert(Array.isArray(items)); + this.items = items; + return this; + } -Witness.prototype.fromItems = function fromItems(items) { - assert(Array.isArray(items)); - this.items = items; - return this; -}; + /** + * Insantiate witness from an array of buffers. + * @param {Buffer[]} items + * @returns {Witness} + */ -/** - * Insantiate witness from an array of buffers. - * @param {Buffer[]} items - * @returns {Witness} - */ + static fromItems(items) { + return new this().fromItems(items); + } -Witness.fromItems = function fromItems(items) { - return new Witness().fromItems(items); -}; + /** + * Convert witness to a stack. + * @returns {Stack} + */ -/** - * Convert witness to a stack. - * @returns {Stack} - */ - -Witness.prototype.toStack = function toStack() { - return new Stack(this.toArray()); -}; + toStack() { + return new Stack(this.toArray()); + } -/** - * Inject properties from a stack. - * @private - * @param {Stack} stack - */ + /** + * Inject properties from a stack. + * @private + * @param {Stack} stack + */ -Witness.prototype.fromStack = function fromStack(stack) { - return this.fromArray(stack.items); -}; + fromStack(stack) { + return this.fromArray(stack.items); + } -/** - * Insantiate witness from a stack. - * @param {Stack} stack - * @returns {Witness} - */ + /** + * Insantiate witness from a stack. + * @param {Stack} stack + * @returns {Witness} + */ -Witness.fromStack = function fromStack(stack) { - return new Witness().fromStack(stack); -}; + static fromStack(stack) { + return new this().fromStack(stack); + } -/** - * Inspect a Witness object. - * @returns {String} Human-readable script. - */ + /** + * Inspect a Witness object. + * @returns {String} Human-readable script. + */ -Witness.prototype.inspect = function inspect() { - return ``; -}; + [inspectSymbol]() { + return ``; + } -/** - * Clone the witness object. - * @returns {Witness} A clone of the current witness object. - */ + /** + * Clone the witness object. + * @returns {Witness} A clone of the current witness object. + */ -Witness.prototype.clone = function clone() { - return new Witness().inject(this); -}; + clone() { + return new this.constructor().inject(this); + } -/** - * Inject properties from witness. - * Used for cloning. - * @private - * @param {Witness} witness - * @returns {Witness} - */ + /** + * Inject properties from witness. + * Used for cloning. + * @private + * @param {Witness} witness + * @returns {Witness} + */ + + inject(witness) { + this.items = witness.items.slice(); + return this; + } -Witness.prototype.inject = function inject(witness) { - this.items = witness.items.slice(); - return this; -}; + /** + * Compile witness (NOP). + * @returns {Witness} + */ -/** - * Compile witness (NOP). - * @returns {Witness} - */ + compile() { + return this; + } -Witness.prototype.compile = function compile() { - return this; -}; + /** + * "Guess" the type of the witness. + * This method is not 100% reliable. + * @returns {ScriptType} + */ -/** - * "Guess" the type of the witness. - * This method is not 100% reliable. - * @returns {ScriptType} - */ + getInputType() { + if (this.isPubkeyhashInput()) + return scriptTypes.WITNESSPUBKEYHASH; -Witness.prototype.getInputType = function getInputType() { - if (this.isPubkeyhashInput()) - return scriptTypes.WITNESSPUBKEYHASH; + if (this.isScripthashInput()) + return scriptTypes.WITNESSSCRIPTHASH; - if (this.isScripthashInput()) - return scriptTypes.WITNESSSCRIPTHASH; + return scriptTypes.NONSTANDARD; + } - return scriptTypes.NONSTANDARD; -}; + /** + * "Guess" the address of the witness. + * This method is not 100% reliable. + * @returns {Address|null} + */ -/** - * "Guess" the address of the witness. - * This method is not 100% reliable. - * @returns {Address|null} - */ + getInputAddress() { + return Address.fromWitness(this); + } -Witness.prototype.getInputAddress = function getInputAddress() { - return Address.fromWitness(this); -}; + /** + * "Test" whether the witness is a pubkey input. + * Always returns false. + * @returns {Boolean} + */ -/** - * "Test" whether the witness is a pubkey input. - * Always returns false. - * @returns {Boolean} - */ + isPubkeyInput() { + return false; + } -Witness.prototype.isPubkeyInput = function isPubkeyInput() { - return false; -}; + /** + * Get P2PK signature if present. + * Always returns null. + * @returns {Buffer|null} + */ -/** - * Get P2PK signature if present. - * Always returns null. - * @returns {Buffer|null} - */ + getPubkeyInput() { + return null; + } -Witness.prototype.getPubkeyInput = function getPubkeyInput() { - return null; -}; + /** + * "Guess" whether the witness is a pubkeyhash input. + * This method is not 100% reliable. + * @returns {Boolean} + */ -/** - * "Guess" whether the witness is a pubkeyhash input. - * This method is not 100% reliable. - * @returns {Boolean} - */ + isPubkeyhashInput() { + return this.items.length === 2 + && common.isSignatureEncoding(this.items[0]) + && common.isKeyEncoding(this.items[1]); + } -Witness.prototype.isPubkeyhashInput = function isPubkeyhashInput() { - return this.items.length === 2 - && common.isSignatureEncoding(this.items[0]) - && common.isKeyEncoding(this.items[1]); -}; + /** + * Get P2PKH signature and key if present. + * @returns {Array} [sig, key] + */ -/** - * Get P2PKH signature and key if present. - * @returns {Array} [sig, key] - */ + getPubkeyhashInput() { + if (!this.isPubkeyhashInput()) + return [null, null]; + return [this.items[0], this.items[1]]; + } -Witness.prototype.getPubkeyhashInput = function getPubkeyhashInput() { - if (!this.isPubkeyhashInput()) - return [null, null]; - return [this.items[0], this.items[1]]; -}; + /** + * "Test" whether the witness is a multisig input. + * Always returns false. + * @returns {Boolean} + */ -/** - * "Test" whether the witness is a multisig input. - * Always returns false. - * @returns {Boolean} - */ + isMultisigInput() { + return false; + } -Witness.prototype.isMultisigInput = function isMultisigInput() { - return false; -}; + /** + * Get multisig signatures key if present. + * Always returns null. + * @returns {Buffer[]|null} + */ -/** - * Get multisig signatures key if present. - * Always returns null. - * @returns {Buffer[]|null} - */ + getMultisigInput() { + return null; + } -Witness.prototype.getMultisigInput = function getMultisigInput() { - return null; -}; + /** + * "Guess" whether the witness is a scripthash input. + * This method is not 100% reliable. + * @returns {Boolean} + */ -/** - * "Guess" whether the witness is a scripthash input. - * This method is not 100% reliable. - * @returns {Boolean} - */ + isScripthashInput() { + return this.items.length > 0 && !this.isPubkeyhashInput(); + } -Witness.prototype.isScripthashInput = function isScripthashInput() { - return this.items.length > 0 && !this.isPubkeyhashInput(); -}; + /** + * Get P2SH redeem script if present. + * @returns {Buffer|null} + */ -/** - * Get P2SH redeem script if present. - * @returns {Buffer|null} - */ + getScripthashInput() { + if (!this.isScripthashInput()) + return null; + return this.items[this.items.length - 1]; + } -Witness.prototype.getScripthashInput = function getScripthashInput() { - if (!this.isScripthashInput()) - return null; - return this.items[this.items.length - 1]; -}; + /** + * "Guess" whether the witness is an unknown/non-standard type. + * This method is not 100% reliable. + * @returns {Boolean} + */ -/** - * "Guess" whether the witness is an unknown/non-standard type. - * This method is not 100% reliable. - * @returns {Boolean} - */ + isUnknownInput() { + return this.getInputType() === scriptTypes.NONSTANDARD; + } -Witness.prototype.isUnknownInput = function isUnknownInput() { - return this.getInputType() === scriptTypes.NONSTANDARD; -}; + /** + * Test the witness against a bloom filter. + * @param {Bloom} filter + * @returns {Boolean} + */ -/** - * Test the witness against a bloom filter. - * @param {Bloom} filter - * @returns {Boolean} - */ + test(filter) { + for (const item of this.items) { + if (item.length === 0) + continue; -Witness.prototype.test = function test(filter) { - for (const item of this.items) { - if (item.length === 0) - continue; + if (filter.test(item)) + return true; + } - if (filter.test(item)) - return true; + return false; } - return false; -}; + /** + * Grab and deserialize the redeem script from the witness. + * @returns {Script} Redeem script. + */ -/** - * Grab and deserialize the redeem script from the witness. - * @returns {Script} Redeem script. - */ + getRedeem() { + if (this.items.length === 0) + return null; -Witness.prototype.getRedeem = function getRedeem() { - if (this.items.length === 0) - return null; - - const redeem = this.items[this.items.length - 1]; + const redeem = this.items[this.items.length - 1]; - if (!redeem) - return null; + if (!redeem) + return null; - return Script.fromRaw(redeem); -}; + return Script.fromRaw(redeem); + } -/** - * Find a data element in a witness. - * @param {Buffer} data - Data element to match against. - * @returns {Number} Index (`-1` if not present). - */ + /** + * Find a data element in a witness. + * @param {Buffer} data - Data element to match against. + * @returns {Number} Index (`-1` if not present). + */ + + indexOf(data) { + for (let i = 0; i < this.items.length; i++) { + const item = this.items[i]; + if (item.equals(data)) + return i; + } + return -1; + } -Witness.prototype.indexOf = function indexOf(data) { - return util.indexOf(this.items, data); -}; + /** + * Calculate size of the witness + * excluding the varint size bytes. + * @returns {Number} + */ -/** - * Calculate size of the witness - * excluding the varint size bytes. - * @returns {Number} - */ + getSize() { + let size = 0; -Witness.prototype.getSize = function getSize() { - let size = 0; + for (const item of this.items) + size += encoding.sizeVarBytes(item); - for (const item of this.items) - size += encoding.sizeVarBytes(item); + return size; + } - return size; -}; + /** + * Calculate size of the witness + * including the varint size bytes. + * @returns {Number} + */ -/** - * Calculate size of the witness - * including the varint size bytes. - * @returns {Number} - */ + getVarSize() { + return encoding.sizeVarint(this.items.length) + this.getSize(); + } -Witness.prototype.getVarSize = function getVarSize() { - return encoding.sizeVarint(this.items.length) + this.getSize(); -}; + /** + * Write witness to a buffer writer. + * @param {BufferWriter} bw + */ -/** - * Write witness to a buffer writer. - * @param {BufferWriter} bw - */ + toWriter(bw) { + bw.writeVarint(this.items.length); -Witness.prototype.toWriter = function toWriter(bw) { - bw.writeVarint(this.items.length); + for (const item of this.items) + bw.writeVarBytes(item); - for (const item of this.items) - bw.writeVarBytes(item); + return bw; + } - return bw; -}; + /** + * Encode the witness to a Buffer. + * @param {String} enc - Encoding, either `'hex'` or `null`. + * @returns {Buffer|String} Serialized script. + */ -/** - * Encode the witness to a Buffer. - * @param {String} enc - Encoding, either `'hex'` or `null`. - * @returns {Buffer|String} Serialized script. - */ + toRaw() { + const size = this.getVarSize(); + return this.toWriter(bio.write(size)).render(); + } -Witness.prototype.toRaw = function toRaw() { - const size = this.getVarSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + /** + * Convert witness to a hex string. + * @returns {String} + */ -/** - * Convert witness to a hex string. - * @returns {String} - */ + toJSON() { + return this.toRaw().toString('hex'); + } -Witness.prototype.toJSON = function toJSON() { - return this.toRaw().toString('hex'); -}; + /** + * Inject properties from json object. + * @private + * @param {String} json + */ -/** - * Inject properties from json object. - * @private - * @param {String} json - */ + fromJSON(json) { + assert(typeof json === 'string', 'Witness must be a string.'); + return this.fromRaw(Buffer.from(json, 'hex')); + } -Witness.prototype.fromJSON = function fromJSON(json) { - assert(typeof json === 'string', 'Witness must be a string.'); - return this.fromRaw(Buffer.from(json, 'hex')); -}; + /** + * Insantiate witness from a hex string. + * @param {String} json + * @returns {Witness} + */ -/** - * Insantiate witness from a hex string. - * @param {String} json - * @returns {Witness} - */ + static fromJSON(json) { + return new this().fromJSON(json); + } -Witness.fromJSON = function fromJSON(json) { - return new Witness().fromJSON(json); -}; + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ + fromReader(br) { + const count = br.readVarint(); -Witness.prototype.fromReader = function fromReader(br) { - const count = br.readVarint(); + for (let i = 0; i < count; i++) + this.items.push(br.readVarBytes()); - for (let i = 0; i < count; i++) - this.items.push(br.readVarBytes()); + return this; + } - return this; -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + fromRaw(data) { + return this.fromReader(bio.read(data)); + } -Witness.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + /** + * Create a witness from a buffer reader. + * @param {BufferReader} br + */ -/** - * Create a witness from a buffer reader. - * @param {BufferReader} br - */ + static fromReader(br) { + return new this().fromReader(br); + } -Witness.fromReader = function fromReader(br) { - return new Witness().fromReader(br); -}; + /** + * Create a witness from a serialized buffer. + * @param {Buffer|String} data - Serialized witness. + * @param {String?} enc - Either `"hex"` or `null`. + * @returns {Witness} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } -/** - * Create a witness from a serialized buffer. - * @param {Buffer|String} data - Serialized witness. - * @param {String?} enc - Either `"hex"` or `null`. - * @returns {Witness} - */ + /** + * Inject items from string. + * @private + * @param {String|String[]} items + */ -Witness.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new Witness().fromRaw(data); -}; + fromString(items) { + if (!Array.isArray(items)) { + assert(typeof items === 'string'); -/** - * Inject items from string. - * @private - * @param {String|String[]} items - */ + items = items.trim(); -Witness.prototype.fromString = function fromString(items) { - if (!Array.isArray(items)) { - assert(typeof items === 'string'); + if (items.length === 0) + return this; - items = items.trim(); + items = items.split(/\s+/); + } - if (items.length === 0) - return this; + for (const item of items) + this.items.push(Buffer.from(item, 'hex')); - items = items.split(/\s+/); + return this; } - for (const item of items) - this.items.push(Buffer.from(item, 'hex')); - - return this; -}; - -/** - * Parse a test script/array - * string into a witness object. _Must_ - * contain only stack items (no non-push - * opcodes). - * @param {String|String[]} items - Script string. - * @returns {Witness} - * @throws Parse error. - */ - -Witness.fromString = function fromString(items) { - return new Witness().fromString(items); -}; + /** + * Parse a test script/array + * string into a witness object. _Must_ + * contain only stack items (no non-push + * opcodes). + * @param {String|String[]} items - Script string. + * @returns {Witness} + * @throws Parse error. + */ + + static fromString(items) { + return new this().fromString(items); + } -/** - * Test an object to see if it is a Witness. - * @param {Object} obj - * @returns {Boolean} - */ + /** + * Test an object to see if it is a Witness. + * @param {Object} obj + * @returns {Boolean} + */ -Witness.isWitness = function isWitness(obj) { - return obj instanceof Witness; -}; + static isWitness(obj) { + return obj instanceof Witness; + } +} /* * Expose diff --git a/lib/types.js b/lib/types.js index fac6ea310..d7a7ac488 100644 --- a/lib/types.js +++ b/lib/types.js @@ -1,8 +1,4 @@ -/** - * An inverse enum. Retrieves key by value. - * @typedef {Object} RevMap - * @global - */ +'use strict'; /** * One of {@link module:constants.inv}. @@ -36,24 +32,6 @@ * @global */ -/** - * Unix time (seconds). - * @typedef {Number} Seconds - * @global - */ - -/** - * Unix time (milliseconds). - * @typedef {Number} Milliseconds - * @global - */ - -/** - * Wallet ID - * @typedef {String} WalletID - * @global - */ - /** * Base58 string. * @typedef {String} Base58String @@ -61,20 +39,20 @@ */ /** - * Base58 address. - * @typedef {String} Base58Address + * Bech32 string. + * @typedef {String} Bech32String * @global */ /** - * Buffer or hex-string hash. - * @typedef {Buffer|String} Hash + * Serialized address. + * @typedef {Base58String|Bech32String} AddressString * @global */ /** - * Reversed hex-string hash (uint256le). - * @typedef {String} ReversedHash + * Buffer or hex-string hash. + * @typedef {Buffer|String} Hash * @global */ @@ -113,76 +91,6 @@ * @global */ -/** - * @typedef {Object} NakedCoin - * @property {Number} version - Transaction version. - * @property {Number} height - Transaction height (-1 if unconfirmed). - * @property {Amount} value - Output value in satoshis. - * @property {Script} script - Output script. - * @property {Boolean} coinbase - Whether the containing - * transaction is a coinbase. - * @property {Hash} hash - Transaction hash. - * @property {Number} index - Output index. - * @global - */ - -/** - * @typedef {Object} NakedBlock - * @property {Number} version - Transaction version. Note that BCoin reads - * versions as unsigned even though they are signed at the protocol level. - * This value will never be negative. - * @property {Hash} prevBlock - * @property {Hash} merkleRoot - * @property {Number} time - * @property {Number} bits - * @property {Number} nonce - * @property {Number} height - * @property {Number} totalTX - * @property {NakedTX[]?} txs - Only present on blocks. - * @property {Hash[]?} hashes - Only present on merkleblocks. - * @property {Buffer?} flags - Only present on merkleblocks. - * @global - */ - -/** - * @typedef {Object} NakedInput - * @property {Outpoint} prevout - * @property {NakedScript} script - Input script. - * @property {Number} sequence - nSequence. - * @property {NakedWitness} witness - Witness. - * @global - */ - -/** - * @typedef {Object} NakedOutput - * @property {Amount} value - Value in satoshis. - * @property {NakedScript} script - Output script. - * @global - */ - -/** - * @typedef {Object} NakedTX - * @property {Number} version - * @property {Number} flag - * @property {NakedInput[]} inputs - * @property {NakedOutput[]} outputs - * @property {Number} locktime - * @global - */ - -/** - * @typedef {Object} NakedScript - * @property {Buffer} raw - Raw code. - * @property {Array} code - Parsed code. - * @global - */ - -/** - * @typedef {Object} NakedWitness - * @property {Buffer[]} items - Stack items. - * @global - */ - /** * One of `main`, `testnet`, `regtest`, `segnet3`, `segnet4`. * @typedef {String} NetworkType diff --git a/lib/utils/asn1.js b/lib/utils/asn1.js deleted file mode 100644 index 8dae485b0..000000000 --- a/lib/utils/asn1.js +++ /dev/null @@ -1,548 +0,0 @@ -/*! - * asn1.js - asn1 parsing for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - * - * Parts of this software are based on asn1.js. - * https://github.com/indutny/asn1.js - * - * Copyright Fedor Indutny, 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 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. - */ - -'use strict'; - -const BufferReader = require('./reader'); - -/** - * @exports utils/asn1 - */ - -const ASN1 = exports; - -/** - * Read next tag. - * @param {BufferReader} br - * @returns {Object} - */ - -ASN1.readTag = function readTag(br) { - let type = br.readU8(); - const primitive = (type & 0x20) === 0; - - if ((type & 0x1f) === 0x1f) { - let oct = type; - type = 0; - while ((oct & 0x80) === 0x80) { - oct = br.readU8(); - type <<= 7; - type |= oct & 0x7f; - } - } else { - type &= 0x1f; - } - - return { - type: type, - primitive: primitive, - size: ASN1.readSize(br, primitive) - }; -}; - -/** - * Read tag size. - * @param {BufferReader} br - * @param {Boolean} primitive - * @returns {Number} - * @throws on indefinite size - */ - -ASN1.readSize = function readSize(br, primitive) { - let size = br.readU8(); - - // Indefinite form - if (!primitive && size === 0x80) - throw new Error('Indefinite size.'); - - // Definite form - if ((size & 0x80) === 0) { - // Short form - return size; - } - - // Long form - const bytes = size & 0x7f; - - if (bytes > 3) - throw new Error('Length octet is too long.'); - - size = 0; - for (let i = 0; i < bytes; i++) { - size <<= 8; - size |= br.readU8(); - } - - return size; -}; - -/** - * Read implicit SEQ. - * @param {BufferReader} br - * @returns {Buffer} - */ - -ASN1.readSeq = function readSeq(br) { - const tag = ASN1.implicit(br, 0x10); - return br.readBytes(tag.size); -}; - -/** - * Read next tag and assert implicit. - * @param {BufferReader} br - * @param {Number} type - * @returns {Object} - * @throws on unexpected tag - */ - -ASN1.implicit = function implicit(br, type) { - const tag = ASN1.readTag(br); - - if (tag.type !== type) - throw new Error(`Unexpected tag: ${tag.type}.`); - - return tag; -}; - -/** - * Read implicit tag. - * @param {BufferReader} br - * @param {Number} type - * @returns {Boolean} - */ - -ASN1.explicit = function explicit(br, type) { - const offset = br.offset; - const tag = ASN1.readTag(br); - - if (tag.type !== type) { - br.offset = offset; - return false; - } - - return true; -}; - -/** - * Read next implicit SEQ and return a new reader. - * @param {BufferReader} br - * @returns {BufferReader} - */ - -ASN1.seq = function seq(br) { - return new BufferReader(ASN1.readSeq(br), true); -}; - -/** - * Read implicit int. - * @param {BufferReader} br - * @param {Boolean?} cast - * @returns {Buffer|Number} - */ - -ASN1.readInt = function readInt(br, cast) { - const tag = ASN1.implicit(br, 0x02); - const num = br.readBytes(tag.size); - - if (cast) - return num.readUIntBE(0, num.length); - - return num; -}; - -/** - * Read explicit int. - * @param {BufferReader} br - * @param {Number} type - * @param {Boolean?} readNum - * @returns {Buffer|Number} `-1` on not present. - */ - -ASN1.readExplicitInt = function readExplicitInt(br, type, readNum) { - if (!ASN1.explicit(br, type)) - return -1; - - return ASN1.readInt(br, readNum); -}; - -/** - * Read and align an implicit bitstr. - * @param {BufferReader} br - * @returns {Buffer} - */ - -ASN1.readBitstr = function readBitstr(br) { - const tag = ASN1.implicit(br, 0x03); - const str = br.readBytes(tag.size); - return ASN1.alignBitstr(str); -}; - -/** - * Read an implicit string (any type). - * @param {BufferReader} br - * @returns {String} - */ - -ASN1.readString = function readString(br) { - const tag = ASN1.readTag(br); - - switch (tag.type) { - case 0x03: { // bitstr - const str = br.readBytes(tag.size); - return ASN1.alignBitstr(str).toString('utf8'); - } - // Note: - // Fuck all these. - case 0x04: // octstr - case 0x12: // numstr - case 0x13: // prinstr - case 0x14: // t61str - case 0x15: // videostr - case 0x16: // ia5str - case 0x19: // graphstr - case 0x0c: // utf8str - case 0x1a: // iso646str - case 0x1b: // genstr - case 0x1c: // unistr - case 0x1d: // charstr - case 0x1e: { // bmpstr - return br.readString('utf8', tag.size); - } - default: { - throw new Error(`Unexpected tag: ${tag.type}.`); - } - } -}; - -/** - * Align a bitstr. - * @param {Buffer} data - * @returns {Buffer} - */ - -ASN1.alignBitstr = function alignBitstr(data) { - const padding = data[0]; - const bits = (data.length - 1) * 8 - padding; - const buf = data.slice(1); - const shift = 8 - (bits % 8); - - if (shift === 8 || buf.length === 0) - return buf; - - const out = Buffer.allocUnsafe(buf.length); - out[0] = buf[0] >>> shift; - - for (let i = 1; i < buf.length; i++) { - out[i] = buf[i - 1] << (8 - shift); - out[i] |= buf[i] >>> shift; - } - - return out; -}; - -/** - * Read an entire certificate. - * @param {BufferReader} br - * @returns {Object} - */ - -ASN1.readCert = function readCert(br) { - const buf = br; - - buf.start(); - - br = ASN1.seq(buf); - - return { - tbs: ASN1.readTBS(br), - sigAlg: ASN1.readAlgIdent(br), - sig: ASN1.readBitstr(br), - raw: buf.endData(true) - }; -}; - -/** - * Read only the TBS certificate. - * @param {BufferReader} br - * @returns {Object} - */ - -ASN1.readTBS = function readTBS(br) { - const buf = br; - - buf.start(); - - br = ASN1.seq(buf); - - return { - version: ASN1.readExplicitInt(br, 0x00, true), - serial: ASN1.readInt(br), - sig: ASN1.readAlgIdent(br), - issuer: ASN1.readName(br), - validity: ASN1.readValidity(br), - subject: ASN1.readName(br), - pubkey: ASN1.readPubkey(br), - raw: buf.endData(true) - }; -}; - -/** - * Read an implicit pubkey. - * @param {BufferReader} br - * @returns {Object} - */ - -ASN1.readPubkey = function readPubkey(br) { - br = ASN1.seq(br); - return { - alg: ASN1.readAlgIdent(br), - pubkey: ASN1.readBitstr(br) - }; -}; - -/** - * Read implicit name. - * @param {BufferReader} br - * @returns {Object[]} - */ - -ASN1.readName = function readName(br) { - const values = []; - - br = ASN1.seq(br); - - while (br.left()) { - ASN1.implicit(br, 0x11); // set - ASN1.implicit(br, 0x10); // seq - values.push({ - type: ASN1.readOID(br), - value: ASN1.readString(br) - }); - } - - return values; -}; - -/** - * Read implicit validity timerange. - * @param {BufferReader} br - * @returns {Object} - */ - -ASN1.readValidity = function readValidity(br) { - br = ASN1.seq(br); - return { - notBefore: ASN1.readTime(br), - notAfter: ASN1.readTime(br) - }; -}; - -/** - * Read implicit timestamp. - * @param {BufferReader} br - * @returns {Number} - */ - -ASN1.readTime = function readTime(br) { - const tag = ASN1.readTag(br); - const str = br.readString('ascii', tag.size); - let year, mon, day, hour, min, sec; - - switch (tag.type) { - case 0x17: // utctime - year = str.slice(0, 2) | 0; - mon = str.slice(2, 4) | 0; - day = str.slice(4, 6) | 0; - hour = str.slice(6, 8) | 0; - min = str.slice(8, 10) | 0; - sec = str.slice(10, 12) | 0; - if (year < 70) - year = 2000 + year; - else - year = 1900 + year; - break; - case 0x18: // gentime - year = str.slice(0, 4) | 0; - mon = str.slice(4, 6) | 0; - day = str.slice(6, 8) | 0; - hour = str.slice(8, 10) | 0; - min = str.slice(10, 12) | 0; - sec = str.slice(12, 14) | 0; - break; - default: - throw new Error(`Unexpected tag: ${tag.type}.`); - } - - return Date.UTC(year, mon - 1, day, hour, min, sec, 0) / 1000; -}; - -/** - * Read and format OID to string. - * @param {BufferReader} br - * @returns {String} - */ - -ASN1.readOID = function readOID(br) { - const tag = ASN1.implicit(br, 0x06); - const data = br.readBytes(tag.size); - return ASN1.formatOID(data); -}; - -/** - * Format an OID buffer to a string. - * @param {Buffer} data - * @returns {String} - */ - -ASN1.formatOID = function formatOID(data) { - const br = new BufferReader(data); - const ids = []; - let ident = 0; - let subident = 0; - - while (br.left()) { - subident = br.readU8(); - ident <<= 7; - ident |= subident & 0x7f; - if ((subident & 0x80) === 0) { - ids.push(ident); - ident = 0; - } - } - - if (subident & 0x80) - ids.push(ident); - - const first = (ids[0] / 40) | 0; - const second = ids[0] % 40; - const result = [first, second].concat(ids.slice(1)); - - return result.join('.'); -}; - -/** - * Read algorithm identifier. - * @param {BufferReader} br - * @returns {Object} - */ - -ASN1.readAlgIdent = function readAlgIdent(br) { - let params = null; - - br = ASN1.seq(br); - - const alg = ASN1.readOID(br); - - if (br.left() > 0) { - const tag = ASN1.readTag(br); - params = br.readBytes(tag.size); - if (params.length === 0) - params = null; - } - - return { - alg: alg, - params: params - }; -}; - -/** - * Read RSA public key. - * @param {BufferReader} br - * @returns {Object} - */ - -ASN1.readRSAPublic = function readRSAPublic(br) { - br = ASN1.seq(br); - return { - modulus: ASN1.readInt(br), - publicExponent: ASN1.readInt(br) - }; -}; - -/** - * Read RSA private key. - * @param {BufferReader} br - * @returns {Object} - */ - -ASN1.readRSAPrivate = function readRSAPrivate(br) { - br = ASN1.seq(br); - return { - version: ASN1.readInt(br, true), - modulus: ASN1.readInt(br), - publicExponent: ASN1.readInt(br), - privateExponent: ASN1.readInt(br), - prime1: ASN1.readInt(br), - prime2: ASN1.readInt(br), - exponent1: ASN1.readInt(br), - exponent2: ASN1.readInt(br), - coefficient: ASN1.readInt(br) - }; -}; - -/** - * Read RSA public key from buffer. - * @param {Buffer} data - * @returns {Object} - */ - -ASN1.parseRSAPublic = function parseRSAPublic(data) { - return ASN1.readRSAPublic(new BufferReader(data, true)); -}; - -/** - * Read RSA private key from buffer. - * @param {Buffer} data - * @returns {Object} - */ - -ASN1.parseRSAPrivate = function parseRSAPrivate(data) { - return ASN1.readRSAPrivate(new BufferReader(data, true)); -}; - -/** - * Read certificate from buffer. - * @param {Buffer} data - * @returns {Object} - */ - -ASN1.parseCert = function parseCert(data) { - return ASN1.readCert(new BufferReader(data, true)); -}; - -/** - * Read TBS certificate from buffer. - * @param {Buffer} data - * @returns {Object} - */ - -ASN1.parseTBS = function parseTBS(data) { - return ASN1.readTBS(new BufferReader(data, true)); -}; diff --git a/lib/utils/asyncemitter.js b/lib/utils/asyncemitter.js deleted file mode 100644 index 3f1c32780..000000000 --- a/lib/utils/asyncemitter.js +++ /dev/null @@ -1,382 +0,0 @@ -/*! - * asyncemitter.js - event emitter which resolves promises. - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); - -/** - * Represents a promise-resolving event emitter. - * @alias module:utils.AsyncEmitter - * @see EventEmitter - * @constructor - */ - -function AsyncEmitter() { - if (!(this instanceof AsyncEmitter)) - return new AsyncEmitter(); - - this._events = Object.create(null); -} - -/** - * Add a listener. - * @param {String} type - * @param {Function} handler - */ - -AsyncEmitter.prototype.addListener = function addListener(type, handler) { - return this._push(type, handler, false); -}; - -/** - * Add a listener. - * @param {String} type - * @param {Function} handler - */ - -AsyncEmitter.prototype.on = function on(type, handler) { - return this.addListener(type, handler); -}; - -/** - * Add a listener to execute once. - * @param {String} type - * @param {Function} handler - */ - -AsyncEmitter.prototype.once = function once(type, handler) { - return this._push(type, handler, true); -}; - -/** - * Prepend a listener. - * @param {String} type - * @param {Function} handler - */ - -AsyncEmitter.prototype.prependListener = function prependListener(type, handler) { - return this._unshift(type, handler, false); -}; - -/** - * Prepend a listener to execute once. - * @param {String} type - * @param {Function} handler - */ - -AsyncEmitter.prototype.prependOnceListener = function prependOnceListener(type, handler) { - return this._unshift(type, handler, true); -}; - -/** - * Push a listener. - * @private - * @param {String} type - * @param {Function} handler - * @param {Boolean} once - */ - -AsyncEmitter.prototype._push = function _push(type, handler, once) { - assert(typeof type === 'string', '`type` must be a string.'); - - if (!this._events[type]) - this._events[type] = []; - - this._events[type].push(new Listener(handler, once)); - - this.emit('newListener', type, handler); -}; - -/** - * Unshift a listener. - * @param {String} type - * @param {Function} handler - * @param {Boolean} once - */ - -AsyncEmitter.prototype._unshift = function _unshift(type, handler, once) { - assert(typeof type === 'string', '`type` must be a string.'); - - if (!this._events[type]) - this._events[type] = []; - - this._events[type].unshift(new Listener(handler, once)); - - this.emit('newListener', type, handler); -}; - -/** - * Remove a listener. - * @param {String} type - * @param {Function} handler - */ - -AsyncEmitter.prototype.removeListener = function removeListener(type, handler) { - assert(typeof type === 'string', '`type` must be a string.'); - - const listeners = this._events[type]; - - if (!listeners) - return; - - let index = -1; - - for (let i = 0; i < listeners.length; i++) { - const listener = listeners[i]; - if (listener.handler === handler) { - index = i; - break; - } - } - - if (index === -1) - return; - - listeners.splice(index, 1); - - if (listeners.length === 0) - delete this._events[type]; - - this.emit('removeListener', type, handler); -}; - -/** - * Set max listeners. - * @param {Number} max - */ - -AsyncEmitter.prototype.setMaxListeners = function setMaxListeners(max) { - assert(typeof max === 'number', '`max` must be a number.'); - assert(max >= 0, '`max` must be non-negative.'); - assert(Number.isSafeInteger(max), '`max` must be an integer.'); -}; - -/** - * Remove all listeners. - * @param {String?} type - */ - -AsyncEmitter.prototype.removeAllListeners = function removeAllListeners(type) { - if (arguments.length === 0) { - this._events = Object.create(null); - return; - } - - assert(typeof type === 'string', '`type` must be a string.'); - - delete this._events[type]; -}; - -/** - * Get listeners array. - * @param {String} type - * @returns {Function[]} - */ - -AsyncEmitter.prototype.listeners = function listeners(type) { - assert(typeof type === 'string', '`type` must be a string.'); - - const listeners = this._events[type]; - - if (!listeners) - return []; - - const result = []; - - for (const listener of listeners) - result.push(listener.handler); - - return result; -}; - -/** - * Get listener count for an event. - * @param {String} type - */ - -AsyncEmitter.prototype.listenerCount = function listenerCount(type) { - assert(typeof type === 'string', '`type` must be a string.'); - - const listeners = this._events[type]; - - if (!listeners) - return 0; - - return listeners.length; -}; - -/** - * Emit an event synchronously. - * @method - * @param {String} type - * @param {...Object} args - * @returns {Promise} - */ - -AsyncEmitter.prototype.emit = function emit(type) { - assert(typeof type === 'string', '`type` must be a string.'); - - const listeners = this._events[type]; - - if (!listeners || listeners.length === 0) { - if (type === 'error') { - const error = arguments[1]; - - if (error instanceof Error) - throw error; - - const err = new Error(`Uncaught, unspecified "error" event. (${error})`); - err.context = error; - throw err; - } - return; - } - - let args; - - for (let i = 0; i < listeners.length; i++) { - const listener = listeners[i]; - const handler = listener.handler; - - if (listener.once) { - listeners.splice(i, 1); - i--; - } - - switch (arguments.length) { - case 1: - handler(); - break; - case 2: - handler(arguments[1]); - break; - case 3: - handler(arguments[1], arguments[2]); - break; - case 4: - handler(arguments[1], arguments[2], arguments[3]); - break; - default: - if (!args) { - args = new Array(arguments.length - 1); - for (let j = 1; j < arguments.length; j++) - args[j - 1] = arguments[j]; - } - handler.apply(null, args); - break; - } - } -}; - -/** - * Emit an event. Wait for promises to resolve. - * @method - * @param {String} type - * @param {...Object} args - * @returns {Promise} - */ - -AsyncEmitter.prototype.fire = async function fire(type) { - assert(typeof type === 'string', '`type` must be a string.'); - - const listeners = this._events[type]; - - if (!listeners || listeners.length === 0) { - if (type === 'error') { - const error = arguments[1]; - - if (error instanceof Error) - throw error; - - const err = new Error(`Uncaught, unspecified "error" event. (${error})`); - err.context = error; - throw err; - } - return; - } - - let args; - - for (let i = 0; i < listeners.length; i++) { - const listener = listeners[i]; - const handler = listener.handler; - - if (listener.once) { - listeners.splice(i, 1); - i--; - } - - switch (arguments.length) { - case 1: - await handler(); - break; - case 2: - await handler(arguments[1]); - break; - case 3: - await handler(arguments[1], arguments[2]); - break; - case 4: - await handler(arguments[1], arguments[2], arguments[3]); - break; - default: - if (!args) { - args = new Array(arguments.length - 1); - for (let j = 1; j < arguments.length; j++) - args[j - 1] = arguments[j]; - } - await handler.apply(null, args); - break; - } - } -}; - -/** - * Emit an event. Ignore rejections. - * @method - * @param {String} type - * @param {...Object} args - * @returns {Promise} - */ - -AsyncEmitter.prototype.tryFire = async function tryFire(type) { - try { - await this.emit.apply(this, arguments); - } catch (e) { - if (type === 'error') - return; - - try { - await this.emit('error', e); - } catch (e) { - ; - } - } -}; - -/** - * Event Listener - * @constructor - * @ignore - * @param {Function} handler - * @param {Boolean} once - * @property {Function} handler - * @property {Boolean} once - */ - -function Listener(handler, once) { - assert(typeof handler === 'function', '`handler` must be a function.'); - assert(typeof once === 'boolean', '`once` must be a function.'); - this.handler = handler; - this.once = once; -} - -/* - * Expose - */ - -module.exports = AsyncEmitter; diff --git a/lib/utils/asyncobject.js b/lib/utils/asyncobject.js deleted file mode 100644 index 3e306f421..000000000 --- a/lib/utils/asyncobject.js +++ /dev/null @@ -1,231 +0,0 @@ -/*! - * async.js - async object class for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const EventEmitter = require('events'); -const Lock = require('./lock'); - -/** - * An abstract object that handles state and - * provides recallable open and close methods. - * @alias module:utils.AsyncObject - * @constructor - * @property {Boolean} loading - * @property {Boolean} closing - * @property {Boolean} loaded - */ - -function AsyncObject() { - assert(this instanceof AsyncObject); - - EventEmitter.call(this); - - this._asyncLock = new Lock(); - this._hooks = Object.create(null); - - this.loading = false; - this.closing = false; - this.loaded = false; -} - -Object.setPrototypeOf(AsyncObject.prototype, EventEmitter.prototype); - -/** - * Open the object (recallable). - * @method - * @returns {Promise} - */ - -AsyncObject.prototype.open = async function open() { - const unlock = await this._asyncLock.lock(); - try { - return await this.__open(); - } finally { - unlock(); - } -}; - -/** - * Open the object (without a lock). - * @method - * @private - * @returns {Promise} - */ - -AsyncObject.prototype.__open = async function __open() { - if (this.loaded) - return; - - await this.fire('preopen'); - - this.loading = true; - - try { - await this._open(); - } catch (e) { - this.loading = false; - this.emit('error', e); - throw e; - } - - this.loading = false; - this.loaded = true; - - await this.fire('open'); -}; - -/** - * Close the object (recallable). - * @method - * @returns {Promise} - */ - -AsyncObject.prototype.close = async function close() { - const unlock = await this._asyncLock.lock(); - try { - return await this.__close(); - } finally { - unlock(); - } -}; - -/** - * Close the object (without a lock). - * @method - * @private - * @returns {Promise} - */ - -AsyncObject.prototype.__close = async function __close() { - if (!this.loaded) - return; - - await this.fire('preclose'); - - this.closing = true; - - try { - await this._close(); - } catch (e) { - this.closing = false; - this.emit('error', e); - throw e; - } - - this.closing = false; - this.loaded = false; - - await this.fire('close'); -}; - -/** - * Close the object (recallable). - * @method - * @returns {Promise} - */ - -AsyncObject.prototype.destroy = AsyncObject.prototype.close; - -/** - * Initialize the object. - * @private - * @returns {Promise} - */ - -AsyncObject.prototype._open = function _open(callback) { - throw new Error('Abstract method.'); -}; - -/** - * Close the object. - * @private - * @returns {Promise} - */ - -AsyncObject.prototype._close = function _close(callback) { - throw new Error('Abstract method.'); -}; - -/** - * Add a hook listener. - * @param {String} type - * @param {Function} handler - */ - -AsyncObject.prototype.hook = function hook(type, handler) { - assert(typeof type === 'string', '`type` must be a string.'); - - if (!this._hooks[type]) - this._hooks[type] = []; - - this._hooks[type].push(handler); -}; - -/** - * Emit events and hooks for type. - * @method - * @param {String} type - * @param {...Object} args - * @returns {Promise} - */ - -AsyncObject.prototype.fire = async function fire() { - await this.fireHook.apply(this, arguments); - this.emit.apply(this, arguments); -}; - -/** - * Emit an asynchronous event (hook). - * Wait for promises to resolve. - * @method - * @param {String} type - * @param {...Object} args - * @returns {Promise} - */ - -AsyncObject.prototype.fireHook = async function fireHook(type) { - assert(typeof type === 'string', '`type` must be a string.'); - - const listeners = this._hooks[type]; - - if (!listeners || listeners.length === 0) - return; - - let args; - - for (const handler of listeners) { - switch (arguments.length) { - case 1: - await handler(); - break; - case 2: - await handler(arguments[1]); - break; - case 3: - await handler(arguments[1], arguments[2]); - break; - case 4: - await handler(arguments[1], arguments[2], arguments[3]); - break; - default: - if (!args) { - args = new Array(arguments.length - 1); - for (let i = 1; i < arguments.length; i++) - args[i - 1] = arguments[i]; - } - await handler.apply(null, args); - break; - } - } -}; - -/* - * Expose - */ - -module.exports = AsyncObject; diff --git a/lib/utils/base32.js b/lib/utils/base32.js deleted file mode 100644 index a70f4f92a..000000000 --- a/lib/utils/base32.js +++ /dev/null @@ -1,186 +0,0 @@ -/*! - * base32.js - base32 for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module utils/base32 - */ - -const base32 = 'abcdefghijklmnopqrstuvwxyz234567'; -const padding = [0, 6, 4, 3, 1]; -const unbase32 = {}; - -for (let i = 0; i < base32.length; i++) - unbase32[base32[i]] = i; - -/** - * Encode a base32 string. - * @param {Buffer} data - * @returns {String} - */ - -exports.encode = function encode(data) { - let str = ''; - let mode = 0; - let left = 0; - - for (let i = 0; i < data.length; i++) { - const ch = data[i]; - switch (mode) { - case 0: - str += base32[ch >>> 3]; - left = (ch & 7) << 2; - mode = 1; - break; - case 1: - str += base32[left | (ch >>> 6)]; - str += base32[(ch >>> 1) & 31]; - left = (ch & 1) << 4; - mode = 2; - break; - case 2: - str += base32[left | (ch >>> 4)]; - left = (ch & 15) << 1; - mode = 3; - break; - case 3: - str += base32[left | (ch >>> 7)]; - str += base32[(ch >>> 2) & 31]; - left = (ch & 3) << 3; - mode = 4; - break; - case 4: - str += base32[left | (ch >>> 5)]; - str += base32[ch & 31]; - mode = 0; - break; - } - } - - if (mode > 0) { - str += base32[left]; - for (let i = 0; i < padding[mode]; i++) - str += '='; - } - - return str; -}; - -/** - * Decode a base32 string. - * @param {String} str - * @returns {Buffer} - */ - -exports.decode = function decode(str) { - const data = Buffer.allocUnsafe(str.length * 5 / 8 | 0); - let mode = 0; - let left = 0; - let j = 0; - let i; - - for (i = 0; i < str.length; i++) { - const ch = unbase32[str[i]]; - - if (ch == null) - break; - - switch (mode) { - case 0: - left = ch; - mode = 1; - break; - case 1: - data[j++] = (left << 3) | (ch >>> 2); - left = ch & 3; - mode = 2; - break; - case 2: - left = left << 5 | ch; - mode = 3; - break; - case 3: - data[j++] = (left << 1) | (ch >>> 4); - left = ch & 15; - mode = 4; - break; - case 4: - data[j++] = (left << 4) | (ch >>> 1); - left = ch & 1; - mode = 5; - break; - case 5: - left = left << 5 | ch; - mode = 6; - break; - case 6: - data[j++] = (left << 2) | (ch >>> 3); - left = ch & 7; - mode = 7; - break; - case 7: - data[j++] = (left << 5) | ch; - mode = 0; - break; - } - } - - switch (mode) { - case 0: - break; - case 1: - case 3: - case 6: - throw new Error('Invalid base32 string.'); - case 2: - if (left > 0) - throw new Error('Invalid padding.'); - - if (str.slice(i, i + 6) !== '======') - throw new Error('Invalid base32 character.'); - - if (unbase32[str[i + 6]] != null) - throw new Error('Invalid padding.'); - - break; - case 4: - if (left > 0) - throw new Error('Invalid padding.'); - - if (str.slice(i, i + 4) !== '====') - throw new Error('Invalid base32 character.'); - - if (unbase32[str[i + 4]] != null) - throw new Error('Invalid padding.'); - - break; - case 5: - if (left > 0) - throw new Error('Invalid padding.'); - - if (str.slice(i, i + 3) !== '===') - throw new Error('Invalid base32 character.'); - - if (unbase32[str[i + 3]] != null) - throw new Error('Invalid padding.'); - - break; - case 7: - if (left > 0) - throw new Error('Invalid padding.'); - - if (str[i] !== '=') - throw new Error('Invalid base32 character.'); - - if (unbase32[str[i + 1]] != null) - throw new Error('Invalid padding.'); - - break; - } - - return data.slice(0, j); -}; diff --git a/lib/utils/base58.js b/lib/utils/base58.js deleted file mode 100644 index 5ef9baf2a..000000000 --- a/lib/utils/base58.js +++ /dev/null @@ -1,148 +0,0 @@ -/*! - * base58.js - base58 for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module utils/base58 - */ - -const assert = require('assert'); -const native = require('../native').binding; - -/* - * Base58 - */ - -const base58 = '' - + '123456789' - + 'ABCDEFGHJKLMNPQRSTUVWXYZ' - + 'abcdefghijkmnopqrstuvwxyz'; - -const unbase58 = {}; - -for (let i = 0; i < base58.length; i++) - unbase58[base58[i]] = i; - -/** - * Encode a base58 string. - * @see https://github.com/bitcoin/bitcoin/blob/master/src/base58.cpp - * @param {Buffer} data - * @returns {Base58String} - */ - -exports.encode = function encode(data) { - let zeroes = 0; - let i = 0; - - for (; i < data.length; i++) { - if (data[i] !== 0) - break; - zeroes++; - } - - const b58 = Buffer.allocUnsafe(((data.length * 138 / 100) | 0) + 1); - b58.fill(0); - - let length = 0; - - for (; i < data.length; i++) { - let carry = data[i]; - let j = 0; - - for (let k = b58.length - 1; k >= 0; k--, j++) { - if (carry === 0 && j >= length) - break; - carry += 256 * b58[k]; - b58[k] = carry % 58; - carry = carry / 58 | 0; - } - - assert(carry === 0); - length = j; - } - - i = b58.length - length; - while (i < b58.length && b58[i] === 0) - i++; - - let str = ''; - - for (let j = 0; j < zeroes; j++) - str += '1'; - - for (; i < b58.length; i++) - str += base58[b58[i]]; - - return str; -}; - -if (native) - exports.encode = native.toBase58; - -/** - * Decode a base58 string. - * @see https://github.com/bitcoin/bitcoin/blob/master/src/base58.cpp - * @param {Base58String} str - * @returns {Buffer} - * @throws on non-base58 character. - */ - -exports.decode = function decode(str) { - let zeroes = 0; - let i = 0; - - for (; i < str.length; i++) { - if (str[i] !== '1') - break; - zeroes++; - } - - const b256 = Buffer.allocUnsafe(((str.length * 733) / 1000 | 0) + 1); - b256.fill(0); - - let length = 0; - - for (; i < str.length; i++) { - const ch = unbase58[str[i]]; - - if (ch == null) - throw new Error('Non-base58 character.'); - - let carry = ch; - let j = 0; - - for (let k = b256.length - 1; k >= 0; k--, j++) { - if (carry === 0 && j >= length) - break; - carry += 58 * b256[k]; - b256[k] = carry % 256; - carry = carry / 256 | 0; - } - - assert(carry === 0); - length = j; - } - - i = 0; - while (i < b256.length && b256[i] === 0) - i++; - - const out = Buffer.allocUnsafe(zeroes + (b256.length - i)); - - let j; - for (j = 0; j < zeroes; j++) - out[j] = 0; - - while (i < b256.length) - out[j++] = b256[i++]; - - return out; -}; - -if (native) - exports.decode = native.fromBase58; diff --git a/lib/utils/bech32.js b/lib/utils/bech32.js deleted file mode 100644 index b4d08df69..000000000 --- a/lib/utils/bech32.js +++ /dev/null @@ -1,334 +0,0 @@ -/*! - * bech32.js - bech32 for bcoin - * Copyright (c) 2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - * - * Parts of this software are based on "bech32". - * https://github.com/sipa/bech32 - * - * Copyright (c) 2017 Pieter Wuille - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 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. - */ - -'use strict'; - -const native = require('../native').binding; - -/** - * @module utils/bech32 - */ - -const POOL65 = Buffer.allocUnsafe(65); -const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; -const TABLE = [ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, - -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, - 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, - -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, - 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 -]; - -/** - * Update checksum. - * @ignore - * @param {Number} chk - * @returns {Number} - */ - -function polymod(pre) { - const b = pre >>> 25; - return ((pre & 0x1ffffff) << 5) - ^ (-((b >> 0) & 1) & 0x3b6a57b2) - ^ (-((b >> 1) & 1) & 0x26508e6d) - ^ (-((b >> 2) & 1) & 0x1ea119fa) - ^ (-((b >> 3) & 1) & 0x3d4233dd) - ^ (-((b >> 4) & 1) & 0x2a1462b3); -} - -/** - * Encode hrp and data as a bech32 string. - * @ignore - * @param {String} hrp - * @param {Buffer} data - * @returns {String} - */ - -function serialize(hrp, data) { - let chk = 1; - let i; - - for (i = 0; i < hrp.length; i++) { - const ch = hrp.charCodeAt(i); - - if ((ch >> 5) === 0) - throw new Error('Invalid bech32 character.'); - - chk = polymod(chk) ^ (ch >> 5); - } - - if (i + 7 + data.length > 90) - throw new Error('Invalid bech32 data length.'); - - chk = polymod(chk); - - let str = ''; - - for (let i = 0; i < hrp.length; i++) { - const ch = hrp.charCodeAt(i); - chk = polymod(chk) ^ (ch & 0x1f); - str += hrp[i]; - } - - str += '1'; - - for (let i = 0; i < data.length; i++) { - const ch = data[i]; - - if ((ch >> 5) !== 0) - throw new Error('Invalid bech32 value.'); - - chk = polymod(chk) ^ ch; - str += CHARSET[ch]; - } - - for (let i = 0; i < 6; i++) - chk = polymod(chk); - - chk ^= 1; - - for (let i = 0; i < 6; i++) - str += CHARSET[(chk >>> ((5 - i) * 5)) & 0x1f]; - - return str; -} - -/** - * Decode a bech32 string. - * @param {String} str - * @returns {Array} [hrp, data] - */ - -function deserialize(str) { - let dlen = 0; - - if (str.length < 8 || str.length > 90) - throw new Error('Invalid bech32 string length.'); - - while (dlen < str.length && str[(str.length - 1) - dlen] !== '1') - dlen++; - - const hlen = str.length - (1 + dlen); - - if (hlen < 1 || dlen < 6) - throw new Error('Invalid bech32 data length.'); - - dlen -= 6; - - const data = Buffer.allocUnsafe(dlen); - - let chk = 1; - let lower = false; - let upper = false; - let hrp = ''; - - for (let i = 0; i < hlen; i++) { - let ch = str.charCodeAt(i); - - if (ch < 0x21 || ch > 0x7e) - throw new Error('Invalid bech32 character.'); - - if (ch >= 0x61 && ch <= 0x7a) { - lower = true; - } else if (ch >= 0x41 && ch <= 0x5a) { - upper = true; - ch = (ch - 0x41) + 0x61; - } - - hrp += String.fromCharCode(ch); - chk = polymod(chk) ^ (ch >> 5); - } - - chk = polymod(chk); - - let i; - for (i = 0; i < hlen; i++) - chk = polymod(chk) ^ (str.charCodeAt(i) & 0x1f); - - i++; - - while (i < str.length) { - const ch = str.charCodeAt(i); - const v = (ch & 0x80) ? -1 : TABLE[ch]; - - if (ch >= 0x61 && ch <= 0x7a) - lower = true; - else if (ch >= 0x41 && ch <= 0x5a) - upper = true; - - if (v === -1) - throw new Error('Invalid bech32 character.'); - - chk = polymod(chk) ^ v; - - if (i + 6 < str.length) - data[i - (1 + hlen)] = v; - - i++; - } - - if (lower && upper) - throw new Error('Invalid bech32 casing.'); - - if (chk !== 1) - throw new Error('Invalid bech32 checksum.'); - - return [hrp, data.slice(0, dlen)]; -} - -/** - * Convert serialized data to bits, - * suitable to be serialized as bech32. - * @param {Buffer} data - * @param {Buffer} output - * @param {Number} frombits - * @param {Number} tobits - * @param {Number} pad - * @param {Number} off - * @returns {Buffer} - */ - -function convert(data, output, frombits, tobits, pad, off) { - const maxv = (1 << tobits) - 1; - let acc = 0; - let bits = 0; - let j = 0; - - if (pad !== -1) - output[j++] = pad; - - for (let i = off; i < data.length; i++) { - const value = data[i]; - - if ((value >> frombits) !== 0) - throw new Error('Invalid bech32 bits.'); - - acc = (acc << frombits) | value; - bits += frombits; - - while (bits >= tobits) { - bits -= tobits; - output[j++] = (acc >>> bits) & maxv; - } - } - - if (pad !== -1) { - if (bits > 0) - output[j++] = (acc << (tobits - bits)) & maxv; - } else { - if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) - throw new Error('Invalid bech32 bits.'); - } - - return output.slice(0, j); -} - -/** - * Serialize data to bech32 address. - * @param {String} hrp - * @param {Number} version - * @param {Buffer} hash - * @returns {String} - */ - -function encode(hrp, version, hash) { - const output = POOL65; - - if (version < 0 || version > 16) - throw new Error('Invalid bech32 version.'); - - if (hash.length < 2 || hash.length > 40) - throw new Error('Invalid bech32 data length.'); - - const data = convert(hash, output, 8, 5, version, 0); - - return serialize(hrp, data); -} - -if (native) - encode = native.toBech32; - -/** - * Deserialize data from bech32 address. - * @param {String} str - * @returns {Object} - */ - -function decode(str) { - const [hrp, data] = deserialize(str); - - if (data.length === 0 || data.length > 65) - throw new Error('Invalid bech32 data length.'); - - if (data[0] > 16) - throw new Error('Invalid bech32 version.'); - - const version = data[0]; - const output = data; - const hash = convert(data, output, 5, 8, -1, 1); - - if (hash.length < 2 || hash.length > 40) - throw new Error('Invalid bech32 data length.'); - - return new AddrResult(hrp, version, hash); -} - -if (native) - decode = native.fromBech32; - -/** - * AddrResult - * @constructor - * @private - * @param {String} hrp - * @param {Number} version - * @param {Buffer} hash - * @property {String} hrp - * @property {Number} version - * @property {Buffer} hash - */ - -function AddrResult(hrp, version, hash) { - this.hrp = hrp; - this.version = version; - this.hash = hash; -} - -/* - * Expose - */ - -exports.deserialize = deserialize; -exports.serialize = serialize; -exports.convert = convert; -exports.encode = encode; -exports.decode = decode; diff --git a/lib/utils/binary.js b/lib/utils/binary.js new file mode 100644 index 000000000..46b9a9842 --- /dev/null +++ b/lib/utils/binary.js @@ -0,0 +1,103 @@ +/*! + * binary.js - binary search utils for bcoin + * Copyright (c) 2014-2015, Fedor Indutny (MIT License) + * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +/** + * Perform a binary search on a sorted array. + * @param {Array} items + * @param {Object} key + * @param {Function} compare + * @param {Boolean?} insert + * @returns {Number} Index. + */ + +exports.search = function search(items, key, compare, insert) { + let start = 0; + let end = items.length - 1; + + while (start <= end) { + const pos = (start + end) >>> 1; + const cmp = compare(items[pos], key); + + if (cmp === 0) + return pos; + + if (cmp < 0) + start = pos + 1; + else + end = pos - 1; + } + + if (!insert) + return -1; + + return start; +}; + +/** + * Perform a binary insert on a sorted array. + * @param {Array} items + * @param {Object} item + * @param {Function} compare + * @returns {Number} index + */ + +exports.insert = function insert(items, item, compare, uniq) { + const i = exports.search(items, item, compare, true); + + if (uniq && i < items.length) { + if (compare(items[i], item) === 0) + return -1; + } + + if (i === 0) + items.unshift(item); + else if (i === items.length) + items.push(item); + else + items.splice(i, 0, item); + + return i; +}; + +/** + * Perform a binary removal on a sorted array. + * @param {Array} items + * @param {Object} item + * @param {Function} compare + * @returns {Boolean} + */ + +exports.remove = function remove(items, item, compare) { + const i = exports.search(items, item, compare, false); + + if (i === -1) + return false; + + splice(items, i); + + return true; +}; + +/* + * Helpers + */ + +function splice(list, i) { + if (i === 0) { + list.shift(); + return; + } + + let k = i + 1; + + while (k < list.length) + list[i++] = list[k++]; + + list.pop(); +} diff --git a/lib/utils/bloom.js b/lib/utils/bloom.js deleted file mode 100644 index 1452bc596..000000000 --- a/lib/utils/bloom.js +++ /dev/null @@ -1,382 +0,0 @@ -/*! - * bloom.js - bloom filter for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const murmur3 = require('./murmur3'); -const BufferReader = require('./reader'); -const StaticWriter = require('./staticwriter'); -const encoding = require('./encoding'); -const sum32 = murmur3.sum32; -const mul32 = murmur3.mul32; -const DUMMY = Buffer.alloc(0); - -/* - * Constants - */ - -const LN2SQUARED = 0.4804530139182014246671025263266649717305529515945455; -const LN2 = 0.6931471805599453094172321214581765680755001343602552; - -/** - * Bloom Filter - * @alias module:utils.Bloom - * @constructor - * @param {Number} size - Filter size in bits. - * @param {Number} n - Number of hash functions. - * @param {Number} tweak - Seed value. - * @param {Number|String} - Update type. - * @property {Buffer} filter - * @property {Number} size - * @property {Number} n - * @property {Number} tweak - * @property {Number} update - Update flag (see {@link Bloom.flags}). - */ - -function Bloom(size, n, tweak, update) { - if (!(this instanceof Bloom)) - return new Bloom(size, n, tweak, update); - - this.filter = DUMMY; - this.size = 0; - this.n = 0; - this.tweak = 0; - this.update = Bloom.flags.NONE; - - if (size != null) - this.fromOptions(size, n, tweak, update); -} - -/** - * Max bloom filter size. - * @const {Number} - * @default - */ - -Bloom.MAX_BLOOM_FILTER_SIZE = 36000; - -/** - * Max number of hash functions. - * @const {Number} - * @default - */ - -Bloom.MAX_HASH_FUNCS = 50; - -/** - * Bloom filter update flags. - * @enum {Number} - * @default - */ - -Bloom.flags = { - /** - * Never update the filter with outpoints. - */ - - NONE: 0, - - /** - * Always update the filter with outpoints. - */ - - ALL: 1, - - /** - * Only update the filter with outpoints if it is - * "asymmetric" in terms of addresses (pubkey/multisig). - */ - - PUBKEY_ONLY: 2 -}; - -/** - * Bloom filter update flags by value. - * @const {RevMap} - */ - -Bloom.flagsByVal = { - 0: 'NONE', - 1: 'ALL', - 2: 'PUBKEY_ONLY' -}; - -/** - * Inject properties from options. - * @private - * @param {Number} size - Filter size in bits. - * @param {Number} n - Number of hash functions. - * @param {Number} tweak - Seed value. - * @param {Number|String} - Update type. - * @returns {Bloom} - */ - -Bloom.prototype.fromOptions = function fromOptions(size, n, tweak, update) { - assert(typeof size === 'number', '`size` must be a number.'); - assert(size > 0, '`size` must be greater than zero.'); - assert(Number.isSafeInteger(size), '`size` must be an integer.'); - - size -= size % 8; - - const filter = Buffer.allocUnsafe(size / 8); - filter.fill(0); - - if (tweak == null || tweak === -1) - tweak = (Math.random() * 0x100000000) >>> 0; - - if (update == null || update === -1) - update = Bloom.flags.NONE; - - if (typeof update === 'string') { - update = Bloom.flags[update.toUpperCase()]; - assert(update != null, 'Unknown update flag.'); - } - - assert(size > 0, '`size` must be greater than zero.'); - assert(n > 0, '`n` must be greater than zero.'); - assert(Number.isSafeInteger(n), '`n` must be an integer.'); - assert(typeof tweak === 'number', '`tweak` must be a number.'); - assert(Number.isSafeInteger(tweak), '`tweak` must be an integer.'); - assert(Bloom.flagsByVal[update], 'Unknown update flag.'); - - this.filter = filter; - this.size = size; - this.n = n; - this.tweak = tweak; - this.update = update; - - return this; -}; - -/** - * Instantiate bloom filter from options. - * @param {Number} size - Filter size in bits. - * @param {Number} n - Number of hash functions. - * @param {Number} tweak - Seed value. - * @param {Number|String} - Update type. - * @returns {Bloom} - */ - -Bloom.fromOptions = function fromOptions(size, n, tweak, update) { - return new Bloom().fromOptions(size, n, tweak, update); -}; - -/** - * Perform the mumur3 hash on data. - * @param {Buffer} val - * @param {Number} n - * @returns {Number} - */ - -Bloom.prototype.hash = function hash(val, n) { - return murmur3(val, sum32(mul32(n, 0xfba4c795), this.tweak)) % this.size; -}; - -/** - * Reset the filter. - */ - -Bloom.prototype.reset = function reset() { - this.filter.fill(0); -}; - -/** - * Add data to the filter. - * @param {Buffer|String} - * @param {String?} enc - Can be any of the Buffer object's encodings. - */ - -Bloom.prototype.add = function add(val, enc) { - if (typeof val === 'string') - val = Buffer.from(val, enc); - - for (let i = 0; i < this.n; i++) { - const index = this.hash(val, i); - this.filter[index >>> 3] |= 1 << (7 & index); - } -}; - -/** - * Test whether data is present in the filter. - * @param {Buffer|String} val - * @param {String?} enc - Can be any of the Buffer object's encodings. - * @returns {Boolean} - */ - -Bloom.prototype.test = function test(val, enc) { - if (typeof val === 'string') - val = Buffer.from(val, enc); - - for (let i = 0; i < this.n; i++) { - const index = this.hash(val, i); - if ((this.filter[index >>> 3] & (1 << (7 & index))) === 0) - return false; - } - - return true; -}; - -/** - * Test whether data is present in the - * filter and potentially add data. - * @param {Buffer|String} val - * @param {String?} enc - Can be any of the Buffer object's encodings. - * @returns {Boolean} Whether data was added. - */ - -Bloom.prototype.added = function added(val, enc) { - let ret = false; - - if (typeof val === 'string') - val = Buffer.from(val, enc); - - for (let i = 0; i < this.n; i++) { - const index = this.hash(val, i); - if (!ret && (this.filter[index >>> 3] & (1 << (7 & index))) === 0) - ret = true; - this.filter[index >>> 3] |= 1 << (7 & index); - } - - return ret; -}; - -/** - * Create a filter from a false positive rate. - * @param {Number} items - Expected number of items. - * @param {Number} rate - False positive rate (0.0-1.0). - * @param {Number|String} update - * @example - * Bloom.fromRate(800000, 0.0001, 'none'); - * @returns {Boolean} - */ - -Bloom.fromRate = function fromRate(items, rate, update) { - assert(typeof items === 'number', '`items` must be a number.'); - assert(items > 0, '`items` must be greater than zero.'); - assert(Number.isSafeInteger(items), '`items` must be an integer.'); - assert(typeof rate === 'number', '`rate` must be a number.'); - assert(rate >= 0 && rate <= 1, '`rate` must be between 0.0 and 1.0.'); - - const bits = (-1 / LN2SQUARED * items * Math.log(rate)) | 0; - const size = Math.max(8, bits); - - if (update !== -1) { - assert(size <= Bloom.MAX_BLOOM_FILTER_SIZE * 8, - 'Bloom filter size violates policy limits!'); - } - - const n = Math.max(1, (size / items * LN2) | 0); - - if (update !== -1) { - assert(n <= Bloom.MAX_HASH_FUNCS, - 'Bloom filter size violates policy limits!'); - } - - return new Bloom(size, n, -1, update); -}; - -/** - * Ensure the filter is within the size limits. - * @returns {Boolean} - */ - -Bloom.prototype.isWithinConstraints = function isWithinConstraints() { - if (this.size > Bloom.MAX_BLOOM_FILTER_SIZE * 8) - return false; - - if (this.n > Bloom.MAX_HASH_FUNCS) - return false; - - return true; -}; - -/** - * Get serialization size. - * @returns {Number} - */ - -Bloom.prototype.getSize = function getSize() { - return encoding.sizeVarBytes(this.filter) + 9; -}; - -/** - * Write filter to buffer writer. - * @param {BufferWriter} bw - */ - -Bloom.prototype.toWriter = function toWriter(bw) { - bw.writeVarBytes(this.filter); - bw.writeU32(this.n); - bw.writeU32(this.tweak); - bw.writeU8(this.update); - return bw; -}; - -/** - * Serialize bloom filter. - * @returns {Buffer} - */ - -Bloom.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -Bloom.prototype.fromReader = function fromReader(br) { - this.filter = br.readVarBytes(); - this.n = br.readU32(); - this.tweak = br.readU32(); - this.update = br.readU8(); - assert(Bloom.flagsByVal[this.update] != null, 'Unknown update flag.'); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -Bloom.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate bloom filter from buffer reader. - * @param {BufferReader} br - * @returns {Bloom} - */ - -Bloom.fromReader = function fromReader(br) { - return new Bloom().fromReader(br); -}; - -/** - * Instantiate bloom filter from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {Bloom} - */ - -Bloom.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new Bloom().fromRaw(data); -}; - -/* - * Expose - */ - -module.exports = Bloom; diff --git a/lib/utils/co.js b/lib/utils/co.js deleted file mode 100644 index 7fb6fff7d..000000000 --- a/lib/utils/co.js +++ /dev/null @@ -1,334 +0,0 @@ -/*! - * co.js - promise and generator control flow for bcoin - * Originally based on yoursnetwork's "asink" module. - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module utils/co - */ - -const assert = require('assert'); - -/** - * Execute an instantiated generator. - * @param {Generator} gen - * @returns {Promise} - */ - -function exec(gen) { - return new Promise((resolve, reject) => { - const step = (value, rejection) => { - let next; - - try { - if (rejection) - next = gen.throw(value); - else - next = gen.next(value); - } catch (e) { - reject(e); - return; - } - - if (next.done) { - resolve(next.value); - return; - } - - if (!isPromise(next.value)) { - step(next.value, false); - return; - } - - // eslint-disable-next-line no-use-before-define - next.value.then(succeed, fail); - }; - - const succeed = (value) => { - step(value, false); - }; - - const fail = (value) => { - step(value, true); - }; - - step(undefined, false); - }); -} - -/** - * Execute generator function - * with a context and execute. - * @param {GeneratorFunction} generator - * @param {Object?} self - * @returns {Promise} - */ - -function spawn(generator, self) { - const gen = generator.call(self); - return exec(gen); -} - -/** - * Wrap a generator function to be - * executed into a function that - * returns a promise. - * @param {GeneratorFunction} - * @returns {Function} - */ - -function co(generator) { - return function() { - const gen = generator.apply(this, arguments); - return exec(gen); - }; -} - -/** - * Test whether an object is a promise. - * @param {Object} obj - * @returns {Boolean} - */ - -function isPromise(obj) { - return obj && typeof obj.then === 'function'; -} - -/** - * Wait for a nextTick with a promise. - * @returns {Promise} - */ - -function wait() { - return new Promise(resolve => setImmediate(resolve)); -}; - -/** - * Wait for a timeout with a promise. - * @param {Number} time - * @returns {Promise} - */ - -function timeout(time) { - return new Promise(resolve => setTimeout(resolve, time)); -} - -/** - * Wrap `resolve` and `reject` into - * a node.js style callback. - * @param {Function} resolve - * @param {Function} reject - * @returns {Function} - */ - -function wrap(resolve, reject) { - return function(err, result) { - if (err) { - reject(err); - return; - } - resolve(result); - }; -} - -/** - * Wrap a function that accepts node.js - * style callbacks into a function that - * returns a promise. - * @param {Function} func - * @returns {AsyncFunction} - */ - -function promisify(func) { - return function(...args) { - return new Promise((resolve, reject) => { - args.push(wrap(resolve, reject)); - func.call(this, ...args); - }); - }; -} - -/** - * Wrap a promise-returning function - * into a function that accepts a - * node.js style callback. - * @param {AsyncFunction} func - * @returns {Function} - */ - -function callbackify(func) { - return function(...args) { - if (args.length === 0 - || typeof args[args.length - 1] !== 'function') { - throw new Error(`${func.name || 'Function'} requires a callback.`); - } - - const callback = args.pop(); - - func.call(this, ...args).then((value) => { - setImmediate(() => callback(null, value)); - }, (err) => { - setImmediate(() => callback(err)); - }); - }; -} - -/** - * Execute each promise and - * have them pass a truth test. - * @method - * @param {Promise[]} jobs - * @returns {Promise} - */ - -async function every(jobs) { - const result = await Promise.all(jobs); - - for (const item of result) { - if (!item) - return false; - } - - return true; -} - -/** - * Start an interval. Wait for promise - * to resolve on each iteration. - * @param {Function} func - * @param {Number?} time - * @param {Object?} self - * @returns {Object} - */ - -function startInterval(func, time, self) { - const ctx = { - timer: null, - stopped: false, - running: false, - resolve: null - }; - - const cb = async () => { - assert(ctx.timer != null); - ctx.timer = null; - - try { - ctx.running = true; - await func.call(self); - } finally { - ctx.running = false; - if (!ctx.stopped) - ctx.timer = setTimeout(cb, time); - else if (ctx.resolve) - ctx.resolve(); - } - }; - - ctx.timer = setTimeout(cb, time); - - return ctx; -} - -/** - * Clear an interval. - * @param {Object} ctx - */ - -function stopInterval(ctx) { - assert(ctx); - - if (ctx.timer != null) { - clearTimeout(ctx.timer); - ctx.timer = null; - } - - ctx.stopped = true; - - if (ctx.running) { - return new Promise((r) => { - ctx.resolve = r; - }); - } - - return Promise.resolve(); -} - -/** - * Start a timeout. - * @param {Function} func - * @param {Number?} time - * @param {Object?} self - * @returns {Object} - */ - -function startTimeout(func, time, self) { - return { - timer: setTimeout(func.bind(self), time), - stopped: false - }; -} - -/** - * Clear a timeout. - * @param {Object} ctx - */ - -function stopTimeout(ctx) { - assert(ctx); - if (ctx.timer != null) { - clearTimeout(ctx.timer); - ctx.timer = null; - } - ctx.stopped = true; -} - -/** - * Create a job object. - * @returns {Job} - */ - -function job(resolve, reject) { - return new Job(resolve, reject); -} - -/** - * Job - * @constructor - * @ignore - * @param {Function} resolve - * @param {Function} reject - * @property {Function} resolve - * @property {Function} reject - */ - -function Job(resolve, reject) { - this.resolve = resolve; - this.reject = reject; -} - -/* - * Expose - */ - -exports = co; -exports.exec = exec; -exports.spawn = spawn; -exports.co = co; -exports.wait = wait; -exports.timeout = timeout; -exports.wrap = wrap; -exports.promisify = promisify; -exports.callbackify = callbackify; -exports.every = every; -exports.setInterval = startInterval; -exports.clearInterval = stopInterval; -exports.setTimeout = startTimeout; -exports.clearTimeout = stopTimeout; -exports.job = job; - -module.exports = exports; diff --git a/lib/utils/encoding.js b/lib/utils/encoding.js deleted file mode 100644 index 81c7db34d..000000000 --- a/lib/utils/encoding.js +++ /dev/null @@ -1,857 +0,0 @@ -/*! - * encoding.js - encoding utils for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module utils/encoding - */ - -const {U64, I64} = require('./int64'); -const UINT128_MAX = U64.UINT64_MAX.shrn(7); -const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER; -const encoding = exports; - -/** - * An empty buffer. - * @const {Buffer} - * @default - */ - -encoding.DUMMY = Buffer.from([0]); - -/** - * A hash of all zeroes with a `1` at the - * end (used for the SIGHASH_SINGLE bug). - * @const {Buffer} - * @default - */ - -encoding.ONE_HASH = Buffer.from( - '0100000000000000000000000000000000000000000000000000000000000000', - 'hex' -); - -/** - * A hash of all zeroes. - * @const {Buffer} - * @default - */ - -encoding.ZERO_HASH = Buffer.from( - '0000000000000000000000000000000000000000000000000000000000000000', - 'hex' -); - -/** - * A hash of all 0xff. - * @const {Buffer} - * @default - */ - -encoding.MAX_HASH = Buffer.from( - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - 'hex' -); - -/** - * A hash of all zeroes. - * @const {String} - * @default - */ - -encoding.NULL_HASH = - '0000000000000000000000000000000000000000000000000000000000000000'; - -/** - * A hash of all 0xff. - * @const {String} - * @default - */ - -encoding.HIGH_HASH = - 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; - -/** - * A hash of all zeroes. - * @const {Buffer} - * @default - */ - -encoding.ZERO_HASH160 = Buffer.from( - '0000000000000000000000000000000000000000', - 'hex' -); - -/** - * A hash of all 0xff. - * @const {String} - * @default - */ - -encoding.MAX_HASH160 = Buffer.from( - 'ffffffffffffffffffffffffffffffffffffffff', - 'hex' -); - -/** - * A hash of all zeroes. - * @const {String} - * @default - */ - -encoding.NULL_HASH160 = '0000000000000000000000000000000000000000'; - -/** - * A hash of all 0xff. - * @const {String} - * @default - */ - -encoding.HIGH_HASH160 = 'ffffffffffffffffffffffffffffffffffffffff'; - -/** - * A compressed pubkey of all zeroes. - * @const {Buffer} - * @default - */ - -encoding.ZERO_KEY = Buffer.from( - '000000000000000000000000000000000000000000000000000000000000000000', - 'hex' -); - -/** - * A 73 byte signature of all zeroes. - * @const {Buffer} - * @default - */ - -encoding.ZERO_SIG = Buffer.from('' - + '0000000000000000000000000000000000000000000000000000000000000000' - + '0000000000000000000000000000000000000000000000000000000000000000' - + '000000000000000000', - 'hex' -); - -/** - * A 64 byte signature of all zeroes. - * @const {Buffer} - * @default - */ - -encoding.ZERO_SIG64 = Buffer.from('' - + '0000000000000000000000000000000000000000000000000000000000000000' - + '0000000000000000000000000000000000000000000000000000000000000000', - 'hex' -); - -/** - * 4 zero bytes. - * @const {Buffer} - * @default - */ - -encoding.ZERO_U32 = Buffer.from('00000000', 'hex'); - -/** - * 8 zero bytes. - * @const {Buffer} - * @default - */ - -encoding.ZERO_U64 = Buffer.from('0000000000000000', 'hex'); - -/** - * Read uint64le as a js number. - * @param {Buffer} data - * @param {Number} off - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.readU64 = function readU64(data, off) { - const hi = data.readUInt32LE(off + 4, true); - const lo = data.readUInt32LE(off, true); - enforce((hi & 0xffe00000) === 0, off, 'Number exceeds 2^53-1'); - return hi * 0x100000000 + lo; -}; - -/** - * Read uint64be as a js number. - * @param {Buffer} data - * @param {Number} off - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.readU64BE = function readU64BE(data, off) { - const hi = data.readUInt32BE(off, true); - const lo = data.readUInt32BE(off + 4, true); - enforce((hi & 0xffe00000) === 0, off, 'Number exceeds 2^53-1'); - return hi * 0x100000000 + lo; -}; - -/** - * Read int64be as a js number. - * @param {Buffer} data - * @param {Number} off - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.readI64 = function readI64(data, off) { - const hi = data.readInt32LE(off + 4, true); - const lo = data.readUInt32LE(off, true); - enforce(isSafe(hi, lo), 'Number exceeds 2^53-1'); - return hi * 0x100000000 + lo; -}; - -/** - * Read int64be as a js number. - * @param {Buffer} data - * @param {Number} off - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.readI64BE = function readI64BE(data, off) { - const hi = data.readInt32BE(off, true); - const lo = data.readUInt32BE(off + 4, true); - enforce(isSafe(hi, lo), 'Number exceeds 2^53-1'); - return hi * 0x100000000 + lo; -}; - -/** - * Write a javascript number as a uint64le. - * @param {Buffer} dst - * @param {Number} num - * @param {Number} off - * @returns {Number} Buffer offset. - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.writeU64 = function writeU64(dst, num, off) { - return write64(dst, num, off, false); -}; - -/** - * Write a javascript number as a uint64be. - * @param {Buffer} dst - * @param {Number} num - * @param {Number} off - * @returns {Number} Buffer offset. - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.writeU64BE = function writeU64BE(dst, num, off) { - return write64(dst, num, off, true); -}; - -/** - * Write a javascript number as an int64le. - * @param {Buffer} dst - * @param {Number} num - * @param {Number} off - * @returns {Number} Buffer offset. - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.writeI64 = function writeI64(dst, num, off) { - return write64(dst, num, off, false); -}; - -/** - * Write a javascript number as an int64be. - * @param {Buffer} dst - * @param {Number} num - * @param {Number} off - * @returns {Number} Buffer offset. - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.writeI64BE = function writeI64BE(dst, num, off) { - return write64(dst, num, off, true); -}; - -/** - * Read uint64le. - * @param {Buffer} data - * @param {Number} off - * @returns {U64} - */ - -encoding.readU64N = function readU64N(data, off) { - return U64.readLE(data, off); -}; - -/** - * Read uint64be. - * @param {Buffer} data - * @param {Number} off - * @returns {U64} - */ - -encoding.readU64BEN = function readU64BEN(data, off) { - return U64.readBE(data, off); -}; - -/** - * Read int64le. - * @param {Buffer} data - * @param {Number} off - * @returns {I64} - */ - -encoding.readI64N = function readI64N(data, off) { - return I64.readLE(data, off); -}; -/** - * Read int64be. - * @param {Buffer} data - * @param {Number} off - * @returns {I64} - */ - -encoding.readI64BEN = function readI64BEN(data, off) { - return I64.readBE(data, off); -}; - -/** - * Write uint64le. - * @param {Buffer} dst - * @param {U64} num - * @param {Number} off - * @returns {Number} Buffer offset. - */ - -encoding.writeU64N = function writeU64N(dst, num, off) { - enforce(!num.sign, off, 'Signed'); - return num.writeLE(dst, off); -}; - -/** - * Write uint64be. - * @param {Buffer} dst - * @param {U64} num - * @param {Number} off - * @returns {Number} Buffer offset. - */ - -encoding.writeU64BEN = function writeU64BEN(dst, num, off) { - enforce(!num.sign, off, 'Signed'); - return num.writeBE(dst, off); -}; - -/** - * Write int64le. - * @param {Buffer} dst - * @param {U64} num - * @param {Number} off - * @returns {Number} Buffer offset. - */ - -encoding.writeI64N = function writeI64N(dst, num, off) { - enforce(num.sign, off, 'Not signed'); - return num.writeLE(dst, off); -}; - -/** - * Write int64be. - * @param {Buffer} dst - * @param {I64} num - * @param {Number} off - * @returns {Number} Buffer offset. - */ - -encoding.writeI64BEN = function writeI64BEN(dst, num, off) { - enforce(num.sign, off, 'Not signed'); - return num.writeBE(dst, off); -}; - -/** - * Read a varint. - * @param {Buffer} data - * @param {Number} off - * @returns {Object} - */ - -encoding.readVarint = function readVarint(data, off) { - let value, size; - - assert(off < data.length, off); - - switch (data[off]) { - case 0xff: - size = 9; - assert(off + size <= data.length, off); - value = encoding.readU64(data, off + 1); - enforce(value > 0xffffffff, off, 'Non-canonical varint'); - break; - case 0xfe: - size = 5; - assert(off + size <= data.length, off); - value = data.readUInt32LE(off + 1, true); - enforce(value > 0xffff, off, 'Non-canonical varint'); - break; - case 0xfd: - size = 3; - assert(off + size <= data.length, off); - value = data[off + 1] | (data[off + 2] << 8); - enforce(value >= 0xfd, off, 'Non-canonical varint'); - break; - default: - size = 1; - value = data[off]; - break; - } - - return new Varint(size, value); -}; - -/** - * Write a varint. - * @param {Buffer} dst - * @param {Number} num - * @param {Number} off - * @returns {Number} Buffer offset. - */ - -encoding.writeVarint = function writeVarint(dst, num, off) { - if (num < 0xfd) { - dst[off++] = num & 0xff; - return off; - } - - if (num <= 0xffff) { - dst[off++] = 0xfd; - dst[off++] = num & 0xff; - dst[off++] = (num >> 8) & 0xff; - return off; - } - - if (num <= 0xffffffff) { - dst[off++] = 0xfe; - dst[off++] = num & 0xff; - dst[off++] = (num >> 8) & 0xff; - dst[off++] = (num >> 16) & 0xff; - dst[off++] = num >>> 24; - return off; - } - - dst[off++] = 0xff; - off = encoding.writeU64(dst, num, off); - return off; -}; - -/** - * Calculate size of varint. - * @param {Number} num - * @returns {Number} size - */ - -encoding.sizeVarint = function sizeVarint(num) { - if (num < 0xfd) - return 1; - - if (num <= 0xffff) - return 3; - - if (num <= 0xffffffff) - return 5; - - return 9; -}; - -/** - * Read a varint. - * @param {Buffer} data - * @param {Number} off - * @returns {Object} - */ - -encoding.readVarintN = function readVarintN(data, off) { - assert(off < data.length, off); - - if (data[off] === 0xff) { - const size = 9; - assert(off + size <= data.length, off); - const value = encoding.readU64N(data, off + 1); - enforce(value.hi !== 0, off, 'Non-canonical varint'); - return new Varint(size, value); - } - - const {size, value} = encoding.readVarint(data, off); - - return new Varint(size, U64.fromInt(value)); -}; - -/** - * Write a varint. - * @param {Buffer} dst - * @param {U64} num - * @param {Number} off - * @returns {Number} Buffer offset. - */ - -encoding.writeVarintN = function writeVarintN(dst, num, off) { - enforce(!num.sign, off, 'Signed'); - - if (num.hi !== 0) { - dst[off++] = 0xff; - return encoding.writeU64N(dst, num, off); - } - - return encoding.writeVarint(dst, num.toInt(), off); -}; - -/** - * Calculate size of varint. - * @param {U64} num - * @returns {Number} size - */ - -encoding.sizeVarintN = function sizeVarintN(num) { - enforce(!num.sign, 0, 'Signed'); - - if (num.hi !== 0) - return 9; - - return encoding.sizeVarint(num.toInt()); -}; - -/** - * Read a varint (type 2). - * @param {Buffer} data - * @param {Number} off - * @returns {Object} - */ - -encoding.readVarint2 = function readVarint2(data, off) { - let num = 0; - let size = 0; - - for (;;) { - assert(off < data.length, off); - - const ch = data[off++]; - size++; - - // Number.MAX_SAFE_INTEGER >>> 7 - enforce(num <= 0x3fffffffffff - (ch & 0x7f), off, 'Number exceeds 2^53-1'); - - // num = (num << 7) | (ch & 0x7f); - num = (num * 0x80) + (ch & 0x7f); - - if ((ch & 0x80) === 0) - break; - - enforce(num !== MAX_SAFE_INTEGER, off, 'Number exceeds 2^53-1'); - num++; - } - - return new Varint(size, num); -}; - -/** - * Write a varint (type 2). - * @param {Buffer} dst - * @param {Number} num - * @param {Number} off - * @returns {Number} Buffer offset. - */ - -encoding.writeVarint2 = function writeVarint2(dst, num, off) { - const tmp = []; - - let len = 0; - - for (;;) { - tmp[len] = (num & 0x7f) | (len ? 0x80 : 0x00); - if (num <= 0x7f) - break; - // num = (num >>> 7) - 1; - num = ((num - (num % 0x80)) / 0x80) - 1; - len++; - } - - assert(off + len + 1 <= dst.length, off); - - do { - dst[off++] = tmp[len]; - } while (len--); - - return off; -}; - -/** - * Calculate size of varint (type 2). - * @param {Number} num - * @returns {Number} size - */ - -encoding.sizeVarint2 = function sizeVarint2(num) { - let size = 0; - - for (;;) { - size++; - if (num <= 0x7f) - break; - // num = (num >>> 7) - 1; - num = ((num - (num % 0x80)) / 0x80) - 1; - } - - return size; -}; - -/** - * Read a varint (type 2). - * @param {Buffer} data - * @param {Number} off - * @returns {Object} - */ - -encoding.readVarint2N = function readVarint2N(data, off) { - const num = new U64(); - - let size = 0; - - for (;;) { - assert(off < data.length, off); - - const ch = data[off++]; - size++; - - enforce(num.lte(UINT128_MAX), off, 'Number exceeds 2^64-1'); - - num.ishln(7).iorn(ch & 0x7f); - - if ((ch & 0x80) === 0) - break; - - enforce(!num.eq(U64.UINT64_MAX), off, 'Number exceeds 2^64-1'); - num.iaddn(1); - } - - return new Varint(size, num); -}; - -/** - * Write a varint (type 2). - * @param {Buffer} dst - * @param {U64} num - * @param {Number} off - * @returns {Number} Buffer offset. - */ - -encoding.writeVarint2N = function writeVarint2N(dst, num, off) { - enforce(!num.sign, off, 'Signed'); - - if (num.hi === 0) - return encoding.writeVarint2(dst, num.toInt(), off); - - num = num.clone(); - - const tmp = []; - - let len = 0; - - for (;;) { - tmp[len] = num.andln(0x7f) | (len ? 0x80 : 0x00); - if (num.lten(0x7f)) - break; - num.ishrn(7).isubn(1); - len++; - } - - enforce(off + len + 1 <= dst.length, off, 'Out of bounds write'); - - do { - dst[off++] = tmp[len]; - } while (len--); - - return off; -}; - -/** - * Calculate size of varint (type 2). - * @param {U64} num - * @returns {Number} size - */ - -encoding.sizeVarint2N = function sizeVarint2N(num) { - enforce(!num.sign, 0, 'Signed'); - - if (num.hi === 0) - return encoding.sizeVarint2(num.toInt()); - - num = num.clone(); - - let size = 0; - - for (;;) { - size++; - if (num.lten(0x7f)) - break; - num.ishrn(7).isubn(1); - } - - return size; -}; - -/** - * Serialize number as a u8. - * @param {Number} num - * @returns {Buffer} - */ - -encoding.U8 = function U8(num) { - const data = Buffer.allocUnsafe(1); - data[0] = num >>> 0; - return data; -}; - -/** - * Serialize number as a u32le. - * @param {Number} num - * @returns {Buffer} - */ - -encoding.U32 = function U32(num) { - const data = Buffer.allocUnsafe(4); - data.writeUInt32LE(num, 0, true); - return data; -}; - -/** - * Serialize number as a u32be. - * @param {Number} num - * @returns {Buffer} - */ - -encoding.U32BE = function U32BE(num) { - const data = Buffer.allocUnsafe(4); - data.writeUInt32BE(num, 0, true); - return data; -}; - -/** - * Get size of varint-prefixed bytes. - * @param {Buffer} data - * @returns {Number} - */ - -encoding.sizeVarBytes = function sizeVarBytes(data) { - return encoding.sizeVarint(data.length) + data.length; -}; - -/** - * Get size of varint-prefixed length. - * @param {Number} len - * @returns {Number} - */ - -encoding.sizeVarlen = function sizeVarlen(len) { - return encoding.sizeVarint(len) + len; -}; - -/** - * Get size of varint-prefixed string. - * @param {String} str - * @returns {Number} - */ - -encoding.sizeVarString = function sizeVarString(str, enc) { - if (typeof str !== 'string') - return encoding.sizeVarBytes(str); - - const len = Buffer.byteLength(str, enc); - - return encoding.sizeVarint(len) + len; -}; - -/** - * EncodingError - * @constructor - * @param {Number} offset - * @param {String} reason - */ - -encoding.EncodingError = function EncodingError(offset, reason, start) { - if (!(this instanceof EncodingError)) - return new EncodingError(offset, reason, start); - - Error.call(this); - - this.type = 'EncodingError'; - this.message = `${reason} (offset=${offset}).`; - - if (Error.captureStackTrace) - Error.captureStackTrace(this, start || EncodingError); -}; - -Object.setPrototypeOf(encoding.EncodingError.prototype, Error.prototype); - -/* - * Helpers - */ - -function isSafe(hi, lo) { - if (hi < 0) { - hi = ~hi; - if (lo === 0) - hi += 1; - } - - return (hi & 0xffe00000) === 0; -} - -function write64(dst, num, off, be) { - let neg = false; - - if (num < 0) { - num = -num; - neg = true; - } - - let hi = (num * (1 / 0x100000000)) | 0; - let lo = num | 0; - - if (neg) { - if (lo === 0) { - hi = (~hi + 1) | 0; - } else { - hi = ~hi; - lo = ~lo + 1; - } - } - - if (be) { - off = dst.writeInt32BE(hi, off, true); - off = dst.writeInt32BE(lo, off, true); - } else { - off = dst.writeInt32LE(lo, off, true); - off = dst.writeInt32LE(hi, off, true); - } - - return off; -} - -function Varint(size, value) { - this.size = size; - this.value = value; -} - -function assert(value, offset) { - if (!value) - throw new encoding.EncodingError(offset, 'Out of bounds read', assert); -} - -function enforce(value, offset, reason) { - if (!value) - throw new encoding.EncodingError(offset, reason, enforce); -} diff --git a/lib/utils/enforce.js b/lib/utils/enforce.js deleted file mode 100644 index 34b363bce..000000000 --- a/lib/utils/enforce.js +++ /dev/null @@ -1,165 +0,0 @@ -/*! - * enforce.js - type enforcement for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const util = require('./util'); - -function enforce(value, name, type, func) { - if (!value) { - if (!func) - func = enforce; - - if (name && !type) - throwError(name, func); - - if (!name) - name = 'value'; - - throwError(`'${name}' must be a(n) ${type}.`, func); - } -} - -function throwError(msg, func) { - const error = new TypeError(msg); - if (Error.captureStackTrace && func) - Error.captureStackTrace(error, func); - throw error; -} - -enforce.none = function none(value, name) { - enforce(value == null, name, 'object', none); -}; - -enforce.nul = function nul(value, name) { - enforce(value === null, name, 'object', nul); -}; - -enforce.undef = function undef(value, name) { - enforce(value === undefined, name, 'object', undef); -}; - -enforce.str = function str(value, name) { - enforce(typeof value === 'string', name, 'string', str); -}; - -enforce.bool = function bool(value, name) { - enforce(typeof value === 'boolean', name, 'boolean', bool); -}; - -enforce.num = function num(value, name) { - enforce(util.isNumber(value), name, 'number', num); -}; - -enforce.obj = function obj(v, name) { - enforce(v && typeof v === 'object' && !Array.isArray(v), name, 'object', obj); -}; - -enforce.array = function array(value, name) { - enforce(Array.isArray(value), name, 'object', array); -}; - -enforce.func = function func(value, name) { - enforce(typeof value === 'function', name, 'function', func); -}; - -enforce.error = function error(value, name) { - enforce(value instanceof Error, name, 'object', error); -}; - -enforce.regexp = function regexp(value, name) { - enforce(value && typeof value.exec === 'function' , name, 'object', regexp); -}; - -enforce.buf = function buf(value, name) { - enforce(Buffer.isBuffer(value), name, 'buffer', buf); -}; - -enforce.len = function len(value, length, name) { - if ((typeof value !== 'string' && !value) || value.length !== length) { - if (!name) - name = 'value'; - throwError(`'${name}' must have a length of ${length}.`, len); - } -}; - -enforce.instance = function instance(obj, parent, name) { - if (!(obj instanceof parent)) { - if (!name) - name = 'value'; - throwError(`'${name}' must be an instance of ${parent.name}.`, instance); - } -}; - -enforce.uint = function uint(value, name) { - enforce(util.isUInt(value), name, 'uint', uint); -}; - -enforce.int = function int(value, name) { - enforce(util.isInt(value), name, 'int', int); -}; - -enforce.u8 = function u8(value, name) { - enforce(util.isU8(value), name, 'uint8', u8); -}; - -enforce.u16 = function u16(value, name) { - enforce(util.isU16(value), name, 'uint16', u16); -}; - -enforce.u32 = function u32(value, name) { - enforce(util.isU32(value), name, 'uint32', u32); -}; - -enforce.u64 = function u64(value, name) { - enforce(util.isU64(value), name, 'uint64', u64); -}; - -enforce.i8 = function i8(value, name) { - enforce(util.isI8(value), name, 'int8', i8); -}; - -enforce.i16 = function i16(value, name) { - enforce(util.isI16(value), name, 'int16', i16); -}; - -enforce.i32 = function i32(value, name) { - enforce(util.isI32(value), name, 'int32', i32); -}; - -enforce.i64 = function i64(value, name) { - enforce(util.isI64(value), name, 'int64', i64); -}; - -enforce.ufloat = function ufloat(value, name) { - enforce(util.isUfloat(value), name, 'positive float', ufloat); -}; - -enforce.float = function float(value, name) { - enforce(util.isFloat(value), name, 'float', float); -}; - -enforce.ascii = function ascii(value, name) { - enforce(util.isAscii(value), name, 'ascii string', ascii); -}; - -enforce.hex = function hex(value, name) { - enforce(util.isHex(value), name, 'hex string', hex); -}; - -enforce.hex160 = function hex160(value, name) { - enforce(util.isHex160(value), name, '160 bit hex string', hex160); -}; - -enforce.hex256 = function hex256(value, name) { - enforce(util.isHex256(value), name, '256 bit hex string', hex256); -}; - -enforce.base58 = function base58(value, name) { - enforce(util.isBase58(value), name, 'base58 string', base58); -}; - -module.exports = enforce; diff --git a/lib/utils/fixed.js b/lib/utils/fixed.js new file mode 100644 index 000000000..d7fd96715 --- /dev/null +++ b/lib/utils/fixed.js @@ -0,0 +1,216 @@ +/*! + * fixed.js - fixed number parsing + * Copyright (c) 2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const assert = require('bsert'); + +/** + * Convert int to fixed number string and reduce by a + * power of ten (uses no floating point arithmetic). + * @param {Number} num + * @param {Number} exp - Number of decimal places. + * @returns {String} Fixed number string. + */ + +exports.encode = function encode(num, exp) { + // assert(Number.isSafeInteger(num), 'Invalid integer value.'); + + let sign = ''; + + if (num < 0) { + num = -num; + sign = '-'; + } + + const mult = pow10(exp); + + let lo = num % mult; + let hi = (num - lo) / mult; + + lo = lo.toString(10); + hi = hi.toString(10); + + while (lo.length < exp) + lo = '0' + lo; + + lo = lo.replace(/0+$/, ''); + + assert(lo.length <= exp, 'Invalid integer value.'); + + if (lo.length === 0) + lo = '0'; + + if (exp === 0) + return `${sign}${hi}`; + + return `${sign}${hi}.${lo}`; +}; + +/** + * Parse a fixed number string and multiply by a + * power of ten (uses no floating point arithmetic). + * @param {String} str + * @param {Number} exp - Number of decimal places. + * @returns {Number} Integer. + */ + +exports.decode = function decode(str, exp) { + assert(typeof str === 'string'); + assert(str.length <= 32, 'Fixed number string too large.'); + + let sign = 1; + + if (str.length > 0 && str[0] === '-') { + str = str.substring(1); + sign = -1; + } + + let hi = str; + let lo = '0'; + + const index = str.indexOf('.'); + + if (index !== -1) { + hi = str.substring(0, index); + lo = str.substring(index + 1); + } + + hi = hi.replace(/^0+/, ''); + lo = lo.replace(/0+$/, ''); + + assert(hi.length <= 16 - exp, + 'Fixed number string exceeds 2^53-1.'); + + assert(lo.length <= exp, + 'Too many decimal places in fixed number string.'); + + if (hi.length === 0) + hi = '0'; + + while (lo.length < exp) + lo += '0'; + + if (lo.length === 0) + lo = '0'; + + assert(/^\d+$/.test(hi) && /^\d+$/.test(lo), + 'Non-numeric characters in fixed number string.'); + + hi = parseInt(hi, 10); + lo = parseInt(lo, 10); + + const mult = pow10(exp); + const maxLo = modSafe(mult); + const maxHi = divSafe(mult); + + assert(hi < maxHi || (hi === maxHi && lo <= maxLo), + 'Fixed number string exceeds 2^53-1.'); + + return sign * (hi * mult + lo); +}; + +/** + * Convert int to float and reduce by a power + * of ten (uses no floating point arithmetic). + * @param {Number} num + * @param {Number} exp - Number of decimal places. + * @returns {Number} Double float. + */ + +exports.toFloat = function toFloat(num, exp) { + return parseFloat(exports.encode(num, exp)); +}; + +/** + * Parse a double float number and multiply by a + * power of ten (uses no floating point arithmetic). + * @param {Number} num + * @param {Number} exp - Number of decimal places. + * @returns {Number} Integer. + */ + +exports.fromFloat = function fromFloat(num, exp) { + assert(typeof num === 'number' && isFinite(num)); + assert(Number.isSafeInteger(exp)); + return exports.decode(num.toFixed(exp), exp); +}; + +/* + * Helpers + */ + +function pow10(exp) { + switch (exp) { + case 0: + return 1; + case 1: + return 10; + case 2: + return 100; + case 3: + return 1000; + case 4: + return 10000; + case 5: + return 100000; + case 6: + return 1000000; + case 7: + return 10000000; + case 8: + return 100000000; + } + throw new Error('Exponent is too large.'); +} + +function modSafe(mod) { + switch (mod) { + case 1: + return 0; + case 10: + return 1; + case 100: + return 91; + case 1000: + return 991; + case 10000: + return 991; + case 100000: + return 40991; + case 1000000: + return 740991; + case 10000000: + return 4740991; + case 100000000: + return 54740991; + } + throw new Error('Exponent is too large.'); +} + +function divSafe(div) { + switch (div) { + case 1: + return 9007199254740991; + case 10: + return 900719925474099; + case 100: + return 90071992547409; + case 1000: + return 9007199254740; + case 10000: + return 900719925474; + case 100000: + return 90071992547; + case 1000000: + return 9007199254; + case 10000000: + return 900719925; + case 100000000: + return 90071992; + } + throw new Error('Exponent is too large.'); +} diff --git a/lib/utils/fs-browser.js b/lib/utils/fs-browser.js deleted file mode 100644 index 212272704..000000000 --- a/lib/utils/fs-browser.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -exports.unsupported = true; diff --git a/lib/utils/fs.js b/lib/utils/fs.js deleted file mode 100644 index 5c1a7be96..000000000 --- a/lib/utils/fs.js +++ /dev/null @@ -1,175 +0,0 @@ -/*! - * fs.js - promisified fs module for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const fs = require('fs'); -const co = require('./co'); - -exports.access = co.promisify(fs.access); -exports.accessSync = fs.accessSync; -exports.appendFile = co.promisify(fs.appendFile); -exports.appendFileSync = fs.appendFileSync; -exports.chmod = co.promisify(fs.chmod); -exports.chmodSync = fs.chmodSync; -exports.chown = co.promisify(fs.chown); -exports.chownSync = fs.chownSync; -exports.close = co.promisify(fs.close); -exports.closeSync = fs.closeSync; -exports.constants = fs.constants; -exports.createReadStream = fs.createReadStream; -exports.createWriteStream = fs.createWriteStream; -exports.exists = async (file) => { - try { - await exports.stat(file); - return true; - } catch (e) { - if (e.code === 'ENOENT') - return false; - throw e; - } -}; -exports.existsSync = (file) => { - try { - exports.statSync(file); - return true; - } catch (e) { - if (e.code === 'ENOENT') - return false; - throw e; - } -}; -exports.fchmod = co.promisify(fs.fchmod); -exports.fchmodSync = fs.fchmodSync; -exports.fchown = co.promisify(fs.fchown); -exports.fchownSync = fs.fchownSync; -exports.fdatasync = co.promisify(fs.fdatasync); -exports.fdatasyncSync = fs.fdatasyncSync; -exports.fstat = co.promisify(fs.fstat); -exports.fstatSync = fs.fstatSync; -exports.fsync = co.promisify(fs.fsync); -exports.fsyncSync = fs.fsyncSync; -exports.ftruncate = co.promisify(fs.ftruncate); -exports.ftruncateSync = fs.ftruncateSync; -exports.futimes = co.promisify(fs.futimes); -exports.futimesSync = fs.futimesSync; -exports.lchmod = co.promisify(fs.lchmod); -exports.lchmodSync = fs.lchmodSync; -exports.lchown = co.promisify(fs.lchown); -exports.lchownSync = fs.lchownSync; -exports.link = co.promisify(fs.link); -exports.linkSync = fs.linkSync; -exports.lstat = co.promisify(fs.lstat); -exports.lstatSync = fs.lstatSync; -exports.mkdir = co.promisify(fs.mkdir); -exports.mkdirSync = fs.mkdirSync; -exports.mkdtemp = co.promisify(fs.mkdtemp); -exports.mkdtempSync = fs.mkdtempSync; -exports.open = co.promisify(fs.open); -exports.openSync = fs.openSync; -exports.read = co.promisify(fs.read); -exports.readSync = fs.readSync; -exports.readdir = co.promisify(fs.readdir); -exports.readdirSync = fs.readdirSync; -exports.readFile = co.promisify(fs.readFile); -exports.readFileSync = fs.readFileSync; -exports.readlink = co.promisify(fs.readlink); -exports.readlinkSync = fs.readlinkSync; -exports.realpath = co.promisify(fs.realpath); -exports.realpathSync = fs.realpathSync; -exports.rename = co.promisify(fs.rename); -exports.renameSync = fs.renameSync; -exports.rmdir = co.promisify(fs.rmdir); -exports.rmdirSync = fs.rmdirSync; -exports.stat = co.promisify(fs.stat); -exports.statSync = fs.statSync; -exports.symlink = co.promisify(fs.symlink); -exports.symlinkSync = fs.symlinkSync; -exports.truncate = co.promisify(fs.truncate); -exports.truncateSync = fs.truncateSync; -exports.unlink = co.promisify(fs.unlink); -exports.unlinkSync = fs.unlinkSync; -exports.unwatchFile = fs.unwatchFile; -exports.utimes = co.promisify(fs.utimes); -exports.utimesSync = fs.utimesSync; -exports.watch = fs.watch; -exports.watchFile = fs.watchFile; -exports.write = co.promisify(fs.write); -exports.writeSync = fs.writeSync; -exports.writeFile = co.promisify(fs.writeFile); -exports.writeFileSync = fs.writeFileSync; - -exports.mkdirpSync = function mkdirpSync(dir, mode) { - if (mode == null) - mode = 0o750; - - let [path, parts] = getParts(dir); - - for (const part of parts) { - path += part; - - try { - const stat = exports.statSync(path); - if (!stat.isDirectory()) - throw new Error('Could not create directory.'); - } catch (e) { - if (e.code === 'ENOENT') - exports.mkdirSync(path, mode); - else - throw e; - } - - path += '/'; - } -}; - -exports.mkdirp = async function mkdirp(dir, mode) { - if (mode == null) - mode = 0o750; - - let [path, parts] = getParts(dir); - - for (const part of parts) { - path += part; - - try { - const stat = await exports.stat(path); - if (!stat.isDirectory()) - throw new Error('Could not create directory.'); - } catch (e) { - if (e.code === 'ENOENT') - await exports.mkdir(path, mode); - else - throw e; - } - - path += '/'; - } -}; - -function getParts(path) { - path = path.replace(/\\/g, '/'); - path = path.replace(/(^|\/)\.\//, '$1'); - path = path.replace(/\/+\.?$/, ''); - - const parts = path.split(/\/+/); - - let root = ''; - - if (process.platform === 'win32') { - if (parts[0].indexOf(':') !== -1) - root = parts.shift() + '/'; - } - - if (parts.length > 0) { - if (parts[0].length === 0) { - parts.shift(); - root = '/'; - } - } - - return [root, parts]; -} diff --git a/lib/utils/gcs.js b/lib/utils/gcs.js deleted file mode 100644 index 5ee294140..000000000 --- a/lib/utils/gcs.js +++ /dev/null @@ -1,542 +0,0 @@ -/*! - * gcs.js - gcs filters for bcoin - * Copyright (c) 2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const {U64} = require('./int64'); -const digest = require('../crypto/digest'); -const siphash = require('../crypto/siphash'); -const DUMMY = Buffer.alloc(0); -const EOF = new U64(-1); - -/** - * GCSFilter - * @alias module:utils.GCSFilter - * @constructor - */ - -function GCSFilter() { - this.n = 0; - this.p = 0; - this.m = new U64(0); - this.data = DUMMY; -} - -GCSFilter.prototype.hash = function hash(enc) { - const h = digest.hash256(this.data); - return enc === 'hex' ? h.toString('hex') : h; -}; - -GCSFilter.prototype.header = function header(prev) { - return digest.root256(this.hash(), prev); -}; - -GCSFilter.prototype.match = function match(key, data) { - const br = new BitReader(this.data); - const term = siphash24(data, key).imod(this.m); - - let last = new U64(0); - - while (last.lt(term)) { - const value = this.readU64(br); - - if (value === EOF) - return false; - - value.iadd(last); - - if (value.eq(term)) - return true; - - last = value; - } - - return false; -}; - -GCSFilter.prototype.matchAny = function matchAny(key, items) { - assert(items.length > 0); - - const br = new BitReader(this.data); - const last1 = new U64(0); - const values = []; - - for (const item of items) { - const hash = siphash24(item, key).imod(this.m); - values.push(hash); - } - - values.sort(compare); - - let last2 = values[0]; - let i = 1; - - for (;;) { - const cmp = last1.cmp(last2); - - if (cmp === 0) - break; - - if (cmp > 0) { - if (i < values.length) { - last2 = values[i]; - i += 1; - continue; - } - return false; - } - - const value = this.readU64(br); - - if (value === EOF) - return false; - - last1.iadd(value); - } - - return true; -}; - -GCSFilter.prototype.readU64 = function readU64(br) { - try { - return this._readU64(br); - } catch (e) { - if (e.message === 'EOF') - return EOF; - throw e; - } -}; - -GCSFilter.prototype._readU64 = function _readU64(br) { - const num = new U64(0); - - // Unary - while (br.readBit()) - num.iaddn(1); - - const rem = br.readBits64(this.p); - - return num.ishln(this.p).ior(rem); -}; - -GCSFilter.prototype.toBytes = function toBytes() { - return this.data; -}; - -GCSFilter.prototype.toNBytes = function toNBytes() { - const data = Buffer.allocUnsafe(4 + this.data.length); - data.writeUInt32BE(this.n, 0, true); - this.data.copy(data, 4); - return data; -}; - -GCSFilter.prototype.toPBytes = function toPBytes() { - const data = Buffer.allocUnsafe(1 + this.data.length); - data.writeUInt8(this.p, 0, true); - this.data.copy(data, 1); - return data; -}; - -GCSFilter.prototype.toNPBytes = function toNPBytes() { - const data = Buffer.allocUnsafe(5 + this.data.length); - data.writeUInt32BE(this.n, 0, true); - data.writeUInt8(this.p, 4, true); - this.data.copy(data, 5); - return data; -}; - -GCSFilter.prototype.toRaw = function toRaw() { - assert(this.p === 20); - return this.toNBytes(); -}; - -GCSFilter.prototype.fromItems = function fromItems(P, key, items) { - assert(typeof P === 'number' && isFinite(P)); - assert(P >= 0 && P <= 32); - - assert(Buffer.isBuffer(key)); - assert(key.length === 16); - - assert(Array.isArray(items)); - assert(items.length > 0); - assert(items.length <= 0xffffffff); - - this.n = items.length; - this.p = P; - this.m = U64(this.n).ishln(this.p); - - const values = []; - - for (const item of items) { - assert(Buffer.isBuffer(item)); - const hash = siphash24(item, key).imod(this.m); - values.push(hash); - } - - values.sort(compare); - - const bw = new BitWriter(); - - let last = new U64(0); - - for (const hash of values) { - const rem = hash.sub(last).imaskn(this.p); - const value = hash.sub(last).isub(rem).ishrn(this.p); - - last = hash; - - // Unary - while (!value.isZero()) { - bw.writeBit(1); - value.isubn(1); - } - bw.writeBit(0); - - bw.writeBits64(rem, this.p); - } - - this.data = bw.render(); - - return this; -}; - -GCSFilter.prototype.fromBytes = function fromBytes(N, P, data) { - assert(typeof N === 'number' && isFinite(N)); - assert(typeof P === 'number' && isFinite(P)); - assert(P >= 0 && P <= 32); - assert(Buffer.isBuffer(data)); - - this.n = N; - this.p = P; - this.m = U64(this.n).ishln(this.p); - this.data = data; - - return this; -}; - -GCSFilter.prototype.fromNBytes = function fromNBytes(P, data) { - assert(typeof P === 'number' && isFinite(P)); - assert(Buffer.isBuffer(data)); - assert(data.length >= 4); - - const N = data.readUInt32BE(0, true); - - return this.fromBytes(N, P, data.slice(4)); -}; - -GCSFilter.prototype.fromPBytes = function fromPBytes(N, data) { - assert(typeof N === 'number' && isFinite(N)); - assert(Buffer.isBuffer(data)); - assert(data.length >= 1); - - const P = data.readUInt8(0, true); - - return this.fromBytes(N, P, data.slice(1)); -}; - -GCSFilter.prototype.fromNPBytes = function fromNPBytes(data) { - assert(Buffer.isBuffer(data)); - assert(data.length >= 5); - - const N = data.readUInt32BE(0, true); - const P = data.readUInt8(4, true); - - return this.fromBytes(N, P, data.slice(5)); -}; - -GCSFilter.prototype.fromRaw = function fromRaw(data) { - return this.fromNBytes(20, data); -}; - -GCSFilter.prototype.fromBlock = function fromBlock(block) { - const hash = block.hash(); - const key = hash.slice(0, 16); - const items = []; - - for (let i = 0; i < block.txs.length; i++) { - const tx = block.txs[i]; - - if (i > 0) { - for (const input of tx.inputs) - items.push(input.prevout.toRaw()); - } - - for (const output of tx.outputs) - getPushes(items, output.script); - } - - return this.fromItems(20, key, items); -}; - -GCSFilter.prototype.fromExtended = function fromExtended(block) { - const hash = block.hash(); - const key = hash.slice(0, 16); - const items = []; - - for (let i = 0; i < block.txs.length; i++) { - const tx = block.txs[i]; - - items.push(tx.hash()); - - if (i > 0) { - for (const input of tx.inputs) { - getWitness(items, input.witness); - getPushes(items, input.script); - } - } - } - - return this.fromItems(20, key, items); -}; - -GCSFilter.fromItems = function fromItems(P, key, items) { - return new GCSFilter().fromItems(P, key, items); -}; - -GCSFilter.fromBytes = function fromBytes(N, P, data) { - return new GCSFilter().fromBytes(N, P, data); -}; - -GCSFilter.fromNBytes = function fromNBytes(P, data) { - return new GCSFilter().fromNBytes(P, data); -}; - -GCSFilter.fromPBytes = function fromPBytes(N, data) { - return new GCSFilter().fromPBytes(N, data); -}; - -GCSFilter.fromNPBytes = function fromNPBytes(data) { - return new GCSFilter().fromNPBytes(data); -}; - -GCSFilter.fromRaw = function fromRaw(data) { - return new GCSFilter().fromRaw(data); -}; - -GCSFilter.fromBlock = function fromBlock(block) { - return new GCSFilter().fromBlock(block); -}; - -GCSFilter.fromExtended = function fromExtended(block) { - return new GCSFilter().fromExtended(block); -}; - -/** - * BitWriter - * @constructor - * @ignore - */ - -function BitWriter() { - this.stream = []; - this.remain = 0; -} - -BitWriter.prototype.writeBit = function writeBit(bit) { - if (this.remain === 0) { - this.stream.push(0); - this.remain = 8; - } - - if (bit) { - const index = this.stream.length - 1; - this.stream[index] |= 1 << (this.remain - 1); - } - - this.remain--; -}; - -BitWriter.prototype.writeByte = function writeByte(ch) { - if (this.remain === 0) { - this.stream.push(0); - this.remain = 8; - } - - const index = this.stream.length - 1; - - this.stream[index] |= (ch >> (8 - this.remain)) & 0xff; - this.stream.push(0); - this.stream[index + 1] = (ch << this.remain) & 0xff; -}; - -BitWriter.prototype.writeBits = function writeBits(num, count) { - assert(count >= 0); - assert(count <= 32); - - num <<= 32 - count; - - while (count >= 8) { - const ch = num >>> 24; - this.writeByte(ch); - num <<= 8; - count -= 8; - } - - while (count > 0) { - const bit = num >>> 31; - this.writeBit(bit); - num <<= 1; - count -= 1; - } -}; - -BitWriter.prototype.writeBits64 = function writeBits64(num, count) { - assert(count >= 0); - assert(count <= 64); - - if (count > 32) { - this.writeBits(num.hi, count - 32); - this.writeBits(num.lo, 32); - } else { - this.writeBits(num.lo, count); - } -}; - -BitWriter.prototype.render = function render() { - const data = Buffer.allocUnsafe(this.stream.length); - - for (let i = 0; i < this.stream.length; i++) - data[i] = this.stream[i]; - - return data; -}; - -/** - * BitReader - * @constructor - * @ignore - */ - -function BitReader(data) { - this.stream = data; - this.pos = 0; - this.remain = 8; -} - -BitReader.prototype.readBit = function readBit() { - if (this.pos >= this.stream.length) - throw new Error('EOF'); - - if (this.remain === 0) { - this.pos += 1; - - if (this.pos >= this.stream.length) - throw new Error('EOF'); - - this.remain = 8; - } - - this.remain -= 1; - - return (this.stream[this.pos] >> this.remain) & 1; -}; - -BitReader.prototype.readByte = function readByte() { - if (this.pos >= this.stream.length) - throw new Error('EOF'); - - if (this.remain === 0) { - this.pos += 1; - - if (this.pos >= this.stream.length) - throw new Error('EOF'); - - this.remain = 8; - } - - if (this.remain === 8) { - const ch = this.stream[this.pos]; - this.pos += 1; - return ch; - } - - let ch = this.stream[this.pos] & ((1 << this.remain) - 1); - ch <<= 8 - this.remain; - - this.pos += 1; - - if (this.pos >= this.stream.length) - throw new Error('EOF'); - - ch |= this.stream[this.pos] >> this.remain; - - return ch; -}; - -BitReader.prototype.readBits = function readBits(count) { - assert(count >= 0); - assert(count <= 32); - - let num = 0; - - while (count >= 8) { - num <<= 8; - num |= this.readByte(); - count -= 8; - } - - while (count > 0) { - num <<= 1; - num |= this.readBit(); - count -= 1; - } - - return num; -}; - -BitReader.prototype.readBits64 = function readBits64(count) { - assert(count >= 0); - assert(count <= 64); - - const num = new U64(); - - if (count > 32) { - num.hi = this.readBits(count - 32); - num.lo = this.readBits(32); - } else { - num.lo = this.readBits(count); - } - - return num; -}; - -/* - * Helpers - */ - -function compare(a, b) { - return a.cmp(b); -} - -function siphash24(data, key) { - const [hi, lo] = siphash(data, key); - return U64.fromBits(hi, lo); -} - -function getPushes(items, script) { - for (const op of script.code) { - if (!op.data || op.data.length === 0) - continue; - - items.push(op.data); - } -} - -function getWitness(items, witness) { - for (const item of witness.items) { - if (item.length === 0) - continue; - - items.push(item); - } -} - -/* - * Expose - */ - -module.exports = GCSFilter; diff --git a/lib/utils/heap.js b/lib/utils/heap.js deleted file mode 100644 index 17eef4cd3..000000000 --- a/lib/utils/heap.js +++ /dev/null @@ -1,241 +0,0 @@ -/*! - * heap.js - heap object for bcoin - * Copyright (c) 2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); - -/** - * Binary Heap - * @alias module:utils.Heap - * @constructor - * @param {Function?} compare - */ - -function Heap(compare) { - if (!(this instanceof Heap)) - return new Heap(compare); - - this.compare = comparator; - this.items = []; - - if (compare) - this.set(compare); -} - -/** - * Initialize and sort heap. - */ - -Heap.prototype.init = function init() { - const n = this.items.length; - - if (n <= 1) - return; - - for (let i = (n / 2 | 0) - 1; i >= 0; i--) - this.down(i, n); -}; - -/** - * Get heap size. - * @returns {Number} - */ - -Heap.prototype.size = function size() { - return this.items.length; -}; - -/** - * Set comparator. - * @param {Function} compare - */ - -Heap.prototype.set = function set(compare) { - assert(typeof compare === 'function', - 'Comparator must be a function.'); - this.compare = compare; -}; - -/** - * Push item onto heap. - * @param {Object} item - * @returns {Number} - */ - -Heap.prototype.insert = function insert(item) { - this.items.push(item); - this.up(this.items.length - 1); - return this.items.length; -}; - -/** - * Pop next item off of heap. - * @param {Object} item - * @returns {Object} - */ - -Heap.prototype.shift = function shift() { - if (this.items.length === 0) - return null; - - const n = this.items.length - 1; - - this.swap(0, n); - this.down(0, n); - - return this.items.pop(); -}; - -/** - * Remove item from heap. - * @param {Number} i - * @returns {Object} - */ - -Heap.prototype.remove = function remove(i) { - if (this.items.length === 0) - return null; - - const n = this.items.length - 1; - - if (i < 0 || i > n) - return null; - - if (n !== i) { - this.swap(i, n); - this.down(i, n); - this.up(i); - } - - return this.items.pop(); -}; - -/** - * Swap indicies. - * @private - * @param {Number} a - * @param {Number} b - */ - -Heap.prototype.swap = function swap(a, b) { - const x = this.items[a]; - const y = this.items[b]; - this.items[a] = y; - this.items[b] = x; -}; - -/** - * Compare indicies. - * @private - * @param {Number} i - * @param {Number} j - * @returns {Boolean} - */ - -Heap.prototype.less = function less(i, j) { - return this.compare(this.items[i], this.items[j]) < 0; -}; - -/** - * Bubble item down. - * @private - * @param {Number} i - * @param {Number} n - */ - -Heap.prototype.down = function down(i, n) { - for (;;) { - const l = 2 * i + 1; - - assert(l >= 0); - - if (l < 0 || l >= n) - break; - - let j = l; - const r = l + 1; - - if (r < n && !this.less(l, r)) - j = r; - - if (!this.less(j, i)) - break; - - this.swap(i, j); - i = j; - } -}; - -/** - * Bubble item up. - * @private - * @param {Number} i - */ - -Heap.prototype.up = function up(i) { - for (;;) { - const j = (i - 1) / 2 | 0; - - assert(j >= 0); - - if (j < 0 || j === i) - break; - - if (!this.less(i, j)) - break; - - this.swap(j, i); - i = j; - } -}; - -/** - * Convert heap to sorted array. - * @returns {Object[]} - */ - -Heap.prototype.toArray = function toArray() { - const heap = new Heap(); - const result = []; - - heap.compare = this.compare; - heap.items = this.items.slice(); - - while (heap.size() > 0) - result.push(heap.shift()); - - return result; -}; - -/** - * Instantiate heap from array and comparator. - * @param {Function} compare - * @param {Object[]} items - * @returns {Heap} - */ - -Heap.fromArray = function fromArray(compare, items) { - const heap = new Heap(); - heap.set(compare); - heap.items = items; - heap.init(); - return heap; -}; - -/* - * Helpers - */ - -function comparator(a, b) { - throw new Error('No heap comparator set.'); -} - -/* - * Expose - */ - -module.exports = Heap; diff --git a/lib/utils/index.js b/lib/utils/index.js index bac0d298c..7fe86858a 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -10,34 +10,9 @@ * @module utils */ -exports.ASN1 = require('./asn1'); -// exports.AsyncEmitter = require('./asyncemitter'); -exports.AsyncObject = require('./asyncobject'); -exports.base32 = require('./base32'); -exports.base58 = require('./base58'); -exports.bech32 = require('./bech32'); -exports.Bloom = require('./bloom'); -exports.co = require('./co'); -exports.encoding = require('./encoding'); -exports.enforce = require('./enforce'); -exports.fs = require('./fs'); -exports.GCSFilter = require('./gcs'); -exports.Heap = require('./heap'); -exports.Int64 = require('./int64'); -exports.IP = require('./ip'); -exports.List = require('./list'); -exports.Lock = require('./lock'); -exports.LRU = require('./lru'); -exports.MappedLock = require('./mappedlock'); -exports.murmur3 = require('./murmur3'); -exports.nfkd = require('./nfkd'); -exports.PEM = require('./pem'); -exports.ProtoWriter = require('./protowriter'); -exports.ProtoReader = require('./protoreader'); -exports.RBT = require('./rbt'); -exports.BufferReader = require('./reader'); -exports.RollingFilter = require('./rollingfilter'); -exports.StaticWriter = require('./staticwriter'); +exports.binary = require('./binary'); +exports.fixed = require('./fixed'); exports.util = require('./util'); -exports.Validator = require('./validator'); -exports.BufferWriter = require('./writer'); + +const {inspect: {custom}} = require('util'); +exports.inspectSymbol = custom || 'inspect'; diff --git a/lib/utils/int64.js b/lib/utils/int64.js deleted file mode 100644 index 2cf24b0d2..000000000 --- a/lib/utils/int64.js +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * int64.js - int64s for bcoin - * Copyright (c) 2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -module.exports = require('n64'); diff --git a/lib/utils/ip.js b/lib/utils/ip.js deleted file mode 100644 index dc84b9154..000000000 --- a/lib/utils/ip.js +++ /dev/null @@ -1,1067 +0,0 @@ -/*! - * ip.js - ip utils for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License). - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - * - * Parts of this software are based on node-ip. - * https://github.com/indutny/node-ip - * Copyright (c) 2012, Fedor Indutny (MIT License). - */ - -/* eslint no-unreachable: "off" */ - -'use strict'; - -const assert = require('assert'); -const os = require('os'); -const base32 = require('./base32'); - -/** - * @exports utils/ip - */ - -const IP = exports; - -/* - * Constants - */ - -const ZERO_IP = Buffer.from('00000000000000000000000000000000', 'hex'); -const LOCAL_IP = Buffer.from('00000000000000000000000000000001', 'hex'); -const RFC6052 = Buffer.from('0064ff9b0000000000000000', 'hex'); -const RFC4862 = Buffer.from('fe80000000000000', 'hex'); -const RFC6145 = Buffer.from('0000000000000000ffff0000', 'hex'); -const TOR_ONION = Buffer.from('fd87d87eeb43', 'hex'); -const SHIFTED = Buffer.from('00000000000000ffff', 'hex'); - -const IPV4_REGEX = /^(\d{1,3}\.){3}\d{1,3}$/; -const IPV6_REGEX = - /^(::)?(((\d{1,3}\.){3}(\d{1,3}){1})?([0-9a-f]){0,4}:{0,2}){1,8}(::)?$/i; - -/** - * IP address of all zeroes. - * @const {Buffer} - */ - -IP.ZERO_IP = ZERO_IP; - -/** - * Address types. - * @enum {Number} - */ - -IP.types = { - DNS: -1, - IPV4: 4, - IPV6: 6, - ONION: 10, - TEREDO: 11 -}; - -/** - * Parse a hostname. - * @param {String} addr - * @param {Number?} fallback - Fallback port. - * @returns {Object} Contains `host`, `port`, and `type`. - */ - -IP.fromHostname = function fromHostname(addr, fallback) { - assert(typeof addr === 'string'); - assert(addr.length > 0, 'Bad address.'); - - let host, port; - if (addr[0] === '[') { - if (addr[addr.length - 1] === ']') { - // Case: - // [::1] - host = addr.slice(1, -1); - port = null; - } else { - // Case: - // [::1]:80 - addr = addr.slice(1); - const parts = addr.split(']:'); - assert(parts.length === 2, 'Bad IPv6 address.'); - host = parts[0]; - port = parts[1]; - } - } else { - const parts = addr.split(':'); - switch (parts.length) { - case 2: - // Cases: - // 127.0.0.1:80 - // localhost:80 - host = parts[0]; - port = parts[1]; - break; - case 1: - // Cases: - // 127.0.0.1 - // localhost - host = parts[0]; - port = null; - break; - default: - // Case: - // ::1 - assert(IP.isV6String(addr), 'Bad IPv6 address.'); - host = addr; - port = null; - break; - } - } - - assert(host.length > 0, 'Bad host.'); - - if (port != null) { - assert(port.length <= 5, 'Bad port.'); - assert(/^\d+$/.test(port), 'Bad port.'); - port = parseInt(port, 10); - assert(port <= 0xffff); - } else { - port = fallback || 0; - } - - const type = IP.getStringType(host); - - let raw; - if (type !== IP.types.DNS) { - raw = IP.toBuffer(host); - host = IP.toString(raw); - } - - let hostname; - if (type === IP.types.IPV6) - hostname = `[${host}]:${port}`; - else - hostname = `${host}:${port}`; - - return new Address(host, port, type, hostname, raw); -}; - -/** - * Concatenate a host and port. - * @param {String} host - * @param {Number} port - * @returns {String} - */ - -IP.toHostname = function toHostname(host, port) { - assert(typeof host === 'string'); - assert(host.length > 0); - assert(typeof port === 'number'); - assert(port >= 0 && port <= 0xffff); - - assert(!/[\[\]]/.test(host), 'Bad host.'); - - const type = IP.getStringType(host); - - if (host.indexOf(':') !== -1) - assert(type === IP.types.IPV6, 'Bad host.'); - - if (type !== IP.types.DNS) - host = IP.normalize(host); - - if (type === IP.types.IPV6) - return `[${host}]:${port}`; - - return `${host}:${port}`; -}; - -/** - * Get address type (-1=dns, 4=ipv4, 6=ipv6, 10=tor). - * @param {String?} str - * @returns {Number} - */ - -IP.getStringType = function getStringType(str) { - if (IP.isV4String(str)) - return IP.types.IPV4; - - if (IP.isV6String(str)) - return IP.types.IPV6; - - if (IP.isOnionString(str)) - return IP.types.ONION; - - return IP.types.DNS; -}; - -/** - * Test whether a string is IPv4. - * @param {String?} str - * @returns {Boolean} - */ - -IP.isV4String = function isV4String(str) { - assert(typeof str === 'string'); - - if (str.length < 7) - return false; - - if (str.length > 15) - return false; - - return IPV4_REGEX.test(str); -}; - -/** - * Test whether a string is IPv6. - * @param {String?} str - * @returns {Boolean} - */ - -IP.isV6String = function isV6String(str) { - assert(typeof str === 'string'); - - if (str.length < 2) - return false; - - if (str.length > 39) - return false; - - return IPV6_REGEX.test(str); -}; - -/** - * Test whether a string is an onion address. - * @param {String?} str - * @returns {Boolean} - */ - -IP.isOnionString = function isOnionString(str) { - assert(typeof str === 'string'); - - if (str.length < 7) - return false; - - return str.slice(-6) === '.onion'; -}; - -/** - * Test whether a buffer is an ipv4-mapped ipv6 address. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isMapped = function isMapped(raw) { - assert(Buffer.isBuffer(raw)); - assert(raw.length === 16); - - return raw[0] === 0x00 - && raw[1] === 0x00 - && raw[2] === 0x00 - && raw[3] === 0x00 - && raw[4] === 0x00 - && raw[5] === 0x00 - && raw[6] === 0x00 - && raw[7] === 0x00 - && raw[8] === 0x00 - && raw[9] === 0x00 - && raw[10] === 0xff - && raw[11] === 0xff; -}; - -/** - * Parse an IP string and return a buffer. - * @param {String} str - * @returns {Buffer} - */ - -IP.toBuffer = function toBuffer(str) { - assert(typeof str === 'string'); - - const raw = Buffer.allocUnsafe(16); - - if (IP.isV4String(str)) { - raw.fill(0); - raw[10] = 0xff; - raw[11] = 0xff; - return IP.parseV4(str, raw, 12); - } - - if (IP.isOnionString(str)) { - const prefix = TOR_ONION; - prefix.copy(raw, 0); - const data = base32.decode(str.slice(0, -6)); - assert(data.length === 10, 'Invalid onion address.'); - data.copy(raw, 6); - return raw; - } - - return IP.parseV6(str, raw, 0); -}; - -/** - * Convert an IPv4 string to a buffer. - * @private - * @param {String} str - * @param {Buffer} raw - * @param {Number} offset - * @returns {Buffer} - */ - -IP.parseV4 = function parseV4(str, raw, offset) { - const parts = str.split('.'); - - assert(parts.length === 4); - - for (let ch of parts) { - assert(ch.length > 0); - assert(ch.length <= 3); - ch = parseInt(ch, 10); - assert(ch >= 0 && ch <= 255); - raw[offset++] = ch; - } - - return raw; -}; - -/** - * Convert an IPv6 string to a buffer. - * @private - * @param {String} str - * @param {Buffer} raw - * @param {Number} offset - * @returns {Buffer} - */ - -IP.parseV6 = function parseV6(str, raw, offset) { - const parts = str.split(':'); - let missing = 8 - parts.length; - - assert(parts.length >= 2, 'Not an IPv6 address.'); - - for (const word of parts) { - if (IP.isV4String(word)) - missing--; - } - - const start = offset; - let colon = false; - - for (let i = 0; i < parts.length; i++) { - let word = parts[i]; - - if (word.length === 0) { - assert(!colon, 'Overuse of double colon in IPv6 address.'); - - colon = true; - missing += 1; - - // Eat extra colons. - // e.g. :::0 - while (i + 1 < parts.length) { - word = parts[i + 1]; - if (word.length !== 0) - break; - missing += 1; - i++; - } - - while (missing > 0) { - raw[offset++] = 0; - raw[offset++] = 0; - missing--; - } - - continue; - } - - if (IP.isV4String(word)) { - IP.parseV4(word, raw, offset); - offset += 4; - continue; - } - - assert(word.length <= 4); - - word = parseInt(word, 16); - - assert(word === word, 'Non-number in IPv6 address.'); - - raw[offset++] = (word >> 8) & 0xff; - raw[offset++] = word & 0xff; - } - - assert(missing === 0, 'IPv6 address has missing sections.'); - assert.strictEqual(offset, start + 16); - - return raw; -}; - -/** - * Convert a buffer to an ip string. - * @param {Buffer} raw - * @returns {String} - */ - -IP.toString = function toString(raw) { - assert(Buffer.isBuffer(raw)); - - if (raw.length === 4) { - let host = ''; - host += raw[0]; - host += '.' + raw[1]; - host += '.' + raw[2]; - host += '.' + raw[3]; - return host; - } - - if (raw.length === 16) { - if (IP.isMapped(raw)) { - let host = ''; - host += raw[12]; - host += '.' + raw[13]; - host += '.' + raw[14]; - host += '.' + raw[15]; - return host; - } - - if (IP.isOnion(raw)) { - const host = base32.encode(raw.slice(6)); - return `${host}.onion`; - } - - let host = ''; - - host += raw.readUInt16BE(0, true).toString(16); - - for (let i = 2; i < 16; i += 2) { - host += ':'; - host += raw.readUInt16BE(i, true).toString(16); - } - - host = host.replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3'); - host = host.replace(/:{3,4}/, '::'); - - return host; - } - - throw new Error(`Invalid IP address: ${raw.toString('hex')}.`); -}; - -/** - * Normalize an ip. - * @param {String} str - * @returns {String} - */ - -IP.normalize = function normalize(str) { - return IP.toString(IP.toBuffer(str)); -}; - -/** - * Test whether the address is IPv4. - * @returns {Boolean} - */ - -IP.isIPv4 = function isIPv4(raw) { - return IP.isMapped(raw); -}; - -/** - * Test whether the address is IPv6. - * @returns {Boolean} - */ - -IP.isIPv6 = function isIPv6(raw) { - return !IP.isMapped(raw) && !IP.isOnion(raw); -}; - -/** - * Get address type. - * @param {Buffer} raw - * @returns {Number} - */ - -IP.getType = function getType(raw) { - if (IP.isIPv4(raw)) - return IP.types.IPV4; - - if (IP.isIPv6(raw)) - return IP.types.IPV6; - - if (IP.isOnion(raw)) - return IP.types.ONION; - - throw new Error('Unknown type.'); -}; - -/** - * Get addr network. Similar to - * type, but includes teredo. - * @param {Buffer} raw - * @returns {Number} - */ - -IP.getNetwork = function getNetwork(raw) { - if (IP.isRFC4380(raw)) - return IP.types.TEREDO; - - return IP.getType(raw); -}; - -/** - * Test whether the host is null. - * @returns {Boolean} - */ - -IP.isNull = function isNull(raw) { - if (IP.isIPv4(raw)) { - // 0.0.0.0 - return raw[12] === 0 - && raw[13] === 0 - && raw[14] === 0 - && raw[15] === 0; - } - // :: - return IP.isEqual(raw, ZERO_IP); -}; - -/** - * Test whether the host is a broadcast address. - * @returns {Boolean} - */ - -IP.isBroadcast = function isBroadcast(raw) { - if (IP.isIPv4(raw)) { - // 255.255.255.255 - return raw[12] === 255 - && raw[13] === 255 - && raw[14] === 255 - && raw[15] === 255; - } - return false; -}; - -/** - * Test whether the ip is RFC 1918. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isRFC1918 = function isRFC1918(raw) { - if (!IP.isIPv4(raw)) - return false; - - if (raw[12] === 10) - return true; - - if (raw[12] === 192 && raw[13] === 168) - return true; - - if (raw[12] === 172 && (raw[13] >= 16 && raw[13] <= 31)) - return true; - - return false; -}; - -/** - * Test whether the ip is RFC 2544. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isRFC2544 = function isRFC2544(raw) { - if (!IP.isIPv4(raw)) - return false; - - if (raw[12] === 198 && (raw[13] === 18 || raw[13] === 19)) - return true; - - if (raw[12] === 169 && raw[13] === 254) - return true; - - return false; -}; - -/** - * Test whether the ip is RFC 3927. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isRFC3927 = function isRFC3927(raw) { - if (!IP.isIPv4(raw)) - return false; - - if (raw[12] === 169 && raw[13] === 254) - return true; - - return false; -}; - -/** - * Test whether the ip is RFC 6598. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isRFC6598 = function isRFC6598(raw) { - if (!IP.isIPv4(raw)) - return false; - - if (raw[12] === 100 - && (raw[13] >= 64 && raw[13] <= 127)) { - return true; - } - - return false; -}; - -/** - * Test whether the ip is RFC 5737. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isRFC5737 = function isRFC5737(raw) { - if (!IP.isIPv4(raw)) - return false; - - if (raw[12] === 192 - && (raw[13] === 0 && raw[14] === 2)) { - return true; - } - - if (raw[12] === 198 && raw[13] === 51 && raw[14] === 100) - return true; - - if (raw[12] === 203 && raw[13] === 0 && raw[14] === 113) - return true; - - return false; -}; - -/** - * Test whether the ip is RFC 3849. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isRFC3849 = function isRFC3849(raw) { - if (raw[0] === 0x20 && raw[1] === 0x01 - && raw[2] === 0x0d && raw[3] === 0xb8) { - return true; - } - - return false; -}; - -/** - * Test whether the ip is RFC 3964. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isRFC3964 = function isRFC3964(raw) { - if (raw[0] === 0x20 && raw[1] === 0x02) - return true; - - return false; -}; - -/** - * Test whether the ip is RFC 6052. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isRFC6052 = function isRFC6052(raw) { - return IP.hasPrefix(raw, RFC6052); -}; - -/** - * Test whether the ip is RFC 4380. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isRFC4380 = function isRFC4380(raw) { - if (raw[0] === 0x20 && raw[1] === 0x01 - && raw[2] === 0x00 && raw[3] === 0x00) { - return true; - } - - return false; -}; - -/** - * Test whether the ip is RFC 4862. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isRFC4862 = function isRFC4862(raw) { - return IP.hasPrefix(raw, RFC4862); -}; - -/** - * Test whether the ip is RFC 4193. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isRFC4193 = function isRFC4193(raw) { - if ((raw[0] & 0xfe) === 0xfc) - return true; - - return false; -}; - -/** - * Test whether the ip is RFC 6145. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isRFC6145 = function isRFC6145(raw) { - return IP.hasPrefix(raw, RFC6145); -}; - -/** - * Test whether the ip is RFC 4843. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isRFC4843 = function isRFC4843(raw) { - if (raw[0] === 0x20 && raw[1] === 0x01 - && raw[2] === 0x00 && (raw[3] & 0xf0) === 0x10) { - return true; - } - - return false; -}; - -/** - * Test whether the ip has a tor onion prefix. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isOnion = function isOnion(raw) { - return IP.hasPrefix(raw, TOR_ONION); -}; - -/** - * Test whether the ip is local. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isLocal = function isLocal(raw) { - if (IP.isIPv4(raw)) { - if (raw[12] === 127 && raw[13] === 0) - return true; - return false; - } - - if (IP.isEqual(raw, LOCAL_IP)) - return true; - - return false; -}; - -/** - * Test whether the ip is a multicast address. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isMulticast = function isMulticast(raw) { - if (IP.isIPv4(raw)) { - if ((raw[12] & 0xf0) === 0xe0) - return true; - return false; - } - return raw[0] === 0xff; -}; - -/** - * Test whether the ip is valid. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isValid = function isValid(raw) { - if (IP.hasPrefix(raw, SHIFTED)) - return false; - - if (IP.isNull(raw)) - return false; - - if (IP.isBroadcast(raw)) - return false; - - if (IP.isRFC3849(raw)) - return false; - - return true; -}; - -/** - * Test whether the ip is routable. - * @param {Buffer} raw - * @returns {Boolean} - */ - -IP.isRoutable = function isRoutable(raw) { - if (!IP.isValid(raw)) - return false; - - if (IP.isRFC1918(raw)) - return false; - - if (IP.isRFC2544(raw)) - return false; - - if (IP.isRFC3927(raw)) - return false; - - if (IP.isRFC4862(raw)) - return false; - - if (IP.isRFC6598(raw)) - return false; - - if (IP.isRFC5737(raw)) - return false; - - if (IP.isRFC4193(raw) && !IP.isOnion(raw)) - return false; - - if (IP.isRFC4843(raw)) - return false; - - if (IP.isLocal(raw)) - return false; - - return true; -}; - -/** - * Calculate reachable score from source to destination. - * @param {Buffer} src - * @param {Buffer} dest - * @returns {Number} Ranges from 0-6. - */ - -IP.getReachability = function getReachability(src, dest) { - const UNREACHABLE = 0; - const DEFAULT = 1; - const TEREDO = 2; - const IPV6_WEAK = 3; - const IPV4 = 4; - const IPV6_STRONG = 5; - const PRIVATE = 6; - - if (!IP.isRoutable(src)) - return UNREACHABLE; - - const srcNet = IP.getNetwork(src); - const destNet = IP.getNetwork(dest); - const types = IP.types; - - switch (destNet) { - case types.IPV4: - switch (srcNet) { - case types.IPV4: - return IPV4; - default: - return DEFAULT; - } - break; - case types.IPV6: - switch (srcNet) { - case types.TEREDO: - return TEREDO; - case types.IPV4: - return IPV4; - case types.IPV6: - if (IP.isRFC3964(src) - || IP.isRFC6052(src) - || IP.isRFC6145(src)) { - // tunnel - return IPV6_WEAK; - } - return IPV6_STRONG; - default: - return DEFAULT; - } - break; - case types.ONION: - switch (srcNet) { - case types.IPV4: - return IPV4; - case types.ONION: - return PRIVATE; - default: - return DEFAULT; - } - break; - case types.TEREDO: - switch (srcNet) { - case types.TEREDO: - return TEREDO; - case types.IPV6: - return IPV6_WEAK; - case types.IPV4: - return IPV4; - default: - return DEFAULT; - } - break; - default: - switch (srcNet) { - case types.TEREDO: - return TEREDO; - case types.IPV6: - return IPV6_WEAK; - case types.IPV4: - return IPV4; - case types.ONION: - return PRIVATE; - default: - return DEFAULT; - } - break; - } -}; - -/** - * Test whether an IP has a prefix. - * @param {Buffer} raw - * @param {Buffer} prefix - * @returns {Boolean} - */ - -IP.hasPrefix = function hasPrefix(raw, prefix) { - assert(Buffer.isBuffer(raw)); - assert(Buffer.isBuffer(prefix)); - assert(raw.length >= prefix.length); - - for (let i = 0; i < prefix.length; i++) { - if (raw[i] !== prefix[i]) - return false; - } - - return true; -}; - -/** - * Test whether two IPs are equal. - * @param {Buffer} a - * @param {Buffer} b - * @returns {Boolean} - */ - -IP.isEqual = function isEqual(a, b) { - assert(a.length === 16); - assert(b.length === 16); - return a.equals(b); -}; - -/** - * Get IP address from network interfaces. - * @param {String?} name - `public` or `private`. - * @param {String?} family - IP family name. - * @returns {String} - */ - -IP.getInterfaces = function getInterfaces(name, family) { - const interfaces = os.networkInterfaces(); - const result = []; - - for (const key of Object.keys(interfaces)) { - const items = interfaces[key]; - - for (const details of items) { - const type = details.family.toLowerCase(); - - if (family && type !== family) - continue; - - if (details.internal) - continue; - - let raw; - try { - raw = IP.toBuffer(details.address); - } catch (e) { - continue; - } - - if (IP.isNull(raw)) - continue; - - if (IP.isLocal(raw)) - continue; - - if (name === 'public') { - if (!IP.isRoutable(raw)) - continue; - } else if (name === 'private') { - if (IP.isRoutable(raw)) - continue; - } - - result.push(IP.toString(raw)); - } - } - - return result; -}; - -/** - * Get private IP from network interfaces. - * @param {String?} family - IP family name. - * @returns {String} - */ - -IP.getPrivate = function getPrivate(family) { - return IP.getInterfaces('private', family); -}; - -/** - * Get public IP from network interfaces. - * @param {String?} family - IP family name. - * @returns {String} - */ - -IP.getPublic = function getPublic(family) { - return IP.getInterfaces('public', family); -}; - -/** - * Represents a parsed address. - * @constructor - * @alias module:utils/ip.Address - * @param {String} host - * @param {Number} port - * @param {Number} type - * @param {String} hostname - * @param {Buffer|null} raw - * @property {String} host - * @property {Number} port - * @property {Number} type - * @property {String} hostname - * @property {Buffer} raw - */ - -function Address(host, port, type, hostname, raw) { - this.host = host || '0.0.0.0'; - this.port = port || 0; - this.type = type || IP.types.IPV4; - this.hostname = hostname || '0.0.0.0:0'; - this.raw = raw || ZERO_IP; -} - -/* - * Expose - */ - -IP.Address = Address; diff --git a/lib/utils/list.js b/lib/utils/list.js deleted file mode 100644 index 75844e170..000000000 --- a/lib/utils/list.js +++ /dev/null @@ -1,280 +0,0 @@ -/*! - * list.js - double linked list for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); - -/** - * A double linked list. - * @alias module:utils.List - * @constructor - * @property {ListItem|null} head - * @property {ListItem|null} tail - * @property {Number} size - */ - -function List() { - if (!(this instanceof List)) - return new List(); - - this.head = null; - this.tail = null; - this.size = 0; -} - -/** - * Reset the cache. Clear all items. - */ - -List.prototype.reset = function reset() { - let item, next; - - for (item = this.head; item; item = next) { - next = item.next; - item.prev = null; - item.next = null; - } - - assert(!item); - - this.head = null; - this.tail = null; - this.size = 0; -}; - -/** - * Remove the first item in the list. - * @returns {ListItem} - */ - -List.prototype.shift = function shift() { - const item = this.head; - - if (!item) - return null; - - this.remove(item); - - return item; -}; - -/** - * Prepend an item to the linked list (sets new head). - * @param {ListItem} - * @returns {Boolean} - */ - -List.prototype.unshift = function unshift(item) { - return this.insert(null, item); -}; - -/** - * Append an item to the linked list (sets new tail). - * @param {ListItem} - * @returns {Boolean} - */ - -List.prototype.push = function push(item) { - return this.insert(this.tail, item); -}; - -/** - * Remove the last item in the list. - * @returns {ListItem} - */ - -List.prototype.pop = function pop() { - const item = this.tail; - - if (!item) - return null; - - this.remove(item); - - return item; -}; - -/** - * Insert item into the linked list. - * @private - * @param {ListItem|null} ref - * @param {ListItem} item - * @returns {Boolean} - */ - -List.prototype.insert = function insert(ref, item) { - if (item.prev || item.next || item === this.head) - return false; - - assert(!item.prev); - assert(!item.next); - - if (ref == null) { - if (!this.head) { - this.head = item; - this.tail = item; - } else { - this.head.prev = item; - item.next = this.head; - this.head = item; - } - this.size++; - return true; - } - - item.next = ref.next; - item.prev = ref; - ref.next = item; - - if (ref === this.tail) - this.tail = item; - - this.size++; - - return true; -}; - -/** - * Remove item from the linked list. - * @private - * @param {ListItem} - * @returns {Boolean} - */ - -List.prototype.remove = function remove(item) { - if (!item.prev && !item.next && item !== this.head) - return false; - - if (item.prev) - item.prev.next = item.next; - - if (item.next) - item.next.prev = item.prev; - - if (item === this.head) - this.head = item.next; - - if (item === this.tail) - this.tail = item.prev || this.head; - - if (!this.head) - assert(!this.tail); - - if (!this.tail) - assert(!this.head); - - item.prev = null; - item.next = null; - - this.size--; - - return true; -}; - -/** - * Replace an item in-place. - * @param {ListItem} ref - * @param {ListItem} item - */ - -List.prototype.replace = function replace(ref, item) { - if (ref.prev) - ref.prev.next = item; - - if (ref.next) - ref.next.prev = item; - - item.prev = ref.prev; - item.next = ref.next; - - ref.next = null; - ref.prev = null; - - if (this.head === ref) - this.head = item; - - if (this.tail === ref) - this.tail = item; -}; - -/** - * Slice the list to an array of items. - * Will remove the items sliced. - * @param {Number?} total - * @returns {ListItem[]} - */ - -List.prototype.slice = function slice(total) { - const items = []; - let item, next; - - if (total == null) - total = -1; - - for (item = this.head; item; item = next) { - next = item.next; - item.prev = null; - item.next = null; - - this.size--; - - items.push(item); - - if (items.length === total) - break; - } - - if (next) { - this.head = next; - next.prev = null; - } else { - this.head = null; - this.tail = null; - } - - return items; -}; - -/** - * Convert the list to an array of items. - * @returns {ListItem[]} - */ - -List.prototype.toArray = function toArray() { - const items = []; - - for (let item = this.head; item; item = item.next) - items.push(item); - - return items; -}; - -/** - * Represents an linked list item. - * @alias module:utils.ListItem - * @constructor - * @private - * @param {String} key - * @param {Object} value - */ - -function ListItem(value) { - this.next = null; - this.prev = null; - this.value = value; -} - -/* - * Expose - */ - -exports = List; -exports.List = List; -exports.ListItem = ListItem; -exports.Item = ListItem; - -module.exports = exports; diff --git a/lib/utils/lock.js b/lib/utils/lock.js deleted file mode 100644 index 8b0e7fa74..000000000 --- a/lib/utils/lock.js +++ /dev/null @@ -1,213 +0,0 @@ -/*! - * lock.js - lock and queue for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); - -/** - * Represents a mutex lock for locking asynchronous object methods. - * @alias module:utils.Lock - * @constructor - * @param {Boolean?} named - Whether to - * maintain a map of queued jobs by job name. - */ - -function Lock(named) { - if (!(this instanceof Lock)) - return Lock.create(named); - - this.named = named === true; - - this.jobs = []; - this.busy = false; - this.destroyed = false; - - this.map = new Map(); - this.current = null; - - this.unlocker = this.unlock.bind(this); -} - -/** - * Create a closure scoped lock. - * @param {Boolean?} named - * @returns {Function} Lock method. - */ - -Lock.create = function create(named) { - const lock = new Lock(named); - return function _lock(arg1, arg2) { - return lock.lock(arg1, arg2); - }; -}; - -/** - * Test whether the lock has a pending - * job or a job in progress (by name). - * @param {String} name - * @returns {Boolean} - */ - -Lock.prototype.has = function has(name) { - assert(this.named, 'Must use named jobs.'); - - if (this.current === name) - return true; - - const count = this.map.get(name); - - if (count == null) - return false; - - return count > 0; -}; - -/** - * Test whether the lock has - * a pending job by name. - * @param {String} name - * @returns {Boolean} - */ - -Lock.prototype.hasPending = function hasPending(name) { - assert(this.named, 'Must use named jobs.'); - - const count = this.map.get(name); - - if (count == null) - return false; - - return count > 0; -}; - -/** - * Lock the parent object and all its methods - * which use the lock. Begin to queue calls. - * @param {String?} name - Job name. - * @param {Boolean?} force - Bypass the lock. - * @returns {Promise} - Returns {Function}, must be - * called once the method finishes executing in order - * to resolve the queue. - */ - -Lock.prototype.lock = function lock(arg1, arg2) { - let name, force; - - if (this.named) { - name = arg1 || null; - force = arg2; - } else { - name = null; - force = arg1; - } - - if (this.destroyed) - return Promise.reject(new Error('Lock is destroyed.')); - - if (force) { - assert(this.busy); - return Promise.resolve(nop); - } - - if (this.busy) { - if (name) { - let count = this.map.get(name); - if (!count) - count = 0; - this.map.set(name, count + 1); - } - return new Promise((resolve, reject) => { - this.jobs.push(new Job(resolve, reject, name)); - }); - } - - this.busy = true; - this.current = name; - - return Promise.resolve(this.unlocker); -}; - -/** - * The actual unlock callback. - * @private - */ - -Lock.prototype.unlock = function unlock() { - assert(this.destroyed || this.busy); - - this.busy = false; - this.current = null; - - if (this.jobs.length === 0) - return; - - assert(!this.destroyed); - - const job = this.jobs.shift(); - - if (job.name) { - let count = this.map.get(job.name); - assert(count > 0); - if (--count === 0) - this.map.delete(job.name); - else - this.map.set(job.name, count); - } - - this.busy = true; - this.current = job.name; - - job.resolve(this.unlocker); -}; - -/** - * Destroy the lock. Purge all pending calls. - */ - -Lock.prototype.destroy = function destroy() { - assert(!this.destroyed, 'Lock is already destroyed.'); - - this.destroyed = true; - - const jobs = this.jobs; - - this.busy = false; - this.jobs = []; - this.map.clear(); - this.current = null; - - for (const job of jobs) - job.reject(new Error('Lock was destroyed.')); -}; - -/** - * Lock Job - * @constructor - * @ignore - * @param {Function} resolve - * @param {Function} reject - * @param {String?} name - */ - -function Job(resolve, reject, name) { - this.resolve = resolve; - this.reject = reject; - this.name = name || null; -} - -/* - * Helpers - */ - -function nop() {} - -/* - * Expose - */ - -module.exports = Lock; diff --git a/lib/utils/lru.js b/lib/utils/lru.js deleted file mode 100644 index 3f4ec104b..000000000 --- a/lib/utils/lru.js +++ /dev/null @@ -1,502 +0,0 @@ -/*! - * lru.js - LRU cache for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); - -/** - * An LRU cache, used for caching {@link ChainEntry}s. - * @alias module:utils.LRU - * @constructor - * @param {Number} capacity - * @param {Function?} getSize - */ - -function LRU(capacity, getSize) { - if (!(this instanceof LRU)) - return new LRU(capacity, getSize); - - this.map = new Map(); - this.size = 0; - this.items = 0; - this.head = null; - this.tail = null; - this.pending = null; - - assert(typeof capacity === 'number', 'Capacity must be a number.'); - assert(capacity >= 0, 'Capacity cannot be negative.'); - assert(!getSize || typeof getSize === 'function', 'Bad size callback.'); - - this.capacity = capacity; - this.getSize = getSize; -} - -/** - * Calculate size of an item. - * @private - * @param {LRUItem} item - * @returns {Number} Size. - */ - -LRU.prototype._getSize = function _getSize(item) { - if (this.getSize) { - const keySize = Math.floor(item.key.length * 1.375); - return 120 + keySize + this.getSize(item.value); - } - - return 1; -}; - -/** - * Compact the LRU linked list. - * @private - */ - -LRU.prototype._compact = function _compact() { - if (this.size <= this.capacity) - return; - - let item, next; - for (item = this.head; item; item = next) { - if (this.size <= this.capacity) - break; - this.size -= this._getSize(item); - this.items--; - this.map.delete(item.key); - next = item.next; - item.prev = null; - item.next = null; - } - - if (!item) { - this.head = null; - this.tail = null; - return; - } - - this.head = item; - item.prev = null; -}; - -/** - * Reset the cache. Clear all items. - */ - -LRU.prototype.reset = function reset() { - let item, next; - - for (item = this.head; item; item = next) { - this.map.delete(item.key); - this.items--; - next = item.next; - item.prev = null; - item.next = null; - } - - assert(!item); - - this.size = 0; - this.head = null; - this.tail = null; -}; - -/** - * Add an item to the cache. - * @param {String|Number} key - * @param {Object} value - */ - -LRU.prototype.set = function set(key, value) { - if (this.capacity === 0) - return; - - key = String(key); - - let item = this.map.get(key); - - if (item) { - this.size -= this._getSize(item); - item.value = value; - this.size += this._getSize(item); - this._removeList(item); - this._appendList(item); - this._compact(); - return; - } - - item = new LRUItem(key, value); - - this.map.set(key, item); - - this._appendList(item); - - this.size += this._getSize(item); - this.items++; - - this._compact(); -}; - -/** - * Retrieve an item from the cache. - * @param {String|Number} key - * @returns {Object} Item. - */ - -LRU.prototype.get = function get(key) { - if (this.capacity === 0) - return null; - - key = String(key); - - const item = this.map.get(key); - - if (!item) - return null; - - this._removeList(item); - this._appendList(item); - - return item.value; -}; - -/** - * Test whether the cache contains a key. - * @param {String|Number} key - * @returns {Boolean} - */ - -LRU.prototype.has = function has(key) { - if (this.capacity === 0) - return false; - return this.map.has(String(key)); -}; - -/** - * Remove an item from the cache. - * @param {String|Number} key - * @returns {Boolean} Whether an item was removed. - */ - -LRU.prototype.remove = function remove(key) { - if (this.capacity === 0) - return false; - - key = String(key); - - const item = this.map.get(key); - - if (!item) - return false; - - this.size -= this._getSize(item); - this.items--; - - this.map.delete(key); - - this._removeList(item); - - return true; -}; - -/** - * Prepend an item to the linked list (sets new head). - * @private - * @param {LRUItem} - */ - -LRU.prototype._prependList = function _prependList(item) { - this._insertList(null, item); -}; - -/** - * Append an item to the linked list (sets new tail). - * @private - * @param {LRUItem} - */ - -LRU.prototype._appendList = function _appendList(item) { - this._insertList(this.tail, item); -}; - -/** - * Insert item into the linked list. - * @private - * @param {LRUItem|null} ref - * @param {LRUItem} item - */ - -LRU.prototype._insertList = function _insertList(ref, item) { - assert(!item.next); - assert(!item.prev); - - if (ref == null) { - if (!this.head) { - this.head = item; - this.tail = item; - } else { - this.head.prev = item; - item.next = this.head; - this.head = item; - } - return; - } - - item.next = ref.next; - item.prev = ref; - ref.next = item; - - if (ref === this.tail) - this.tail = item; -}; - -/** - * Remove item from the linked list. - * @private - * @param {LRUItem} - */ - -LRU.prototype._removeList = function _removeList(item) { - if (item.prev) - item.prev.next = item.next; - - if (item.next) - item.next.prev = item.prev; - - if (item === this.head) - this.head = item.next; - - if (item === this.tail) - this.tail = item.prev || this.head; - - if (!this.head) - assert(!this.tail); - - if (!this.tail) - assert(!this.head); - - item.prev = null; - item.next = null; -}; - -/** - * Collect all keys in the cache, sorted by LRU. - * @returns {String[]} - */ - -LRU.prototype.keys = function keys() { - const items = []; - - for (let item = this.head; item; item = item.next) { - if (item === this.head) - assert(!item.prev); - if (!item.prev) - assert(item === this.head); - if (!item.next) - assert(item === this.tail); - items.push(item.key); - } - - return items; -}; - -/** - * Collect all values in the cache, sorted by LRU. - * @returns {String[]} - */ - -LRU.prototype.values = function values() { - const items = []; - - for (let item = this.head; item; item = item.next) - items.push(item.value); - - return items; -}; - -/** - * Convert the LRU cache to an array of items. - * @returns {Object[]} - */ - -LRU.prototype.toArray = function toArray() { - const items = []; - - for (let item = this.head; item; item = item.next) - items.push(item); - - return items; -}; - -/** - * Create an atomic batch for the lru - * (used for caching database writes). - * @returns {LRUBatch} - */ - -LRU.prototype.batch = function batch() { - return new LRUBatch(this); -}; - -/** - * Start the pending batch. - */ - -LRU.prototype.start = function start() { - assert(!this.pending); - this.pending = this.batch(); -}; - -/** - * Clear the pending batch. - */ - -LRU.prototype.clear = function clear() { - assert(this.pending); - this.pending.clear(); -}; - -/** - * Drop the pending batch. - */ - -LRU.prototype.drop = function drop() { - assert(this.pending); - this.pending = null; -}; - -/** - * Commit the pending batch. - */ - -LRU.prototype.commit = function commit() { - assert(this.pending); - this.pending.commit(); - this.pending = null; -}; - -/** - * Push an item onto the pending batch. - * @param {String} key - * @param {Object} value - */ - -LRU.prototype.push = function push(key, value) { - assert(this.pending); - - if (this.capacity === 0) - return; - - this.pending.set(key, value); -}; - -/** - * Push a removal onto the pending batch. - * @param {String} key - */ - -LRU.prototype.unpush = function unpush(key) { - assert(this.pending); - - if (this.capacity === 0) - return; - - this.pending.remove(key); -}; - -/** - * Represents an LRU item. - * @alias module:utils.LRUItem - * @constructor - * @private - * @param {String} key - * @param {Object} value - */ - -function LRUItem(key, value) { - this.key = key; - this.value = value; - this.next = null; - this.prev = null; -} - -/** - * LRU Batch - * @alias module:utils.LRUBatch - * @constructor - * @param {LRU} lru - */ - -function LRUBatch(lru) { - this.lru = lru; - this.ops = []; -} - -/** - * Push an item onto the batch. - * @param {String} key - * @param {Object} value - */ - -LRUBatch.prototype.set = function set(key, value) { - this.ops.push(new LRUOp(false, key, value)); -}; - -/** - * Push a removal onto the batch. - * @param {String} key - */ - -LRUBatch.prototype.remove = function remove(key) { - this.ops.push(new LRUOp(true, key, null)); -}; - -/** - * Clear the batch. - */ - -LRUBatch.prototype.clear = function clear() { - this.ops.length = 0; -}; - -/** - * Commit the batch. - */ - -LRUBatch.prototype.commit = function commit() { - for (const op of this.ops) { - if (op.remove) { - this.lru.remove(op.key); - continue; - } - this.lru.set(op.key, op.value); - } - - this.ops.length = 0; -}; - -/** - * LRU Op - * @alias module:utils.LRUOp - * @constructor - * @private - * @param {Boolean} remove - * @param {String} key - * @param {Object} value - */ - -function LRUOp(remove, key, value) { - this.remove = remove; - this.key = key; - this.value = value; -} - -/* - * Expose - */ - -module.exports = LRU; diff --git a/lib/utils/mappedlock.js b/lib/utils/mappedlock.js deleted file mode 100644 index a9d7fe975..000000000 --- a/lib/utils/mappedlock.js +++ /dev/null @@ -1,173 +0,0 @@ -/*! - * mappedlock.js - lock and queue for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); - -/** - * Represents a mutex lock for locking asynchronous object methods. - * Locks methods according to passed-in key. - * @alias module:utils.MappedLock - * @constructor - */ - -function MappedLock() { - if (!(this instanceof MappedLock)) - return MappedLock.create(); - - this.jobs = new Map(); - this.busy = new Set(); - this.destroyed = false; -} - -/** - * Create a closure scoped lock. - * @returns {Function} Lock method. - */ - -MappedLock.create = function create() { - const lock = new MappedLock(); - return function _lock(key, force) { - return lock.lock(key, force); - }; -}; - -/** - * Test whether the lock has a pending - * job or a job in progress (by name). - * @param {String} name - * @returns {Boolean} - */ - -MappedLock.prototype.has = function has(name) { - return this.busy.has(name); -}; - -/** - * Test whether the lock has - * a pending job by name. - * @param {String} name - * @returns {Boolean} - */ - -MappedLock.prototype.hasPending = function hasPending(name) { - return this.jobs.has(name); -}; - -/** - * Lock the parent object and all its methods - * which use the lock with a specified key. - * Begin to queue calls. - * @param {String|Number} key - * @param {Boolean?} force - Force a call. - * @returns {Promise} - Returns {Function}, must be - * called once the method finishes executing in order - * to resolve the queue. - */ - -MappedLock.prototype.lock = function lock(key, force) { - if (this.destroyed) - return Promise.reject(new Error('Lock is destroyed.')); - - if (key == null) - return Promise.resolve(nop); - - if (force) { - assert(this.busy.has(key)); - return Promise.resolve(nop); - } - - if (this.busy.has(key)) { - return new Promise((resolve, reject) => { - if (!this.jobs.has(key)) - this.jobs.set(key, []); - this.jobs.get(key).push(new Job(resolve, reject)); - }); - } - - this.busy.add(key); - - return Promise.resolve(this.unlock(key)); -}; - -/** - * Create an unlock callback. - * @private - * @param {String} key - * @returns {Function} Unlocker. - */ - -MappedLock.prototype.unlock = function unlock(key) { - const self = this; - return function unlocker() { - const jobs = self.jobs.get(key); - - assert(self.destroyed || self.busy.has(key)); - self.busy.delete(key); - - if (!jobs) - return; - - assert(!self.destroyed); - - const job = jobs.shift(); - assert(job); - - if (jobs.length === 0) - self.jobs.delete(key); - - self.busy.add(key); - - job.resolve(unlocker); - }; -}; - -/** - * Destroy the lock. Purge all pending calls. - */ - -MappedLock.prototype.destroy = function destroy() { - assert(!this.destroyed, 'Lock is already destroyed.'); - - const map = this.jobs; - - this.destroyed = true; - - this.jobs = new Map(); - this.busy = new Map(); - - for (const jobs of map.values()) { - for (const job of jobs) - job.reject(new Error('Lock was destroyed.')); - } -}; - -/** - * Lock Job - * @constructor - * @ignore - * @param {Function} resolve - * @param {Function} reject - */ - -function Job(resolve, reject) { - this.resolve = resolve; - this.reject = reject; -} - -/* - * Helpers - */ - -function nop() {} - -/* - * Expose - */ - -module.exports = MappedLock; diff --git a/lib/utils/murmur3.js b/lib/utils/murmur3.js deleted file mode 100644 index 57bda5db9..000000000 --- a/lib/utils/murmur3.js +++ /dev/null @@ -1,112 +0,0 @@ -/*! - * murmur3.js - murmur3 hash for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const native = require('../native').binding; - -/** - * Murmur3 hash. - * @alias module:utils.murmur3 - * @param {Buffer} data - * @param {Number} seed - * @returns {Number} - */ - -function murmur3(data, seed) { - const tail = data.length - (data.length % 4); - const c1 = 0xcc9e2d51; - const c2 = 0x1b873593; - let h1 = seed; - let k1; - - for (let i = 0; i < tail; i += 4) { - k1 = (data[i + 3] << 24) - | (data[i + 2] << 16) - | (data[i + 1] << 8) - | data[i]; - k1 = mul32(k1, c1); - k1 = rotl32(k1, 15); - k1 = mul32(k1, c2); - h1 ^= k1; - h1 = rotl32(h1, 13); - h1 = sum32(mul32(h1, 5), 0xe6546b64); - } - - k1 = 0; - switch (data.length & 3) { - case 3: - k1 ^= data[tail + 2] << 16; - case 2: - k1 ^= data[tail + 1] << 8; - case 1: - k1 ^= data[tail + 0]; - k1 = mul32(k1, c1); - k1 = rotl32(k1, 15); - k1 = mul32(k1, c2); - h1 ^= k1; - } - - h1 ^= data.length; - h1 ^= h1 >>> 16; - h1 = mul32(h1, 0x85ebca6b); - h1 ^= h1 >>> 13; - h1 = mul32(h1, 0xc2b2ae35); - h1 ^= h1 >>> 16; - - if (h1 < 0) - h1 += 0x100000000; - - return h1; -} - -if (native) - murmur3 = native.murmur3; - -function mul32(a, b) { - const alo = a & 0xffff; - const blo = b & 0xffff; - const ahi = a >>> 16; - const bhi = b >>> 16; - - let lo = alo * blo; - let hi = (ahi * blo + bhi * alo) & 0xffff; - - hi += lo >>> 16; - lo &= 0xffff; - - let r = (hi << 16) | lo; - - if (r < 0) - r += 0x100000000; - - return r; -} - -function sum32(a, b) { - let r = (a + b) & 0xffffffff; - - if (r < 0) - r += 0x100000000; - - return r; -} - -function rotl32(w, b) { - return (w << b) | (w >>> (32 - b)); -} - -/** - * Expose - */ - -exports = murmur3; -exports.murmur3 = murmur3; -exports.mul32 = mul32; -exports.sum32 = sum32; -exports.rotl32 = rotl32; -module.exports = exports; diff --git a/lib/utils/pem.js b/lib/utils/pem.js deleted file mode 100644 index 2d68372e2..000000000 --- a/lib/utils/pem.js +++ /dev/null @@ -1,148 +0,0 @@ -/*! - * pem.js - pem parsing for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); - -/** - * @exports utils/pem - */ - -const PEM = exports; - -/** - * Parse PEM into separated chunks. - * @param {String} pem - * @returns {Object[]} - * @throws on parse error - */ - -PEM.parse = function parse(pem) { - const chunks = []; - let chunk = ''; - let tag; - - while (pem.length) { - let m; - - m = /^-----BEGIN ([^\-]+)-----/.exec(pem); - if (m) { - pem = pem.substring(m[0].length); - tag = m[1]; - continue; - } - - m = /^-----END ([^\-]+)-----/.exec(pem); - if (m) { - pem = pem.substring(m[0].length); - - assert(tag === m[1], 'Tag mismatch.'); - - const type = tag.split(' ')[0].toLowerCase(); - const data = Buffer.from(chunk, 'base64'); - - chunks.push({ - tag: tag, - type: type, - data: data - }); - - chunk = ''; - tag = null; - - continue; - } - - m = /^[a-zA-Z0-9\+=\/]+/.exec(pem); - if (m) { - pem = pem.substring(m[0].length); - chunk += m[0]; - continue; - } - - m = /^\s+/.exec(pem); - if (m) { - pem = pem.substring(m[0].length); - continue; - } - - throw new Error('PEM parse error.'); - } - - assert(chunks.length !== 0, 'PEM parse error.'); - assert(!tag, 'Un-ended tag.'); - assert(chunk.length === 0, 'Trailing data.'); - - return chunks; -}; - -/** - * Decode PEM into a manageable format. - * @param {String} pem - * @returns {Object} - * @throws on parse error - */ - -PEM.decode = function decode(pem) { - const chunks = PEM.parse(pem); - const body = chunks[0]; - const extra = chunks[1]; - - let params = null; - - if (extra) { - if (extra.tag.indexOf('PARAMETERS') !== -1) - params = extra.data; - } - - let alg = null; - - switch (body.type) { - case 'dsa': - alg = 'dsa'; - break; - case 'rsa': - alg = 'rsa'; - break; - case 'ec': - alg = 'ecdsa'; - break; - } - - return { - type: body.type, - alg: alg, - data: body.data, - params: params - }; -}; - -/** - * Encode DER to PEM. - * @param {Buffer} der - * @param {String} type - e.g. "ec". - * @param {String?} suffix - e.g. "public key". - * @returns {String} - */ - -PEM.encode = function encode(der, type, suffix) { - let pem = ''; - - if (suffix) - type += ' ' + suffix; - - type = type.toUpperCase(); - der = der.toString('base64'); - - for (let i = 0; i < der.length; i += 64) - pem += der.slice(i, i + 64) + '\n'; - - return '' - + `-----BEGIN ${type}-----\n` - + pem - + `-----END ${type}-----\n`; -}; diff --git a/lib/utils/protoreader.js b/lib/utils/protoreader.js deleted file mode 100644 index 7a6452ec6..000000000 --- a/lib/utils/protoreader.js +++ /dev/null @@ -1,221 +0,0 @@ -/*! - * protoreader.js - protobufs for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const BufferReader = require('../utils/reader'); - -/* - * Constants - */ - -const wireType = { - VARINT: 0, - FIXED64: 1, - DELIMITED: 2, - START_GROUP: 3, - END_GROUP: 4, - FIXED32: 5 -}; - -/** - * ProtoBuf Reader - * @alias module:utils.ProtoReader - * @constructor - */ - -function ProtoReader(data, zeroCopy) { - if (!(this instanceof ProtoReader)) - return new ProtoReader(data, zeroCopy); - - BufferReader.call(this, data, zeroCopy); -} - -Object.setPrototypeOf(ProtoReader.prototype, BufferReader.prototype); - -ProtoReader.prototype.readVarint = function readVarint() { - const {size, value} = _readVarint(this.data, this.offset); - this.offset += size; - return value; -}; - -ProtoReader.prototype.readFieldValue = function readFieldValue(tag, opt) { - const field = this.readField(tag, opt); - - if (!field) - return -1; - - assert(field.value != null); - - return field.value; -}; - -ProtoReader.prototype.readFieldU64 = function readFieldU64(tag, opt) { - const field = this.readField(tag, opt); - - if (!field) - return -1; - - assert(field.type === wireType.VARINT || field.type === wireType.FIXED64); - - return field.value; -}; - -ProtoReader.prototype.readFieldU32 = function readFieldU32(tag, opt) { - const field = this.readField(tag, opt); - - if (!field) - return -1; - - assert(field.type === wireType.VARINT || field.type === wireType.FIXED32); - - return field.value; -}; - -ProtoReader.prototype.readFieldBytes = function readFieldBytes(tag, opt) { - const field = this.readField(tag, opt); - - if (!field) - return null; - - assert(field.data); - - return field.data; -}; - -ProtoReader.prototype.readFieldString = function readFieldString(tag, opt, enc) { - const field = this.readField(tag, opt); - - if (!field) - return null; - - assert(field.data); - - return field.data.toString(enc || 'utf8'); -}; - -ProtoReader.prototype.nextTag = function nextTag() { - if (this.left() === 0) - return -1; - - const field = this.readField(); - - this.seek(-field.size); - - return field.tag; -}; - -ProtoReader.prototype.readField = function readField(tag, opt) { - const offset = this.offset; - const header = this.readVarint(); - const field = new Field(header); - - if (tag != null && field.tag !== tag) { - assert(opt, 'Non-optional field not present.'); - this.offset = offset; - return null; - } - - switch (field.type) { - case wireType.VARINT: - field.value = this.readVarint(); - break; - case wireType.FIXED64: - field.value = this.readU64(); - break; - case wireType.DELIMITED: - field.data = this.readVarBytes(); - break; - case wireType.START_GROUP: - field.group = []; - for (;;) { - const inner = this.readField(); - if (inner.type === wireType.END_GROUP) - break; - field.group.push(inner); - } - break; - case wireType.END_GROUP: - assert(false, 'Unexpected end group.'); - break; - case wireType.FIXED32: - field.value = this.readU32(); - break; - default: - assert(false, 'Bad wire type.'); - break; - } - - field.size = this.offset - offset; - - return field; -}; - -/* - * Encoding - */ - -function _readVarint(data, off) { - let num = 0; - let ch = 0x80; - let size = 0; - - while (ch & 0x80) { - if (off >= data.length) { - num = 0; - break; - } - - ch = data[off++]; - - // Optimization for javascript insanity. - switch (size) { - case 0: - case 1: - case 2: - case 3: - num += (ch & 0x7f) << (7 * size); - break; - case 4: - num += (ch & 0x7f) * (1 << (7 * size)); - break; - default: - num += (ch & 0x7f) * Math.pow(2, 7 * size); - break; - } - - size++; - - assert(size < 7, 'Number exceeds 2^53-1.'); - } - - return new Varint(size, num); -} - -/* - * Helpers - */ - -function Field(header) { - this.tag = header >>> 3; - this.type = header & 7; - this.size = 0; - this.value = 0; - this.data = null; - this.group = null; -} - -function Varint(size, value) { - this.size = size; - this.value = value; -} - -/* - * Expose - */ - -module.exports = ProtoReader; diff --git a/lib/utils/protowriter.js b/lib/utils/protowriter.js deleted file mode 100644 index 5f1a7abdb..000000000 --- a/lib/utils/protowriter.js +++ /dev/null @@ -1,182 +0,0 @@ -/*! - * protowriter.js - protobufs for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -/** - * @module utils/protobuf - */ - -const assert = require('assert'); -const BufferWriter = require('../utils/writer'); - -/* - * Constants - */ - -const wireType = { - VARINT: 0, - FIXED64: 1, - DELIMITED: 2, - START_GROUP: 3, - END_GROUP: 4, - FIXED32: 5 -}; - -/** - * ProtoBuf Writer - * @alias module:utils.ProtoWriter - * @constructor - */ - -function ProtoWriter() { - if (!(this instanceof ProtoWriter)) - return new ProtoWriter(); - - BufferWriter.call(this); -} - -Object.setPrototypeOf(ProtoWriter.prototype, BufferWriter.prototype); - -ProtoWriter.prototype.writeVarint = function writeVarint(num) { - const size = sizeVarint(num); - - // Avoid an extra allocation until - // we make bufferwriter more hackable. - // More insanity here... - switch (size) { - case 6: { - const value = slipVarint(num); - this.writeU32BE(value / 0x10000 | 0); - this.writeU16BE(value & 0xffff); - break; - } - case 5: { - const value = slipVarint(num); - this.writeU32BE(value / 0x100 | 0); - this.writeU8(value & 0xff); - break; - } - case 4: { - const value = slipVarint(num); - this.writeU32BE(value); - break; - } - case 3: { - const value = slipVarint(num); - this.writeU16BE(value >> 8); - this.writeU8(value & 0xff); - break; - } - case 2: { - const value = slipVarint(num); - this.writeU16BE(value); - break; - } - case 1: { - const value = slipVarint(num); - this.writeU8(value); - break; - } - default: { - const value = Buffer.allocUnsafe(size); - _writeVarint(value, num, 0); - this.writeBytes(value); - break; - } - } -}; - -ProtoWriter.prototype.writeFieldVarint = function writeFieldVarint(tag, value) { - const header = (tag << 3) | wireType.VARINT; - this.writeVarint(header); - this.writeVarint(value); -}; - -ProtoWriter.prototype.writeFieldU64 = function writeFieldU64(tag, value) { - assert(Number.isSafeInteger(value)); - this.writeFieldVarint(tag, value); -}; - -ProtoWriter.prototype.writeFieldU32 = function writeFieldU32(tag, value) { - assert(value <= 0xffffffff); - this.writeFieldVarint(tag, value); -}; - -ProtoWriter.prototype.writeFieldBytes = function writeFieldBytes(tag, data) { - const header = (tag << 3) | wireType.DELIMITED; - this.writeVarint(header); - this.writeVarint(data.length); - this.writeBytes(data); -}; - -ProtoWriter.prototype.writeFieldString = function writeFieldString(tag, data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc || 'utf8'); - this.writeFieldBytes(tag, data); -}; - -/* - * Encoding - */ - -function _writeVarint(data, num, off) { - assert(Number.isSafeInteger(num), 'Number exceeds 2^53-1.'); - - do { - assert(off < data.length); - let ch = num & 0x7f; - num -= num % 0x80; - num /= 0x80; - if (num !== 0) - ch |= 0x80; - data[off] = ch; - off++; - } while (num > 0); - - return off; -}; - -function slipVarint(num) { - assert(Number.isSafeInteger(num), 'Number exceeds 2^53-1.'); - - let data = 0; - let size = 0; - - do { - assert(size < 7); - let ch = num & 0x7f; - num -= num % 0x80; - num /= 0x80; - if (num !== 0) - ch |= 0x80; - data *= 256; - data += ch; - size++; - } while (num > 0); - - return data; -} - -function sizeVarint(num) { - assert(Number.isSafeInteger(num), 'Number exceeds 2^53-1.'); - - let size = 0; - - do { - num -= num % 0x80; - num /= 0x80; - size++; - } while (num > 0); - - return size; -}; - -/* - * Expose - */ - -module.exports = ProtoWriter; diff --git a/lib/utils/rbt.js b/lib/utils/rbt.js deleted file mode 100644 index 17454395f..000000000 --- a/lib/utils/rbt.js +++ /dev/null @@ -1,899 +0,0 @@ -/*! - * rbt.js - iterative red black tree for bcoin - * Copyright (c) 2016-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const RED = 0; -const BLACK = 1; -let SENTINEL; - -/** - * An iterative red black tree. - * @alias module:utils.RBT - * @constructor - * @param {Function} compare - Comparator. - * @param {Boolean?} unique - */ - -function RBT(compare, unique) { - if (!(this instanceof RBT)) - return new RBT(compare, unique); - - assert(typeof compare === 'function'); - - this.root = SENTINEL; - this.compare = compare; - this.unique = unique || false; -} - -/** - * Clear the tree. - */ - -RBT.prototype.reset = function reset() { - this.root = SENTINEL; -}; - -/** - * Do a key lookup. - * @param {Buffer|String} key - * @returns {Buffer?} value - */ - -RBT.prototype.search = function search(key) { - let current = this.root; - - while (!current.isNull()) { - const cmp = this.compare(key, current.key); - - if (cmp === 0) - return current; - - if (cmp < 0) - current = current.left; - else - current = current.right; - } - - return null; -}; - -/** - * Insert a record. - * @param {Buffer|String} key - * @param {Buffer} value - */ - -RBT.prototype.insert = function insert(key, value) { - let current = this.root; - let left = false; - let parent; - - while (!current.isNull()) { - const cmp = this.compare(key, current.key); - - if (this.unique && cmp === 0) { - current.key = key; - current.value = value; - return current; - } - - parent = current; - - if (cmp < 0) { - left = true; - current = current.left; - } else { - left = false; - current = current.right; - } - } - - const node = new RBTNode(key, value); - - if (!parent) { - this.root = node; - this.insertFixup(node); - return node; - } - - node.parent = parent; - - if (left) - parent.left = node; - else - parent.right = node; - - this.insertFixup(node); - - return node; -}; - -/** - * Repaint necessary nodes after insertion. - * @private - * @param {RBTNode} x - */ - -RBT.prototype.insertFixup = function insertFixup(x) { - x.color = RED; - - while (x !== this.root && x.parent.color === RED) { - if (x.parent === x.parent.parent.left) { - const y = x.parent.parent.right; - if (!y.isNull() && y.color === RED) { - x.parent.color = BLACK; - y.color = BLACK; - x.parent.parent.color = RED; - x = x.parent.parent; - } else { - if (x === x.parent.right) { - x = x.parent; - this.rotl(x); - } - x.parent.color = BLACK; - x.parent.parent.color = RED; - this.rotr(x.parent.parent); - } - } else { - const y = x.parent.parent.left; - if (!y.isNull() && y.color === RED) { - x.parent.color = BLACK; - y.color = BLACK; - x.parent.parent.color = RED; - x = x.parent.parent; - } else { - if (x === x.parent.left) { - x = x.parent; - this.rotr(x); - } - x.parent.color = BLACK; - x.parent.parent.color = RED; - this.rotl(x.parent.parent); - } - } - } - - this.root.color = BLACK; -}; - -/** - * Remove a record. - * @param {Buffer|String} key - * @returns {Boolean} - */ - -RBT.prototype.remove = function remove(key) { - let current = this.root; - - while (!current.isNull()) { - const cmp = this.compare(key, current.key); - - if (cmp === 0) { - this.removeNode(current); - return current; - } - - if (cmp < 0) - current = current.left; - else - current = current.right; - } - - return null; -}; - -/** - * Remove a single node. - * @private - * @param {RBTNode} z - */ - -RBT.prototype.removeNode = function removeNode(z) { - let y = z; - - if (!z.left.isNull() && !z.right.isNull()) - y = this.successor(z); - - const x = y.left.isNull() ? y.right : y.left; - x.parent = y.parent; - - if (y.parent.isNull()) { - this.root = x; - } else { - if (y === y.parent.left) - y.parent.left = x; - else - y.parent.right = x; - } - - if (y !== z) { - z.key = y.key; - z.value = y.value; - } - - if (y.color === BLACK) - this.removeFixup(x); -}; - -/** - * Repaint necessary nodes after removal. - * @private - * @param {RBTNode} x - */ - -RBT.prototype.removeFixup = function removeFixup(x) { - while (x !== this.root && x.color === BLACK) { - if (x === x.parent.left) { - let w = x.parent.right; - - if (w.color === RED) { - w.color = BLACK; - x.parent.color = RED; - this.rotl(x.parent); - w = x.parent.right; - } - - if (w.left.color === BLACK && w.right.color === BLACK) { - w.color = RED; - x = x.parent; - } else { - if (w.right.color === BLACK) { - w.left.color = BLACK; - w.color = RED; - this.rotr(w); - w = x.parent.right; - } - w.color = x.parent.color; - x.parent.color = BLACK; - w.right.color = BLACK; - this.rotl(x.parent); - x = this.root; - } - } else { - let w = x.parent.left; - - if (w.color === RED) { - w.color = BLACK; - x.parent.color = RED; - this.rotr(x.parent); - w = x.parent.left; - } - - if (w.right.color === BLACK && w.left.color === BLACK) { - w.color = RED; - x = x.parent; - } else { - if (w.left.color === BLACK) { - w.right.color = BLACK; - w.color = RED; - this.rotl(w); - w = x.parent.left; - } - w.color = x.parent.color; - x.parent.color = BLACK; - w.left.color = BLACK; - this.rotr(x.parent); - x = this.root; - } - } - } - - x.color = BLACK; -}; - -/** - * Do a left rotate. - * @private - * @param {RBTNode} x - */ - -RBT.prototype.rotl = function rotl(x) { - const y = x.right; - - x.right = y.left; - - if (!y.left.isNull()) - y.left.parent = x; - - y.parent = x.parent; - - if (x.parent.isNull()) { - this.root = y; - } else { - if (x === x.parent.left) - x.parent.left = y; - else - x.parent.right = y; - } - - y.left = x; - x.parent = y; -}; - -/** - * Do a right rotate. - * @private - * @param {RBTNode} x - */ - -RBT.prototype.rotr = function rotr(x) { - const y = x.left; - - x.left = y.right; - - if (!y.right.isNull()) - y.right.parent = x; - - y.parent = x.parent; - - if (x.parent.isNull()) { - this.root = y; - } else { - if (x === x.parent.right) - x.parent.right = y; - else - x.parent.left = y; - } - - y.right = x; - x.parent = y; -}; - -/** - * Minimum subtree. - * @private - * @param {RBTNode} z - * @returns {RBTNode} - */ - -RBT.prototype.min = function min(z) { - if (z.isNull()) - return z; - - while (!z.left.isNull()) - z = z.left; - - return z; -}; - -/** - * Maximum subtree. - * @private - * @param {RBTNode} z - * @returns {RBTNode} - */ - -RBT.prototype.max = function max(z) { - if (z.isNull()) - return z; - - while (!z.right.isNull()) - z = z.right; - - return z; -}; - -/** - * Successor node. - * @private - * @param {RBTNode} x - * @returns {RBTNode} - */ - -RBT.prototype.successor = function successor(x) { - if (!x.right.isNull()) { - x = x.right; - - while (!x.left.isNull()) - x = x.left; - - return x; - } - - let y = x.parent; - while (!y.isNull() && x === y.right) { - x = y; - y = y.parent; - } - - return y; -}; - -/** - * Predecessor node. - * @private - * @param {RBTNode} x - * @returns {RBTNode} - */ - -RBT.prototype.predecessor = function predecessor(x) { - if (!x.left.isNull()) { - x = x.left; - - while (!x.right.isNull()) - x = x.right; - - return x; - } - - let y = x.parent; - while (!y.isNull() && x === y.left) { - x = y; - y = y.parent; - } - - return y; -}; - -/** - * Take a snapshot and return - * a cloned root node (iterative). - * @returns {RBTNode} - */ - -RBT.prototype.clone = function clone() { - if (this.root.isNull()) - return SENTINEL; - - const stack = []; - - let current = this.root; - let left = true; - let parent, snapshot; - - for (;;) { - if (!current.isNull()) { - const copy = current.clone(); - - if (parent) - copy.parent = parent; - - if (left) { - if (parent) - parent.left = copy; - else - snapshot = copy; - } else { - if (parent) - parent.right = copy; - else - snapshot = copy; - } - - stack.push(copy); - parent = copy; - left = true; - current = current.left; - continue; - } - - if (stack.length === 0) - break; - - current = stack.pop(); - parent = current; - left = false; - current = current.right; - } - - assert(snapshot); - - return snapshot; -}; - -/** - * Take a snapshot and return - * a cloned root node (recursive). - * @returns {RBTNode} - */ - -RBT.prototype.snapshot = function snapshot() { - if (this.root.isNull()) - return SENTINEL; - - const node = this.root.clone(); - - copyLeft(node, node.left); - copyRight(node, node.right); - - return node; -}; - -/** - * Create an iterator. - * @param {RBTNode?} snapshot - * @returns {Iterator} - */ - -RBT.prototype.iterator = function iterator(snapshot) { - return new Iterator(this, snapshot || this.root); -}; - -/** - * Traverse between a range of keys and collect records. - * @param {Buffer} min - * @param {Buffer} max - * @returns {RBTNode[]} Records. - */ - -RBT.prototype.range = function range(min, max) { - const iter = this.iterator(); - const items = []; - - if (min) - iter.seekMin(min); - else - iter.seekFirst(); - - while (iter.next()) { - if (max && iter.compare(max) > 0) - break; - - items.push(iter.data()); - } - - return items; -}; - -/** - * Iterator - * @constructor - * @ignore - * @param {RBT} tree - * @param {RBTNode} snapshot - * @property {RBT} tree - * @property {RBTNode} current - * @property {Object} key - * @property {Object} value - */ - -function Iterator(tree, snapshot) { - this.tree = tree; - this.root = snapshot; - this.current = snapshot; - this.key = null; - this.value = null; -} - -/** - * Compare keys using tree's comparator. - * @param {Object} key - */ - -Iterator.prototype.compare = function compare(key) { - assert(this.key != null, 'No key.'); - return this.tree.compare(this.key, key); -}; - -/** - * Test whether current node is valid. - */ - -Iterator.prototype.valid = function valid() { - return !this.current.isNull(); -}; - -/** - * Seek to the root. - */ - -Iterator.prototype.reset = function reset() { - this.current = this.root; - this.key = null; - this.value = null; -}; - -/** - * Seek to the start of the tree. - */ - -Iterator.prototype.seekFirst = function seekFirst() { - this.current = this.tree.min(this.root); - this.key = this.current.key; - this.value = this.current.value; -}; - -/** - * Seek to the end of the tree. - */ - -Iterator.prototype.seekLast = function seekLast() { - this.current = this.tree.max(this.root); - this.key = this.current.key; - this.value = this.current.value; -}; - -/** - * Seek to a key from the current node (gte). - * @param {String} key - */ - -Iterator.prototype.seek = function seek(key) { - return this.seekMin(key); -}; - -/** - * Seek to a key from the current node (gte). - * @param {String} key - */ - -Iterator.prototype.seekMin = function seekMin(key) { - assert(key != null, 'No key passed to seek.'); - - let root = this.current; - let current = SENTINEL; - - while (!root.isNull()) { - const cmp = this.tree.compare(root.key, key); - - if (cmp === 0) { - current = root; - break; - } - - if (cmp > 0) { - current = root; - root = root.left; - } else { - root = root.right; - } - } - - this.current = current; - this.key = current.key; - this.value = current.value; -}; - -/** - * Seek to a key from the current node (lte). - * @param {String} key - */ - -Iterator.prototype.seekMax = function seekMax(key) { - assert(key != null, 'No key passed to seek.'); - - let root = this.current; - let current = SENTINEL; - - while (!root.isNull()) { - const cmp = this.tree.compare(root.key, key); - - if (cmp === 0) { - current = root; - break; - } - - if (cmp < 0) { - current = root; - root = root.right; - } else { - root = root.left; - } - } - - this.current = current; - this.key = current.key; - this.value = current.value; -}; - -/** - * Seek to previous node. - * @param {String} key - */ - -Iterator.prototype.prev = function prev() { - if (this.current.isNull()) { - this.key = null; - this.value = null; - return false; - } - - this.key = this.current.key; - this.value = this.current.value; - this.current = this.tree.predecessor(this.current); - - return true; -}; - -/** - * Seek to next node. - * @returns {Boolean} - */ - -Iterator.prototype.next = function next() { - if (this.current.isNull()) { - this.key = null; - this.value = null; - return false; - } - - this.key = this.current.key; - this.value = this.current.value; - this.current = this.tree.successor(this.current); - - return true; -}; - -/** - * Return the current key/value pair. - * @returns {RBTData} - */ - -Iterator.prototype.data = function data() { - assert(this.key != null, 'No data available.'); - return new RBTData(this.key, this.value); -}; - -/** - * RBT Node - * @constructor - * @ignore - * @private - * @param {Buffer} key - * @param {Buffer} value - * @property {Buffer} key - * @property {Buffer} value - * @property {Number} color - * @property {RBTNode|RBTSentinel} parent - * @property {RBTNode|RBTSentinel} left - * @property {RBTNode|RBTSentinel} right - */ - -function RBTNode(key, value) { - this.key = key; - this.value = value; - this.color = RED; - this.parent = SENTINEL; - this.left = SENTINEL; - this.right = SENTINEL; -} - -/** - * Clone the node. - * @returns {RBTNode} - */ - -RBTNode.prototype.clone = function clone() { - const node = new RBTNode(this.key, this.value); - node.color = this.color; - node.parent = this.parent; - node.left = this.left; - node.right = this.right; - return node; -}; - -/** - * Clone the node (key/value only). - * @returns {RBTData} - */ - -RBTNode.prototype.copy = function copy() { - return new RBTData(this.key, this.value); -}; - -/** - * Inspect the rbt node. - * @returns {Object} - */ - -RBTNode.prototype.inspect = function inspect() { - return { - key: this.key, - value: this.value, - color: this.color === RED ? 'red' : 'black', - left: this.left, - right: this.right - }; -}; - -/** - * Test whether the node is a leaf. - * Always returns false. - * @returns {Boolean} - */ - -RBTNode.prototype.isNull = function isNull() { - return false; -}; - -/** - * RBT Sentinel Node - * @constructor - * @ignore - * @property {null} key - * @property {null} value - * @property {Number} [color=BLACK] - * @property {null} parent - * @property {null} left - * @property {null} right - */ - -function RBTSentinel() { - this.key = null; - this.value = null; - this.color = BLACK; - this.parent = null; - this.left = null; - this.right = null; -} - -/** - * Inspect the rbt node. - * @returns {String} - */ - -RBTSentinel.prototype.inspect = function inspect() { - return 'NIL'; -}; - -/** - * Test whether the node is a leaf. - * Always returns true. - * @returns {Boolean} - */ - -RBTSentinel.prototype.isNull = function isNull() { - return true; -}; - -/** - * RBT key/value pair - * @constructor - * @ignore - * @param {Buffer} key - * @param {Buffer} value - * @property {Buffer} key - * @property {Buffer} value - */ - -function RBTData(key, value) { - this.key = key; - this.value = value; -} - -/** - * Inspect the rbt data. - * @returns {Object} - */ - -RBTData.prototype.inspect = function inspect() { - return { - key: this.key, - value: this.value - }; -}; - -/* - * Helpers - */ - -SENTINEL = new RBTSentinel(); - -function copyLeft(parent, node) { - if (!node.isNull()) { - parent.left = node.clone(); - parent.left.parent = parent; - copyLeft(parent.left, node.left); - copyRight(parent.left, node.right); - } -} - -function copyRight(parent, node) { - if (!node.isNull()) { - parent.right = node.clone(); - parent.right.parent = parent; - copyLeft(parent.right, node.left); - copyRight(parent.right, node.right); - } -} - -/* - * Expose - */ - -module.exports = RBT; diff --git a/lib/utils/reader.js b/lib/utils/reader.js deleted file mode 100644 index c2e75c7a7..000000000 --- a/lib/utils/reader.js +++ /dev/null @@ -1,601 +0,0 @@ -/*! - * reader.js - buffer reader for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const encoding = require('./encoding'); -const digest = require('../crypto/digest'); - -const EMPTY = Buffer.alloc(0); - -/** - * An object that allows reading of buffers in a sane manner. - * @alias module:utils.BufferReader - * @constructor - * @param {Buffer} data - * @param {Boolean?} zeroCopy - Do not reallocate buffers when - * slicing. Note that this can lead to memory leaks if not used - * carefully. - */ - -function BufferReader(data, zeroCopy) { - if (!(this instanceof BufferReader)) - return new BufferReader(data, zeroCopy); - - assert(Buffer.isBuffer(data), 'Must pass a Buffer.'); - - this.data = data; - this.offset = 0; - this.zeroCopy = zeroCopy || false; - this.stack = []; -} - -/** - * Assertion. - * @param {Boolean} value - */ - -BufferReader.prototype.assert = function assert(value) { - if (!value) - throw new encoding.EncodingError(this.offset, 'Out of bounds read', assert); -}; - -/** - * Assertion. - * @param {Boolean} value - * @param {String} reason - */ - -BufferReader.prototype.enforce = function enforce(value, reason) { - if (!value) - throw new encoding.EncodingError(this.offset, reason, enforce); -}; - -/** - * Get total size of passed-in Buffer. - * @returns {Buffer} - */ - -BufferReader.prototype.getSize = function getSize() { - return this.data.length; -}; - -/** - * Calculate number of bytes left to read. - * @returns {Number} - */ - -BufferReader.prototype.left = function left() { - this.assert(this.offset <= this.data.length); - return this.data.length - this.offset; -}; - -/** - * Seek to a position to read from by offset. - * @param {Number} off - Offset (positive or negative). - */ - -BufferReader.prototype.seek = function seek(off) { - this.assert(this.offset + off >= 0); - this.assert(this.offset + off <= this.data.length); - this.offset += off; - return off; -}; - -/** - * Mark the current starting position. - */ - -BufferReader.prototype.start = function start() { - this.stack.push(this.offset); - return this.offset; -}; - -/** - * Stop reading. Pop the start position off the stack - * and calculate the size of the data read. - * @returns {Number} Size. - * @throws on empty stack. - */ - -BufferReader.prototype.end = function end() { - assert(this.stack.length > 0); - - const start = this.stack.pop(); - - return this.offset - start; -}; - -/** - * Stop reading. Pop the start position off the stack - * and return the data read. - * @param {Bolean?} zeroCopy - Do a fast buffer - * slice instead of allocating a new buffer (warning: - * may cause memory leaks if not used with care). - * @returns {Buffer} Data read. - * @throws on empty stack. - */ - -BufferReader.prototype.endData = function endData(zeroCopy) { - assert(this.stack.length > 0); - - const start = this.stack.pop(); - const end = this.offset; - const size = end - start; - const data = this.data; - - if (size === data.length) - return data; - - if (this.zeroCopy || zeroCopy) - return data.slice(start, end); - - const ret = Buffer.allocUnsafe(size); - data.copy(ret, 0, start, end); - - return ret; -}; - -/** - * Destroy the reader. Remove references to the data. - */ - -BufferReader.prototype.destroy = function destroy() { - this.data = EMPTY; - this.offset = 0; - this.stack.length = 0; -}; - -/** - * Read uint8. - * @returns {Number} - */ - -BufferReader.prototype.readU8 = function readU8() { - this.assert(this.offset + 1 <= this.data.length); - const ret = this.data[this.offset]; - this.offset += 1; - return ret; -}; - -/** - * Read uint16le. - * @returns {Number} - */ - -BufferReader.prototype.readU16 = function readU16() { - this.assert(this.offset + 2 <= this.data.length); - const ret = this.data.readUInt16LE(this.offset, true); - this.offset += 2; - return ret; -}; - -/** - * Read uint16be. - * @returns {Number} - */ - -BufferReader.prototype.readU16BE = function readU16BE() { - this.assert(this.offset + 2 <= this.data.length); - const ret = this.data.readUInt16BE(this.offset, true); - this.offset += 2; - return ret; -}; - -/** - * Read uint32le. - * @returns {Number} - */ - -BufferReader.prototype.readU32 = function readU32() { - this.assert(this.offset + 4 <= this.data.length); - const ret = this.data.readUInt32LE(this.offset, true); - this.offset += 4; - return ret; -}; - -/** - * Read uint32be. - * @returns {Number} - */ - -BufferReader.prototype.readU32BE = function readU32BE() { - this.assert(this.offset + 4 <= this.data.length); - const ret = this.data.readUInt32BE(this.offset, true); - this.offset += 4; - return ret; -}; - -/** - * Read uint64le as a js number. - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -BufferReader.prototype.readU64 = function readU64() { - this.assert(this.offset + 8 <= this.data.length); - const ret = encoding.readU64(this.data, this.offset); - this.offset += 8; - return ret; -}; - -/** - * Read uint64be as a js number. - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -BufferReader.prototype.readU64BE = function readU64BE() { - this.assert(this.offset + 8 <= this.data.length); - const ret = encoding.readU64BE(this.data, this.offset); - this.offset += 8; - return ret; -}; - -/** - * Read int8. - * @returns {Number} - */ - -BufferReader.prototype.readI8 = function readI8() { - this.assert(this.offset + 1 <= this.data.length); - const ret = this.data.readInt8(this.offset, true); - this.offset += 1; - return ret; -}; - -/** - * Read int16le. - * @returns {Number} - */ - -BufferReader.prototype.readI16 = function readI16() { - this.assert(this.offset + 2 <= this.data.length); - const ret = this.data.readInt16LE(this.offset, true); - this.offset += 2; - return ret; -}; - -/** - * Read int16be. - * @returns {Number} - */ - -BufferReader.prototype.readI16BE = function readI16BE() { - this.assert(this.offset + 2 <= this.data.length); - const ret = this.data.readInt16BE(this.offset, true); - this.offset += 2; - return ret; -}; - -/** - * Read int32le. - * @returns {Number} - */ - -BufferReader.prototype.readI32 = function readI32() { - this.assert(this.offset + 4 <= this.data.length); - const ret = this.data.readInt32LE(this.offset, true); - this.offset += 4; - return ret; -}; - -/** - * Read int32be. - * @returns {Number} - */ - -BufferReader.prototype.readI32BE = function readI32BE() { - this.assert(this.offset + 4 <= this.data.length); - const ret = this.data.readInt32BE(this.offset, true); - this.offset += 4; - return ret; -}; - -/** - * Read int64le as a js number. - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -BufferReader.prototype.readI64 = function readI64() { - this.assert(this.offset + 8 <= this.data.length); - const ret = encoding.readI64(this.data, this.offset); - this.offset += 8; - return ret; -}; - -/** - * Read int64be as a js number. - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -BufferReader.prototype.readI64BE = function readI64BE() { - this.assert(this.offset + 8 <= this.data.length); - const ret = encoding.readI64BE(this.data, this.offset); - this.offset += 8; - return ret; -}; - -/** - * Read uint64le. - * @returns {U64} - */ - -BufferReader.prototype.readU64N = function readU64N() { - this.assert(this.offset + 8 <= this.data.length); - const ret = encoding.readU64N(this.data, this.offset); - this.offset += 8; - return ret; -}; - -/** - * Read uint64be. - * @returns {U64} - */ - -BufferReader.prototype.readU64BEN = function readU64BEN() { - this.assert(this.offset + 8 <= this.data.length); - const ret = encoding.readU64BEN(this.data, this.offset); - this.offset += 8; - return ret; -}; - -/** - * Read int64le. - * @returns {I64} - */ - -BufferReader.prototype.readI64N = function readI64N() { - this.assert(this.offset + 8 <= this.data.length); - const ret = encoding.readI64N(this.data, this.offset); - this.offset += 8; - return ret; -}; - -/** - * Read int64be. - * @returns {I64} - */ - -BufferReader.prototype.readI64BEN = function readI64BEN() { - this.assert(this.offset + 8 <= this.data.length); - const ret = encoding.readI64BEN(this.data, this.offset); - this.offset += 8; - return ret; -}; - -/** - * Read float le. - * @returns {Number} - */ - -BufferReader.prototype.readFloat = function readFloat() { - this.assert(this.offset + 4 <= this.data.length); - const ret = this.data.readFloatLE(this.offset, true); - this.offset += 4; - return ret; -}; - -/** - * Read float be. - * @returns {Number} - */ - -BufferReader.prototype.readFloatBE = function readFloatBE() { - this.assert(this.offset + 4 <= this.data.length); - const ret = this.data.readFloatBE(this.offset, true); - this.offset += 4; - return ret; -}; - -/** - * Read double float le. - * @returns {Number} - */ - -BufferReader.prototype.readDouble = function readDouble() { - this.assert(this.offset + 8 <= this.data.length); - const ret = this.data.readDoubleLE(this.offset, true); - this.offset += 8; - return ret; -}; - -/** - * Read double float be. - * @returns {Number} - */ - -BufferReader.prototype.readDoubleBE = function readDoubleBE() { - this.assert(this.offset + 8 <= this.data.length); - const ret = this.data.readDoubleBE(this.offset, true); - this.offset += 8; - return ret; -}; - -/** - * Read a varint. - * @returns {Number} - */ - -BufferReader.prototype.readVarint = function readVarint() { - const {size, value} = encoding.readVarint(this.data, this.offset); - this.offset += size; - return value; -}; - -/** - * Read a varint. - * @returns {U64} - */ - -BufferReader.prototype.readVarintN = function readVarintN() { - const {size, value} = encoding.readVarintN(this.data, this.offset); - this.offset += size; - return value; -}; - -/** - * Read a varint (type 2). - * @returns {Number} - */ - -BufferReader.prototype.readVarint2 = function readVarint2() { - const {size, value} = encoding.readVarint2(this.data, this.offset); - this.offset += size; - return value; -}; - -/** - * Read a varint (type 2). - * @returns {U64} - */ - -BufferReader.prototype.readVarint2N = function readVarint2N() { - const {size, value} = encoding.readVarint2N(this.data, this.offset); - this.offset += size; - return value; -}; - -/** - * Read N bytes (will do a fast slice if zero copy). - * @param {Number} size - * @param {Bolean?} zeroCopy - Do a fast buffer - * slice instead of allocating a new buffer (warning: - * may cause memory leaks if not used with care). - * @returns {Buffer} - */ - -BufferReader.prototype.readBytes = function readBytes(size, zeroCopy) { - assert(size >= 0); - this.assert(this.offset + size <= this.data.length); - - let ret; - if (this.zeroCopy || zeroCopy) { - ret = this.data.slice(this.offset, this.offset + size); - } else { - ret = Buffer.allocUnsafe(size); - this.data.copy(ret, 0, this.offset, this.offset + size); - } - - this.offset += size; - - return ret; -}; - -/** - * Read a varint number of bytes (will do a fast slice if zero copy). - * @param {Bolean?} zeroCopy - Do a fast buffer - * slice instead of allocating a new buffer (warning: - * may cause memory leaks if not used with care). - * @returns {Buffer} - */ - -BufferReader.prototype.readVarBytes = function readVarBytes(zeroCopy) { - return this.readBytes(this.readVarint(), zeroCopy); -}; - -/** - * Read a string. - * @param {String} enc - Any buffer-supported encoding. - * @param {Number} size - * @returns {String} - */ - -BufferReader.prototype.readString = function readString(enc, size) { - assert(size >= 0); - this.assert(this.offset + size <= this.data.length); - const ret = this.data.toString(enc, this.offset, this.offset + size); - this.offset += size; - return ret; -}; - -/** - * Read a 32-byte hash. - * @param {String} enc - `"hex"` or `null`. - * @returns {Hash|Buffer} - */ - -BufferReader.prototype.readHash = function readHash(enc) { - if (enc) - return this.readString(enc, 32); - return this.readBytes(32); -}; - -/** - * Read string of a varint length. - * @param {String} enc - Any buffer-supported encoding. - * @param {Number?} limit - Size limit. - * @returns {String} - */ - -BufferReader.prototype.readVarString = function readVarString(enc, limit) { - const size = this.readVarint(); - this.enforce(!limit || size <= limit, 'String exceeds limit.'); - return this.readString(enc, size); -}; - -/** - * Read a null-terminated string. - * @param {String} enc - Any buffer-supported encoding. - * @returns {String} - */ - -BufferReader.prototype.readNullString = function readNullString(enc) { - this.assert(this.offset + 1 <= this.data.length); - - let i = this.offset; - for (; i < this.data.length; i++) { - if (this.data[i] === 0) - break; - } - - this.assert(i !== this.data.length); - - const ret = this.readString(enc, i - this.offset); - - this.offset = i + 1; - - return ret; -}; - -/** - * Create a checksum from the last start position. - * @returns {Number} Checksum. - */ - -BufferReader.prototype.createChecksum = function createChecksum() { - let start = 0; - - if (this.stack.length > 0) - start = this.stack[this.stack.length - 1]; - - const data = this.data.slice(start, this.offset); - - return digest.hash256(data).readUInt32LE(0, true); -}; - -/** - * Verify a 4-byte checksum against a calculated checksum. - * @returns {Number} checksum - * @throws on bad checksum - */ - -BufferReader.prototype.verifyChecksum = function verifyChecksum() { - const chk = this.createChecksum(); - const checksum = this.readU32(); - this.enforce(chk === checksum, 'Checksum mismatch.'); - return checksum; -}; - -/* - * Expose - */ - -module.exports = BufferReader; diff --git a/lib/utils/rollingfilter.js b/lib/utils/rollingfilter.js deleted file mode 100644 index df6d8e250..000000000 --- a/lib/utils/rollingfilter.js +++ /dev/null @@ -1,255 +0,0 @@ -/*! - * rollingfilter.js - rolling bloom filter for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const murmur3 = require('./murmur3'); -const sum32 = murmur3.sum32; -const mul32 = murmur3.mul32; -const DUMMY = Buffer.alloc(0); - -/** - * A rolling bloom filter used internally - * (do not relay this on the p2p network). - * @alias module:utils.RollingFilter - * @constructor - * @param {Number} items - Expected number of items. - * @param {Number} rate - False positive rate (0.0-1.0). - */ - -function RollingFilter(items, rate) { - if (!(this instanceof RollingFilter)) - return new RollingFilter(items, rate); - - this.entries = 0; - this.generation = 1; - this.n = 0; - this.limit = 0; - this.size = 0; - this.items = 0; - this.tweak = 0; - this.filter = DUMMY; - - if (items != null) - this.fromRate(items, rate); -} - -/** - * Inject properties from items and FPR. - * @private - * @param {Number} items - Expected number of items. - * @param {Number} rate - False positive rate (0.0-1.0). - * @returns {RollingFilter} - */ - -RollingFilter.prototype.fromRate = function fromRate(items, rate) { - assert(typeof items === 'number', '`items` must be a number.'); - assert(items > 0, '`items` must be greater than zero.'); - assert(Number.isSafeInteger(items), '`items` must be an integer.'); - assert(typeof rate === 'number', '`rate` must be a number.'); - assert(rate >= 0 && rate <= 1, '`rate` must be between 0.0 and 1.0.'); - - const logRate = Math.log(rate); - - const n = Math.max(1, Math.min(Math.round(logRate / Math.log(0.5)), 50)); - const limit = (items + 1) / 2 | 0; - - const max = limit * 3; - - let size = -1 * n * max / Math.log(1.0 - Math.exp(logRate / n)); - size = Math.ceil(size); - - items = ((size + 63) / 64 | 0) << 1; - items >>>= 0; - items = Math.max(1, items); - - const tweak = (Math.random() * 0x100000000) >>> 0; - - const filter = Buffer.allocUnsafe(items * 8); - filter.fill(0); - - this.n = n; - this.limit = limit; - this.size = size; - this.items = items; - this.tweak = tweak; - this.filter = filter; - - return this; -}; - -/** - * Instantiate rolling filter from items and FPR. - * @param {Number} items - Expected number of items. - * @param {Number} rate - False positive rate (0.0-1.0). - * @returns {RollingFilter} - */ - -RollingFilter.fromRate = function fromRate(items, rate) { - return new RollingFilter().fromRate(items, rate); -}; - -/** - * Perform the mumur3 hash on data. - * @param {Buffer} val - * @param {Number} seed - * @returns {Number} - */ - -RollingFilter.prototype.hash = function hash(val, n) { - return murmur3(val, sum32(mul32(n, 0xfba4c795), this.tweak)); -}; - -/** - * Reset the filter. - */ - -RollingFilter.prototype.reset = function reset() { - if (this.entries === 0) - return; - - this.entries = 0; - this.generation = 1; - this.filter.fill(0); -}; - -/** - * Add data to the filter. - * @param {Buffer|String} - * @param {String?} enc - Can be any of the Buffer object's encodings. - */ - -RollingFilter.prototype.add = function add(val, enc) { - if (typeof val === 'string') - val = Buffer.from(val, enc); - - if (this.entries === this.limit) { - this.entries = 0; - this.generation += 1; - - if (this.generation === 4) - this.generation = 1; - - const m1 = (this.generation & 1) * 0xffffffff; - const m2 = (this.generation >>> 1) * 0xffffffff; - - for (let i = 0; i < this.items; i += 2) { - const pos1 = i * 8; - const pos2 = (i + 1) * 8; - const v1 = read(this.filter, pos1); - const v2 = read(this.filter, pos2); - const mhi = (v1.hi ^ m1) | (v2.hi ^ m2); - const mlo = (v1.lo ^ m1) | (v2.lo ^ m2); - - v1.hi &= mhi; - v1.lo &= mlo; - v2.hi &= mhi; - v2.lo &= mlo; - - write(this.filter, v1, pos1); - write(this.filter, v2, pos2); - } - } - - this.entries += 1; - - for (let i = 0; i < this.n; i++) { - const hash = this.hash(val, i); - const bits = hash & 0x3f; - const pos = (hash >>> 6) % this.items; - const pos1 = (pos & ~1) * 8; - const pos2 = (pos | 1) * 8; - const bit = bits % 8; - const oct = (bits - bit) / 8; - - this.filter[pos1 + oct] &= ~(1 << bit); - this.filter[pos1 + oct] |= (this.generation & 1) << bit; - - this.filter[pos2 + oct] &= ~(1 << bit); - this.filter[pos2 + oct] |= (this.generation >>> 1) << bit; - } -}; - -/** - * Test whether data is present in the filter. - * @param {Buffer|String} val - * @param {String?} enc - Can be any of the Buffer object's encodings. - * @returns {Boolean} - */ - -RollingFilter.prototype.test = function test(val, enc) { - if (this.entries === 0) - return false; - - if (typeof val === 'string') - val = Buffer.from(val, enc); - - for (let i = 0; i < this.n; i++) { - const hash = this.hash(val, i); - const bits = hash & 0x3f; - const pos = (hash >>> 6) % this.items; - const pos1 = (pos & ~1) * 8; - const pos2 = (pos | 1) * 8; - const bit = bits % 8; - const oct = (bits - bit) / 8; - - const bit1 = (this.filter[pos1 + oct] >>> bit) & 1; - const bit2 = (this.filter[pos2 + oct] >>> bit) & 1; - - if ((bit1 | bit2) === 0) - return false; - } - - return true; -}; - -/** - * Test whether data is present in the - * filter and potentially add data. - * @param {Buffer|String} val - * @param {String?} enc - Can be any of the Buffer object's encodings. - * @returns {Boolean} Whether data was added. - */ - -RollingFilter.prototype.added = function added(val, enc) { - if (typeof val === 'string') - val = Buffer.from(val, enc); - - if (!this.test(val)) { - this.add(val); - return true; - } - - return false; -}; - -/* - * Helpers - */ - -function U64(hi, lo) { - this.hi = hi; - this.lo = lo; -} - -function read(data, off) { - const hi = data.readUInt32LE(off + 4, true); - const lo = data.readUInt32LE(off, true); - return new U64(hi, lo); -} - -function write(data, value, off) { - data.writeUInt32LE(value.hi, off + 4, true); - data.writeUInt32LE(value.lo, off, true); -} - -/* - * Expose - */ - -module.exports = RollingFilter; diff --git a/lib/utils/staticwriter.js b/lib/utils/staticwriter.js deleted file mode 100644 index 0530c0965..000000000 --- a/lib/utils/staticwriter.js +++ /dev/null @@ -1,461 +0,0 @@ -/*! - * staticwriter.js - buffer writer for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const encoding = require('./encoding'); -const digest = require('../crypto/digest'); - -const EMPTY = Buffer.alloc(0); -const POOLSIZE = 100 << 10; - -let POOL = null; - -/** - * Statically allocated buffer writer. - * @alias module:utils.StaticWriter - * @constructor - * @param {Number} size - */ - -function StaticWriter(size) { - if (!(this instanceof StaticWriter)) - return new StaticWriter(size); - - this.data = size ? Buffer.allocUnsafe(size) : EMPTY; - this.offset = 0; -} - -/** - * Allocate writer from preallocated 100kb pool. - * @param {Number} size - * @returns {StaticWriter} - */ - -StaticWriter.pool = function pool(size) { - if (size <= POOLSIZE) { - if (!POOL) - POOL = Buffer.allocUnsafeSlow(POOLSIZE); - - const bw = new StaticWriter(0); - bw.data = POOL.slice(0, size); - return bw; - } - - return new StaticWriter(size); -}; - -/** - * Allocate and render the final buffer. - * @returns {Buffer} Rendered buffer. - */ - -StaticWriter.prototype.render = function render() { - const data = this.data; - assert(this.offset === data.length); - this.destroy(); - return data; -}; - -/** - * Get size of data written so far. - * @returns {Number} - */ - -StaticWriter.prototype.getSize = function getSize() { - return this.offset; -}; - -/** - * Seek to relative offset. - * @param {Number} offset - */ - -StaticWriter.prototype.seek = function seek(offset) { - this.offset += offset; -}; - -/** - * Destroy the buffer writer. - */ - -StaticWriter.prototype.destroy = function destroy() { - this.data = EMPTY; - this.offset = 0; -}; - -/** - * Write uint8. - * @param {Number} value - */ - -StaticWriter.prototype.writeU8 = function writeU8(value) { - this.offset = this.data.writeUInt8(value, this.offset, true); -}; - -/** - * Write uint16le. - * @param {Number} value - */ - -StaticWriter.prototype.writeU16 = function writeU16(value) { - this.offset = this.data.writeUInt16LE(value, this.offset, true); -}; - -/** - * Write uint16be. - * @param {Number} value - */ - -StaticWriter.prototype.writeU16BE = function writeU16BE(value) { - this.offset = this.data.writeUInt16BE(value, this.offset, true); -}; - -/** - * Write uint32le. - * @param {Number} value - */ - -StaticWriter.prototype.writeU32 = function writeU32(value) { - this.offset = this.data.writeUInt32LE(value, this.offset, true); -}; - -/** - * Write uint32be. - * @param {Number} value - */ - -StaticWriter.prototype.writeU32BE = function writeU32BE(value) { - this.offset = this.data.writeUInt32BE(value, this.offset, true); -}; - -/** - * Write uint64le. - * @param {Number} value - */ - -StaticWriter.prototype.writeU64 = function writeU64(value) { - this.offset = encoding.writeU64(this.data, value, this.offset); -}; - -/** - * Write uint64be. - * @param {Number} value - */ - -StaticWriter.prototype.writeU64BE = function writeU64BE(value) { - this.offset = encoding.writeU64BE(this.data, value, this.offset); -}; - -/** - * Write uint64le. - * @param {U64} value - */ - -StaticWriter.prototype.writeU64N = function writeU64N(value) { - this.offset = encoding.writeU64N(this.data, value, this.offset); -}; - -/** - * Write uint64be. - * @param {U64} value - */ - -StaticWriter.prototype.writeU64BEN = function writeU64BEN(value) { - this.offset = encoding.writeU64BEN(this.data, value, this.offset); -}; - -/** - * Write int8. - * @param {Number} value - */ - -StaticWriter.prototype.writeI8 = function writeI8(value) { - this.offset = this.data.writeInt8(value, this.offset, true); -}; - -/** - * Write int16le. - * @param {Number} value - */ - -StaticWriter.prototype.writeI16 = function writeI16(value) { - this.offset = this.data.writeInt16LE(value, this.offset, true); -}; - -/** - * Write int16be. - * @param {Number} value - */ - -StaticWriter.prototype.writeI16BE = function writeI16BE(value) { - this.offset = this.data.writeInt16BE(value, this.offset, true); -}; - -/** - * Write int32le. - * @param {Number} value - */ - -StaticWriter.prototype.writeI32 = function writeI32(value) { - this.offset = this.data.writeInt32LE(value, this.offset, true); -}; - -/** - * Write int32be. - * @param {Number} value - */ - -StaticWriter.prototype.writeI32BE = function writeI32BE(value) { - this.offset = this.data.writeInt32BE(value, this.offset, true); -}; - -/** - * Write int64le. - * @param {Number} value - */ - -StaticWriter.prototype.writeI64 = function writeI64(value) { - this.offset = encoding.writeI64(this.data, value, this.offset); -}; - -/** - * Write int64be. - * @param {Number} value - */ - -StaticWriter.prototype.writeI64BE = function writeI64BE(value) { - this.offset = encoding.writeI64BE(this.data, value, this.offset); -}; - -/** - * Write int64le. - * @param {I64} value - */ - -StaticWriter.prototype.writeI64N = function writeI64N(value) { - this.offset = encoding.writeI64N(this.data, value, this.offset); -}; - -/** - * Write int64be. - * @param {I64} value - */ - -StaticWriter.prototype.writeI64BEN = function writeI64BEN(value) { - this.offset = encoding.writeI64BEN(this.data, value, this.offset); -}; - -/** - * Write float le. - * @param {Number} value - */ - -StaticWriter.prototype.writeFloat = function writeFloat(value) { - this.offset = this.data.writeFloatLE(value, this.offset, true); -}; - -/** - * Write float be. - * @param {Number} value - */ - -StaticWriter.prototype.writeFloatBE = function writeFloatBE(value) { - this.offset = this.data.writeFloatBE(value, this.offset, true); -}; - -/** - * Write double le. - * @param {Number} value - */ - -StaticWriter.prototype.writeDouble = function writeDouble(value) { - this.offset = this.data.writeDoubleLE(value, this.offset, true); -}; - -/** - * Write double be. - * @param {Number} value - */ - -StaticWriter.prototype.writeDoubleBE = function writeDoubleBE(value) { - this.offset = this.data.writeDoubleBE(value, this.offset, true); -}; - -/** - * Write a varint. - * @param {Number} value - */ - -StaticWriter.prototype.writeVarint = function writeVarint(value) { - this.offset = encoding.writeVarint(this.data, value, this.offset); -}; - -/** - * Write a varint. - * @param {U64} value - */ - -StaticWriter.prototype.writeVarintN = function writeVarintN(value) { - this.offset = encoding.writeVarintN(this.data, value, this.offset); -}; - -/** - * Write a varint (type 2). - * @param {Number} value - */ - -StaticWriter.prototype.writeVarint2 = function writeVarint2(value) { - this.offset = encoding.writeVarint2(this.data, value, this.offset); -}; - -/** - * Write a varint (type 2). - * @param {U64} value - */ - -StaticWriter.prototype.writeVarint2N = function writeVarint2N(value) { - this.offset = encoding.writeVarint2N(this.data, value, this.offset); -}; - -/** - * Write bytes. - * @param {Buffer} value - */ - -StaticWriter.prototype.writeBytes = function writeBytes(value) { - if (value.length === 0) - return; - - value.copy(this.data, this.offset); - - this.offset += value.length; -}; - -/** - * Write bytes with a varint length before them. - * @param {Buffer} value - */ - -StaticWriter.prototype.writeVarBytes = function writeVarBytes(value) { - this.writeVarint(value.length); - this.writeBytes(value); -}; - -/** - * Copy bytes. - * @param {Buffer} value - * @param {Number} start - * @param {Number} end - */ - -StaticWriter.prototype.copy = function copy(value, start, end) { - const len = end - start; - - if (len === 0) - return; - - value.copy(this.data, this.offset, start, end); - this.offset += len; -}; - -/** - * Write string to buffer. - * @param {String} value - * @param {String?} enc - Any buffer-supported encoding. - */ - -StaticWriter.prototype.writeString = function writeString(value, enc) { - if (value.length === 0) - return; - - const size = Buffer.byteLength(value, enc); - - this.data.write(value, this.offset, enc); - - this.offset += size; -}; - -/** - * Write a 32 byte hash. - * @param {Hash} value - */ - -StaticWriter.prototype.writeHash = function writeHash(value) { - if (typeof value !== 'string') { - assert(value.length === 32); - this.writeBytes(value); - return; - } - assert(value.length === 64); - this.data.write(value, this.offset, 'hex'); - this.offset += 32; -}; - -/** - * Write a string with a varint length before it. - * @param {String} - * @param {String?} enc - Any buffer-supported encoding. - */ - -StaticWriter.prototype.writeVarString = function writeVarString(value, enc) { - if (value.length === 0) { - this.writeVarint(0); - return; - } - - const size = Buffer.byteLength(value, enc); - - this.writeVarint(size); - this.data.write(value, this.offset, enc); - - this.offset += size; -}; - -/** - * Write a null-terminated string. - * @param {String|Buffer} - * @param {String?} enc - Any buffer-supported encoding. - */ - -StaticWriter.prototype.writeNullString = function writeNullString(value, enc) { - this.writeString(value, enc); - this.writeU8(0); -}; - -/** - * Calculate and write a checksum for the data written so far. - */ - -StaticWriter.prototype.writeChecksum = function writeChecksum() { - const data = this.data.slice(0, this.offset); - const hash = digest.hash256(data); - hash.copy(this.data, this.offset, 0, 4); - this.offset += 4; -}; - -/** - * Fill N bytes with value. - * @param {Number} value - * @param {Number} size - */ - -StaticWriter.prototype.fill = function fill(value, size) { - assert(size >= 0); - - if (size === 0) - return; - - this.data.fill(value, this.offset, this.offset + size); - this.offset += size; -}; - -/* - * Expose - */ - -module.exports = StaticWriter; diff --git a/lib/utils/util.js b/lib/utils/util.js index 63eb65ec0..0ab6d5c43 100644 --- a/lib/utils/util.js +++ b/lib/utils/util.js @@ -7,8 +7,7 @@ 'use strict'; -const assert = require('assert'); -const nodeUtil = require('util'); +const assert = require('bsert'); /** * @exports utils/util @@ -16,376 +15,15 @@ const nodeUtil = require('util'); const util = exports; -/* - * Constants - */ - -const inspectOptions = { - showHidden: false, - depth: 20, - colors: false, - customInspect: true, - showProxy: false, - maxArrayLength: Infinity, - breakLength: 60 -}; - -/** - * Test whether a number is Number, - * finite, and below MAX_SAFE_INTEGER. - * @param {Number?} value - * @returns {Boolean} - */ - -util.isNumber = function isNumber(value) { - return typeof value === 'number' - && isFinite(value) - && value >= -Number.MAX_SAFE_INTEGER - && value <= Number.MAX_SAFE_INTEGER; -}; - -/** - * Test whether an object is an int. - * @param {Number?} value - * @returns {Boolean} - */ - -util.isInt = function isInt(value) { - return Number.isSafeInteger(value); -}; - -/** - * Test whether an object is a uint. - * @param {Number?} value - * @returns {Boolean} - */ - -util.isUint = function isUint(value) { - return util.isInt(value) && value >= 0; -}; - -/** - * Test whether a number is a float. - * @param {Number?} value - * @returns {Boolean} - */ - -util.isFloat = function isFloat(value) { - return typeof value === 'number' && isFinite(value); -}; - -/** - * Test whether a number is a positive float. - * @param {Number?} value - * @returns {Boolean} - */ - -util.isUfloat = function isUfloat(value) { - return util.isFloat(value) && value >= 0; -}; - -/** - * Test whether an object is an int8. - * @param {Number?} value - * @returns {Boolean} - */ - -util.isI8 = function isI8(value) { - return (value | 0) === value && value >= -0x80 && value <= 0x7f; -}; - -/** - * Test whether an object is an int16. - * @param {Number?} value - * @returns {Boolean} - */ - -util.isI16 = function isI16(value) { - return (value | 0) === value && value >= -0x8000 && value <= 0x7fff; -}; - -/** - * Test whether an object is an int32. - * @param {Number?} value - * @returns {Boolean} - */ - -util.isI32 = function isI32(value) { - return (value | 0) === value; -}; - -/** - * Test whether an object is a int53. - * @param {Number?} value - * @returns {Boolean} - */ - -util.isI64 = function isI64(value) { - return util.isInt(value); -}; - -/** - * Test whether an object is a uint8. - * @param {Number?} value - * @returns {Boolean} - */ - -util.isU8 = function isU8(value) { - return (value & 0xff) === value; -}; - -/** - * Test whether an object is a uint16. - * @param {Number?} value - * @returns {Boolean} - */ - -util.isU16 = function isU16(value) { - return (value & 0xffff) === value; -}; - -/** - * Test whether an object is a uint32. - * @param {Number?} value - * @returns {Boolean} - */ - -util.isU32 = function isU32(value) { - return (value >>> 0) === value; -}; - -/** - * Test whether an object is a uint53. - * @param {Number?} value - * @returns {Boolean} - */ - -util.isU64 = function isU64(value) { - return util.isUint(value); -}; - -/** - * Test whether a string is a plain - * ascii string (no control characters). - * @param {String} str - * @returns {Boolean} - */ - -util.isAscii = function isAscii(str) { - return typeof str === 'string' && /^[\t\n\r -~]*$/.test(str); -}; - -/** - * Test whether a string is base58 (note that you - * may get a false positive on a hex string). - * @param {String?} str - * @returns {Boolean} - */ - -util.isBase58 = function isBase58(str) { - return typeof str === 'string' && /^[1-9A-Za-z]+$/.test(str); -}; - -/** - * Test whether a string is bech32 (note that - * this doesn't guarantee address is bech32). - * @param {String?} str - * @returns {Boolean} - */ - -util.isBech32 = function isBech32(str) { - if (typeof str !== 'string') - return false; - - if (str.toUpperCase() !== str && str.toLowerCase() !== str) - return false; - - if (str.length < 8 || str.length > 90) - return false; - - // it's unlikely any network will have hrp other than a-z symbols. - return /^[a-z]{2}1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]+$/i.test(str); -}; - -/** - * Test whether a string is hex (length must be even). - * Note that this _could_ await a false positive on - * base58 strings. - * @param {String?} str - * @returns {Boolean} - */ - -util.isHex = function isHex(str) { - if (typeof str !== 'string') - return false; - return str.length % 2 === 0 && /^[0-9A-Fa-f]+$/.test(str); -}; - -/** - * Test whether an object is a 160 bit hash (hex string). - * @param {String?} hash - * @returns {Boolean} - */ - -util.isHex160 = function isHex160(hash) { - if (typeof hash !== 'string') - return false; - return hash.length === 40 && util.isHex(hash); -}; - -/** - * Test whether an object is a 256 bit hash (hex string). - * @param {String?} hash - * @returns {Boolean} - */ - -util.isHex256 = function isHex256(hash) { - if (typeof hash !== 'string') - return false; - return hash.length === 64 && util.isHex(hash); -}; - -/** - * Test whether the result of a positive - * addition would be below MAX_SAFE_INTEGER. - * @param {Number} value - * @returns {Boolean} - */ - -util.isSafeAddition = function isSafeAddition(a, b) { - // We only work on positive numbers. - assert(a >= 0); - assert(b >= 0); - - // Fast case. - if (a <= 0xfffffffffffff && b <= 0xfffffffffffff) - return true; - - // Do a 64 bit addition and check the top 11 bits. - let ahi = (a * (1 / 0x100000000)) | 0; - const alo = a | 0; - - let bhi = (b * (1 / 0x100000000)) | 0; - const blo = b | 0; - - // Credit to @indutny for this method. - const lo = (alo + blo) | 0; - - const s = lo >> 31; - const as = alo >> 31; - const bs = blo >> 31; - - const c = ((as & bs) | (~s & (as ^ bs))) & 1; - - let hi = (((ahi + bhi) | 0) + c) | 0; - - hi >>>= 0; - ahi >>>= 0; - bhi >>>= 0; - - // Overflow? - if (hi < ahi || hi < bhi) - return false; - - return (hi & 0xffe00000) === 0; -}; - -/** - * util.inspect() with 20 levels of depth. - * @param {Object|String} obj - * @param {Boolean?} color - * @return {String} - */ - -util.inspectify = function inspectify(obj, color) { - if (typeof obj === 'string') - return obj; - - inspectOptions.colors = color !== false; - - return nodeUtil.inspect(obj, inspectOptions); -}; - -/** - * Format a string. - * @function - * @param {...String} args - * @returns {String} - */ - -util.fmt = nodeUtil.format; - -/** - * Format a string. - * @param {Array} args - * @param {Boolean?} color - * @return {String} - */ - -util.format = function format(args, color) { - if (args.length > 0 && args[0] && typeof args[0] === 'object') { - if (color == null) - color = Boolean(process.stdout && process.stdout.isTTY); - return util.inspectify(args[0], color); - } - return util.fmt(...args); -}; - -/** - * Write a message to stdout (console in browser). - * @param {Object|String} obj - * @param {...String} args - */ - -util.log = function log(...args) { - if (!process.stdout) { - let msg; - if (args.length > 0) { - msg = typeof args[0] !== 'object' - ? util.fmt(...args) - : args[0]; - } - console.log(msg); - return; - } - - const msg = util.format(args); - - process.stdout.write(msg + '\n'); -}; - -/** - * Write a message to stderr (console in browser). - * @param {Object|String} obj - * @param {...String} args - */ - -util.error = function error(...args) { - if (!process.stderr) { - let msg; - if (args.length > 0) { - msg = typeof args[0] !== 'object' - ? util.fmt(...args) - : args[0]; - } - console.error(msg); - return; - } - - const msg = util.format(args); - - process.stderr.write(msg + '\n'); -}; - /** * Return hrtime (shim for browser). * @param {Array} time * @returns {Array} [seconds, nanoseconds] */ -util.hrtime = function hrtime(time) { +util.bench = function bench(time) { if (!process.hrtime) { - const now = util.ms(); + const now = Date.now(); if (time) { const [hi, lo] = time; @@ -418,7 +56,7 @@ util.hrtime = function hrtime(time) { */ util.now = function now() { - return Math.floor(util.ms() / 1000); + return Math.floor(Date.now() / 1000); }; /** @@ -457,605 +95,20 @@ util.time = function time(date) { }; /** - * Get random range. - * @param {Number} min - * @param {Number} max - * @returns {Number} - */ - -util.random = function random(min, max) { - return Math.floor(Math.random() * (max - min)) + min; -}; - -/** - * Create a 32 or 64 bit nonce. - * @param {Number} size - * @returns {Buffer} - */ - -util.nonce = function nonce(size) { - let n, data; - - if (!size) - size = 8; - - switch (size) { - case 8: - data = Buffer.allocUnsafe(8); - n = util.random(0, 0x100000000); - data.writeUInt32LE(n, 0, true); - n = util.random(0, 0x100000000); - data.writeUInt32LE(n, 4, true); - break; - case 4: - data = Buffer.allocUnsafe(4); - n = util.random(0, 0x100000000); - data.writeUInt32LE(n, 0, true); - break; - default: - assert(false, 'Bad nonce size.'); - break; - } - - return data; -}; - -/** - * String comparator (memcmp + length comparison). - * @param {Buffer} a - * @param {Buffer} b - * @returns {Number} -1, 1, or 0. - */ - -util.strcmp = function strcmp(a, b) { - const len = Math.min(a.length, b.length); - - for (let i = 0; i < len; i++) { - if (a[i] < b[i]) - return -1; - if (a[i] > b[i]) - return 1; - } - - if (a.length < b.length) - return -1; - - if (a.length > b.length) - return 1; - - return 0; -}; - -/** - * Convert bytes to mb. - * @param {Number} size - * @returns {Number} mb - */ - -util.mb = function mb(size) { - return Math.floor(size / 1024 / 1024); -}; - -/** - * Find index of a buffer in an array of buffers. - * @param {Buffer[]} items - * @param {Buffer} data - Target buffer to find. - * @returns {Number} Index (-1 if not found). - */ - -util.indexOf = function indexOf(items, data) { - assert(Array.isArray(items)); - assert(Buffer.isBuffer(data)); - - for (let i = 0; i < items.length; i++) { - const item = items[i]; - - assert(Buffer.isBuffer(item)); - - if (item.equals(data)) - return i; - } - - return -1; -}; - -/** - * Convert a number to a padded uint8 - * string (3 digits in decimal). - * @param {Number} num - * @returns {String} Padded number. - */ - -util.pad8 = function pad8(num) { - assert(typeof num === 'number'); - assert(num >= 0); - - num = num.toString(10); - - switch (num.length) { - case 1: - return '00' + num; - case 2: - return '0' + num; - case 3: - return num; - } - - throw new Error('Number too big.'); -}; - -/** - * Convert a number to a padded uint32 - * string (10 digits in decimal). - * @param {Number} num - * @returns {String} Padded number. - */ - -util.pad32 = function pad32(num) { - assert(typeof num === 'number'); - assert(num >= 0); - - num = num.toString(10); - - switch (num.length) { - case 1: - return '000000000' + num; - case 2: - return '00000000' + num; - case 3: - return '0000000' + num; - case 4: - return '000000' + num; - case 5: - return '00000' + num; - case 6: - return '0000' + num; - case 7: - return '000' + num; - case 8: - return '00' + num; - case 9: - return '0' + num; - case 10: - return num; - } - - throw new Error('Number too big.'); -}; - -/** - * Convert a number to a padded uint8 - * string (2 digits in hex). - * @param {Number} num - * @returns {String} Padded number. - */ - -util.hex8 = function hex8(num) { - assert(typeof num === 'number'); - assert(num >= 0); - - num = num.toString(16); - - switch (num.length) { - case 1: - return '0' + num; - case 2: - return num; - } - - throw new Error('Number too big.'); -}; - -/** - * Convert a number to a padded uint32 - * string (8 digits in hex). - * @param {Number} num - * @returns {String} Padded number. - */ - -util.hex32 = function hex32(num) { - assert(typeof num === 'number'); - assert(num >= 0); - - num = num.toString(16); - - switch (num.length) { - case 1: - return '0000000' + num; - case 2: - return '000000' + num; - case 3: - return '00000' + num; - case 4: - return '0000' + num; - case 5: - return '000' + num; - case 6: - return '00' + num; - case 7: - return '0' + num; - case 8: - return num; - } - - throw new Error('Number too big.'); -}; - -/** - * Reverse a hex-string (used because of - * bitcoind's affinity for uint256le). - * @param {String} data - Hex string. + * Reverse a hex-string. + * @param {Buffer} * @returns {String} Reversed hex string. */ -util.revHex = function revHex(data) { - assert(typeof data === 'string'); - assert(data.length > 0); - assert(data.length % 2 === 0); - - let out = ''; - - for (let i = 0; i < data.length; i += 2) - out = data.slice(i, i + 2) + out; - - return out; -}; - -/** - * Reverse an object's keys and values. - * @param {Object} obj - * @returns {Object} Reversed object. - */ - -util.reverse = function reverse(obj) { - const reversed = {}; - - for (const key of Object.keys(obj)) - reversed[obj[key]] = key; - - return reversed; -}; - -/** - * Perform a binary search on a sorted array. - * @param {Array} items - * @param {Object} key - * @param {Function} compare - * @param {Boolean?} insert - * @returns {Number} Index. - */ - -util.binarySearch = function binarySearch(items, key, compare, insert) { - let start = 0; - let end = items.length - 1; - - while (start <= end) { - const pos = (start + end) >>> 1; - const cmp = compare(items[pos], key); - - if (cmp === 0) - return pos; - - if (cmp < 0) - start = pos + 1; - else - end = pos - 1; - } - - if (!insert) - return -1; - - return start; -}; - -/** - * Perform a binary insert on a sorted array. - * @param {Array} items - * @param {Object} item - * @param {Function} compare - * @returns {Number} index - */ - -util.binaryInsert = function binaryInsert(items, item, compare, uniq) { - const i = util.binarySearch(items, item, compare, true); - - if (uniq && i < items.length) { - if (compare(items[i], item) === 0) - return -1; - } - - if (i === 0) - items.unshift(item); - else if (i === items.length) - items.push(item); - else - items.splice(i, 0, item); - - return i; -}; - -/** - * Perform a binary removal on a sorted array. - * @param {Array} items - * @param {Object} item - * @param {Function} compare - * @returns {Boolean} - */ - -util.binaryRemove = function binaryRemove(items, item, compare) { - const i = util.binarySearch(items, item, compare, false); - - if (i === -1) - return false; - - items.splice(i, 1); - - return true; -}; - -/** - * Quick test to see if a string is uppercase. - * @param {String} str - * @returns {Boolean} - */ - -util.isUpperCase = function isUpperCase(str) { - assert(typeof str === 'string'); - - if (str.length === 0) - return false; - - return (str.charCodeAt(0) & 32) === 0; -}; - -/** - * Test to see if a string starts with a prefix. - * @param {String} str - * @param {String} prefix - * @returns {Boolean} - */ - -util.startsWith = function startsWith(str, prefix) { - assert(typeof str === 'string'); - - if (!str.startsWith) - return str.indexOf(prefix) === 0; - - return str.startsWith(prefix); -}; - -/** - * Get memory usage info. - * @returns {Object} - */ - -util.memoryUsage = function memoryUsage() { - if (!process.memoryUsage) { - return { - total: 0, - jsHeap: 0, - jsHeapTotal: 0, - nativeHeap: 0, - external: 0 - }; - } - - const mem = process.memoryUsage(); - - return { - total: util.mb(mem.rss), - jsHeap: util.mb(mem.heapUsed), - jsHeapTotal: util.mb(mem.heapTotal), - nativeHeap: util.mb(mem.rss - mem.heapTotal), - external: util.mb(mem.external) - }; -}; - -/** - * Convert int to fixed number string and reduce by a - * power of ten (uses no floating point arithmetic). - * @param {Number} num - * @param {Number} exp - Number of decimal places. - * @returns {String} Fixed number string. - */ - -util.toFixed = function toFixed(num, exp) { - assert(typeof num === 'number'); - // The max Flo is 160m, which when you count the satoshis of, it is larger than the max safe integer value - // assert(Number.isSafeInteger(num), 'Invalid integer value.'); - - let sign = ''; - - if (num < 0) { - num = -num; - sign = '-'; - } - - const mult = pow10(exp); - - let lo = num % mult; - let hi = (num - lo) / mult; - - lo = lo.toString(10); - hi = hi.toString(10); +util.revHex = function revHex(buf) { + assert(Buffer.isBuffer(buf)); - while (lo.length < exp) - lo = '0' + lo; - - lo = lo.replace(/0+$/, ''); - - assert(lo.length <= exp, 'Invalid integer value.'); - - if (lo.length === 0) - lo = '0'; - - if (exp === 0) - return `${sign}${hi}`; - - return `${sign}${hi}.${lo}`; + return Buffer.from(buf).reverse().toString('hex'); }; -/** - * Parse a fixed number string and multiply by a - * power of ten (uses no floating point arithmetic). - * @param {String} str - * @param {Number} exp - Number of decimal places. - * @returns {Number} Integer. - */ - -util.fromFixed = function fromFixed(str, exp) { +util.fromRev = function fromRev(str) { assert(typeof str === 'string'); - assert(str.length <= 32, 'Fixed number string too large.'); - - let sign = 1; - - if (str.length > 0 && str[0] === '-') { - str = str.substring(1); - sign = -1; - } - - let hi = str; - let lo = '0'; - - const index = str.indexOf('.'); - - if (index !== -1) { - hi = str.substring(0, index); - lo = str.substring(index + 1); - } - - hi = hi.replace(/^0+/, ''); - lo = lo.replace(/0+$/, ''); - - assert(hi.length <= 16 - exp, - 'Fixed number string exceeds 2^53-1.'); - - assert(lo.length <= exp, - 'Too many decimal places in fixed number string.'); - - if (hi.length === 0) - hi = '0'; - - while (lo.length < exp) - lo += '0'; - - if (lo.length === 0) - lo = '0'; - - assert(/^\d+$/.test(hi) && /^\d+$/.test(lo), - 'Non-numeric characters in fixed number string.'); + assert((str.length & 1) === 0); - hi = parseInt(hi, 10); - lo = parseInt(lo, 10); - - const mult = pow10(exp); - const maxLo = modSafe(mult); - const maxHi = divSafe(mult); - - assert(hi < maxHi || (hi === maxHi && lo <= maxLo), - 'Fixed number string exceeds 2^53-1.'); - - return sign * (hi * mult + lo); + return Buffer.from(str, 'hex').reverse(); }; - -/** - * Convert int to float and reduce by a power - * of ten (uses no floating point arithmetic). - * @param {Number} num - * @param {Number} exp - Number of decimal places. - * @returns {Number} Double float. - */ - -util.toFloat = function toFloat(num, exp) { - return Number(util.toFixed(num, exp)); -}; - -/** - * Parse a double float number and multiply by a - * power of ten (uses no floating point arithmetic). - * @param {Number} num - * @param {Number} exp - Number of decimal places. - * @returns {Number} Integer. - */ - -util.fromFloat = function fromFloat(num, exp) { - assert(typeof num === 'number' && isFinite(num)); - assert(Number.isSafeInteger(exp)); - return util.fromFixed(num.toFixed(exp), exp); -}; - -/* - * Helpers - */ - -function pow10(exp) { - switch (exp) { - case 0: - return 1; - case 1: - return 10; - case 2: - return 100; - case 3: - return 1000; - case 4: - return 10000; - case 5: - return 100000; - case 6: - return 1000000; - case 7: - return 10000000; - case 8: - return 100000000; - } - throw new Error('Exponent is too large.'); -} - -function modSafe(mod) { - switch (mod) { - case 1: - return 0; - case 10: - return 1; - case 100: - return 91; - case 1000: - return 991; - case 10000: - return 991; - case 100000: - return 40991; - case 1000000: - return 740991; - case 10000000: - return 4740991; - case 100000000: - return 54740991; - } - throw new Error('Exponent is too large.'); -} - -function divSafe(div) { - switch (div) { - case 1: - return 9007199254740991; - case 10: - return 900719925474099; - case 100: - return 90071992547409; - case 1000: - return 9007199254740; - case 10000: - return 900719925474; - case 100000: - return 90071992547; - case 1000000: - return 9007199254; - case 10000000: - return 900719925; - case 100000000: - return 90071992; - } - throw new Error('Exponent is too large.'); -} diff --git a/lib/utils/validator.js b/lib/utils/validator.js deleted file mode 100644 index 159851433..000000000 --- a/lib/utils/validator.js +++ /dev/null @@ -1,698 +0,0 @@ -/*! - * validator.js - validator for bcoin - * Copyright (c) 2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const util = require('../utils/util'); - -/** - * Validator - * @alias module:utils.Validator - * @constructor - * @param {Object} options - */ - -function Validator(data) { - if (!(this instanceof Validator)) - return new Validator(data); - - this.data = []; - - if (data) - this.init(data); -} - -/** - * Initialize the validator. - * @private - * @param {Object} data - */ - -Validator.prototype.init = function init(data) { - assert(data && typeof data === 'object'); - - if (!Array.isArray(data)) - data = [data]; - - this.data = data; -}; - -/** - * Test whether value is present. - * @param {String} key - * @returns {Boolean} - */ - -Validator.prototype.has = function has(key) { - assert(typeof key === 'string' || typeof key === 'number', - 'Key must be a string or number.'); - - for (const map of this.data) { - const value = map[key]; - if (value != null) - return true; - } - - return false; -}; - -/** - * Get a value (no type validation). - * @param {String} key - * @param {Object?} fallback - * @returns {Object|null} - */ - -Validator.prototype.get = function get(key, fallback) { - if (fallback === undefined) - fallback = null; - - if (Array.isArray(key)) { - const keys = key; - for (const key of keys) { - const value = this.get(key); - if (value !== null) - return value; - } - return fallback; - } - - assert(typeof key === 'string' || typeof key === 'number', - 'Key must be a string or number.'); - - for (const map of this.data) { - if (!map || typeof map !== 'object') - throw new ValidationError('data', 'object'); - - const value = map[key]; - - if (value != null) - return value; - } - - return fallback; -}; - -/** - * Get a value's type. - * @param {String} key - * @returns {String} - */ - -Validator.prototype.typeOf = function typeOf(key) { - const value = this.get(key); - - if (value == null) - return 'null'; - - return typeof value; -}; - -/** - * Get a value (as a string). - * @param {String} key - * @param {Object?} fallback - * @returns {String|null} - */ - -Validator.prototype.str = function str(key, fallback) { - const value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'string') - throw new ValidationError(key, 'number'); - - return value; -}; - -/** - * Get a value (as an integer). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Validator.prototype.int = function int(key, fallback) { - let value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'string') { - if (typeof value !== 'number') - throw new ValidationError(key, 'int'); - - if (!Number.isSafeInteger(value)) - throw new ValidationError(key, 'int'); - - return value; - } - - if (!/^\-?\d+$/.test(value)) - throw new ValidationError(key, 'int'); - - value = parseInt(value, 10); - - if (!Number.isSafeInteger(value)) - throw new ValidationError(key, 'int'); - - return value; -}; - -/** - * Get a value (as a signed integer). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Validator.prototype.uint = function uint(key, fallback) { - const value = this.int(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (value < 0) - throw new ValidationError(key, 'uint'); - - return value; -}; - -/** - * Get a value (as a float). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Validator.prototype.float = function float(key, fallback) { - let value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'string') { - if (typeof value !== 'number') - throw new ValidationError(key, 'float'); - - if (!isFinite(value)) - throw new ValidationError(key, 'float'); - - return value; - } - - if (!/^\-?\d*(?:\.\d*)?$/.test(value)) - throw new ValidationError(key, 'float'); - - if (!/\d/.test(value)) - throw new ValidationError(key, 'float'); - - value = parseFloat(value); - - if (!isFinite(value)) - throw new ValidationError(key, 'float'); - - return value; -}; - -/** - * Get a value (as a positive float). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Validator.prototype.ufloat = function ufloat(key, fallback) { - const value = this.float(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (value < 0) - throw new ValidationError(key, 'positive float'); - - return value; -}; - -/** - * Get a value (as a fixed number). - * @param {String} key - * @param {Number?} exp - * @param {Object?} fallback - * @returns {Number|null} - */ - -Validator.prototype.fixed = function fixed(key, exp, fallback) { - const value = this.float(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - try { - return util.fromFloat(value, exp || 0); - } catch (e) { - throw new ValidationError(key, 'fixed number'); - } -}; - -/** - * Get a value (as a positive fixed number). - * @param {String} key - * @param {Number?} exp - * @param {Object?} fallback - * @returns {Number|null} - */ - -Validator.prototype.ufixed = function ufixed(key, exp, fallback) { - const value = this.fixed(key, exp); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (value < 0) - throw new ValidationError(key, 'positive fixed number'); - - return value; -}; - -/** - * Get a value (as an int32). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Validator.prototype.i8 = function i8(key, fallback) { - const value = this.int(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (value < -0x80 || value > 0x7f) - throw new ValidationError(key, 'i8'); - - return value; -}; - -/** - * Get a value (as an int32). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Validator.prototype.i16 = function i16(key, fallback) { - const value = this.int(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (value < -0x8000 || value > 0x7fff) - throw new ValidationError(key, 'i16'); - - return value; -}; - -/** - * Get a value (as an int32). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Validator.prototype.i32 = function i32(key, fallback) { - const value = this.int(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if ((value | 0) !== value) - throw new ValidationError(key, 'int32'); - - return value; -}; - -/** - * Get a value (as an int64). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Validator.prototype.i64 = function i64(key, fallback) { - return this.int(key, fallback); -}; - -/** - * Get a value (as a uint32). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Validator.prototype.u8 = function u8(key, fallback) { - const value = this.uint(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if ((value & 0xff) !== value) - throw new ValidationError(key, 'uint8'); - - return value; -}; - -/** - * Get a value (as a uint16). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Validator.prototype.u16 = function u16(key, fallback) { - const value = this.uint(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if ((value & 0xffff) !== value) - throw new ValidationError(key, 'uint16'); - - return value; -}; - -/** - * Get a value (as a uint32). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Validator.prototype.u32 = function u32(key, fallback) { - const value = this.uint(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if ((value >>> 0) !== value) - throw new ValidationError(key, 'uint32'); - - return value; -}; - -/** - * Get a value (as a uint64). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|null} - */ - -Validator.prototype.u64 = function u64(key, fallback) { - return this.uint(key, fallback); -}; - -/** - * Get a value (as a reverse hash). - * @param {String} key - * @param {Object?} fallback - * @returns {Hash|null} - */ - -Validator.prototype.hash = function hash(key, fallback) { - const value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'string') { - if (!Buffer.isBuffer(value)) - throw new ValidationError(key, 'hash'); - - if (value.length !== 32) - throw new ValidationError(key, 'hash'); - - return value.toString('hex'); - } - - if (value.length !== 64) - throw new ValidationError(key, 'hex string'); - - if (!/^[0-9a-f]+$/i.test(value)) - throw new ValidationError(key, 'hex string'); - - let out = ''; - - for (let i = 0; i < value.length; i += 2) - out = value.slice(i, i + 2) + out; - - return out; -}; - -/** - * Get a value (as a number or reverse hash). - * @param {String} key - * @param {Object?} fallback - * @returns {Number|Hash|null} - */ - -Validator.prototype.numhash = function numhash(key, fallback) { - if (this.typeOf(key) === 'string') - return this.hash(key, fallback); - return this.uint(key, fallback); -}; - -/** - * Get a value (as a boolean). - * @param {String} key - * @param {Object?} fallback - * @returns {Boolean|null} - */ - -Validator.prototype.bool = function bool(key, fallback) { - const value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - // Bitcoin Core compat. - if (typeof value === 'number') { - if (value === 1) - return true; - - if (value === 0) - return false; - } - - if (typeof value !== 'string') { - if (typeof value !== 'boolean') - throw new ValidationError(key, 'boolean'); - return value; - } - - if (value === 'true' || value === '1') - return true; - - if (value === 'false' || value === '0') - return false; - - throw new ValidationError(key, 'boolean'); -}; - -/** - * Get a value (as a buffer). - * @param {String} key - * @param {Object?} fallback - * @param {String?} enc - * @returns {Buffer|null} - */ - -Validator.prototype.buf = function buf(key, fallback, enc) { - const value = this.get(key); - - if (!enc) - enc = 'hex'; - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'string') { - if (!Buffer.isBuffer(value)) - throw new ValidationError(key, 'buffer'); - return value; - } - - const data = Buffer.from(value, enc); - - if (data.length !== Buffer.byteLength(value, enc)) - throw new ValidationError(key, `${enc} string`); - - return data; -}; - -/** - * Get a value (as an array). - * @param {String} key - * @param {Object?} fallback - * @returns {Array|String[]|null} - */ - -Validator.prototype.array = function array(key, fallback) { - const value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'string') { - if (!Array.isArray(value)) - throw new ValidationError(key, 'list/array'); - return value; - } - - const parts = value.trim().split(/\s*,\s*/); - const result = []; - - for (const part of parts) { - if (part.length === 0) - continue; - - result.push(part); - } - - return result; -}; - -/** - * Get a value (as an object). - * @param {String} key - * @param {Object?} fallback - * @returns {Object|null} - */ - -Validator.prototype.obj = function obj(key, fallback) { - const value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'object') - throw new ValidationError(key, 'object'); - - return value; -}; - -/** - * Get a value (as a function). - * @param {String} key - * @param {Object?} fallback - * @returns {Function|null} - */ - -Validator.prototype.func = function func(key, fallback) { - const value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'function') - throw new ValidationError(key, 'function'); - - return value; -}; - -/* - * Helpers - */ - -function fmt(key) { - if (Array.isArray(key)) - key = key[0]; - - if (typeof key === 'number') - return `Param #${key}`; - - return key; -} - -function ValidationError(key, type) { - if (!(this instanceof ValidationError)) - return new ValidationError(key, type); - - Error.call(this); - - this.type = 'ValidationError'; - this.message = `${fmt(key)} must be a ${type}.`; - - if (Error.captureStackTrace) - Error.captureStackTrace(this, ValidationError); -} - -Object.setPrototypeOf(ValidationError.prototype, Error.prototype); - -/* - * Expose - */ - -module.exports = Validator; diff --git a/lib/utils/writer.js b/lib/utils/writer.js deleted file mode 100644 index 700cfdbd9..000000000 --- a/lib/utils/writer.js +++ /dev/null @@ -1,622 +0,0 @@ -/*! - * writer.js - buffer writer for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const encoding = require('./encoding'); -const digest = require('../crypto/digest'); - -/* - * Constants - */ - -const SEEK = 0; -const UI8 = 1; -const UI16 = 2; -const UI16BE = 3; -const UI32 = 4; -const UI32BE = 5; -const UI64 = 6; -const UI64BE = 7; -const UI64N = 8; -const UI64BEN = 9; -const I8 = 10; -const I16 = 11; -const I16BE = 12; -const I32 = 13; -const I32BE = 14; -const I64 = 15; -const I64BE = 16; -const I64N = 17; -const I64BEN = 18; -const FL = 19; -const FLBE = 20; -const DBL = 21; -const DBLBE = 22; -const VARINT = 23; -const VARINTN = 24; -const VARINT2 = 25; -const VARINT2N = 26; -const BYTES = 27; -const STR = 28; -const CHECKSUM = 29; -const FILL = 30; - -/** - * An object that allows writing of buffers in a - * sane manner. This buffer writer is extremely - * optimized since it does not actually write - * anything until `render` is called. It makes - * one allocation: at the end, once it knows the - * size of the buffer to be allocated. Because - * of this, it can also act as a size calculator - * which is useful for guaging block size - * without actually serializing any data. - * @alias module:utils.BufferWriter - * @constructor - */ - -function BufferWriter() { - if (!(this instanceof BufferWriter)) - return new BufferWriter(); - - this.ops = []; - this.offset = 0; -} - -/** - * Allocate and render the final buffer. - * @returns {Buffer} Rendered buffer. - */ - -BufferWriter.prototype.render = function render() { - const data = Buffer.allocUnsafe(this.offset); - let off = 0; - - for (const op of this.ops) { - switch (op.type) { - case SEEK: - off += op.value; - break; - case UI8: - off = data.writeUInt8(op.value, off, true); - break; - case UI16: - off = data.writeUInt16LE(op.value, off, true); - break; - case UI16BE: - off = data.writeUInt16BE(op.value, off, true); - break; - case UI32: - off = data.writeUInt32LE(op.value, off, true); - break; - case UI32BE: - off = data.writeUInt32BE(op.value, off, true); - break; - case UI64: - off = encoding.writeU64(data, op.value, off); - break; - case UI64BE: - off = encoding.writeU64BE(data, op.value, off); - break; - case UI64N: - off = encoding.writeU64N(data, op.value, off); - break; - case UI64BEN: - off = encoding.writeU64BEN(data, op.value, off); - break; - case I8: - off = data.writeInt8(op.value, off, true); - break; - case I16: - off = data.writeInt16LE(op.value, off, true); - break; - case I16BE: - off = data.writeInt16BE(op.value, off, true); - break; - case I32: - off = data.writeInt32LE(op.value, off, true); - break; - case I32BE: - off = data.writeInt32BE(op.value, off, true); - break; - case I64: - off = encoding.writeI64(data, op.value, off); - break; - case I64BE: - off = encoding.writeI64BE(data, op.value, off); - break; - case I64N: - off = encoding.writeI64N(data, op.value, off); - break; - case I64BEN: - off = encoding.writeI64BEN(data, op.value, off); - break; - case FL: - off = data.writeFloatLE(op.value, off, true); - break; - case FLBE: - off = data.writeFloatBE(op.value, off, true); - break; - case DBL: - off = data.writeDoubleLE(op.value, off, true); - break; - case DBLBE: - off = data.writeDoubleBE(op.value, off, true); - break; - case VARINT: - off = encoding.writeVarint(data, op.value, off); - break; - case VARINTN: - off = encoding.writeVarintN(data, op.value, off); - break; - case VARINT2: - off = encoding.writeVarint2(data, op.value, off); - break; - case VARINT2N: - off = encoding.writeVarint2N(data, op.value, off); - break; - case BYTES: - off += op.value.copy(data, off); - break; - case STR: - off += data.write(op.value, off, op.enc); - break; - case CHECKSUM: - off += digest.hash256(data.slice(0, off)).copy(data, off, 0, 4); - break; - case FILL: - data.fill(op.value, off, off + op.size); - off += op.size; - break; - default: - assert(false, 'Bad type.'); - break; - } - } - - assert(off === data.length); - - this.destroy(); - - return data; -}; - -/** - * Get size of data written so far. - * @returns {Number} - */ - -BufferWriter.prototype.getSize = function getSize() { - return this.offset; -}; - -/** - * Seek to relative offset. - * @param {Number} offset - */ - -BufferWriter.prototype.seek = function seek(offset) { - this.offset += offset; - this.ops.push(new WriteOp(SEEK, offset)); -}; - -/** - * Destroy the buffer writer. Remove references to `ops`. - */ - -BufferWriter.prototype.destroy = function destroy() { - this.ops.length = 0; - this.offset = 0; -}; - -/** - * Write uint8. - * @param {Number} value - */ - -BufferWriter.prototype.writeU8 = function writeU8(value) { - this.offset += 1; - this.ops.push(new WriteOp(UI8, value)); -}; - -/** - * Write uint16le. - * @param {Number} value - */ - -BufferWriter.prototype.writeU16 = function writeU16(value) { - this.offset += 2; - this.ops.push(new WriteOp(UI16, value)); -}; - -/** - * Write uint16be. - * @param {Number} value - */ - -BufferWriter.prototype.writeU16BE = function writeU16BE(value) { - this.offset += 2; - this.ops.push(new WriteOp(UI16BE, value)); -}; - -/** - * Write uint32le. - * @param {Number} value - */ - -BufferWriter.prototype.writeU32 = function writeU32(value) { - this.offset += 4; - this.ops.push(new WriteOp(UI32, value)); -}; - -/** - * Write uint32be. - * @param {Number} value - */ - -BufferWriter.prototype.writeU32BE = function writeU32BE(value) { - this.offset += 4; - this.ops.push(new WriteOp(UI32BE, value)); -}; - -/** - * Write uint64le. - * @param {Number} value - */ - -BufferWriter.prototype.writeU64 = function writeU64(value) { - this.offset += 8; - this.ops.push(new WriteOp(UI64, value)); -}; - -/** - * Write uint64be. - * @param {Number} value - */ - -BufferWriter.prototype.writeU64BE = function writeU64BE(value) { - this.offset += 8; - this.ops.push(new WriteOp(UI64BE, value)); -}; - -/** - * Write uint64le. - * @param {U64} value - */ - -BufferWriter.prototype.writeU64N = function writeU64N(value) { - this.offset += 8; - this.ops.push(new WriteOp(UI64N, value)); -}; - -/** - * Write uint64be. - * @param {U64} value - */ - -BufferWriter.prototype.writeU64BEN = function writeU64BEN(value) { - this.offset += 8; - this.ops.push(new WriteOp(UI64BEN, value)); -}; - -/** - * Write int8. - * @param {Number} value - */ - -BufferWriter.prototype.writeI8 = function writeI8(value) { - this.offset += 1; - this.ops.push(new WriteOp(I8, value)); -}; - -/** - * Write int16le. - * @param {Number} value - */ - -BufferWriter.prototype.writeI16 = function writeI16(value) { - this.offset += 2; - this.ops.push(new WriteOp(I16, value)); -}; - -/** - * Write int16be. - * @param {Number} value - */ - -BufferWriter.prototype.writeI16BE = function writeI16BE(value) { - this.offset += 2; - this.ops.push(new WriteOp(I16BE, value)); -}; - -/** - * Write int32le. - * @param {Number} value - */ - -BufferWriter.prototype.writeI32 = function writeI32(value) { - this.offset += 4; - this.ops.push(new WriteOp(I32, value)); -}; - -/** - * Write int32be. - * @param {Number} value - */ - -BufferWriter.prototype.writeI32BE = function writeI32BE(value) { - this.offset += 4; - this.ops.push(new WriteOp(I32BE, value)); -}; - -/** - * Write int64le. - * @param {Number} value - */ - -BufferWriter.prototype.writeI64 = function writeI64(value) { - this.offset += 8; - this.ops.push(new WriteOp(I64, value)); -}; - -/** - * Write int64be. - * @param {Number} value - */ - -BufferWriter.prototype.writeI64BE = function writeI64BE(value) { - this.offset += 8; - this.ops.push(new WriteOp(I64BE, value)); -}; - -/** - * Write int64le. - * @param {I64} value - */ - -BufferWriter.prototype.writeI64N = function writeI64N(value) { - this.offset += 8; - this.ops.push(new WriteOp(I64N, value)); -}; - -/** - * Write int64be. - * @param {I64} value - */ - -BufferWriter.prototype.writeI64BEN = function writeI64BEN(value) { - this.offset += 8; - this.ops.push(new WriteOp(I64BEN, value)); -}; - -/** - * Write float le. - * @param {Number} value - */ - -BufferWriter.prototype.writeFloat = function writeFloat(value) { - this.offset += 4; - this.ops.push(new WriteOp(FL, value)); -}; - -/** - * Write float be. - * @param {Number} value - */ - -BufferWriter.prototype.writeFloatBE = function writeFloatBE(value) { - this.offset += 4; - this.ops.push(new WriteOp(FLBE, value)); -}; - -/** - * Write double le. - * @param {Number} value - */ - -BufferWriter.prototype.writeDouble = function writeDouble(value) { - this.offset += 8; - this.ops.push(new WriteOp(DBL, value)); -}; - -/** - * Write double be. - * @param {Number} value - */ - -BufferWriter.prototype.writeDoubleBE = function writeDoubleBE(value) { - this.offset += 8; - this.ops.push(new WriteOp(DBLBE, value)); -}; - -/** - * Write a varint. - * @param {Number} value - */ - -BufferWriter.prototype.writeVarint = function writeVarint(value) { - this.offset += encoding.sizeVarint(value); - this.ops.push(new WriteOp(VARINT, value)); -}; - -/** - * Write a varint. - * @param {U64} value - */ - -BufferWriter.prototype.writeVarintN = function writeVarintN(value) { - this.offset += encoding.sizeVarintN(value); - this.ops.push(new WriteOp(VARINTN, value)); -}; - -/** - * Write a varint (type 2). - * @param {Number} value - */ - -BufferWriter.prototype.writeVarint2 = function writeVarint2(value) { - this.offset += encoding.sizeVarint2(value); - this.ops.push(new WriteOp(VARINT2, value)); -}; - -/** - * Write a varint (type 2). - * @param {U64} value - */ - -BufferWriter.prototype.writeVarint2N = function writeVarint2N(value) { - this.offset += encoding.sizeVarint2N(value); - this.ops.push(new WriteOp(VARINT2N, value)); -}; - -/** - * Write bytes. - * @param {Buffer} value - */ - -BufferWriter.prototype.writeBytes = function writeBytes(value) { - if (value.length === 0) - return; - - this.offset += value.length; - this.ops.push(new WriteOp(BYTES, value)); -}; - -/** - * Write bytes with a varint length before them. - * @param {Buffer} value - */ - -BufferWriter.prototype.writeVarBytes = function writeVarBytes(value) { - this.offset += encoding.sizeVarint(value.length); - this.ops.push(new WriteOp(VARINT, value.length)); - - if (value.length === 0) - return; - - this.offset += value.length; - this.ops.push(new WriteOp(BYTES, value)); -}; - -/** - * Copy bytes. - * @param {Buffer} value - * @param {Number} start - * @param {Number} end - */ - -BufferWriter.prototype.copy = function copy(value, start, end) { - assert(end >= start); - value = value.slice(start, end); - this.writeBytes(value); -}; - -/** - * Write string to buffer. - * @param {String} value - * @param {String?} enc - Any buffer-supported encoding. - */ - -BufferWriter.prototype.writeString = function writeString(value, enc) { - if (value.length === 0) - return; - - this.offset += Buffer.byteLength(value, enc); - this.ops.push(new WriteOp(STR, value, enc)); -}; - -/** - * Write a 32 byte hash. - * @param {Hash} value - */ - -BufferWriter.prototype.writeHash = function writeHash(value) { - if (typeof value !== 'string') { - assert(value.length === 32); - this.writeBytes(value); - return; - } - assert(value.length === 64); - this.writeString(value, 'hex'); -}; - -/** - * Write a string with a varint length before it. - * @param {String} - * @param {String?} enc - Any buffer-supported encoding. - */ - -BufferWriter.prototype.writeVarString = function writeVarString(value, enc) { - if (value.length === 0) { - this.ops.push(new WriteOp(VARINT, 0)); - return; - } - - const size = Buffer.byteLength(value, enc); - - this.offset += encoding.sizeVarint(size); - this.offset += size; - - this.ops.push(new WriteOp(VARINT, size)); - - this.ops.push(new WriteOp(STR, value, enc)); -}; - -/** - * Write a null-terminated string. - * @param {String|Buffer} - * @param {String?} enc - Any buffer-supported encoding. - */ - -BufferWriter.prototype.writeNullString = function writeNullString(value, enc) { - this.writeString(value, enc); - this.writeU8(0); -}; - -/** - * Calculate and write a checksum for the data written so far. - */ - -BufferWriter.prototype.writeChecksum = function writeChecksum() { - this.offset += 4; - this.ops.push(new WriteOp(CHECKSUM)); -}; - -/** - * Fill N bytes with value. - * @param {Number} value - * @param {Number} size - */ - -BufferWriter.prototype.fill = function fill(value, size) { - assert(size >= 0); - - if (size === 0) - return; - - this.offset += size; - this.ops.push(new WriteOp(FILL, value, null, size)); -}; - -/* - * Helpers - */ - -function WriteOp(type, value, enc, size) { - this.type = type; - this.value = value; - this.enc = enc; - this.size = size; -} - -/* - * Expose - */ - -module.exports = BufferWriter; diff --git a/lib/wallet/account.js b/lib/wallet/account.js index 4d7dd7ed7..b83e5a813 100644 --- a/lib/wallet/account.js +++ b/lib/wallet/account.js @@ -6,996 +6,964 @@ 'use strict'; -const util = require('../utils/util'); -const assert = require('assert'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); -const encoding = require('../utils/encoding'); +const assert = require('bsert'); +const bio = require('bufio'); +const binary = require('../utils/binary'); const Path = require('./path'); const common = require('./common'); const Script = require('../script/script'); const WalletKey = require('./walletkey'); -const HD = require('../hd/hd'); +const {HDPublicKey} = require('../hd/hd'); +const {inspectSymbol} = require('../utils'); /** + * Account * Represents a BIP44 Account belonging to a {@link Wallet}. * Note that this object does not enforce locks. Any method * that does a write is internal API only and will lead * to race conditions if used elsewhere. * @alias module:wallet.Account - * @constructor - * @param {Object} options - * @param {WalletDB} options.db - * @param {HDPublicKey} options.accountKey - * @param {Boolean?} options.witness - Whether to use witness programs. - * @param {Number} options.accountIndex - The BIP44 account index. - * @param {Number?} options.receiveDepth - The index of the _next_ receiving - * address. - * @param {Number?} options.changeDepth - The index of the _next_ change - * address. - * @param {String?} options.type - Type of wallet (pubkeyhash, multisig) - * (default=pubkeyhash). - * @param {Number?} options.m - `m` value for multisig. - * @param {Number?} options.n - `n` value for multisig. - * @param {String?} options.wid - Wallet ID - * @param {String?} options.name - Account name */ -function Account(db, options) { - if (!(this instanceof Account)) - return new Account(db, options); - - assert(db, 'Database is required.'); - - this.db = db; - this.network = db.network; - this.wallet = null; - - this.receive = null; - this.change = null; - this.nested = null; - - this.wid = 0; - this.id = null; - this.name = null; - this.initialized = false; - this.witness = this.db.options.witness === true; - this.watchOnly = false; - this.type = Account.types.PUBKEYHASH; - this.m = 1; - this.n = 1; - this.accountIndex = 0; - this.receiveDepth = 0; - this.changeDepth = 0; - this.nestedDepth = 0; - this.lookahead = 10; - this.accountKey = null; - this.keys = []; - - if (options) - this.fromOptions(options); -} - -/** - * Account types. - * @enum {Number} - * @default - */ +class Account { + /** + * Create an account. + * @constructor + * @param {Object} options + */ + + constructor(wdb, options) { + assert(wdb, 'Database is required.'); + + this.wdb = wdb; + this.network = wdb.network; + + this.wid = 0; + this.id = null; + this.accountIndex = 0; + this.name = null; + this.initialized = false; + this.witness = wdb.options.witness === true; + this.watchOnly = false; + this.type = Account.types.PUBKEYHASH; + this.m = 1; + this.n = 1; + this.receiveDepth = 0; + this.changeDepth = 0; + this.nestedDepth = 0; + this.lookahead = 10; + this.accountKey = null; + this.keys = []; + + if (options) + this.fromOptions(options); + } + + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + assert(options, 'Options are required.'); + assert((options.wid >>> 0) === options.wid); + assert(common.isName(options.id), 'Bad Wallet ID.'); + assert(HDPublicKey.isHDPublicKey(options.accountKey), + 'Account key is required.'); + assert((options.accountIndex >>> 0) === options.accountIndex, + 'Account index is required.'); + + this.wid = options.wid; + this.id = options.id; + + if (options.accountIndex != null) { + assert((options.accountIndex >>> 0) === options.accountIndex); + this.accountIndex = options.accountIndex; + } -Account.types = { - PUBKEYHASH: 0, - MULTISIG: 1 -}; + if (options.name != null) { + assert(common.isName(options.name), 'Bad account name.'); + this.name = options.name; + } -/** - * Account types by value. - * @const {RevMap} - */ + if (options.initialized != null) { + assert(typeof options.initialized === 'boolean'); + this.initialized = options.initialized; + } -Account.typesByVal = { - 0: 'pubkeyhash', - 1: 'multisig' -}; + if (options.witness != null) { + assert(typeof options.witness === 'boolean'); + this.witness = options.witness; + } -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ + if (options.watchOnly != null) { + assert(typeof options.watchOnly === 'boolean'); + this.watchOnly = options.watchOnly; + } -Account.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Options are required.'); - assert(util.isU32(options.wid)); - assert(common.isName(options.id), 'Bad Wallet ID.'); - assert(HD.isHD(options.accountKey), 'Account key is required.'); - assert(util.isU32(options.accountIndex), 'Account index is required.'); + if (options.type != null) { + if (typeof options.type === 'string') { + this.type = Account.types[options.type.toUpperCase()]; + assert(this.type != null); + } else { + assert(typeof options.type === 'number'); + this.type = options.type; + assert(Account.typesByVal[this.type]); + } + } - this.wid = options.wid; - this.id = options.id; + if (options.m != null) { + assert((options.m & 0xff) === options.m); + this.m = options.m; + } - if (options.name != null) { - assert(common.isName(options.name), 'Bad account name.'); - this.name = options.name; - } + if (options.n != null) { + assert((options.n & 0xff) === options.n); + this.n = options.n; + } - if (options.initialized != null) { - assert(typeof options.initialized === 'boolean'); - this.initialized = options.initialized; - } + if (options.receiveDepth != null) { + assert((options.receiveDepth >>> 0) === options.receiveDepth); + this.receiveDepth = options.receiveDepth; + } - if (options.witness != null) { - assert(typeof options.witness === 'boolean'); - this.witness = options.witness; - } + if (options.changeDepth != null) { + assert((options.changeDepth >>> 0) === options.changeDepth); + this.changeDepth = options.changeDepth; + } - if (options.watchOnly != null) { - assert(typeof options.watchOnly === 'boolean'); - this.watchOnly = options.watchOnly; - } + if (options.nestedDepth != null) { + assert((options.nestedDepth >>> 0) === options.nestedDepth); + this.nestedDepth = options.nestedDepth; + } - if (options.type != null) { - if (typeof options.type === 'string') { - this.type = Account.types[options.type.toUpperCase()]; - assert(this.type != null); - } else { - assert(typeof options.type === 'number'); - this.type = options.type; - assert(Account.typesByVal[this.type]); + if (options.lookahead != null) { + assert((options.lookahead >>> 0) === options.lookahead); + assert(options.lookahead >= 0); + assert(options.lookahead <= Account.MAX_LOOKAHEAD); + this.lookahead = options.lookahead; } - } - if (options.m != null) { - assert(util.isU8(options.m)); - this.m = options.m; - } + this.accountKey = options.accountKey; - if (options.n != null) { - assert(util.isU8(options.n)); - this.n = options.n; - } + if (this.n > 1) + this.type = Account.types.MULTISIG; - if (options.accountIndex != null) { - assert(util.isU32(options.accountIndex)); - this.accountIndex = options.accountIndex; - } + if (!this.name) + this.name = this.accountIndex.toString(10); - if (options.receiveDepth != null) { - assert(util.isU32(options.receiveDepth)); - this.receiveDepth = options.receiveDepth; - } + if (this.m < 1 || this.m > this.n) + throw new Error('m ranges between 1 and n'); - if (options.changeDepth != null) { - assert(util.isU32(options.changeDepth)); - this.changeDepth = options.changeDepth; - } + if (options.keys) { + assert(Array.isArray(options.keys)); + for (const key of options.keys) + this.pushKey(key); + } - if (options.nestedDepth != null) { - assert(util.isU32(options.nestedDepth)); - this.nestedDepth = options.nestedDepth; + return this; } - if (options.lookahead != null) { - assert(util.isU32(options.lookahead)); - assert(options.lookahead >= 0); - assert(options.lookahead <= Account.MAX_LOOKAHEAD); - this.lookahead = options.lookahead; + /** + * Instantiate account from options. + * @param {WalletDB} wdb + * @param {Object} options + * @returns {Account} + */ + + static fromOptions(wdb, options) { + return new this(wdb).fromOptions(options); } - this.accountKey = options.accountKey; + /** + * Attempt to intialize the account (generating + * the first addresses along with the lookahead + * addresses). Called automatically from the + * walletdb. + * @returns {Promise} + */ - if (this.n > 1) - this.type = Account.types.MULTISIG; + async init(b) { + // Waiting for more keys. + if (this.keys.length !== this.n - 1) { + assert(!this.initialized); + this.save(b); + return; + } - if (!this.name) - this.name = this.accountIndex.toString(10); + assert(this.receiveDepth === 0); + assert(this.changeDepth === 0); + assert(this.nestedDepth === 0); - if (this.m < 1 || this.m > this.n) - throw new Error('m ranges between 1 and n'); + this.initialized = true; - if (options.keys) { - assert(Array.isArray(options.keys)); - for (const key of options.keys) - this.pushKey(key); + await this.initDepth(b); } - return this; -}; + /** + * Add a public account key to the account (multisig). + * Does not update the database. + * @param {HDPublicKey} key - Account (bip44) + * key (can be in base58 form). + * @throws Error on non-hdkey/non-accountkey. + */ -/** - * Instantiate account from options. - * @param {WalletDB} db - * @param {Object} options - * @returns {Account} - */ + pushKey(key) { + if (typeof key === 'string') + key = HDPublicKey.fromBase58(key, this.network); -Account.fromOptions = function fromOptions(db, options) { - return new Account(db).fromOptions(options); -}; + if (!HDPublicKey.isHDPublicKey(key)) + throw new Error('Must add HD keys to wallet.'); -/* - * Default address lookahead. - * @const {Number} - */ + if (!key.isAccount()) + throw new Error('Must add HD account keys to BIP44 wallet.'); -Account.MAX_LOOKAHEAD = 40; + if (this.type !== Account.types.MULTISIG) + throw new Error('Cannot add keys to non-multisig wallet.'); -/** - * Attempt to intialize the account (generating - * the first addresses along with the lookahead - * addresses). Called automatically from the - * walletdb. - * @returns {Promise} - */ + if (key.equals(this.accountKey)) + throw new Error('Cannot add own key.'); -Account.prototype.init = async function init() { - // Waiting for more keys. - if (this.keys.length !== this.n - 1) { - assert(!this.initialized); - this.save(); - return; - } + const index = binary.insert(this.keys, key, cmp, true); - assert(this.receiveDepth === 0); - assert(this.changeDepth === 0); - assert(this.nestedDepth === 0); + if (index === -1) + return false; - this.initialized = true; + if (this.keys.length > this.n - 1) { + binary.remove(this.keys, key, cmp); + throw new Error('Cannot add more keys.'); + } - await this.initDepth(); -}; + return true; + } -/** - * Open the account (done after retrieval). - * @returns {Promise} - */ + /** + * Remove a public account key to the account (multisig). + * Does not update the database. + * @param {HDPublicKey} key - Account (bip44) + * key (can be in base58 form). + * @throws Error on non-hdkey/non-accountkey. + */ -Account.prototype.open = function open() { - if (!this.initialized) - return Promise.resolve(); + spliceKey(key) { + if (typeof key === 'string') + key = HDPublicKey.fromBase58(key, this.network); - if (this.receive) - return Promise.resolve(); + if (!HDPublicKey.isHDPublicKey(key)) + throw new Error('Must add HD keys to wallet.'); - this.receive = this.deriveReceive(this.receiveDepth - 1); - this.change = this.deriveChange(this.changeDepth - 1); + if (!key.isAccount()) + throw new Error('Must add HD account keys to BIP44 wallet.'); - if (this.witness) - this.nested = this.deriveNested(this.nestedDepth - 1); + if (this.type !== Account.types.MULTISIG) + throw new Error('Cannot remove keys from non-multisig wallet.'); - return Promise.resolve(); -}; + if (this.keys.length === this.n - 1) + throw new Error('Cannot remove key.'); -/** - * Add a public account key to the account (multisig). - * Does not update the database. - * @param {HDPublicKey} key - Account (bip44) - * key (can be in base58 form). - * @throws Error on non-hdkey/non-accountkey. - */ + return binary.remove(this.keys, key, cmp); + } -Account.prototype.pushKey = function pushKey(key) { - if (typeof key === 'string') - key = HD.PublicKey.fromBase58(key, this.network); + /** + * Add a public account key to the account (multisig). + * Saves the key in the wallet database. + * @param {HDPublicKey} key + * @returns {Promise} + */ - assert(key.network === this.network, - 'Network mismatch for account key.'); + async addSharedKey(b, key) { + const result = this.pushKey(key); - if (!HD.isPublic(key)) - throw new Error('Must add HD keys to wallet.'); + if (await this.hasDuplicate()) { + this.spliceKey(key); + throw new Error('Cannot add a key from another account.'); + } - if (!key.isAccount()) - throw new Error('Must add HD account keys to BIP44 wallet.'); + // Try to initialize again. + await this.init(b); - if (this.type !== Account.types.MULTISIG) - throw new Error('Cannot add keys to non-multisig wallet.'); + return result; + } - if (key.equals(this.accountKey)) - throw new Error('Cannot add own key.'); + /** + * Ensure accounts are not sharing keys. + * @private + * @returns {Promise} + */ - const index = util.binaryInsert(this.keys, key, cmp, true); + async hasDuplicate() { + if (this.keys.length !== this.n - 1) + return false; - if (index === -1) - return false; + const ring = this.deriveReceive(0); + const hash = ring.getScriptHash(); - if (this.keys.length > this.n - 1) { - util.binaryRemove(this.keys, key, cmp); - throw new Error('Cannot add more keys.'); + return this.wdb.hasPath(this.wid, hash); } - return true; -}; - -/** - * Remove a public account key to the account (multisig). - * Does not update the database. - * @param {HDPublicKey} key - Account (bip44) - * key (can be in base58 form). - * @throws Error on non-hdkey/non-accountkey. - */ - -Account.prototype.spliceKey = function spliceKey(key) { - if (typeof key === 'string') - key = HD.PublicKey.fromBase58(key, this.network); + /** + * Remove a public account key from the account (multisig). + * Remove the key from the wallet database. + * @param {HDPublicKey} key + * @returns {Promise} + */ - assert(key.network === this.network, - 'Network mismatch for account key.'); + removeSharedKey(b, key) { + const result = this.spliceKey(key); - if (!HD.isPublic(key)) - throw new Error('Must add HD keys to wallet.'); + if (!result) + return false; - if (!key.isAccount()) - throw new Error('Must add HD account keys to BIP44 wallet.'); + this.save(b); - if (this.type !== Account.types.MULTISIG) - throw new Error('Cannot remove keys from non-multisig wallet.'); - - if (this.keys.length === this.n - 1) - throw new Error('Cannot remove key.'); + return true; + } - return util.binaryRemove(this.keys, key, cmp); -}; + /** + * Create a new receiving address (increments receiveDepth). + * @returns {Promise} - Returns {@link WalletKey} + */ -/** - * Add a public account key to the account (multisig). - * Saves the key in the wallet database. - * @param {HDPublicKey} key - * @returns {Promise} - */ + createReceive(b) { + return this.createKey(b, 0); + } -Account.prototype.addSharedKey = async function addSharedKey(key) { - const result = this.pushKey(key); + /** + * Create a new change address (increments receiveDepth). + * @returns {Promise} - Returns {@link WalletKey} + */ - if (await this.hasDuplicate()) { - this.spliceKey(key); - throw new Error('Cannot add a key from another account.'); + createChange(b) { + return this.createKey(b, 1); } - // Try to initialize again. - await this.init(); - - return result; -}; + /** + * Create a new change address (increments receiveDepth). + * @returns {Promise} - Returns {@link WalletKey} + */ -/** - * Ensure accounts are not sharing keys. - * @private - * @returns {Promise} - */ + createNested(b) { + return this.createKey(b, 2); + } -Account.prototype.hasDuplicate = function hasDuplicate() { - if (this.keys.length !== this.n - 1) - return false; + /** + * Create a new address (increments depth). + * @param {Boolean} change + * @returns {Promise} - Returns {@link WalletKey}. + */ - const ring = this.deriveReceive(0); - const hash = ring.getScriptHash('hex'); + async createKey(b, branch) { + let key, lookahead; - return this.wallet.hasAddress(hash); -}; + switch (branch) { + case 0: + key = this.deriveReceive(this.receiveDepth); + lookahead = this.deriveReceive(this.receiveDepth + this.lookahead); + await this.saveKey(b, lookahead); + this.receiveDepth += 1; + this.receive = key; + break; + case 1: + key = this.deriveChange(this.changeDepth); + lookahead = this.deriveChange(this.changeDepth + this.lookahead); + await this.saveKey(b, lookahead); + this.changeDepth += 1; + this.change = key; + break; + case 2: + key = this.deriveNested(this.nestedDepth); + lookahead = this.deriveNested(this.nestedDepth + this.lookahead); + await this.saveKey(b, lookahead); + this.nestedDepth += 1; + this.nested = key; + break; + default: + throw new Error(`Bad branch: ${branch}.`); + } -/** - * Remove a public account key from the account (multisig). - * Remove the key from the wallet database. - * @param {HDPublicKey} key - * @returns {Promise} - */ + this.save(b); -Account.prototype.removeSharedKey = function removeSharedKey(key) { - const result = this.spliceKey(key); + return key; + } - if (!result) - return false; + /** + * Derive a receiving address at `index`. Do not increment depth. + * @param {Number} index + * @returns {WalletKey} + */ - this.save(); + deriveReceive(index, master) { + return this.deriveKey(0, index, master); + } - return true; -}; + /** + * Derive a change address at `index`. Do not increment depth. + * @param {Number} index + * @returns {WalletKey} + */ -/** - * Create a new receiving address (increments receiveDepth). - * @returns {WalletKey} - */ + deriveChange(index, master) { + return this.deriveKey(1, index, master); + } -Account.prototype.createReceive = function createReceive() { - return this.createKey(0); -}; + /** + * Derive a nested address at `index`. Do not increment depth. + * @param {Number} index + * @returns {WalletKey} + */ -/** - * Create a new change address (increments receiveDepth). - * @returns {WalletKey} - */ + deriveNested(index, master) { + if (!this.witness) + throw new Error('Cannot derive nested on non-witness account.'); -Account.prototype.createChange = function createChange() { - return this.createKey(1); -}; + return this.deriveKey(2, index, master); + } -/** - * Create a new change address (increments receiveDepth). - * @returns {WalletKey} - */ + /** + * Derive an address from `path` object. + * @param {Path} path + * @param {MasterKey} master + * @returns {WalletKey} + */ -Account.prototype.createNested = function createNested() { - return this.createKey(2); -}; + derivePath(path, master) { + switch (path.keyType) { + case Path.types.HD: { + return this.deriveKey(path.branch, path.index, master); + } + case Path.types.KEY: { + assert(this.type === Account.types.PUBKEYHASH); -/** - * Create a new address (increments depth). - * @param {Boolean} change - * @returns {Promise} - Returns {@link WalletKey}. - */ + let data = path.data; -Account.prototype.createKey = async function createKey(branch) { - let key, lookahead; - - switch (branch) { - case 0: - key = this.deriveReceive(this.receiveDepth); - lookahead = this.deriveReceive(this.receiveDepth + this.lookahead); - await this.saveKey(lookahead); - this.receiveDepth++; - this.receive = key; - break; - case 1: - key = this.deriveChange(this.changeDepth); - lookahead = this.deriveReceive(this.changeDepth + this.lookahead); - await this.saveKey(lookahead); - this.changeDepth++; - this.change = key; - break; - case 2: - key = this.deriveNested(this.nestedDepth); - lookahead = this.deriveNested(this.nestedDepth + this.lookahead); - await this.saveKey(lookahead); - this.nestedDepth++; - this.nested = key; - break; - default: - throw new Error(`Bad branch: ${branch}.`); - } - - this.save(); + if (path.encrypted) { + data = master.decipher(data, path.hash); + if (!data) + return null; + } - return key; -}; + return WalletKey.fromImport(this, data); + } + case Path.types.ADDRESS: { + return null; + } + default: { + throw new Error('Bad key type.'); + } + } + } -/** - * Derive a receiving address at `index`. Do not increment depth. - * @param {Number} index - * @returns {WalletKey} - */ + /** + * Derive an address at `index`. Do not increment depth. + * @param {Number} branch + * @param {Number} index + * @returns {WalletKey} + */ -Account.prototype.deriveReceive = function deriveReceive(index, master) { - return this.deriveKey(0, index, master); -}; + deriveKey(branch, index, master) { + assert(typeof branch === 'number'); -/** - * Derive a change address at `index`. Do not increment depth. - * @param {Number} index - * @returns {WalletKey} - */ + const keys = []; -Account.prototype.deriveChange = function deriveChange(index, master) { - return this.deriveKey(1, index, master); -}; + let key; + if (master && master.key && !this.watchOnly) { + const type = this.network.keyPrefix.coinType; + key = master.key.deriveAccount(44, type, this.accountIndex); + key = key.derive(branch).derive(index); + } else { + key = this.accountKey.derive(branch).derive(index); + } -/** - * Derive a nested address at `index`. Do not increment depth. - * @param {Number} index - * @returns {WalletKey} - */ + const ring = WalletKey.fromHD(this, key, branch, index); -Account.prototype.deriveNested = function deriveNested(index, master) { - if (!this.witness) - throw new Error('Cannot derive nested on non-witness account.'); + switch (this.type) { + case Account.types.PUBKEYHASH: + break; + case Account.types.MULTISIG: + keys.push(key.publicKey); - return this.deriveKey(2, index, master); -}; + for (const shared of this.keys) { + const key = shared.derive(branch).derive(index); + keys.push(key.publicKey); + } -/** - * Derive an address from `path` object. - * @param {Path} path - * @param {MasterKey} master - * @returns {WalletKey} - */ + ring.script = Script.fromMultisig(this.m, this.n, keys); -Account.prototype.derivePath = function derivePath(path, master) { - switch (path.keyType) { - case Path.types.HD: { - return this.deriveKey(path.branch, path.index, master); + break; } - case Path.types.KEY: { - assert(this.type === Account.types.PUBKEYHASH); - - let data = path.data; - - if (path.encrypted) { - data = master.decipher(data, path.hash); - if (!data) - return null; - } - return WalletKey.fromImport(this, data); - } - case Path.types.ADDRESS: { - return null; - } - default: { - throw new Error('Bad key type.'); - } + return ring; } -}; - -/** - * Derive an address at `index`. Do not increment depth. - * @param {Number} branch - Whether the address on the change branch. - * @param {Number} index - * @returns {WalletKey} - */ - -Account.prototype.deriveKey = function deriveKey(branch, index, master) { - assert(typeof branch === 'number'); - const keys = []; + /** + * Save the account to the database. Necessary + * when address depth and keys change. + * @returns {Promise} + */ - let key; - if (master && master.key && !this.watchOnly) { - key = master.key.deriveAccount(44, this.accountIndex); - key = key.derive(branch).derive(index); - } else { - key = this.accountKey.derive(branch).derive(index); + save(b) { + return this.wdb.saveAccount(b, this); } - const ring = WalletKey.fromHD(this, key, branch, index); - - switch (this.type) { - case Account.types.PUBKEYHASH: - break; - case Account.types.MULTISIG: - keys.push(key.publicKey); - - for (const shared of this.keys) { - const key = shared.derive(branch).derive(index); - keys.push(key.publicKey); - } + /** + * Save addresses to path map. + * @param {WalletKey[]} rings + * @returns {Promise} + */ - ring.script = Script.fromMultisig(this.m, this.n, keys); - - break; + saveKey(b, ring) { + return this.wdb.saveKey(b, this.wid, ring); } - return ring; -}; + /** + * Save paths to path map. + * @param {Path[]} rings + * @returns {Promise} + */ -/** - * Save the account to the database. Necessary - * when address depth and keys change. - * @returns {Promise} - */ + savePath(b, path) { + return this.wdb.savePath(b, this.wid, path); + } -Account.prototype.save = function save() { - return this.db.saveAccount(this); -}; + /** + * Initialize address depths (including lookahead). + * @returns {Promise} + */ -/** - * Save addresses to path map. - * @param {WalletKey[]} rings - * @returns {Promise} - */ + async initDepth(b) { + // Receive Address + this.receiveDepth = 1; -Account.prototype.saveKey = function saveKey(ring) { - return this.db.saveKey(this.wallet, ring); -}; - -/** - * Save paths to path map. - * @param {Path[]} rings - * @returns {Promise} - */ + for (let i = 0; i <= this.lookahead; i++) { + const key = this.deriveReceive(i); + await this.saveKey(b, key); + } -Account.prototype.savePath = function savePath(path) { - return this.db.savePath(this.wallet, path); -}; + // Change Address + this.changeDepth = 1; -/** - * Initialize address depths (including lookahead). - * @returns {Promise} - */ + for (let i = 0; i <= this.lookahead; i++) { + const key = this.deriveChange(i); + await this.saveKey(b, key); + } -Account.prototype.initDepth = async function initDepth() { - // Receive Address - this.receive = this.deriveReceive(0); - this.receiveDepth = 1; + // Nested Address + if (this.witness) { + this.nestedDepth = 1; - await this.saveKey(this.receive); + for (let i = 0; i <= this.lookahead; i++) { + const key = this.deriveNested(i); + await this.saveKey(b, key); + } + } - // Lookahead - for (let i = 0; i < this.lookahead; i++) { - const key = this.deriveReceive(i + 1); - await this.saveKey(key); + this.save(b); } - // Change Address - this.change = this.deriveChange(0); - this.changeDepth = 1; + /** + * Allocate new lookahead addresses if necessary. + * @param {Number} receiveDepth + * @param {Number} changeDepth + * @param {Number} nestedDepth + * @returns {Promise} - Returns {@link WalletKey}. + */ - await this.saveKey(this.change); + async syncDepth(b, receive, change, nested) { + let derived = false; + let result = null; - // Lookahead - for (let i = 0; i < this.lookahead; i++) { - const key = this.deriveChange(i + 1); - await this.saveKey(key); - } + if (receive > this.receiveDepth) { + const depth = this.receiveDepth + this.lookahead; - // Nested Address - if (this.witness) { - this.nested = this.deriveNested(0); - this.nestedDepth = 1; + assert(receive <= depth + 1); - await this.saveKey(this.nested); + for (let i = depth; i < receive + this.lookahead; i++) { + const key = this.deriveReceive(i); + await this.saveKey(b, key); + result = key; + } - // Lookahead - for (let i = 0; i < this.lookahead; i++) { - const key = this.deriveNested(i + 1); - await this.saveKey(key); - } - } + this.receiveDepth = receive; - this.save(); -}; + derived = true; + } -/** - * Allocate new lookahead addresses if necessary. - * @param {Number} receiveDepth - * @param {Number} changeDepth - * @param {Number} nestedDepth - * @returns {Promise} - Returns {@link WalletKey}. - */ + if (change > this.changeDepth) { + const depth = this.changeDepth + this.lookahead; -Account.prototype.syncDepth = async function syncDepth(receive, change, nested) { - let derived = false; - let result = null; + assert(change <= depth + 1); - if (receive > this.receiveDepth) { - const depth = this.receiveDepth + this.lookahead; + for (let i = depth; i < change + this.lookahead; i++) { + const key = this.deriveChange(i); + await this.saveKey(b, key); + } - assert(receive <= depth + 1); + this.changeDepth = change; - for (let i = depth; i < receive + this.lookahead; i++) { - const key = this.deriveReceive(i); - await this.saveKey(key); + derived = true; } - this.receive = this.deriveReceive(receive - 1); - this.receiveDepth = receive; + if (this.witness && nested > this.nestedDepth) { + const depth = this.nestedDepth + this.lookahead; - derived = true; - result = this.receive; - } + assert(nested <= depth + 1); - if (change > this.changeDepth) { - const depth = this.changeDepth + this.lookahead; + for (let i = depth; i < nested + this.lookahead; i++) { + const key = this.deriveNested(i); + await this.saveKey(b, key); + result = key; + } - assert(change <= depth + 1); + this.nestedDepth = nested; - for (let i = depth; i < change + this.lookahead; i++) { - const key = this.deriveChange(i); - await this.saveKey(key); + derived = true; + result = this.nested; } - this.change = this.deriveChange(change - 1); - this.changeDepth = change; + if (derived) + this.save(b); - derived = true; + return result; } - if (this.witness && nested > this.nestedDepth) { - const depth = this.nestedDepth + this.lookahead; + /** + * Allocate new lookahead addresses. + * @param {Number} lookahead + * @returns {Promise} + */ - assert(nested <= depth + 1); + async setLookahead(b, lookahead) { + if (lookahead === this.lookahead) + return; - for (let i = depth; i < nested + this.lookahead; i++) { - const key = this.deriveNested(i); - await this.saveKey(key); - } + if (lookahead < this.lookahead) { + const diff = this.lookahead - lookahead; - this.nested = this.deriveNested(nested - 1); - this.nestedDepth = nested; + this.receiveDepth += diff; + this.changeDepth += diff; - derived = true; - result = this.nested; - } + if (this.witness) + this.nestedDepth += diff; - if (derived) - this.save(); + this.lookahead = lookahead; - return result; -}; + this.save(b); -/** - * Allocate new lookahead addresses. - * @param {Number} lookahead - * @returns {Promise} - */ + return; + } -Account.prototype.setLookahead = async function setLookahead(lookahead) { - if (lookahead === this.lookahead) { - this.db.logger.warning( - 'Lookahead is not changing for: %s/%s.', - this.id, this.name); - return; - } + { + const depth = this.receiveDepth + this.lookahead; + const target = this.receiveDepth + lookahead; - if (lookahead < this.lookahead) { - const diff = this.lookahead - lookahead; + for (let i = depth; i < target; i++) { + const key = this.deriveReceive(i); + await this.saveKey(b, key); + } + } - this.receiveDepth += diff; - this.receive = this.deriveReceive(this.receiveDepth - 1); + { + const depth = this.changeDepth + this.lookahead; + const target = this.changeDepth + lookahead; - this.changeDepth += diff; - this.change = this.deriveChange(this.changeDepth - 1); + for (let i = depth; i < target; i++) { + const key = this.deriveChange(i); + await this.saveKey(b, key); + } + } if (this.witness) { - this.nestedDepth += diff; - this.nested = this.deriveNested(this.nestedDepth - 1); + const depth = this.nestedDepth + this.lookahead; + const target = this.nestedDepth + lookahead; + + for (let i = depth; i < target; i++) { + const key = this.deriveNested(i); + await this.saveKey(b, key); + } } this.lookahead = lookahead; - - this.save(); - - return; + this.save(b); } - { - const depth = this.receiveDepth + this.lookahead; - const target = this.receiveDepth + lookahead; + /** + * Get current receive key. + * @returns {WalletKey} + */ - for (let i = depth; i < target; i++) { - const key = this.deriveReceive(i); - await this.saveKey(key); - } + receiveKey() { + if (!this.initialized) + return null; + + return this.deriveReceive(this.receiveDepth - 1); } - { - const depth = this.changeDepth + this.lookahead; - const target = this.changeDepth + lookahead; + /** + * Get current change key. + * @returns {WalletKey} + */ - for (let i = depth; i < target; i++) { - const key = this.deriveChange(i); - await this.saveKey(key); - } + changeKey() { + if (!this.initialized) + return null; + + return this.deriveChange(this.changeDepth - 1); } - if (this.witness) { - const depth = this.nestedDepth + this.lookahead; - const target = this.nestedDepth + lookahead; + /** + * Get current nested key. + * @returns {WalletKey} + */ - for (let i = depth; i < target; i++) { - const key = this.deriveNested(i); - await this.saveKey(key); - } - } + nestedKey() { + if (!this.initialized) + return null; - this.lookahead = lookahead; - this.save(); -}; + if (!this.witness) + return null; -/** - * Get current receive address. - * @param {String?} enc - `"base58"` or `null`. - * @returns {Address|Base58Address} - */ + return this.deriveNested(this.nestedDepth - 1); + } -Account.prototype.getAddress = function getAddress(enc) { - return this.getReceive(enc); -}; + /** + * Get current receive address. + * @returns {Address} + */ -/** - * Get current receive address. - * @param {String?} enc - `"base58"` or `null`. - * @returns {Address|Base58Address} - */ + receiveAddress() { + const key = this.receiveKey(); -Account.prototype.getReceive = function getReceive(enc) { - if (!this.receive) - return null; - return this.receive.getAddress(enc); -}; + if (!key) + return null; -/** - * Get current change address. - * @param {String?} enc - `"base58"` or `null`. - * @returns {Address|Base58Address} - */ + return key.getAddress(); + } -Account.prototype.getChange = function getChange(enc) { - if (!this.change) - return null; + /** + * Get current change address. + * @returns {Address} + */ - return this.change.getAddress(enc); -}; + changeAddress() { + const key = this.changeKey(); -/** - * Get current nested address. - * @param {String?} enc - `"base58"` or `null`. - * @returns {Address|Base58Address} - */ + if (!key) + return null; -Account.prototype.getNested = function getNested(enc) { - if (!this.nested) - return null; + return key.getAddress(); + } - return this.nested.getAddress(enc); -}; + /** + * Get current nested address. + * @returns {Address} + */ -/** - * Convert the account to a more inspection-friendly object. - * @returns {Object} - */ + nestedAddress() { + const key = this.nestedKey(); -Account.prototype.inspect = function inspect() { - return { - wid: this.wid, - name: this.name, - network: this.network, - initialized: this.initialized, - witness: this.witness, - watchOnly: this.watchOnly, - type: Account.typesByVal[this.type].toLowerCase(), - m: this.m, - n: this.n, - accountIndex: this.accountIndex, - receiveDepth: this.receiveDepth, - changeDepth: this.changeDepth, - nestedDepth: this.nestedDepth, - lookahead: this.lookahead, - address: this.initialized - ? this.receive.getAddress() - : null, - nestedAddress: this.initialized && this.nested - ? this.nested.getAddress() - : null, - accountKey: this.accountKey.toBase58(), - keys: this.keys.map((key) => { - return key.toBase58(); - }) - }; -}; + if (!key) + return null; -/** - * Convert the account to an object suitable for - * serialization. - * @returns {Object} - */ + return key.getAddress(); + } + + /** + * Convert the account to a more inspection-friendly object. + * @returns {Object} + */ + + [inspectSymbol]() { + const receive = this.receiveAddress(); + const change = this.changeAddress(); + const nested = this.nestedAddress(); + + return { + id: this.id, + wid: this.wid, + name: this.name, + network: this.network.type, + initialized: this.initialized, + witness: this.witness, + watchOnly: this.watchOnly, + type: Account.typesByVal[this.type].toLowerCase(), + m: this.m, + n: this.n, + accountIndex: this.accountIndex, + receiveDepth: this.receiveDepth, + changeDepth: this.changeDepth, + nestedDepth: this.nestedDepth, + lookahead: this.lookahead, + receiveAddress: receive ? receive.toString(this.network) : null, + changeAddress: change ? change.toString(this.network) : null, + nestedAddress: nested ? nested.toString(this.network) : null, + accountKey: this.accountKey.toBase58(this.network), + keys: this.keys.map(key => key.toBase58(this.network)) + }; + } + + /** + * Convert the account to an object suitable for + * serialization. + * @returns {Object} + */ + + toJSON(balance) { + const receive = this.receiveAddress(); + const change = this.changeAddress(); + const nested = this.nestedAddress(); + + return { + name: this.name, + initialized: this.initialized, + witness: this.witness, + watchOnly: this.watchOnly, + type: Account.typesByVal[this.type].toLowerCase(), + m: this.m, + n: this.n, + accountIndex: this.accountIndex, + receiveDepth: this.receiveDepth, + changeDepth: this.changeDepth, + nestedDepth: this.nestedDepth, + lookahead: this.lookahead, + receiveAddress: receive ? receive.toString(this.network) : null, + changeAddress: change ? change.toString(this.network) : null, + nestedAddress: nested ? nested.toString(this.network) : null, + accountKey: this.accountKey.toBase58(this.network), + keys: this.keys.map(key => key.toBase58(this.network)), + balance: balance ? balance.toJSON(true) : null + }; + } + + /** + * Calculate serialization size. + * @returns {Number} + */ + + getSize() { + let size = 0; + size += 92; + size += this.keys.length * 74; + return size; + } + + /** + * Serialize the account. + * @returns {Buffer} + */ + + toRaw() { + const size = this.getSize(); + const bw = bio.write(size); + + let flags = 0; + + if (this.initialized) + flags |= 1; + + if (this.witness) + flags |= 2; + + bw.writeU8(flags); + bw.writeU8(this.type); + bw.writeU8(this.m); + bw.writeU8(this.n); + bw.writeU32(this.receiveDepth); + bw.writeU32(this.changeDepth); + bw.writeU32(this.nestedDepth); + bw.writeU8(this.lookahead); + writeKey(this.accountKey, bw); + bw.writeU8(this.keys.length); + + for (const key of this.keys) + writeKey(key, bw); + + return bw.render(); + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + * @returns {Object} + */ + + fromRaw(data) { + const br = bio.read(data); + const flags = br.readU8(); + + this.initialized = (flags & 1) !== 0; + this.witness = (flags & 2) !== 0; + this.type = br.readU8(); + this.m = br.readU8(); + this.n = br.readU8(); + this.receiveDepth = br.readU32(); + this.changeDepth = br.readU32(); + this.nestedDepth = br.readU32(); + this.lookahead = br.readU8(); + this.accountKey = readKey(br); + + assert(this.type < Account.typesByVal.length); + + const count = br.readU8(); + + for (let i = 0; i < count; i++) { + const key = readKey(br); + binary.insert(this.keys, key, cmp, true); + } -Account.prototype.toJSON = function toJSON(minimal) { - return { - wid: minimal ? undefined : this.wid, - id: minimal ? undefined : this.id, - name: this.name, - initialized: this.initialized, - witness: this.witness, - watchOnly: this.watchOnly, - type: Account.typesByVal[this.type].toLowerCase(), - m: this.m, - n: this.n, - accountIndex: this.accountIndex, - receiveDepth: this.receiveDepth, - changeDepth: this.changeDepth, - nestedDepth: this.nestedDepth, - lookahead: this.lookahead, - receiveAddress: this.receive - ? this.receive.getAddress('string') - : null, - nestedAddress: this.nested - ? this.nested.getAddress('string') - : null, - changeAddress: this.change - ? this.change.getAddress('string') - : null, - accountKey: this.accountKey.toBase58(), - keys: this.keys.map((key) => { - return key.toBase58(); - }) - }; -}; + return this; + } -/** - * Calculate serialization size. - * @returns {Number} - */ + /** + * Instantiate a account from serialized data. + * @param {WalletDB} data + * @param {Buffer} data + * @returns {Account} + */ -Account.prototype.getSize = function getSize() { - let size = 0; - size += encoding.sizeVarString(this.name, 'ascii'); - size += 105; - size += this.keys.length * 82; - return size; -}; + static fromRaw(wdb, data) { + return new this(wdb).fromRaw(data); + } -/** - * Serialize the account. - * @returns {Buffer} - */ + /** + * Test an object to see if it is a Account. + * @param {Object} obj + * @returns {Boolean} + */ -Account.prototype.toRaw = function toRaw() { - const size = this.getSize(); - const bw = new StaticWriter(size); - - bw.writeVarString(this.name, 'ascii'); - bw.writeU8(this.initialized ? 1 : 0); - bw.writeU8(this.witness ? 1 : 0); - bw.writeU8(this.type); - bw.writeU8(this.m); - bw.writeU8(this.n); - bw.writeU32(this.accountIndex); - bw.writeU32(this.receiveDepth); - bw.writeU32(this.changeDepth); - bw.writeU32(this.nestedDepth); - bw.writeU8(this.lookahead); - bw.writeBytes(this.accountKey.toRaw()); - bw.writeU8(this.keys.length); - - for (const key of this.keys) - bw.writeBytes(key.toRaw()); - - return bw.render(); -}; + static isAccount(obj) { + return obj instanceof Account; + } +} /** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @returns {Object} + * Account types. + * @enum {Number} + * @default */ -Account.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); - - this.name = br.readVarString('ascii'); - this.initialized = br.readU8() === 1; - this.witness = br.readU8() === 1; - this.type = br.readU8(); - this.m = br.readU8(); - this.n = br.readU8(); - this.accountIndex = br.readU32(); - this.receiveDepth = br.readU32(); - this.changeDepth = br.readU32(); - this.nestedDepth = br.readU32(); - this.lookahead = br.readU8(); - this.accountKey = HD.PublicKey.fromRaw(br.readBytes(82)); - - assert(Account.typesByVal[this.type]); - - const count = br.readU8(); - - for (let i = 0; i < count; i++) { - const key = HD.PublicKey.fromRaw(br.readBytes(82)); - this.pushKey(key); - } - - return this; +Account.types = { + PUBKEYHASH: 0, + MULTISIG: 1 }; /** - * Instantiate a account from serialized data. - * @param {WalletDB} data - * @param {Buffer} data - * @returns {Account} + * Account types by value. + * @const {Object} */ -Account.fromRaw = function fromRaw(db, data) { - return new Account(db).fromRaw(data); -}; +Account.typesByVal = [ + 'PUBKEYHASH', + 'MULTISIG' +]; /** - * Test an object to see if it is a Account. - * @param {Object} obj - * @returns {Boolean} + * Default address lookahead. + * @const {Number} */ -Account.isAccount = function isAccount(obj) { - return obj instanceof Account; -}; +Account.MAX_LOOKAHEAD = 40; /* * Helpers @@ -1005,6 +973,24 @@ function cmp(a, b) { return a.compare(b); } +function writeKey(key, bw) { + bw.writeU8(key.depth); + bw.writeU32BE(key.parentFingerPrint); + bw.writeU32BE(key.childIndex); + bw.writeBytes(key.chainCode); + bw.writeBytes(key.publicKey); +} + +function readKey(br) { + const key = new HDPublicKey(); + key.depth = br.readU8(); + key.parentFingerPrint = br.readU32BE(); + key.childIndex = br.readU32BE(); + key.chainCode = br.readBytes(32); + key.publicKey = br.readBytes(33); + return key; +} + /* * Expose */ diff --git a/lib/wallet/client-browser.js b/lib/wallet/client-browser.js deleted file mode 100644 index 212272704..000000000 --- a/lib/wallet/client-browser.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -exports.unsupported = true; diff --git a/lib/wallet/client.js b/lib/wallet/client.js index e5281f923..7594a3c6d 100644 --- a/lib/wallet/client.js +++ b/lib/wallet/client.js @@ -7,370 +7,103 @@ 'use strict'; -const IOClient = require('socket.io-client'); -const Network = require('../protocol/network'); -const AsyncObject = require('../utils/asyncobject'); -const TX = require('../primitives/tx'); -const {BlockMeta} = require('./records'); -const Headers = require('../primitives/headers'); +const assert = require('bsert'); +const {NodeClient} = require('bclient'); const util = require('../utils/util'); -const BufferReader = require('../utils/reader'); - -/** - * Bcoin HTTP client. - * @alias module:wallet.WalletClient - * @constructor - * @param {Object|String} options - */ - -function WalletClient(options) { - if (!(this instanceof WalletClient)) - return new WalletClient(options); - - if (!options) - options = {}; - - if (typeof options === 'string') - options = { uri: options }; - - AsyncObject.call(this); - - this.options = options; - this.network = Network.get(options.network); - - this.uri = options.uri || `http://localhost:${this.network.rpcPort}`; - this.apiKey = options.apiKey; - - this.socket = null; -} - -Object.setPrototypeOf(WalletClient.prototype, AsyncObject.prototype); - -/** - * Open the client, wait for socket to connect. - * @alias WalletClient#open - * @returns {Promise} - */ - -WalletClient.prototype._open = async function _open() { - this.socket = new IOClient(this.uri, { - transports: ['websocket'], - forceNew: true - }); - - this.socket.on('error', (err) => { - this.emit('error', err); - }); - - this.socket.on('version', (info) => { - if (info.network !== this.network.type) - this.emit('error', new Error('Wrong network.')); - }); - - this.socket.on('block connect', (entry, txs) => { - let block; - - try { - block = parseBlock(entry, txs); - } catch (e) { - this.emit('error', e); - return; - } - - this.emit('block connect', block.entry, block.txs); - }); - - this.socket.on('block disconnect', (entry) => { - let block; - - try { - block = parseEntry(entry); - } catch (e) { - this.emit('error', e); - return; - } +const TX = require('../primitives/tx'); +const hash256 = require('bcrypto/lib/hash256'); + +const parsers = { + 'block connect': (entry, txs) => parseBlock(entry, txs), + 'block disconnect': entry => [parseEntry(entry)], + 'block rescan': (entry, txs) => parseBlock(entry, txs), + 'chain reset': entry => [parseEntry(entry)], + 'tx': tx => [TX.fromRaw(tx)] +}; - this.emit('block disconnect', block); - }); +class WalletClient extends NodeClient { + constructor(options) { + super(options); + } - this.socket.on('block rescan', (entry, txs, cb) => { - let block; + bind(event, handler) { + const parser = parsers[event]; - try { - block = parseBlock(entry, txs); - } catch (e) { - this.emit('error', e); - cb(); + if (!parser) { + super.bind(event, handler); return; } - this.fire('block rescan', block.entry, block.txs).then(cb, cb); - }); - - this.socket.on('chain reset', (tip) => { - let block; - - try { - block = parseEntry(tip); - } catch (e) { - this.emit('error', e); - return; - } + super.bind(event, (...args) => { + return handler(...parser(...args)); + }); + } - this.emit('chain reset', block); - }); + hook(event, handler) { + const parser = parsers[event]; - this.socket.on('tx', (tx) => { - try { - tx = parseTX(tx); - } catch (e) { - this.emit('error', e); + if (!parser) { + super.hook(event, handler); return; } - this.emit('tx', tx); - }); - - await this.onConnect(); - await this.sendAuth(); - await this.watchChain(); - await this.watchMempool(); -}; - -/** - * Close the client, wait for the socket to close. - * @alias WalletClient#close - * @returns {Promise} - */ - -WalletClient.prototype._close = function _close() { - if (!this.socket) - return Promise.resolve(); - - this.socket.disconnect(); - this.socket = null; - - return Promise.resolve(); -}; - -/** - * Wait for websocket connection. - * @private - * @returns {Promise} - */ - -WalletClient.prototype.onConnect = function onConnect() { - return new Promise((resolve, reject) => { - this.socket.once('connect', resolve); - }); -}; - -/** - * Wait for websocket auth. - * @private - * @returns {Promise} - */ - -WalletClient.prototype.sendAuth = function sendAuth() { - return new Promise((resolve, reject) => { - this.socket.emit('auth', this.apiKey, wrap(resolve, reject)); - }); -}; - -/** - * Watch the blockchain. - * @private - * @returns {Promise} - */ - -WalletClient.prototype.watchChain = function watchChain() { - return new Promise((resolve, reject) => { - this.socket.emit('watch chain', wrap(resolve, reject)); - }); -}; - -/** - * Watch the blockchain. - * @private - * @returns {Promise} - */ - -WalletClient.prototype.watchMempool = function watchMempool() { - return new Promise((resolve, reject) => { - this.socket.emit('watch mempool', wrap(resolve, reject)); - }); -}; -/** - * Get chain tip. - * @returns {Promise} - */ - -WalletClient.prototype.getTip = function getTip() { - return new Promise((resolve, reject) => { - this.socket.emit('get tip', wrap(resolve, reject, parseEntry)); - }); -}; + super.hook(event, (...args) => { + return handler(...parser(...args)); + }); + } -/** - * Get chain entry. - * @param {Hash} hash - * @returns {Promise} - */ + async getTip() { + return parseEntry(await super.getTip()); + } -WalletClient.prototype.getEntry = function getEntry(block) { - return new Promise((resolve, reject) => { - if (typeof block === 'string') + async getEntry(block) { + if (Buffer.isBuffer(block)) block = util.revHex(block); - this.socket.emit('get entry', block, wrap(resolve, reject, parseEntry)); - }); -}; - -/** - * Send a transaction. Do not wait for promise. - * @param {TX} tx - * @returns {Promise} - */ - -WalletClient.prototype.send = function send(tx) { - return new Promise((resolve, reject) => { - this.socket.emit('send', tx.toRaw(), wrap(resolve, reject)); - }); -}; - -/** - * Set bloom filter. - * @param {Bloom} filter - * @returns {Promise} - */ - -WalletClient.prototype.setFilter = function setFilter(filter) { - return new Promise((resolve, reject) => { - this.socket.emit('set filter', filter.toRaw(), wrap(resolve, reject)); - }); -}; - -/** - * Add data to filter. - * @param {Buffer} data - * @returns {Promise} - */ - -WalletClient.prototype.addFilter = function addFilter(chunks) { - if (!Array.isArray(chunks)) - chunks = [chunks]; - - return new Promise((resolve, reject) => { - this.socket.emit('add filter', chunks, wrap(resolve, reject)); - }); -}; - -/** - * Reset filter. - * @returns {Promise} - */ - -WalletClient.prototype.resetFilter = function resetFilter() { - return new Promise((resolve, reject) => { - this.socket.emit('reset filter', wrap(resolve, reject)); - }); -}; - -/** - * Esimate smart fee. - * @param {Number?} blocks - * @returns {Promise} - */ + return parseEntry(await super.getEntry(block)); + } -WalletClient.prototype.estimateFee = function estimateFee(blocks) { - return new Promise((resolve, reject) => { - this.socket.emit('estimate fee', blocks, wrap(resolve, reject)); - }); -}; + async send(tx) { + return super.send(tx.toRaw()); + } -/** - * Rescan for any missed transactions. - * @param {Number|Hash} start - Start block. - * @param {Bloom} filter - * @param {Function} iter - Iterator. - * @returns {Promise} - */ + async setFilter(filter) { + return super.setFilter(filter.toRaw()); + } -WalletClient.prototype.rescan = function rescan(start) { - return new Promise((resolve, reject) => { - if (typeof start === 'string') + async rescan(start) { + if (Buffer.isBuffer(start)) start = util.revHex(start); - this.socket.emit('rescan', start, wrap(resolve, reject)); - }); -}; + return super.rescan(start); + } +} /* * Helpers */ -function parseEntry(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, 'hex'); - - const block = Headers.fromHead(data); - - const br = new BufferReader(data); - br.seek(80); +function parseEntry(data) { + assert(Buffer.isBuffer(data)); + assert(data.length >= 84); - const height = br.readU32(); - const hash = block.hash('hex'); + const hash = hash256.digest(data.slice(0, 80)); - return new BlockMeta(hash, height, block.time); + return { + hash: hash, + height: data.readUInt32LE(80, true), + time: data.readUInt32LE(68, true) + }; } function parseBlock(entry, txs) { const block = parseEntry(entry); const out = []; - for (const raw of txs) { - const tx = parseTX(raw); - out.push(tx); - } - - return new BlockResult(block, out); -} - -function parseTX(data) { - return TX.fromRaw(data, 'hex'); -} - -function BlockResult(entry, txs) { - this.entry = entry; - this.txs = txs; -} - -function wrap(resolve, reject, parse) { - return function(err, result) { - if (err) { - reject(new Error(err.message)); - return; - } - - if (!result) { - resolve(null); - return; - } + for (const tx of txs) + out.push(TX.fromRaw(tx)); - if (!parse) { - resolve(result); - return; - } - - try { - result = parse(result); - } catch (e) { - reject(e); - return; - } - - resolve(result); - }; + return [block, out]; } /* diff --git a/lib/wallet/common.js b/lib/wallet/common.js index 5f45ed659..01a7c209a 100644 --- a/lib/wallet/common.js +++ b/lib/wallet/common.js @@ -6,6 +6,8 @@ 'use strict'; +const {BufferMap} = require('buffer-map'); + /** * @exports wallet/common */ @@ -81,15 +83,15 @@ common.sortCoins = function sortCoins(coins) { */ common.sortDeps = function sortDeps(txs) { - const map = new Map(); + const map = new BufferMap(); for (const tx of txs) { - const hash = tx.hash('hex'); + const hash = tx.hash(); map.set(hash, tx); } - const depMap = new Map(); - const depCount = new Map(); + const depMap = new BufferMap(); + const depCount = new BufferMap(); const top = []; for (const [hash, tx] of map) { @@ -122,8 +124,7 @@ common.sortDeps = function sortDeps(txs) { const result = []; for (const tx of top) { - const hash = tx.hash('hex'); - const deps = depMap.get(hash); + const deps = depMap.get(tx.hash()); result.push(tx); @@ -131,13 +132,12 @@ common.sortDeps = function sortDeps(txs) { continue; for (const tx of deps) { - const hash = tx.hash('hex'); - let count = depCount.get(hash); + let count = depCount.get(tx.hash()); if (--count === 0) top.push(tx); - depCount.set(hash, count); + depCount.set(tx.hash(), count); } } diff --git a/lib/wallet/http-browser.js b/lib/wallet/http-browser.js deleted file mode 100644 index 212272704..000000000 --- a/lib/wallet/http-browser.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -exports.unsupported = true; diff --git a/lib/wallet/http.js b/lib/wallet/http.js index 5aedb8578..0dc6232b0 100644 --- a/lib/wallet/http.js +++ b/lib/wallet/http.js @@ -7,1060 +7,1177 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const path = require('path'); -const HTTPBase = require('../http/base'); -const util = require('../utils/util'); -const base58 = require('../utils/base58'); +const {Server} = require('bweb'); +const Validator = require('bval'); +const {base58} = require('bstring'); const MTX = require('../primitives/mtx'); const Outpoint = require('../primitives/outpoint'); const Script = require('../script/script'); -const digest = require('../crypto/digest'); -const random = require('../crypto/random'); -const ccmp = require('../crypto/ccmp'); +const sha256 = require('bcrypto/lib/sha256'); +const random = require('bcrypto/lib/random'); +const {safeEqual} = require('bcrypto/lib/safe'); const Network = require('../protocol/network'); -const Validator = require('../utils/validator'); const Address = require('../primitives/address'); const KeyRing = require('../primitives/keyring'); +const Mnemonic = require('../hd/mnemonic'); +const HDPrivateKey = require('../hd/private'); +const HDPublicKey = require('../hd/public'); const common = require('./common'); /** - * HTTPServer - * @alias module:wallet.HTTPServer - * @constructor - * @param {Object} options - * @see HTTPBase - * @emits HTTPServer#socket + * HTTP + * @alias module:wallet.HTTP */ -function HTTPServer(options) { - if (!(this instanceof HTTPServer)) - return new HTTPServer(options); +class HTTP extends Server { + /** + * Create an http server. + * @constructor + * @param {Object} options + */ - options = new HTTPOptions(options); + constructor(options) { + super(new HTTPOptions(options)); - HTTPBase.call(this, options); + this.network = this.options.network; + this.logger = this.options.logger.context('wallet-http'); + this.wdb = this.options.node.wdb; + this.rpc = this.options.node.rpc; - this.options = options; - this.network = this.options.network; - this.logger = this.options.logger.context('http'); - this.walletdb = this.options.walletdb; + this.init(); + } - this.server = new HTTPBase(this.options); - this.rpc = this.walletdb.rpc; + /** + * Initialize http server. + * @private + */ - this.init(); -} + init() { + this.on('request', (req, res) => { + if (req.method === 'POST' && req.pathname === '/') + return; -Object.setPrototypeOf(HTTPServer.prototype, HTTPBase.prototype); + this.logger.debug('Request for method=%s path=%s (%s).', + req.method, req.pathname, req.socket.remoteAddress); + }); -/** - * Attach to server. - * @private - * @param {HTTPServer} server - */ + this.on('listening', (address) => { + this.logger.info('Wallet HTTP server listening on %s (port=%d).', + address.address, address.port); + }); -HTTPServer.prototype.attach = function attach(server) { - server.mount('/wallet', this); -}; + this.initRouter(); + this.initSockets(); + } -/** - * Initialize http server. - * @private - */ + /** + * Initialize routes. + * @private + */ + + initRouter() { + if (this.options.cors) + this.use(this.cors()); + + if (!this.options.noAuth) { + this.use(this.basicAuth({ + hash: sha256.digest, + password: this.options.apiKey, + realm: 'wallet' + })); + } -HTTPServer.prototype.init = function init() { - this.on('request', (req, res) => { - if (req.method === 'POST' && req.pathname === '/') - return; + this.use(this.bodyParser({ + type: 'json' + })); - this.logger.debug('Request for method=%s path=%s (%s).', - req.method, req.pathname, req.socket.remoteAddress); - }); + this.use(async (req, res) => { + if (!this.options.walletAuth) { + req.admin = true; + return; + } - this.on('listening', (address) => { - this.logger.info('HTTP server listening on %s (port=%d).', - address.address, address.port); - }); + const valid = Validator.fromRequest(req); + const token = valid.buf('token'); - this.initRouter(); - this.initSockets(); -}; + if (token && safeEqual(token, this.options.adminToken)) { + req.admin = true; + return; + } -/** - * Initialize routes. - * @private - */ + if (req.method === 'POST' && req.path.length === 0) { + res.json(403); + return; + } + }); -HTTPServer.prototype.initRouter = function initRouter() { - this.use(this.cors()); + this.use(this.jsonRPC()); + this.use(this.router()); + + this.error((err, req, res) => { + const code = err.statusCode || 500; + res.json(code, { + error: { + type: err.type, + code: err.code, + message: err.message + } + }); + }); - if (!this.options.noAuth) { - this.use(this.basicAuth({ - password: this.options.apiKey, - realm: 'wallet' - })); - } + this.hook(async (req, res) => { + if (req.path.length < 2) + return; + + if (req.path[0] !== 'wallet') + return; - this.use(this.bodyParser({ - contentType: 'json' - })); + if (req.method === 'PUT' && req.path.length === 2) + return; - this.use(this.jsonRPC(this.rpc)); + const valid = Validator.fromRequest(req); + const id = valid.str('id'); + const token = valid.buf('token'); + + if (!id) { + res.json(403); + return; + } - this.hook(async (req, res) => { - const valid = req.valid(); + if (req.admin || !this.options.walletAuth) { + const wallet = await this.wdb.get(id); - if (req.path.length === 0) - return; + if (!wallet) { + res.json(404); + return; + } - if (req.path[0] === '_admin') - return; + req.wallet = wallet; - if (req.method === 'PUT' && req.path.length === 1) - return; + return; + } - const id = valid.str('id'); - const token = valid.buf('token'); + if (!token) { + res.json(403); + return; + } - if (!this.options.walletAuth) { - const wallet = await this.walletdb.get(id); + let wallet; + try { + wallet = await this.wdb.auth(id, token); + } catch (err) { + this.logger.info('Auth failure for %s: %s.', id, err.message); + res.json(403); + return; + } if (!wallet) { - res.send(404); + res.json(404); return; } req.wallet = wallet; - return; - } + this.logger.info('Successful auth for %s.', id); + }); - let wallet; - try { - wallet = await this.walletdb.auth(id, token); - } catch (err) { - this.logger.info('Auth failure for %s: %s.', id, err.message); - res.error(403, err); - return; - } + // Rescan + this.post('/rescan', async (req, res) => { + if (!req.admin) { + res.json(403); + return; + } - if (!wallet) { - res.send(404); - return; - } + const valid = Validator.fromRequest(req); + const height = valid.u32('height'); - req.wallet = wallet; + res.json(200, { success: true }); - this.logger.info('Successful auth for %s.', id); - }); + await this.wdb.rescan(height); + }); - // Rescan - this.post('/_admin/rescan', async (req, res) => { - const valid = req.valid(); - const height = valid.u32('height'); + // Resend + this.post('/resend', async (req, res) => { + if (!req.admin) { + res.json(403); + return; + } - res.send(200, { success: true }); + await this.wdb.resend(); - await this.walletdb.rescan(height); - }); + res.json(200, { success: true }); + }); - // Resend - this.post('/_admin/resend', async (req, res) => { - await this.walletdb.resend(); + // Backup WalletDB + this.post('/backup', async (req, res) => { + if (!req.admin) { + res.json(403); + return; + } - res.send(200, { success: true }); - }); + const valid = Validator.fromRequest(req); + const path = valid.str('path'); - // Backup WalletDB - this.post('/_admin/backup', async (req, res) => { - const valid = req.valid(); - const path = valid.str('path'); + enforce(path, 'Path is required.'); - enforce(path, 'Path is required.'); + await this.wdb.backup(path); - await this.walletdb.backup(path); + res.json(200, { success: true }); + }); - res.send(200, { success: true }); - }); + // List wallets + this.get('/wallet', async (req, res) => { + if (!req.admin) { + res.json(403); + return; + } - // List wallets - this.get('/_admin/wallets', async (req, res) => { - const wallets = await this.walletdb.getWallets(); - res.send(200, wallets); - }); + const wallets = await this.wdb.getWallets(); + res.json(200, wallets); + }); + + // Get wallet + this.get('/wallet/:id', async (req, res) => { + const balance = await req.wallet.getBalance(); + res.json(200, req.wallet.toJSON(false, balance)); + }); - // Get wallet - this.get('/:id', (req, res) => { - res.send(200, req.wallet.toJSON()); - }); + // Get wallet master key + this.get('/wallet/:id/master', (req, res) => { + if (!req.admin) { + res.json(403); + return; + } + + res.json(200, req.wallet.master.toJSON(this.network, true)); + }); - // Get wallet master key - this.get('/:id/master', (req, res) => { - res.send(200, req.wallet.master.toJSON(true)); - }); + // Create wallet + this.put('/wallet/:id', async (req, res) => { + const valid = Validator.fromRequest(req); + + let master = valid.str('master'); + let mnemonic = valid.str('mnemonic'); + let accountKey = valid.str('accountKey'); + + if (master) + master = HDPrivateKey.fromBase58(master, this.network); + + if (mnemonic) + mnemonic = Mnemonic.fromPhrase(mnemonic); + + if (accountKey) + accountKey = HDPublicKey.fromBase58(accountKey, this.network); + + const wallet = await this.wdb.create({ + id: valid.str('id'), + type: valid.str('type'), + m: valid.u32('m'), + n: valid.u32('n'), + passphrase: valid.str('passphrase'), + master: master, + mnemonic: mnemonic, + witness: valid.bool('witness'), + accountKey: accountKey, + watchOnly: valid.bool('watchOnly') + }); - // Create wallet (compat) - this.post('/', async (req, res) => { - const valid = req.valid(); + const balance = await wallet.getBalance(); - const wallet = await this.walletdb.create({ - id: valid.str('id'), - type: valid.str('type'), - m: valid.u32('m'), - n: valid.u32('n'), - passphrase: valid.str('passphrase'), - master: valid.str('master'), - mnemonic: valid.str('mnemonic'), - witness: valid.bool('witness'), - accountKey: valid.str('accountKey'), - watchOnly: valid.bool('watchOnly') + res.json(200, wallet.toJSON(false, balance)); }); - res.send(200, wallet.toJSON()); - }); - - // Create wallet - this.put('/:id', async (req, res) => { - const valid = req.valid(); - - const wallet = await this.walletdb.create({ - id: valid.str('id'), - type: valid.str('type'), - m: valid.u32('m'), - n: valid.u32('n'), - passphrase: valid.str('passphrase'), - master: valid.str('master'), - mnemonic: valid.str('mnemonic'), - witness: valid.bool('witness'), - accountKey: valid.str('accountKey'), - watchOnly: valid.bool('watchOnly') + // List accounts + this.get('/wallet/:id/account', async (req, res) => { + const accounts = await req.wallet.getAccounts(); + res.json(200, accounts); }); - res.send(200, wallet.toJSON()); - }); + // Get account + this.get('/wallet/:id/account/:account', async (req, res) => { + const valid = Validator.fromRequest(req); + const acct = valid.str('account'); + const account = await req.wallet.getAccount(acct); - // List accounts - this.get('/:id/account', async (req, res) => { - const accounts = await req.wallet.getAccounts(); - res.send(200, accounts); - }); + if (!account) { + res.json(404); + return; + } - // Get account - this.get('/:id/account/:account', async (req, res) => { - const valid = req.valid(); - const acct = valid.str('account'); - const account = await req.wallet.getAccount(acct); + const balance = await req.wallet.getBalance(account.accountIndex); - if (!account) { - res.send(404); - return; - } + res.json(200, account.toJSON(balance)); + }); - res.send(200, account.toJSON()); - }); - - // Create account (compat) - this.post('/:id/account', async (req, res) => { - const valid = req.valid(); - const passphrase = valid.str('passphrase'); - - const options = { - name: valid.str(['account', 'name']), - witness: valid.bool('witness'), - watchOnly: valid.bool('watchOnly'), - type: valid.str('type'), - m: valid.u32('m'), - n: valid.u32('n'), - accountKey: valid.str('accountKey'), - lookahead: valid.u32('lookahead') - }; + // Create account + this.put('/wallet/:id/account/:account', async (req, res) => { + const valid = Validator.fromRequest(req); + const passphrase = valid.str('passphrase'); - const account = await req.wallet.createAccount(options, passphrase); - - res.send(200, account.toJSON()); - }); - - // Create account - this.put('/:id/account/:account', async (req, res) => { - const valid = req.valid(); - const passphrase = valid.str('passphrase'); - - const options = { - name: valid.str('account'), - witness: valid.bool('witness'), - watchOnly: valid.bool('watchOnly'), - type: valid.str('type'), - m: valid.u32('m'), - n: valid.u32('n'), - accountKey: valid.str('accountKey'), - lookahead: valid.u32('lookahead') - }; + let accountKey = valid.get('accountKey'); - const account = await req.wallet.createAccount(options, passphrase); + if (accountKey) + accountKey = HDPublicKey.fromBase58(accountKey, this.network); - res.send(200, account.toJSON()); - }); + const options = { + name: valid.str('account'), + witness: valid.bool('witness'), + type: valid.str('type'), + m: valid.u32('m'), + n: valid.u32('n'), + accountKey: accountKey, + lookahead: valid.u32('lookahead') + }; - // Change passphrase - this.post('/:id/passphrase', async (req, res) => { - const valid = req.valid(); - const old = valid.str('old'); - const new_ = valid.str('new'); + const account = await req.wallet.createAccount(options, passphrase); + const balance = await req.wallet.getBalance(account.accountIndex); - enforce(old || new_, 'Passphrase is required.'); + res.json(200, account.toJSON(balance)); + }); - await req.wallet.setPassphrase(old, new_); + // Change passphrase + this.post('/wallet/:id/passphrase', async (req, res) => { + const valid = Validator.fromRequest(req); + const passphrase = valid.str('passphrase'); + const old = valid.str('old'); - res.send(200, { success: true }); - }); + enforce(passphrase, 'Passphrase is required.'); - // Unlock wallet - this.post('/:id/unlock', async (req, res) => { - const valid = req.valid(); - const passphrase = valid.str('passphrase'); - const timeout = valid.u32('timeout'); + await req.wallet.setPassphrase(passphrase, old); - enforce(passphrase, 'Passphrase is required.'); + res.json(200, { success: true }); + }); - await req.wallet.unlock(passphrase, timeout); + // Unlock wallet + this.post('/wallet/:id/unlock', async (req, res) => { + const valid = Validator.fromRequest(req); + const passphrase = valid.str('passphrase'); + const timeout = valid.u32('timeout'); - res.send(200, { success: true }); - }); + enforce(passphrase, 'Passphrase is required.'); - // Lock wallet - this.post('/:id/lock', async (req, res) => { - await req.wallet.lock(); - res.send(200, { success: true }); - }); + await req.wallet.unlock(passphrase, timeout); - // Import key - this.post('/:id/import', async (req, res) => { - const valid = req.valid(); - const acct = valid.str('account'); - const passphrase = valid.str('passphrase'); - const pub = valid.buf('publicKey'); - const priv = valid.str('privateKey'); - const b58 = valid.str('address'); + res.json(200, { success: true }); + }); - if (pub) { - const key = KeyRing.fromPublic(pub, this.network); - await req.wallet.importKey(acct, key); - res.send(200, { success: true }); - return; - } + // Lock wallet + this.post('/wallet/:id/lock', async (req, res) => { + await req.wallet.lock(); + res.json(200, { success: true }); + }); - if (priv) { - const key = KeyRing.fromSecret(priv, this.network); - await req.wallet.importKey(acct, key, passphrase); - res.send(200, { success: true }); - return; - } + // Import key + this.post('/wallet/:id/import', async (req, res) => { + const valid = Validator.fromRequest(req); + const acct = valid.str('account'); + const passphrase = valid.str('passphrase'); + const pub = valid.buf('publicKey'); + const priv = valid.str('privateKey'); + const address = valid.str('address'); + + if (pub) { + const key = KeyRing.fromPublic(pub); + await req.wallet.importKey(acct, key); + res.json(200, { success: true }); + return; + } - if (b58) { - const addr = Address.fromString(b58, this.network); - await req.wallet.importAddress(acct, addr); - res.send(200, { success: true }); - return; - } + if (priv) { + const key = KeyRing.fromSecret(priv, this.network); + await req.wallet.importKey(acct, key, passphrase); + res.json(200, { success: true }); + return; + } - enforce(false, 'Key or address is required.'); - }); + if (address) { + const addr = Address.fromString(address, this.network); + await req.wallet.importAddress(acct, addr); + res.json(200, { success: true }); + return; + } - // Generate new token - this.post('/:id/retoken', async (req, res) => { - const valid = req.valid(); - const passphrase = valid.str('passphrase'); - const token = await req.wallet.retoken(passphrase); + enforce(false, 'Key or address is required.'); + }); + + // Generate new token + this.post('/wallet/:id/retoken', async (req, res) => { + const valid = Validator.fromRequest(req); + const passphrase = valid.str('passphrase'); + const token = await req.wallet.retoken(passphrase); - res.send(200, { - token: token.toString('hex') + res.json(200, { + token: token.toString('hex') + }); }); - }); - - // Send TX - this.post('/:id/send', async (req, res) => { - const valid = req.valid(); - const passphrase = valid.str('passphrase'); - const outputs = valid.array('outputs', []); - - const options = { - rate: valid.u64('rate'), - blocks: valid.u32('blocks'), - maxFee: valid.u64('maxFee'), - selection: valid.str('selection'), - smart: valid.bool('smart'), - subtractFee: valid.bool('subtractFee'), - subtractIndex: valid.get('subtractIndex'), - depth: valid.u32(['confirmations', 'depth']), - outputs: [] - }; - for (const output of outputs) { - const valid = new Validator([output]); - const raw = valid.buf('script'); + // Send TX + this.post('/wallet/:id/send', async (req, res) => { + const valid = Validator.fromRequest(req); + const passphrase = valid.str('passphrase'); + const outputs = valid.array('outputs', []); + + const options = { + rate: valid.u64('rate'), + blocks: valid.u32('blocks'), + maxFee: valid.u64('maxFee'), + selection: valid.str('selection'), + smart: valid.bool('smart'), + account: valid.str('account'), + sort: valid.bool('sort'), + subtractFee: valid.bool('subtractFee'), + subtractIndex: valid.i32('subtractIndex'), + depth: valid.u32(['confirmations', 'depth']), + outputs: [] + }; + + for (const output of outputs) { + const valid = new Validator(output); + + let addr = valid.str('address'); + let script = valid.buf('script'); + + if (addr) + addr = Address.fromString(addr, this.network); + + if (script) + script = Script.fromRaw(script); + + options.outputs.push({ + address: addr, + script: script, + value: valid.u64('value') + }); + } - let script = null; + const tx = await req.wallet.send(options, passphrase); - if (raw) - script = Script.fromRaw(raw); + const details = await req.wallet.getDetails(tx.hash()); - options.outputs.push({ - script: script, - address: valid.str('address'), - value: valid.u64('value') - }); - } + res.json(200, details.toJSON(this.network, this.wdb.height)); + }); - const tx = await req.wallet.send(options, passphrase); + // Create TX + this.post('/wallet/:id/create', async (req, res) => { + const valid = Validator.fromRequest(req); + const passphrase = valid.str('passphrase'); + const outputs = valid.array('outputs', []); + const sign = valid.bool('sign', true); + + const options = { + rate: valid.u64('rate'), + blocks: valid.u32('blocks'), + maxFee: valid.u64('maxFee'), + selection: valid.str('selection'), + smart: valid.bool('smart'), + account: valid.str('account'), + sort: valid.bool('sort'), + subtractFee: valid.bool('subtractFee'), + subtractIndex: valid.i32('subtractIndex'), + depth: valid.u32(['confirmations', 'depth']), + template: valid.bool('template', sign), + outputs: [] + }; + + for (const output of outputs) { + const valid = new Validator(output); + + let addr = valid.str('address'); + let script = valid.buf('script'); + + if (addr) + addr = Address.fromString(addr, this.network); + + if (script) + script = Script.fromRaw(script); + + options.outputs.push({ + address: addr, + script: script, + value: valid.u64('value') + }); + } - const details = await req.wallet.getDetails(tx.hash('hex')); + const tx = await req.wallet.createTX(options); - res.send(200, details.toJSON()); - }); + if (sign) + await req.wallet.sign(tx, passphrase); - // Create TX - this.post('/:id/create', async (req, res) => { - const valid = req.valid(); - const passphrase = valid.str('passphrase'); - const outputs = valid.array('outputs', []); + res.json(200, tx.getJSON(this.network)); + }); - const options = { - rate: valid.u64('rate'), - maxFee: valid.u64('maxFee'), - selection: valid.str('selection'), - smart: valid.bool('smart'), - subtractFee: valid.bool('subtractFee'), - subtractIndex: valid.get('subtractIndex'), - depth: valid.u32(['confirmations', 'depth']), - outputs: [] - }; + // Sign TX + this.post('/wallet/:id/sign', async (req, res) => { + const valid = Validator.fromRequest(req); + const passphrase = valid.str('passphrase'); + const raw = valid.buf('tx'); - for (const output of outputs) { - const valid = new Validator([output]); - const raw = valid.buf('script'); + enforce(raw, 'TX is required.'); - let script = null; + const tx = MTX.fromRaw(raw); + tx.view = await req.wallet.getCoinView(tx); - if (raw) - script = Script.fromRaw(raw); + await req.wallet.sign(tx, passphrase); - options.outputs.push({ - script: script, - address: valid.str('address'), - value: valid.u64('value') - }); - } + res.json(200, tx.getJSON(this.network)); + }); - const tx = await req.wallet.createTX(options); + // Zap Wallet TXs + this.post('/wallet/:id/zap', async (req, res) => { + const valid = Validator.fromRequest(req); + const acct = valid.str('account'); + const age = valid.u32('age'); - await req.wallet.sign(tx, passphrase); + enforce(age, 'Age is required.'); - res.send(200, tx.getJSON(this.network)); - }); + await req.wallet.zap(acct, age); - // Sign TX - this.post('/:id/sign', async (req, res) => { - const valid = req.valid(); - const passphrase = valid.str('passphrase'); - const raw = valid.buf('tx'); + res.json(200, { success: true }); + }); - enforce(raw, 'TX is required.'); + // Abandon Wallet TX + this.del('/wallet/:id/tx/:hash', async (req, res) => { + const valid = Validator.fromRequest(req); + const hash = valid.brhash('hash'); - const tx = MTX.fromRaw(raw); - tx.view = await req.wallet.getCoinView(tx); + enforce(hash, 'Hash is required.'); - await req.wallet.sign(tx, passphrase); + await req.wallet.abandon(hash); - res.send(200, tx.getJSON(this.network)); - }); + res.json(200, { success: true }); + }); - // Zap Wallet TXs - this.post('/:id/zap', async (req, res) => { - const valid = req.valid(); - const acct = valid.str('account'); - const age = valid.u32('age'); + // List blocks + this.get('/wallet/:id/block', async (req, res) => { + const heights = await req.wallet.getBlocks(); + res.json(200, heights); + }); - enforce(age, 'Age is required.'); + // Get Block Record + this.get('/wallet/:id/block/:height', async (req, res) => { + const valid = Validator.fromRequest(req); + const height = valid.u32('height'); - await req.wallet.zap(acct, age); + enforce(height != null, 'Height is required.'); - res.send(200, { success: true }); - }); + const block = await req.wallet.getBlock(height); - // Abandon Wallet TX - this.del('/:id/tx/:hash', async (req, res) => { - const valid = req.valid(); - const hash = valid.hash('hash'); + if (!block) { + res.json(404); + return; + } - enforce(hash, 'Hash is required.'); + res.json(200, block.toJSON()); + }); - await req.wallet.abandon(hash); + // Add key + this.put('/wallet/:id/shared-key', async (req, res) => { + const valid = Validator.fromRequest(req); + const acct = valid.str('account'); + const b58 = valid.str('accountKey'); - res.send(200, { success: true }); - }); + enforce(b58, 'Key is required.'); - // List blocks - this.get('/:id/block', async (req, res) => { - const heights = await req.wallet.getBlocks(); - res.send(200, heights); - }); + const key = HDPublicKey.fromBase58(b58, this.network); - // Get Block Record - this.get('/:id/block/:height', async (req, res) => { - const valid = req.valid(); - const height = valid.u32('height'); + const added = await req.wallet.addSharedKey(acct, key); - enforce(height != null, 'Height is required.'); + res.json(200, { + success: true, + addedKey: added + }); + }); - const block = await req.wallet.getBlock(height); + // Remove key + this.del('/wallet/:id/shared-key', async (req, res) => { + const valid = Validator.fromRequest(req); + const acct = valid.str('account'); + const b58 = valid.str('accountKey'); - if (!block) { - res.send(404); - return; - } + enforce(b58, 'Key is required.'); + + const key = HDPublicKey.fromBase58(b58, this.network); + + const removed = await req.wallet.removeSharedKey(acct, key); + + res.json(200, { + success: true, + removedKey: removed + }); + }); - res.send(200, block.toJSON()); - }); + // Get key by address + this.get('/wallet/:id/key/:address', async (req, res) => { + const valid = Validator.fromRequest(req); + const b58 = valid.str('address'); - // Add key - this.put('/:id/shared-key', async (req, res) => { - const valid = req.valid(); - const acct = valid.str('account'); - const key = valid.str('accountKey'); + enforce(b58, 'Address is required.'); - enforce(key, 'Key is required.'); + const addr = Address.fromString(b58, this.network); + const key = await req.wallet.getKey(addr); - await req.wallet.addSharedKey(acct, key); + if (!key) { + res.json(404); + return; + } - res.send(200, { success: true }); - }); + res.json(200, key.toJSON(this.network)); + }); - // Remove key - this.del('/:id/shared-key', async (req, res) => { - const valid = req.valid(); - const acct = valid.str('account'); - const key = valid.str('accountKey'); + // Get private key + this.get('/wallet/:id/wif/:address', async (req, res) => { + const valid = Validator.fromRequest(req); + const address = valid.str('address'); + const passphrase = valid.str('passphrase'); - enforce(key, 'Key is required.'); + enforce(address, 'Address is required.'); - await req.wallet.removeSharedKey(acct, key); + const addr = Address.fromString(address, this.network); + const key = await req.wallet.getPrivateKey(addr, passphrase); - res.send(200, { success: true }); - }); + if (!key) { + res.json(404); + return; + } - // Get key by address - this.get('/:id/key/:address', async (req, res) => { - const valid = req.valid(); - const address = valid.str('address'); + res.json(200, { privateKey: key.toSecret(this.network) }); + }); - enforce(address, 'Address is required.'); + // Create address + this.post('/wallet/:id/address', async (req, res) => { + const valid = Validator.fromRequest(req); + const acct = valid.str('account'); + const addr = await req.wallet.createReceive(acct); - const key = await req.wallet.getKey(address); + res.json(200, addr.toJSON(this.network)); + }); - if (!key) { - res.send(404); - return; - } + // Create change address + this.post('/wallet/:id/change', async (req, res) => { + const valid = Validator.fromRequest(req); + const acct = valid.str('account'); + const addr = await req.wallet.createChange(acct); - res.send(200, key.toJSON()); - }); + res.json(200, addr.toJSON(this.network)); + }); - // Get private key - this.get('/:id/wif/:address', async (req, res) => { - const valid = req.valid(); - const address = valid.str('address'); - const passphrase = valid.str('passphrase'); + // Create nested address + this.post('/wallet/:id/nested', async (req, res) => { + const valid = Validator.fromRequest(req); + const acct = valid.str('account'); + const addr = await req.wallet.createNested(acct); - enforce(address, 'Address is required.'); + res.json(200, addr.toJSON(this.network)); + }); - const key = await req.wallet.getPrivateKey(address, passphrase); + // Wallet Balance + this.get('/wallet/:id/balance', async (req, res) => { + const valid = Validator.fromRequest(req); + const acct = valid.str('account'); + const balance = await req.wallet.getBalance(acct); - if (!key) { - res.send(404); - return; - } + if (!balance) { + res.json(404); + return; + } - res.send(200, { privateKey: key.toSecret() }); - }); - - // Create address - this.post('/:id/address', async (req, res) => { - const valid = req.valid(); - const acct = valid.str('account'); - const address = await req.wallet.createReceive(acct); - - res.send(200, address.toJSON()); - }); - - // Create change address - this.post('/:id/change', async (req, res) => { - const valid = req.valid(); - const acct = valid.str('account'); - const address = await req.wallet.createChange(acct); - - res.send(200, address.toJSON()); - }); - - // Create nested address - this.post('/:id/nested', async (req, res) => { - const valid = req.valid(); - const acct = valid.str('account'); - const address = await req.wallet.createNested(acct); - - res.send(200, address.toJSON()); - }); - - // Wallet Balance - this.get('/:id/balance', async (req, res) => { - const valid = req.valid(); - const acct = valid.str('account'); - const balance = await req.wallet.getBalance(acct); - - if (!balance) { - res.send(404); - return; - } + res.json(200, balance.toJSON()); + }); - res.send(200, balance.toJSON()); - }); + // Wallet UTXOs + this.get('/wallet/:id/coin', async (req, res) => { + const valid = Validator.fromRequest(req); + const acct = valid.str('account'); + const coins = await req.wallet.getCoins(acct); + const result = []; - // Wallet UTXOs - this.get('/:id/coin', async (req, res) => { - const valid = req.valid(); - const acct = valid.str('account'); - const coins = await req.wallet.getCoins(acct); - const result = []; + common.sortCoins(coins); - common.sortCoins(coins); + for (const coin of coins) + result.push(coin.getJSON(this.network)); - for (const coin of coins) - result.push(coin.getJSON(this.network)); + res.json(200, result); + }); - res.send(200, result); - }); + // Locked coins + this.get('/wallet/:id/locked', async (req, res) => { + const locked = req.wallet.getLocked(); + const result = []; - // Locked coins - this.get('/:id/locked', async (req, res) => { - const locked = req.wallet.getLocked(); - const result = []; + for (const outpoint of locked) + result.push(outpoint.toJSON()); - for (const outpoint of locked) - result.push(outpoint.toJSON()); + res.json(200, result); + }); - res.send(200, result); - }); + // Lock coin + this.put('/wallet/:id/locked/:hash/:index', async (req, res) => { + const valid = Validator.fromRequest(req); + const hash = valid.brhash('hash'); + const index = valid.u32('index'); - // Lock coin - this.put('/:id/locked/:hash/:index', async (req, res) => { - const valid = req.valid(); - const hash = valid.hash('hash'); - const index = valid.u32('index'); + enforce(hash, 'Hash is required.'); + enforce(index != null, 'Index is required.'); - enforce(hash, 'Hash is required.'); - enforce(index != null, 'Index is required.'); + const outpoint = new Outpoint(hash, index); - const outpoint = new Outpoint(hash, index); + req.wallet.lockCoin(outpoint); - req.wallet.lockCoin(outpoint); + res.json(200, { success: true }); + }); - res.send(200, { success: true }); - }); + // Unlock coin + this.del('/wallet/:id/locked/:hash/:index', async (req, res) => { + const valid = Validator.fromRequest(req); + const hash = valid.brhash('hash'); + const index = valid.u32('index'); - // Unlock coin - this.del('/:id/locked/:hash/:index', async (req, res) => { - const valid = req.valid(); - const hash = valid.hash('hash'); - const index = valid.u32('index'); + enforce(hash, 'Hash is required.'); + enforce(index != null, 'Index is required.'); - enforce(hash, 'Hash is required.'); - enforce(index != null, 'Index is required.'); + const outpoint = new Outpoint(hash, index); - const outpoint = new Outpoint(hash, index); + req.wallet.unlockCoin(outpoint); - req.wallet.unlockCoin(outpoint); + res.json(200, { success: true }); + }); - res.send(200, { success: true }); - }); + // Wallet Coin + this.get('/wallet/:id/coin/:hash/:index', async (req, res) => { + const valid = Validator.fromRequest(req); + const hash = valid.brhash('hash'); + const index = valid.u32('index'); - // Wallet Coin - this.get('/:id/coin/:hash/:index', async (req, res) => { - const valid = req.valid(); - const hash = valid.hash('hash'); - const index = valid.u32('index'); + enforce(hash, 'Hash is required.'); + enforce(index != null, 'Index is required.'); - enforce(hash, 'Hash is required.'); - enforce(index != null, 'Index is required.'); + const coin = await req.wallet.getCoin(hash, index); - const coin = await req.wallet.getCoin(hash, index); + if (!coin) { + res.json(404); + return; + } - if (!coin) { - res.send(404); - return; - } + res.json(200, coin.getJSON(this.network)); + }); - res.send(200, coin.getJSON(this.network)); - }); + // Wallet TXs + this.get('/wallet/:id/tx/history', async (req, res) => { + const valid = Validator.fromRequest(req); + const acct = valid.str('account'); + const txs = await req.wallet.getHistory(acct); - // Wallet TXs - this.get('/:id/tx/history', async (req, res) => { - const valid = req.valid(); - const acct = valid.str('account'); - const txs = await req.wallet.getHistory(acct); + common.sortTX(txs); - common.sortTX(txs); + const details = await req.wallet.toDetails(txs); - const details = await req.wallet.toDetails(txs); + const result = []; - const result = []; + for (const item of details) + result.push(item.toJSON(this.network, this.wdb.height)); - for (const item of details) - result.push(item.toJSON()); + res.json(200, result); + }); - res.send(200, result); - }); + // Wallet Pending TXs + this.get('/wallet/:id/tx/unconfirmed', async (req, res) => { + const valid = Validator.fromRequest(req); + const acct = valid.str('account'); + const txs = await req.wallet.getPending(acct); - // Wallet Pending TXs - this.get('/:id/tx/unconfirmed', async (req, res) => { - const valid = req.valid(); - const acct = valid.str('account'); - const txs = await req.wallet.getPending(acct); + common.sortTX(txs); - common.sortTX(txs); + const details = await req.wallet.toDetails(txs); + const result = []; - const details = await req.wallet.toDetails(txs); - const result = []; + for (const item of details) + result.push(item.toJSON(this.network, this.wdb.height)); - for (const item of details) - result.push(item.toJSON()); + res.json(200, result); + }); - res.send(200, result); - }); + // Wallet TXs within time range + this.get('/wallet/:id/tx/range', async (req, res) => { + const valid = Validator.fromRequest(req); + const acct = valid.str('account'); - // Wallet TXs within time range - this.get('/:id/tx/range', async (req, res) => { - const valid = req.valid(); - const acct = valid.str('account'); + const options = { + start: valid.u32('start'), + end: valid.u32('end'), + limit: valid.u32('limit'), + reverse: valid.bool('reverse') + }; - const options = { - start: valid.u32('start'), - end: valid.u32('end'), - limit: valid.u32('limit'), - reverse: valid.bool('reverse') - }; + const txs = await req.wallet.getRange(acct, options); + const details = await req.wallet.toDetails(txs); + const result = []; - const txs = await req.wallet.getRange(acct, options); - const details = await req.wallet.toDetails(txs); - const result = []; + for (const item of details) + result.push(item.toJSON(this.network, this.wdb.height)); - for (const item of details) - result.push(item.toJSON()); + res.json(200, result); + }); - res.send(200, result); - }); + // Last Wallet TXs + this.get('/wallet/:id/tx/last', async (req, res) => { + const valid = Validator.fromRequest(req); + const acct = valid.str('account'); + const limit = valid.u32('limit'); + const txs = await req.wallet.getLast(acct, limit); + const details = await req.wallet.toDetails(txs); + const result = []; - // Last Wallet TXs - this.get('/:id/tx/last', async (req, res) => { - const valid = req.valid(); - const acct = valid.str('account'); - const limit = valid.u32('limit'); - const txs = await req.wallet.getLast(acct, limit); - const details = await req.wallet.toDetails(txs); - const result = []; + for (const item of details) + result.push(item.toJSON(this.network, this.wdb.height)); - for (const item of details) - result.push(item.toJSON()); + res.json(200, result); + }); - res.send(200, result); - }); + // Wallet TX + this.get('/wallet/:id/tx/:hash', async (req, res) => { + const valid = Validator.fromRequest(req); + const hash = valid.brhash('hash'); - // Wallet TX - this.get('/:id/tx/:hash', async (req, res) => { - const valid = req.valid(); - const hash = valid.hash('hash'); + enforce(hash, 'Hash is required.'); - enforce(hash, 'Hash is required.'); + const tx = await req.wallet.getTX(hash); - const tx = await req.wallet.getTX(hash); + if (!tx) { + res.json(404); + return; + } - if (!tx) { - res.send(404); - return; - } + const details = await req.wallet.toDetails(tx); - const details = await req.wallet.toDetails(tx); + res.json(200, details.toJSON(this.network, this.wdb.height)); + }); - res.send(200, details.toJSON()); - }); + // Resend + this.post('/wallet/:id/resend', async (req, res) => { + await req.wallet.resend(); + res.json(200, { success: true }); + }); + } - // Resend - this.post('/:id/resend', async (req, res) => { - await req.wallet.resend(); - res.send(200, { success: true }); - }); -}; + /** + * Initialize websockets. + * @private + */ -/** - * Initialize websockets. - * @private - */ + initSockets() { + const handleTX = (event, wallet, tx, details) => { + const name = `w:${wallet.id}`; -HTTPServer.prototype.initSockets = function initSockets() { - if (!this.io) - return; + if (!this.channel(name) && !this.channel('w:*')) + return; - this.on('socket', (socket) => { - this.handleSocket(socket); - }); + const json = details.toJSON(this.network, this.wdb.height); - this.walletdb.on('tx', (id, tx, details) => { - const json = details.toJSON(); - this.to(`w:${id}`, 'wallet tx', json); - }); + if (this.channel(name)) + this.to(name, event, wallet.id, json); - this.walletdb.on('confirmed', (id, tx, details) => { - const json = details.toJSON(); - this.to(`w:${id}`, 'wallet confirmed', json); - }); + if (this.channel('w:*')) + this.to('w:*', event, wallet.id, json); + }; - this.walletdb.on('unconfirmed', (id, tx, details) => { - const json = details.toJSON(); - this.to(`w:${id}`, 'wallet unconfirmed', json); - }); + this.wdb.on('tx', (wallet, tx, details) => { + handleTX('tx', wallet, tx, details); + }); - this.walletdb.on('conflict', (id, tx, details) => { - const json = details.toJSON(); - this.to(`w:${id}`, 'wallet conflict', json); - }); + this.wdb.on('confirmed', (wallet, tx, details) => { + handleTX('confirmed', wallet, tx, details); + }); - this.walletdb.on('balance', (id, balance) => { - const json = balance.toJSON(); - this.to(`w:${id}`, 'wallet balance', json); - }); + this.wdb.on('unconfirmed', (wallet, tx, details) => { + handleTX('unconfirmed', wallet, tx, details); + }); - this.walletdb.on('address', (id, receive) => { - const json = []; + this.wdb.on('conflict', (wallet, tx, details) => { + handleTX('conflict', wallet, tx, details); + }); - for (const addr of receive) - json.push(addr.toJSON()); + this.wdb.on('balance', (wallet, balance) => { + const name = `w:${wallet.id}`; - this.to(`w:${id}`, 'wallet address', json); - }); -}; + if (!this.channel(name) && !this.channel('w:*')) + return; -/** - * Handle new websocket. - * @private - * @param {WebSocket} socket - */ + const json = balance.toJSON(); -HTTPServer.prototype.handleSocket = function handleSocket(socket) { - socket.hook('wallet auth', (args) => { - if (socket.auth) - throw new Error('Already authed.'); + if (this.channel(name)) + this.to(name, 'balance', wallet.id, json); - if (!this.options.noAuth) { - const valid = new Validator([args]); - const key = valid.str(0, ''); + if (this.channel('w:*')) + this.to('w:*', 'balance', wallet.id, json); + }); - if (key.length > 255) - throw new Error('Invalid API key.'); + this.wdb.on('address', (wallet, receive) => { + const name = `w:${wallet.id}`; - const data = Buffer.from(key, 'utf8'); - const hash = digest.hash256(data); + if (!this.channel(name) && !this.channel('w:*')) + return; - if (!ccmp(hash, this.options.apiHash)) - throw new Error('Invalid API key.'); - } + const json = []; - socket.auth = true; + for (const addr of receive) + json.push(addr.toJSON(this.network)); - this.logger.info('Successful auth from %s.', socket.remoteAddress); + if (this.channel(name)) + this.to(name, 'address', wallet.id, json); - this.handleAuth(socket); + if (this.channel('w:*')) + this.to('w:*', 'address', wallet.id, json); + }); + } - return null; - }); -}; + /** + * Handle new websocket. + * @private + * @param {WebSocket} socket + */ -/** - * Handle new auth'd websocket. - * @private - * @param {WebSocket} socket - */ + handleSocket(socket) { + socket.hook('auth', (...args) => { + if (socket.channel('auth')) + throw new Error('Already authed.'); -HTTPServer.prototype.handleAuth = function handleAuth(socket) { - socket.hook('wallet join', async (args) => { - const valid = new Validator([args]); - const id = valid.str(0, ''); - const token = valid.buf(1); + if (!this.options.noAuth) { + const valid = new Validator(args); + const key = valid.str(0, ''); - if (!id) - throw new Error('Invalid parameter.'); + if (key.length > 255) + throw new Error('Invalid API key.'); - if (!this.options.walletAuth) { - socket.join(`w:${id}`); - return null; - } + const data = Buffer.from(key, 'utf8'); + const hash = sha256.digest(data); - if (!token) - throw new Error('Invalid parameter.'); + if (!safeEqual(hash, this.options.apiHash)) + throw new Error('Invalid API key.'); + } - let wallet; - try { - wallet = await this.walletdb.auth(id, token); - } catch (e) { - this.logger.info('Wallet auth failure for %s: %s.', id, e.message); - throw new Error('Bad token.'); - } + socket.join('auth'); - if (!wallet) - throw new Error('Wallet does not exist.'); + this.logger.info('Successful auth from %s.', socket.host); - this.logger.info('Successful wallet auth for %s.', id); + this.handleAuth(socket); - socket.join(`w:${id}`); + return null; + }); + } - return null; - }); + /** + * Handle new auth'd websocket. + * @private + * @param {WebSocket} socket + */ + + handleAuth(socket) { + socket.hook('join', async (...args) => { + const valid = new Validator(args); + const id = valid.str(0, ''); + const token = valid.buf(1); + + if (!id) + throw new Error('Invalid parameter.'); + + if (!this.options.walletAuth) { + socket.join('admin'); + } else if (token) { + if (safeEqual(token, this.options.adminToken)) + socket.join('admin'); + } - socket.hook('wallet leave', (args) => { - const valid = new Validator([args]); - const id = valid.str(0, ''); + if (socket.channel('admin') || !this.options.walletAuth) { + socket.join(`w:${id}`); + return null; + } - if (!id) - throw new Error('Invalid parameter.'); + if (id === '*') + throw new Error('Bad token.'); - socket.leave(`w:${id}`); + if (!token) + throw new Error('Invalid parameter.'); - return null; - }); -}; + let wallet; + try { + wallet = await this.wdb.auth(id, token); + } catch (e) { + this.logger.info('Wallet auth failure for %s: %s.', id, e.message); + throw new Error('Bad token.'); + } -/** - * HTTPOptions - * @alias module:http.HTTPOptions - * @constructor - * @param {Object} options - */ + if (!wallet) + throw new Error('Wallet does not exist.'); -function HTTPOptions(options) { - if (!(this instanceof HTTPOptions)) - return new HTTPOptions(options); - - this.network = Network.primary; - this.logger = null; - this.walletdb = null; - this.apiKey = base58.encode(random.randomBytes(20)); - this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'ascii')); - this.serviceHash = this.apiHash; - this.noAuth = false; - this.walletAuth = false; - - this.prefix = null; - this.host = '127.0.0.1'; - this.port = 8080; - this.ssl = false; - this.keyFile = null; - this.certFile = null; - - this.fromOptions(options); -} + this.logger.info('Successful wallet auth for %s.', id); -/** - * Inject properties from object. - * @private - * @param {Object} options - * @returns {HTTPOptions} - */ + socket.join(`w:${id}`); -HTTPOptions.prototype.fromOptions = function fromOptions(options) { - assert(options); - assert(options.walletdb && typeof options.walletdb === 'object', - 'HTTP Server requires a WalletDB.'); + return null; + }); - this.walletdb = options.walletdb; - this.network = options.walletdb.network; - this.logger = options.walletdb.logger; - this.port = this.network.rpcPort + 2; + socket.hook('leave', (...args) => { + const valid = new Validator(args); + const id = valid.str(0, ''); - if (options.logger != null) { - assert(typeof options.logger === 'object'); - this.logger = options.logger; - } + if (!id) + throw new Error('Invalid parameter.'); - if (options.apiKey != null) { - assert(typeof options.apiKey === 'string', - 'API key must be a string.'); - assert(options.apiKey.length <= 255, - 'API key must be under 255 bytes.'); - assert(util.isAscii(options.apiKey), - 'API key must be ASCII.'); - this.apiKey = options.apiKey; - this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'ascii')); - } + socket.leave(`w:${id}`); - if (options.noAuth != null) { - assert(typeof options.noAuth === 'boolean'); - this.noAuth = options.noAuth; + return null; + }); } +} - if (options.walletAuth != null) { - assert(typeof options.walletAuth === 'boolean'); - this.walletAuth = options.walletAuth; +class HTTPOptions { + /** + * HTTPOptions + * @alias module:http.HTTPOptions + * @constructor + * @param {Object} options + */ + + constructor(options) { + this.network = Network.primary; + this.logger = null; + this.node = null; + this.apiKey = base58.encode(random.randomBytes(20)); + this.apiHash = sha256.digest(Buffer.from(this.apiKey, 'ascii')); + this.adminToken = random.randomBytes(32); + this.serviceHash = this.apiHash; + this.noAuth = false; + this.cors = false; + this.walletAuth = false; + + this.prefix = null; + this.host = '127.0.0.1'; + this.port = 8080; + this.ssl = false; + this.keyFile = null; + this.certFile = null; + + this.fromOptions(options); } - if (options.prefix != null) { - assert(typeof options.prefix === 'string'); - this.prefix = options.prefix; - this.keyFile = path.join(this.prefix, 'key.pem'); - this.certFile = path.join(this.prefix, 'cert.pem'); - } + /** + * Inject properties from object. + * @private + * @param {Object} options + * @returns {HTTPOptions} + */ + + fromOptions(options) { + assert(options); + assert(options.node && typeof options.node === 'object', + 'HTTP Server requires a WalletDB.'); + + this.node = options.node; + this.network = options.node.network; + this.logger = options.node.logger; + this.port = this.network.walletPort; + + if (options.logger != null) { + assert(typeof options.logger === 'object'); + this.logger = options.logger; + } - if (options.host != null) { - assert(typeof options.host === 'string'); - this.host = options.host; - } + if (options.apiKey != null) { + assert(typeof options.apiKey === 'string', + 'API key must be a string.'); + assert(options.apiKey.length <= 255, + 'API key must be under 255 bytes.'); + this.apiKey = options.apiKey; + this.apiHash = sha256.digest(Buffer.from(this.apiKey, 'ascii')); + } - if (options.port != null) { - assert(util.isU16(options.port), 'Port must be a number.'); - this.port = options.port; - } + if (options.adminToken != null) { + if (typeof options.adminToken === 'string') { + assert(options.adminToken.length === 64, + 'Admin token must be a 32 byte hex string.'); + const token = Buffer.from(options.adminToken, 'hex'); + assert(token.length === 32, + 'Admin token must be a 32 byte hex string.'); + this.adminToken = token; + } else { + assert(Buffer.isBuffer(options.adminToken), + 'Admin token must be a hex string or buffer.'); + assert(options.adminToken.length === 32, + 'Admin token must be 32 bytes.'); + this.adminToken = options.adminToken; + } + } - if (options.ssl != null) { - assert(typeof options.ssl === 'boolean'); - this.ssl = options.ssl; - } + if (options.noAuth != null) { + assert(typeof options.noAuth === 'boolean'); + this.noAuth = options.noAuth; + } - if (options.keyFile != null) { - assert(typeof options.keyFile === 'string'); - this.keyFile = options.keyFile; - } + if (options.cors != null) { + assert(typeof options.cors === 'boolean'); + this.cors = options.cors; + } - if (options.certFile != null) { - assert(typeof options.certFile === 'string'); - this.certFile = options.certFile; - } + if (options.walletAuth != null) { + assert(typeof options.walletAuth === 'boolean'); + this.walletAuth = options.walletAuth; + } - // Allow no-auth implicitly - // if we're listening locally. - if (!options.apiKey) { - if (this.host === '127.0.0.1' || this.host === '::1') - this.noAuth = true; - } + if (options.prefix != null) { + assert(typeof options.prefix === 'string'); + this.prefix = options.prefix; + this.keyFile = path.join(this.prefix, 'key.pem'); + this.certFile = path.join(this.prefix, 'cert.pem'); + } - return this; -}; + if (options.host != null) { + assert(typeof options.host === 'string'); + this.host = options.host; + } -/** - * Instantiate http options from object. - * @param {Object} options - * @returns {HTTPOptions} - */ + if (options.port != null) { + assert((options.port & 0xffff) === options.port, + 'Port must be a number.'); + this.port = options.port; + } -HTTPOptions.fromOptions = function fromOptions(options) { - return new HTTPOptions().fromOptions(options); -}; + if (options.ssl != null) { + assert(typeof options.ssl === 'boolean'); + this.ssl = options.ssl; + } + + if (options.keyFile != null) { + assert(typeof options.keyFile === 'string'); + this.keyFile = options.keyFile; + } + + if (options.certFile != null) { + assert(typeof options.certFile === 'string'); + this.certFile = options.certFile; + } + + // Allow no-auth implicitly + // if we're listening locally. + if (!options.apiKey) { + if (this.host === '127.0.0.1' || this.host === '::1') + this.noAuth = true; + } + + return this; + } + + /** + * Instantiate http options from object. + * @param {Object} options + * @returns {HTTPOptions} + */ + + static fromOptions(options) { + return new HTTPOptions().fromOptions(options); + } +} /* * Helpers @@ -1078,4 +1195,4 @@ function enforce(value, msg) { * Expose */ -module.exports = HTTPServer; +module.exports = HTTP; diff --git a/lib/wallet/index.js b/lib/wallet/index.js index c132981de..304662ab7 100644 --- a/lib/wallet/index.js +++ b/lib/wallet/index.js @@ -13,7 +13,7 @@ exports.Account = require('./account'); exports.Client = require('./client'); exports.common = require('./common'); -exports.HTTPServer = require('./http'); +exports.HTTP = require('./http'); exports.layout = require('./layout'); exports.MasterKey = require('./masterkey'); exports.NodeClient = require('./nodeclient'); @@ -21,7 +21,7 @@ exports.Path = require('./path'); exports.plugin = require('./plugin'); exports.records = require('./records'); exports.RPC = require('./rpc'); -exports.server = require('./server'); +exports.Node = require('./node'); exports.TXDB = require('./txdb'); exports.WalletDB = require('./walletdb'); exports.Wallet = require('./wallet'); diff --git a/lib/wallet/layout-browser.js b/lib/wallet/layout-browser.js deleted file mode 100644 index cbaa13ff7..000000000 --- a/lib/wallet/layout-browser.js +++ /dev/null @@ -1,242 +0,0 @@ -/*! - * layout-browser.js - walletdb and txdb layout for browser. - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const util = require('../utils/util'); -const pad32 = util.pad32; -const layouts = exports; - -layouts.walletdb = { - binary: false, - p: function p(hash) { - assert(typeof hash === 'string'); - return 'p' + hash; - }, - pp: function pp(key) { - assert(typeof key === 'string'); - return key.slice(1); - }, - P: function P(wid, hash) { - assert(typeof hash === 'string'); - return 'p' + pad32(wid) + hash; - }, - Pp: function Pp(key) { - assert(typeof key === 'string'); - return key.slice(11); - }, - r: function r(wid, index, hash) { - assert(typeof hash === 'string'); - return 'r' + pad32(wid) + pad32(index) + hash; - }, - rr: function rr(key) { - assert(typeof key === 'string'); - return key.slice(21); - }, - w: function w(wid) { - return 'w' + pad32(wid); - }, - ww: function ww(key) { - assert(typeof key === 'string'); - return parseInt(key.slice(1), 10); - }, - l: function l(id) { - assert(typeof id === 'string'); - return 'l' + id; - }, - ll: function ll(key) { - assert(typeof key === 'string'); - return key.slice(1); - }, - a: function a(wid, index) { - return 'a' + pad32(wid) + pad32(index); - }, - i: function i(wid, name) { - assert(typeof name === 'string'); - return 'i' + pad32(wid) + name; - }, - ii: function ii(key) { - assert(typeof key === 'string'); - return [parseInt(key.slice(1, 11), 10), key.slice(11)]; - }, - n: function n(wid, index) { - return 'n' + pad32(wid) + pad32(index); - }, - R: 'R', - h: function h(height) { - return 'h' + pad32(height); - }, - b: function b(height) { - return 'b' + pad32(height); - }, - bb: function bb(key) { - assert(typeof key === 'string'); - return parseInt(key.slice(1), 10); - }, - o: function o(hash, index) { - assert(typeof hash === 'string'); - return 'o' + hash + pad32(index); - }, - oo: function oo(key) { - return [key.slice(1, 65), parseInt(key.slice(65), 10)]; - } -}; - -layouts.txdb = { - binary: false, - prefix: function prefix(wid, key) { - assert(typeof key === 'string'); - return 't' + pad32(wid) + key; - }, - pre: function pre(key) { - assert(typeof key === 'string'); - return parseInt(key.slice(1, 11), 10); - }, - R: 'R', - hi: function hi(ch, hash, index) { - assert(typeof hash === 'string'); - return ch + hash + pad32(index); - }, - hii: function hii(key) { - assert(typeof key === 'string'); - key = key.slice(12); - return [key.slice(0, 64), parseInt(key.slice(64), 10)]; - }, - ih: function ih(ch, index, hash) { - assert(typeof hash === 'string'); - return ch + pad32(index) + hash; - }, - ihh: function ihh(key) { - assert(typeof key === 'string'); - key = key.slice(12); - return [parseInt(key.slice(0, 10), 10), key.slice(10)]; - }, - iih: function iih(ch, index, num, hash) { - assert(typeof hash === 'string'); - return ch + pad32(index) + pad32(num) + hash; - }, - iihh: function iihh(key) { - assert(typeof key === 'string'); - key = key.slice(12); - return [ - parseInt(key.slice(0, 10), 10), - parseInt(key.slice(10, 20), 10), - key.slice(20) - ]; - }, - ihi: function ihi(ch, index, hash, num) { - assert(typeof hash === 'string'); - return ch + pad32(index) + hash + pad32(num); - }, - ihii: function ihii(key) { - assert(typeof key === 'string'); - key = key.slice(12); - return [ - parseInt(key.slice(0, 10), 10), - key.slice(10, 74), - parseInt(key.slice(74), 10) - ]; - }, - ha: function ha(ch, hash) { - assert(typeof hash === 'string'); - return ch + hash; - }, - haa: function haa(key) { - assert(typeof key === 'string'); - key = key.slice(12); - return key; - }, - t: function t(hash) { - return this.ha('t', hash); - }, - tt: function tt(key) { - return this.haa(key); - }, - c: function c(hash, index) { - return this.hi('c', hash, index); - }, - cc: function cc(key) { - return this.hii(key); - }, - d: function d(hash, index) { - return this.hi('d', hash, index); - }, - dd: function dd(key) { - return this.hii(key); - }, - s: function s(hash, index) { - return this.hi('s', hash, index); - }, - ss: function ss(key) { - return this.hii(key); - }, - S: function S(hash, index) { - return this.hi('S', hash, index); - }, - Ss: function Ss(key) { - return this.hii(key); - }, - p: function p(hash) { - return this.ha('p', hash); - }, - pp: function pp(key) { - return this.haa(key); - }, - m: function m(time, hash) { - return this.ih('m', time, hash); - }, - mm: function mm(key) { - return this.ihh(key); - }, - h: function h(height, hash) { - return this.ih('h', height, hash); - }, - hh: function hh(key) { - return this.ihh(key); - }, - T: function T(account, hash) { - return this.ih('T', account, hash); - }, - Tt: function Tt(key) { - return this.ihh(key); - }, - P: function P(account, hash) { - return this.ih('P', account, hash); - }, - Pp: function Pp(key) { - return this.ihh(key); - }, - M: function M(account, time, hash) { - return this.iih('M', account, time, hash); - }, - Mm: function Mm(key) { - return this.iihh(key); - }, - H: function H(account, height, hash) { - return this.iih('H', account, height, hash); - }, - Hh: function Hh(key) { - return this.iihh(key); - }, - C: function C(account, hash, index) { - return this.ihi('C', account, hash, index); - }, - Cc: function Cc(key) { - return this.ihii(key); - }, - r: function r(hash) { - return this.ha('r', hash); - }, - b: function b(height) { - return 'b' + pad32(height); - }, - bb: function bb(key) { - assert(typeof key === 'string'); - key = key.slice(12); - return parseInt(key.slice(0), 10); - } -}; diff --git a/lib/wallet/layout.js b/lib/wallet/layout.js index 3f888752f..dfaebf5df 100644 --- a/lib/wallet/layout.js +++ b/lib/wallet/layout.js @@ -6,173 +6,59 @@ 'use strict'; -const assert = require('assert'); -const layouts = exports; +const bdb = require('bdb'); /* * Wallet Database Layout: + * V -> db version + * O -> flags + * R -> chain sync state + * D -> wallet id depth * p[addr-hash] -> wallet ids * P[wid][addr-hash] -> path data * r[wid][index][hash] -> path account index * w[wid] -> wallet + * W[wid] -> wallet id * l[id] -> wid * a[wid][index] -> account * i[wid][name] -> account index * n[wid][index] -> account name - * t[wid]* -> txdb - * R -> chain sync state * h[height] -> recent block hash * b[height] -> block->wid map * o[hash][index] -> outpoint->wid map + * T[hash] -> tx->wid map + * t[wid]* -> txdb */ -layouts.walletdb = { - binary: true, - p: function p(hash) { - assert(typeof hash === 'string'); - const key = Buffer.allocUnsafe(1 + (hash.length / 2)); - key[0] = 0x70; - key.write(hash, 1, 'hex'); - return key; - }, - pp: function pp(key) { - assert(Buffer.isBuffer(key)); - assert(key.length >= 21); - return key.toString('hex', 1); - }, - P: function P(wid, hash) { - assert(typeof wid === 'number'); - assert(typeof hash === 'string'); - const key = Buffer.allocUnsafe(1 + 4 + (hash.length / 2)); - key[0] = 0x50; - key.writeUInt32BE(wid, 1, true); - key.write(hash, 5, 'hex'); - return key; - }, - Pp: function Pp(key) { - assert(Buffer.isBuffer(key)); - assert(key.length >= 25); - return key.toString('hex', 5); - }, - r: function r(wid, index, hash) { - assert(typeof wid === 'number'); - assert(typeof index === 'number'); - assert(typeof hash === 'string'); - const key = Buffer.allocUnsafe(1 + 4 + 4 + (hash.length / 2)); - key[0] = 0x72; - key.writeUInt32BE(wid, 1, true); - key.writeUInt32BE(index, 5, true); - key.write(hash, 9, 'hex'); - return key; - }, - rr: function rr(key) { - assert(Buffer.isBuffer(key)); - assert(key.length >= 29); - return key.toString('hex', 9); - }, - w: function w(wid) { - assert(typeof wid === 'number'); - const key = Buffer.allocUnsafe(5); - key[0] = 0x77; - key.writeUInt32BE(wid, 1, true); - return key; - }, - ww: function ww(key) { - assert(Buffer.isBuffer(key)); - assert(key.length === 5); - return key.readUInt32BE(1, true); - }, - l: function l(id) { - assert(typeof id === 'string'); - const len = Buffer.byteLength(id, 'ascii'); - const key = Buffer.allocUnsafe(1 + len); - key[0] = 0x6c; - if (len > 0) - key.write(id, 1, 'ascii'); - return key; - }, - ll: function ll(key) { - assert(Buffer.isBuffer(key)); - assert(key.length >= 1); - return key.toString('ascii', 1); - }, - a: function a(wid, index) { - assert(typeof wid === 'number'); - assert(typeof index === 'number'); - const key = Buffer.allocUnsafe(9); - key[0] = 0x61; - key.writeUInt32BE(wid, 1, true); - key.writeUInt32BE(index, 5, true); - return key; - }, - i: function i(wid, name) { - assert(typeof wid === 'number'); - assert(typeof name === 'string'); - const len = Buffer.byteLength(name, 'ascii'); - const key = Buffer.allocUnsafe(5 + len); - key[0] = 0x69; - key.writeUInt32BE(wid, 1, true); - if (len > 0) - key.write(name, 5, 'ascii'); - return key; - }, - ii: function ii(key) { - assert(Buffer.isBuffer(key)); - assert(key.length >= 5); - return [key.readUInt32BE(1, true), key.toString('ascii', 5)]; - }, - n: function n(wid, index) { - assert(typeof wid === 'number'); - assert(typeof index === 'number'); - const key = Buffer.allocUnsafe(9); - key[0] = 0x6e; - key.writeUInt32BE(wid, 1, true); - key.writeUInt32BE(index, 5, true); - return key; - }, - R: Buffer.from([0x52]), - h: function h(height) { - assert(typeof height === 'number'); - const key = Buffer.allocUnsafe(5); - key[0] = 0x68; - key.writeUInt32BE(height, 1, true); - return key; - }, - b: function b(height) { - assert(typeof height === 'number'); - const key = Buffer.allocUnsafe(5); - key[0] = 0x62; - key.writeUInt32BE(height, 1, true); - return key; - }, - bb: function bb(key) { - assert(Buffer.isBuffer(key)); - assert(key.length === 5); - return key.readUInt32BE(1, true); - }, - o: function o(hash, index) { - assert(typeof hash === 'string'); - assert(typeof index === 'number'); - const key = Buffer.allocUnsafe(37); - key[0] = 0x6f; - key.write(hash, 1, 'hex'); - key.writeUInt32BE(index, 33, true); - return key; - }, - oo: function oo(key) { - assert(Buffer.isBuffer(key)); - assert(key.length === 37); - return [key.toString('hex', 1, 33), key.readUInt32BE(33, true)]; - } +exports.wdb = { + V: bdb.key('V'), + O: bdb.key('O'), + R: bdb.key('R'), + D: bdb.key('D'), + p: bdb.key('p', ['hash']), + P: bdb.key('P', ['uint32', 'hash']), + r: bdb.key('r', ['uint32', 'uint32', 'hash']), + w: bdb.key('w', ['uint32']), + W: bdb.key('W', ['uint32']), + l: bdb.key('l', ['ascii']), + a: bdb.key('a', ['uint32', 'uint32']), + i: bdb.key('i', ['uint32', 'ascii']), + n: bdb.key('n', ['uint32', 'uint32']), + h: bdb.key('h', ['uint32']), + b: bdb.key('b', ['uint32']), + o: bdb.key('o', ['hash256', 'uint32']), + T: bdb.key('T', ['hash256']), + t: bdb.key('t', ['uint32']) }; /* * TXDB Database Layout: + * R -> wallet balance + * r[account] -> account balance * t[hash] -> extended tx * c[hash][index] -> coin * d[hash][index] -> undo coin * s[hash][index] -> spent by hash - * o[hash][index] -> orphan inputs * p[hash] -> dummy (pending flag) * m[time][hash] -> dummy (tx by time) * h[height][hash] -> dummy (tx by height) @@ -181,197 +67,24 @@ layouts.walletdb = { * M[account][time][hash] -> dummy (tx by time + account) * H[account][height][hash] -> dummy (tx by height + account) * C[account][hash][index] -> dummy (coin by account) - * r[hash] -> dummy (replace by fee chain) + * b[height] -> block record */ -layouts.txdb = { - binary: true, - prefix: function prefix(wid, key) { - assert(typeof wid === 'number'); - assert(Buffer.isBuffer(key)); - const out = Buffer.allocUnsafe(5 + key.length); - out[0] = 0x74; - out.writeUInt32BE(wid, 1); - key.copy(out, 5); - return out; - }, - pre: function pre(key) { - assert(Buffer.isBuffer(key)); - assert(key.length >= 5); - return key.readUInt32BE(1, true); - }, - R: Buffer.from([0x52]), - hi: function hi(ch, hash, index) { - assert(typeof hash === 'string'); - assert(typeof index === 'number'); - const key = Buffer.allocUnsafe(37); - key[0] = ch; - key.write(hash, 1, 'hex'); - key.writeUInt32BE(index, 33, true); - return key; - }, - hii: function hii(key) { - assert(Buffer.isBuffer(key)); - assert(key.length - 5 === 37); - key = key.slice(6); - return [key.toString('hex', 0, 32), key.readUInt32BE(32, true)]; - }, - ih: function ih(ch, index, hash) { - assert(typeof index === 'number'); - assert(typeof hash === 'string'); - const key = Buffer.allocUnsafe(37); - key[0] = ch; - key.writeUInt32BE(index, 1, true); - key.write(hash, 5, 'hex'); - return key; - }, - ihh: function ihh(key) { - assert(Buffer.isBuffer(key)); - assert(key.length - 5 === 37); - key = key.slice(6); - return [key.readUInt32BE(0, true), key.toString('hex', 4, 36)]; - }, - iih: function iih(ch, index, num, hash) { - assert(typeof index === 'number'); - assert(typeof num === 'number'); - assert(typeof hash === 'string'); - const key = Buffer.allocUnsafe(41); - key[0] = ch; - key.writeUInt32BE(index, 1, true); - key.writeUInt32BE(num, 5, true); - key.write(hash, 9, 'hex'); - return key; - }, - iihh: function iihh(key) { - assert(Buffer.isBuffer(key)); - assert(key.length - 5 === 41); - key = key.slice(6); - return [ - key.readUInt32BE(0, true), - key.readUInt32BE(4, true), - key.toString('hex', 8, 40) - ]; - }, - ihi: function ihi(ch, index, hash, num) { - assert(typeof index === 'number'); - assert(typeof hash === 'string'); - assert(typeof num === 'number'); - const key = Buffer.allocUnsafe(41); - key[0] = ch; - key.writeUInt32BE(index, 1, true); - key.write(hash, 5, 'hex'); - key.writeUInt32BE(num, 37, true); - return key; - }, - ihii: function ihii(key) { - assert(Buffer.isBuffer(key)); - assert(key.length - 5 === 41); - key = key.slice(6); - return [ - key.readUInt32BE(0, true), - key.toString('hex', 4, 36), - key.readUInt32BE(36, true) - ]; - }, - ha: function ha(ch, hash) { - assert(typeof hash === 'string'); - const key = Buffer.allocUnsafe(33); - key[0] = ch; - key.write(hash, 1, 'hex'); - return key; - }, - haa: function haa(key) { - assert(Buffer.isBuffer(key)); - assert(key.length - 5 === 33); - key = key.slice(6); - return key.toString('hex', 0); - }, - t: function t(hash) { - return this.ha(0x74, hash); - }, - tt: function tt(key) { - return this.haa(key); - }, - c: function c(hash, index) { - return this.hi(0x63, hash, index); - }, - cc: function cc(key) { - return this.hii(key); - }, - d: function d(hash, index) { - return this.hi(0x64, hash, index); - }, - dd: function dd(key) { - return this.hii(key); - }, - s: function s(hash, index) { - return this.hi(0x73, hash, index); - }, - ss: function ss(key) { - return this.hii(key); - }, - p: function p(hash) { - return this.ha(0x70, hash); - }, - pp: function pp(key) { - return this.haa(key); - }, - m: function m(time, hash) { - return this.ih(0x6d, time, hash); - }, - mm: function mm(key) { - return this.ihh(key); - }, - h: function h(height, hash) { - return this.ih(0x68, height, hash); - }, - hh: function hh(key) { - return this.ihh(key); - }, - T: function T(account, hash) { - return this.ih(0x54, account, hash); - }, - Tt: function Tt(key) { - return this.ihh(key); - }, - P: function P(account, hash) { - return this.ih(0x50, account, hash); - }, - Pp: function Pp(key) { - return this.ihh(key); - }, - M: function M(account, time, hash) { - return this.iih(0x4d, account, time, hash); - }, - Mm: function Mm(key) { - return this.iihh(key); - }, - H: function H(account, height, hash) { - return this.iih(0x48, account, height, hash); - }, - Hh: function Hh(key) { - return this.iihh(key); - }, - C: function C(account, hash, index) { - return this.ihi(0x43, account, hash, index); - }, - Cc: function Cc(key) { - return this.ihii(key); - }, - r: function r(hash) { - return this.ha(0x72, hash); - }, - b: function b(height) { - assert(typeof height === 'number'); - const key = Buffer.allocUnsafe(5); - key[0] = 0x62; - key.writeUInt32BE(height, 1, true); - return key; - }, - bb: function bb(key) { - assert(Buffer.isBuffer(key)); - assert(key.length - 5 === 5); - key = key.slice(6); - return key.readUInt32BE(0, true); - } +exports.txdb = { + prefix: bdb.key('t', ['uint32']), + R: bdb.key('R'), + r: bdb.key('r', ['uint32']), + t: bdb.key('t', ['hash256']), + c: bdb.key('c', ['hash256', 'uint32']), + d: bdb.key('d', ['hash256', 'uint32']), + s: bdb.key('s', ['hash256', 'uint32']), + p: bdb.key('p', ['hash256']), + m: bdb.key('m', ['uint32', 'hash256']), + h: bdb.key('h', ['uint32', 'hash256']), + T: bdb.key('T', ['uint32', 'hash256']), + P: bdb.key('P', ['uint32', 'hash256']), + M: bdb.key('M', ['uint32', 'uint32', 'hash256']), + H: bdb.key('H', ['uint32', 'uint32', 'hash256']), + C: bdb.key('C', ['uint32', 'hash256', 'uint32']), + b: bdb.key('b', ['uint32']) }; diff --git a/lib/wallet/masterkey.js b/lib/wallet/masterkey.js index d7525f636..d1786e4a1 100644 --- a/lib/wallet/masterkey.js +++ b/lib/wallet/masterkey.js @@ -6,694 +6,749 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); +const bio = require('bufio'); +const {Lock} = require('bmutex'); +const random = require('bcrypto/lib/random'); +const cleanse = require('bcrypto/lib/cleanse'); +const aes = require('bcrypto/lib/aes'); +const sha256 = require('bcrypto/lib/sha256'); +const hash256 = require('bcrypto/lib/hash256'); +const secp256k1 = require('bcrypto/lib/secp256k1'); +const pbkdf2 = require('bcrypto/lib/pbkdf2'); +const scrypt = require('bcrypto/lib/scrypt'); const util = require('../utils/util'); -const Lock = require('../utils/lock'); -const random = require('../crypto/random'); -const cleanse = require('../crypto/cleanse'); -const aes = require('../crypto/aes'); -const pbkdf2 = require('../crypto/pbkdf2'); -const scrypt = require('../crypto/scrypt').derive; -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); -const encoding = require('../utils/encoding'); -const HD = require('../hd/hd'); -const Mnemonic = HD.Mnemonic; +const HDPrivateKey = require('../hd/private'); +const Mnemonic = require('../hd/mnemonic'); +const {encoding} = bio; +const {inspectSymbol} = require('../utils'); /** + * Master Key * Master BIP32 key which can exist * in a timed out encrypted state. * @alias module:wallet.MasterKey - * @constructor - * @param {Object} options */ -function MasterKey(options) { - if (!(this instanceof MasterKey)) - return new MasterKey(options); - - this.encrypted = false; - this.iv = null; - this.ciphertext = null; - this.key = null; - this.mnemonic = null; - - this.alg = MasterKey.alg.PBKDF2; - this.N = 50000; - this.r = 0; - this.p = 0; - - this.aesKey = null; - this.timer = null; - this.until = 0; - this._onTimeout = this.lock.bind(this); - this.locker = new Lock(); - - if (options) - this.fromOptions(options); -} +class MasterKey { + /** + * Create a master key. + * @constructor + * @param {Object} options + */ -/** - * Key derivation salt. - * @const {Buffer} - * @default - */ + constructor(options) { + this.encrypted = false; + this.iv = null; + this.ciphertext = null; + this.key = null; + this.mnemonic = null; -MasterKey.SALT = Buffer.from('bcoin', 'ascii'); + this.alg = MasterKey.alg.PBKDF2; + this.n = 50000; + this.r = 0; + this.p = 0; -/** - * Key derivation algorithms. - * @enum {Number} - * @default - */ + this.aesKey = null; + this.timer = null; + this.until = 0; + this.locker = new Lock(); -MasterKey.alg = { - PBKDF2: 0, - SCRYPT: 1 -}; + if (options) + this.fromOptions(options); + } -/** - * Key derivation algorithms by value. - * @enum {String} - * @default - */ + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ -MasterKey.algByVal = { - 0: 'PBKDF2', - 1: 'SCRYPT' -}; + fromOptions(options) { + assert(options); -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ + if (options.encrypted != null) { + assert(typeof options.encrypted === 'boolean'); + this.encrypted = options.encrypted; + } -MasterKey.prototype.fromOptions = function fromOptions(options) { - assert(options); + if (options.iv) { + assert(Buffer.isBuffer(options.iv)); + this.iv = options.iv; + } - if (options.encrypted != null) { - assert(typeof options.encrypted === 'boolean'); - this.encrypted = options.encrypted; - } + if (options.ciphertext) { + assert(Buffer.isBuffer(options.ciphertext)); + this.ciphertext = options.ciphertext; + } - if (options.iv) { - assert(Buffer.isBuffer(options.iv)); - this.iv = options.iv; - } + if (options.key) { + assert(HDPrivateKey.isHDPrivateKey(options.key)); + this.key = options.key; + } - if (options.ciphertext) { - assert(Buffer.isBuffer(options.ciphertext)); - this.ciphertext = options.ciphertext; - } + if (options.mnemonic) { + assert(options.mnemonic instanceof Mnemonic); + this.mnemonic = options.mnemonic; + } - if (options.key) { - assert(HD.isPrivate(options.key)); - this.key = options.key; - } + if (options.alg != null) { + if (typeof options.alg === 'string') { + this.alg = MasterKey.alg[options.alg.toUpperCase()]; + assert(this.alg != null, 'Unknown algorithm.'); + } else { + assert(typeof options.alg === 'number'); + assert(MasterKey.algByVal[options.alg]); + this.alg = options.alg; + } + } - if (options.mnemonic) { - assert(options.mnemonic instanceof Mnemonic); - this.mnemonic = options.mnemonic; - } + if (options.rounds != null) { + assert((options.rounds >>> 0) === options.rounds); + this.rounds = options.rounds; + } - if (options.alg != null) { - if (typeof options.alg === 'string') { - this.alg = MasterKey.alg[options.alg.toUpperCase()]; - assert(this.alg != null, 'Unknown algorithm.'); - } else { - assert(typeof options.alg === 'number'); - assert(MasterKey.algByVal[options.alg]); - this.alg = options.alg; + if (options.n != null) { + assert((options.n >>> 0) === options.n); + this.n = options.n; } - } - if (options.rounds != null) { - assert(util.isU32(options.rounds)); - this.N = options.rounds; - } + if (options.r != null) { + assert((options.r >>> 0) === options.r); + this.r = options.r; + } + + if (options.p != null) { + assert((options.p >>> 0) === options.p); + this.p = options.p; + } - if (options.N != null) { - assert(util.isU32(options.N)); - this.N = options.N; + assert(this.encrypted ? !this.key : this.key); + + return this; } - if (options.r != null) { - assert(util.isU32(options.r)); - this.r = options.r; + /** + * Instantiate master key from options. + * @returns {MasterKey} + */ + + static fromOptions(options) { + return new this().fromOptions(options); } - if (options.p != null) { - assert(util.isU32(options.p)); - this.p = options.p; + /** + * Decrypt the key and set a timeout to destroy decrypted data. + * @param {Buffer|String} passphrase - Zero this yourself. + * @param {Number} [timeout=60000] timeout in ms. + * @returns {Promise} - Returns {@link HDPrivateKey}. + */ + + async unlock(passphrase, timeout) { + const _unlock = await this.locker.lock(); + try { + return await this._unlock(passphrase, timeout); + } finally { + _unlock(); + } } - assert(this.encrypted ? !this.key : this.key); + /** + * Decrypt the key without a lock. + * @private + * @param {Buffer|String} passphrase - Zero this yourself. + * @param {Number} [timeout=60000] timeout in ms. + * @returns {Promise} - Returns {@link HDPrivateKey}. + */ + + async _unlock(passphrase, timeout) { + if (this.key) { + if (this.encrypted) { + assert(this.timer != null); + this.start(timeout); + } + return this.key; + } - return this; -}; + if (!passphrase) + throw new Error('No passphrase.'); -/** - * Instantiate master key from options. - * @returns {MasterKey} - */ + assert(this.encrypted); -MasterKey.fromOptions = function fromOptions(options) { - return new MasterKey().fromOptions(options); -}; + const key = await this.derive(passphrase); + const data = aes.decipher(this.ciphertext, key, this.iv); -/** - * Decrypt the key and set a timeout to destroy decrypted data. - * @param {Buffer|String} passphrase - Zero this yourself. - * @param {Number} [timeout=60000] timeout in ms. - * @returns {Promise} - Returns {@link HDPrivateKey}. - */ + this.readKey(data); -MasterKey.prototype.unlock = async function unlock(passphrase, timeout) { - const _unlock = await this.locker.lock(); - try { - return await this._unlock(passphrase, timeout); - } finally { - _unlock(); - } -}; + this.start(timeout); -/** - * Decrypt the key without a lock. - * @private - * @param {Buffer|String} passphrase - Zero this yourself. - * @param {Number} [timeout=60000] timeout in ms. - * @returns {Promise} - Returns {@link HDPrivateKey}. - */ + this.aesKey = key; -MasterKey.prototype._unlock = async function _unlock(passphrase, timeout) { - if (this.key) { - if (this.encrypted) { - assert(this.timer != null); - this.start(timeout); - } return this.key; } - if (!passphrase) - throw new Error('No passphrase.'); + /** + * Start the destroy timer. + * @private + * @param {Number} [timeout=60] timeout in seconds. + */ - assert(this.encrypted); + start(timeout) { + if (!timeout) + timeout = 60; - const key = await this.derive(passphrase); - const data = aes.decipher(this.ciphertext, key, this.iv); + this.stop(); - this.fromKeyRaw(data); + if (timeout === -1) + return; - this.start(timeout); + assert((timeout >>> 0) === timeout); - this.aesKey = key; + this.until = util.now() + timeout; + this.timer = setTimeout(() => this.lock(), timeout * 1000); + } - return this.key; -}; + /** + * Stop the destroy timer. + * @private + */ -/** - * Start the destroy timer. - * @private - * @param {Number} [timeout=60000] timeout in ms. - */ + stop() { + if (this.timer != null) { + clearTimeout(this.timer); + this.timer = null; + this.until = 0; + } + } + + /** + * Derive an aes key based on params. + * @param {String|Buffer} passphrase + * @returns {Promise} + */ + + async derive(passwd) { + const salt = MasterKey.SALT; + const n = this.n; + const r = this.r; + const p = this.p; + + if (typeof passwd === 'string') + passwd = Buffer.from(passwd, 'utf8'); + + switch (this.alg) { + case MasterKey.alg.PBKDF2: + return pbkdf2.deriveAsync(sha256, passwd, salt, n, 32); + case MasterKey.alg.SCRYPT: + return scrypt.deriveAsync(passwd, salt, n, r, p, 32); + default: + throw new Error(`Unknown algorithm: ${this.alg}.`); + } + } -MasterKey.prototype.start = function start(timeout) { - if (!timeout) - timeout = 60; + /** + * Encrypt data with in-memory aes key. + * @param {Buffer} data + * @param {Buffer} iv + * @returns {Buffer} + */ - this.stop(); + encipher(data, iv) { + if (!this.aesKey) + return null; - if (timeout === -1) - return; + return aes.encipher(data, this.aesKey, iv.slice(0, 16)); + } - this.until = util.now() + timeout; - this.timer = setTimeout(this._onTimeout, timeout * 1000); -}; + /** + * Decrypt data with in-memory aes key. + * @param {Buffer} data + * @param {Buffer} iv + * @returns {Buffer} + */ -/** - * Stop the destroy timer. - * @private - */ + decipher(data, iv) { + if (!this.aesKey) + return null; -MasterKey.prototype.stop = function stop() { - if (this.timer != null) { - clearTimeout(this.timer); - this.timer = null; - this.until = 0; + return aes.decipher(data, this.aesKey, iv.slice(0, 16)); } -}; -/** - * Derive an aes key based on params. - * @param {String|Buffer} passphrase - * @returns {Promise} - */ + /** + * Destroy the key by zeroing the + * privateKey and chainCode. Stop + * the timer if there is one. + * @returns {Promise} + */ + + async lock() { + const unlock = await this.locker.lock(); + try { + return await this._lock(); + } finally { + unlock(); + } + } + + /** + * Destroy the key by zeroing the + * privateKey and chainCode. Stop + * the timer if there is one. + */ + + _lock() { + if (!this.encrypted) { + assert(this.timer == null); + assert(this.key); + return; + } -MasterKey.prototype.derive = async function derive(passwd) { - const salt = MasterKey.SALT; - const N = this.N; - const r = this.r; - const p = this.p; + this.stop(); - if (typeof passwd === 'string') - passwd = Buffer.from(passwd, 'utf8'); + if (this.key) { + this.key.destroy(true); + this.key = null; + } - switch (this.alg) { - case MasterKey.alg.PBKDF2: - return await pbkdf2.deriveAsync(passwd, salt, N, 32, 'sha256'); - case MasterKey.alg.SCRYPT: - return await scrypt.deriveAsync(passwd, salt, N, r, p, 32); - default: - throw new Error(`Unknown algorithm: ${this.alg}.`); + if (this.aesKey) { + cleanse(this.aesKey); + this.aesKey = null; + } } -}; -/** - * Encrypt data with in-memory aes key. - * @param {Buffer} data - * @param {Buffer} iv - * @returns {Buffer} - */ + /** + * Destroy the key permanently. + */ -MasterKey.prototype.encipher = function encipher(data, iv) { - if (!this.aesKey) - return null; + async destroy() { + await this.lock(); + this.locker.destroy(); + } - if (typeof iv === 'string') - iv = Buffer.from(iv, 'hex'); + /** + * Decrypt the key permanently. + * @param {Buffer|String} passphrase - Zero this yourself. + * @returns {Promise} + */ + + async decrypt(passphrase, clean) { + const unlock = await this.locker.lock(); + try { + return await this._decrypt(passphrase, clean); + } finally { + unlock(); + } + } - return aes.encipher(data, this.aesKey, iv.slice(0, 16)); -}; + /** + * Decrypt the key permanently without a lock. + * @private + * @param {Buffer|String} passphrase - Zero this yourself. + * @returns {Promise} + */ -/** - * Decrypt data with in-memory aes key. - * @param {Buffer} data - * @param {Buffer} iv - * @returns {Buffer} - */ + async _decrypt(passphrase, clean) { + if (!this.encrypted) + throw new Error('Master key is not encrypted.'); -MasterKey.prototype.decipher = function decipher(data, iv) { - if (!this.aesKey) - return null; + if (!passphrase) + throw new Error('No passphrase provided.'); - if (typeof iv === 'string') - iv = Buffer.from(iv, 'hex'); + this._lock(); - return aes.decipher(data, this.aesKey, iv.slice(0, 16)); -}; + const key = await this.derive(passphrase); + const data = aes.decipher(this.ciphertext, key, this.iv); -/** - * Destroy the key by zeroing the - * privateKey and chainCode. Stop - * the timer if there is one. - * @returns {Promise} - */ + this.readKey(data); + this.encrypted = false; + this.iv = null; + this.ciphertext = null; -MasterKey.prototype.lock = async function lock() { - const unlock = await this.locker.lock(); - try { - return await this._lock(); - } finally { - unlock(); - } -}; + if (!clean) { + cleanse(key); + return null; + } -/** - * Destroy the key by zeroing the - * privateKey and chainCode. Stop - * the timer if there is one. - */ + return key; + } -MasterKey.prototype._lock = function _lock() { - if (!this.encrypted) { - assert(this.timer == null); - assert(this.key); - return; + /** + * Encrypt the key permanently. + * @param {Buffer|String} passphrase - Zero this yourself. + * @returns {Promise} + */ + + async encrypt(passphrase, clean) { + const unlock = await this.locker.lock(); + try { + return await this._encrypt(passphrase, clean); + } finally { + unlock(); + } } - this.stop(); + /** + * Encrypt the key permanently without a lock. + * @private + * @param {Buffer|String} passphrase - Zero this yourself. + * @returns {Promise} + */ - if (this.key) { - this.key.destroy(true); - this.key = null; - } + async _encrypt(passphrase, clean) { + if (this.encrypted) + throw new Error('Master key is already encrypted.'); - if (this.aesKey) { - cleanse(this.aesKey); - this.aesKey = null; - } -}; + if (!passphrase) + throw new Error('No passphrase provided.'); -/** - * Destroy the key permanently. - */ + const raw = this.writeKey(); + const iv = random.randomBytes(16); -MasterKey.prototype.destroy = async function destroy() { - await this.lock(); - this.locker.destroy(); -}; + this.stop(); -/** - * Decrypt the key permanently. - * @param {Buffer|String} passphrase - Zero this yourself. - * @returns {Promise} - */ + const key = await this.derive(passphrase); + const data = aes.encipher(raw, key, iv); -MasterKey.prototype.decrypt = async function decrypt(passphrase, clean) { - const unlock = await this.locker.lock(); - try { - return await this._decrypt(passphrase, clean); - } finally { - unlock(); + this.key = null; + this.mnemonic = null; + this.encrypted = true; + this.iv = iv; + this.ciphertext = data; + + if (!clean) { + cleanse(key); + return null; + } + + return key; } -}; -/** - * Decrypt the key permanently without a lock. - * @private - * @param {Buffer|String} passphrase - Zero this yourself. - * @returns {Promise} - */ + /** + * Calculate key serialization size. + * @returns {Number} + */ -MasterKey.prototype._decrypt = async function _decrypt(passphrase, clean) { - if (!this.encrypted) - throw new Error('Master key is not encrypted.'); + keySize() { + let size = 0; - if (!passphrase) - throw new Error('No passphrase provided.'); + size += 64; + size += 1; - this._lock(); + if (this.mnemonic) + size += this.mnemonic.getSize(); - const key = await this.derive(passphrase); - const data = aes.decipher(this.ciphertext, key, this.iv); + return size; + } - this.fromKeyRaw(data); - this.encrypted = false; - this.iv = null; - this.ciphertext = null; + /** + * Serialize key and menmonic to a single buffer. + * @returns {Buffer} + */ - if (!clean) { - cleanse(key); - return null; - } + writeKey() { + const bw = bio.write(this.keySize()); - return key; -}; + bw.writeBytes(this.key.chainCode); + bw.writeBytes(this.key.privateKey); -/** - * Encrypt the key permanently. - * @param {Buffer|String} passphrase - Zero this yourself. - * @returns {Promise} - */ + if (this.mnemonic) { + bw.writeU8(1); + this.mnemonic.toWriter(bw); + } else { + bw.writeU8(0); + } -MasterKey.prototype.encrypt = async function encrypt(passphrase, clean) { - const unlock = await this.locker.lock(); - try { - return await this._encrypt(passphrase, clean); - } finally { - unlock(); + return bw.render(); } -}; - -/** - * Encrypt the key permanently without a lock. - * @private - * @param {Buffer|String} passphrase - Zero this yourself. - * @returns {Promise} - */ -MasterKey.prototype._encrypt = async function _encrypt(passphrase, clean) { - if (this.encrypted) - throw new Error('Master key is already encrypted.'); + /** + * Inject properties from serialized key. + * @param {Buffer} data + */ - if (!passphrase) - throw new Error('No passphrase provided.'); + readKey(data) { + const br = bio.read(data); - const raw = this.toKeyRaw(); - const iv = random.randomBytes(16); + this.key = new HDPrivateKey(); - this.stop(); + if (isLegacy(data)) { + br.seek(13); + this.key.chainCode = br.readBytes(32); + assert(br.readU8() === 0); + this.key.privateKey = br.readBytes(32); + } else { + this.key.chainCode = br.readBytes(32); + this.key.privateKey = br.readBytes(32); + } - const key = await this.derive(passphrase); - const data = aes.encipher(raw, key, iv); + this.key.publicKey = secp256k1.publicKeyCreate(this.key.privateKey, true); - this.key = null; - this.mnemonic = null; - this.encrypted = true; - this.iv = iv; - this.ciphertext = data; + if (br.readU8() === 1) + this.mnemonic = Mnemonic.fromReader(br); - if (!clean) { - cleanse(key); - return null; + return this; } - return key; -}; + /** + * Calculate serialization size. + * @returns {Number} + */ -/** - * Calculate key serialization size. - * @returns {Number} - */ + getSize() { + let size = 0; -MasterKey.prototype.getKeySize = function getKeySize() { - let size = 0; + if (this.encrypted) { + size += 1; + size += encoding.sizeVarBytes(this.iv); + size += encoding.sizeVarBytes(this.ciphertext); + size += 13; + return size; + } - size += this.key.getSize(); - size += 1; + size += 1; + size += this.keySize(); - if (this.mnemonic) - size += this.mnemonic.getSize(); + return size; + } - return size; -}; + /** + * Serialize the key in the form of: + * `[enc-flag][iv?][ciphertext?][extended-key?]` + * @returns {Buffer} + */ -/** - * Serialize key and menmonic to a single buffer. - * @returns {Buffer} - */ + toWriter(bw) { + if (this.encrypted) { + bw.writeU8(1); + bw.writeVarBytes(this.iv); + bw.writeVarBytes(this.ciphertext); -MasterKey.prototype.toKeyRaw = function toKeyRaw() { - const bw = new StaticWriter(this.getKeySize()); + bw.writeU8(this.alg); + bw.writeU32(this.n); + bw.writeU32(this.r); + bw.writeU32(this.p); - this.key.toWriter(bw); + return bw; + } - if (this.mnemonic) { - bw.writeU8(1); - this.mnemonic.toWriter(bw); - } else { bw.writeU8(0); - } - return bw.render(); -}; + bw.writeBytes(this.key.chainCode); + bw.writeBytes(this.key.privateKey); -/** - * Inject properties from serialized key. - * @param {Buffer} data - */ + if (this.mnemonic) { + bw.writeU8(1); + this.mnemonic.toWriter(bw); + } else { + bw.writeU8(0); + } -MasterKey.prototype.fromKeyRaw = function fromKeyRaw(data) { - const br = new BufferReader(data); + return bw; + } - this.key = HD.PrivateKey.fromReader(br); + /** + * Serialize the key in the form of: + * `[enc-flag][iv?][ciphertext?][extended-key?]` + * @returns {Buffer} + */ - if (br.readU8() === 1) - this.mnemonic = Mnemonic.fromReader(br); + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } - return this; -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} raw + */ -/** - * Calculate serialization size. - * @returns {Number} - */ + fromReader(br) { + this.encrypted = br.readU8() === 1; -MasterKey.prototype.getSize = function getSize() { - let size = 0; + if (this.encrypted) { + this.iv = br.readVarBytes(); + this.ciphertext = br.readVarBytes(); - if (this.encrypted) { - size += 1; - size += encoding.sizeVarBytes(this.iv); - size += encoding.sizeVarBytes(this.ciphertext); - size += 13; - return size; - } + this.alg = br.readU8(); - size += 1; - size += encoding.sizeVarlen(this.getKeySize()); + assert(this.alg < MasterKey.algByVal.length); - return size; -}; + this.n = br.readU32(); + this.r = br.readU32(); + this.p = br.readU32(); -/** - * Serialize the key in the form of: - * `[enc-flag][iv?][ciphertext?][extended-key?]` - * @returns {Buffer} - */ - -MasterKey.prototype.toRaw = function toRaw() { - const bw = new StaticWriter(this.getSize()); + return this; + } - if (this.encrypted) { - bw.writeU8(1); - bw.writeVarBytes(this.iv); - bw.writeVarBytes(this.ciphertext); + this.key = new HDPrivateKey(); + this.key.chainCode = br.readBytes(32); + this.key.privateKey = br.readBytes(32); + this.key.publicKey = secp256k1.publicKeyCreate(this.key.privateKey, true); - bw.writeU8(this.alg); - bw.writeU32(this.N); - bw.writeU32(this.r); - bw.writeU32(this.p); + if (br.readU8() === 1) + this.mnemonic = Mnemonic.fromReader(br); - return bw.render(); + return this; } - bw.writeU8(0); + /** + * Instantiate master key from serialized data. + * @returns {MasterKey} + */ - // NOTE: useless varint - const size = this.getKeySize(); - bw.writeVarint(size); + static fromReader(br) { + return new this().fromReader(br); + } - bw.writeBytes(this.key.toRaw()); + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} raw + */ - if (this.mnemonic) { - bw.writeU8(1); - this.mnemonic.toWriter(bw); - } else { - bw.writeU8(0); + fromRaw(raw) { + return this.fromReader(bio.read(raw)); } - return bw.render(); -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} raw - */ + /** + * Instantiate master key from serialized data. + * @returns {MasterKey} + */ -MasterKey.prototype.fromRaw = function fromRaw(raw) { - const br = new BufferReader(raw); + static fromRaw(raw) { + return new this().fromRaw(raw); + } - this.encrypted = br.readU8() === 1; + /** + * Inject properties from an HDPrivateKey. + * @private + * @param {HDPrivateKey} key + * @param {Mnemonic?} mnemonic + */ + + fromKey(key, mnemonic) { + this.encrypted = false; + this.iv = null; + this.ciphertext = null; + this.key = key; + this.mnemonic = mnemonic || null; + return this; + } - if (this.encrypted) { - this.iv = br.readVarBytes(); - this.ciphertext = br.readVarBytes(); + /** + * Instantiate master key from an HDPrivateKey. + * @param {HDPrivateKey} key + * @param {Mnemonic?} mnemonic + * @returns {MasterKey} + */ - this.alg = br.readU8(); + static fromKey(key, mnemonic) { + return new this().fromKey(key, mnemonic); + } - assert(MasterKey.algByVal[this.alg]); + /** + * Convert master key to a jsonifiable object. + * @param {Network?} network + * @param {Boolean?} unsafe - Whether to include + * the key data in the JSON. + * @returns {Object} + */ - this.N = br.readU32(); - this.r = br.readU32(); - this.p = br.readU32(); + toJSON(network, unsafe) { + if (this.encrypted) { + return { + encrypted: true, + until: this.until, + iv: this.iv.toString('hex'), + ciphertext: unsafe ? this.ciphertext.toString('hex') : undefined, + algorithm: MasterKey.algByVal[this.alg].toLowerCase(), + n: this.n, + r: this.r, + p: this.p + }; + } - return this; + return { + encrypted: false, + key: unsafe ? this.key.toJSON(network) : undefined, + mnemonic: unsafe && this.mnemonic ? this.mnemonic.toJSON() : undefined + }; } - // NOTE: useless varint - br.readVarint(); - - this.key = HD.PrivateKey.fromRaw(br.readBytes(82)); + /** + * Inspect the key. + * @returns {Object} + */ - if (br.readU8() === 1) - this.mnemonic = Mnemonic.fromReader(br); + [inspectSymbol]() { + const json = this.toJSON(null, true); - return this; -}; + if (this.key) + json.key = this.key.toJSON(); -/** - * Instantiate master key from serialized data. - * @returns {MasterKey} - */ + if (this.mnemonic) + json.mnemonic = this.mnemonic.toJSON(); -MasterKey.fromRaw = function fromRaw(raw) { - return new MasterKey().fromRaw(raw); -}; + return json; + } -/** - * Inject properties from an HDPrivateKey. - * @private - * @param {HDPrivateKey} key - * @param {Mnemonic?} mnemonic - */ + /** + * Test whether an object is a MasterKey. + * @param {Object} obj + * @returns {Boolean} + */ -MasterKey.prototype.fromKey = function fromKey(key, mnemonic) { - this.encrypted = false; - this.iv = null; - this.ciphertext = null; - this.key = key; - this.mnemonic = mnemonic || null; - return this; -}; + static isMasterKey(obj) { + return obj instanceof MasterKey; + } +} /** - * Instantiate master key from an HDPrivateKey. - * @param {HDPrivateKey} key - * @param {Mnemonic?} mnemonic - * @returns {MasterKey} + * Key derivation salt. + * @const {Buffer} + * @default */ -MasterKey.fromKey = function fromKey(key, mnemonic) { - return new MasterKey().fromKey(key, mnemonic); -}; +MasterKey.SALT = Buffer.from('bcoin', 'ascii'); /** - * Convert master key to a jsonifiable object. - * @param {Boolean?} unsafe - Whether to include - * the key data in the JSON. - * @returns {Object} + * Key derivation algorithms. + * @enum {Number} + * @default */ -MasterKey.prototype.toJSON = function toJSON(unsafe) { - if (this.encrypted) { - return { - encrypted: true, - until: this.until, - iv: this.iv.toString('hex'), - ciphertext: unsafe ? this.ciphertext.toString('hex') : undefined, - algorithm: MasterKey.algByVal[this.alg].toLowerCase(), - N: this.N, - r: this.r, - p: this.p - }; - } - - return { - encrypted: false, - key: unsafe ? this.key.toJSON() : undefined, - mnemonic: unsafe && this.mnemonic ? this.mnemonic.toJSON() : undefined - }; +MasterKey.alg = { + PBKDF2: 0, + SCRYPT: 1 }; /** - * Inspect the key. - * @returns {Object} + * Key derivation algorithms by value. + * @enum {String} + * @default */ -MasterKey.prototype.inspect = function inspect() { - const json = this.toJSON(true); +MasterKey.algByVal = [ + 'PBKDF2', + 'SCRYPT' +]; - if (this.key) - json.key = this.key.toJSON(); +/* + * Helpers + */ - if (this.mnemonic) - json.mnemonic = this.mnemonic.toJSON(); +function isLegacy(data) { + if (data.length < 82) + return false; - return json; -}; + const key = data.slice(0, 78); + const chk = data.readUInt32LE(78, true); -/** - * Test whether an object is a MasterKey. - * @param {Object} obj - * @returns {Boolean} - */ + const hash = hash256.digest(key); -MasterKey.isMasterKey = function isMasterKey(obj) { - return obj instanceof MasterKey; -}; + return hash.readUInt32LE(0, true) === chk; +} /* * Expose diff --git a/lib/wallet/node.js b/lib/wallet/node.js new file mode 100644 index 000000000..2f96e9de0 --- /dev/null +++ b/lib/wallet/node.js @@ -0,0 +1,139 @@ +/*! + * server.js - wallet server for bcoin + * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const assert = require('bsert'); +const Node = require('../node/node'); +const WalletDB = require('./walletdb'); +const HTTP = require('./http'); +const Client = require('./client'); +const RPC = require('./rpc'); + +/** + * Wallet Node + * @extends Node + */ + +class WalletNode extends Node { + /** + * Create a wallet node. + * @constructor + * @param {Object?} options + */ + + constructor(options) { + super('bcoin', 'wallet.conf', 'wallet.log', options); + + this.opened = false; + + this.client = new Client({ + network: this.network, + url: this.config.str('node-url'), + host: this.config.str('node-host'), + port: this.config.uint('node-port', this.network.rpcPort), + ssl: this.config.bool('node-ssl'), + apiKey: this.config.str('node-api-key') + }); + + this.wdb = new WalletDB({ + network: this.network, + logger: this.logger, + workers: this.workers, + client: this.client, + prefix: this.config.prefix, + memory: this.config.bool('memory'), + maxFiles: this.config.uint('max-files'), + cacheSize: this.config.mb('cache-size'), + witness: this.config.bool('witness'), + checkpoints: this.config.bool('checkpoints'), + wipeNoReally: this.config.bool('wipe-no-really'), + spv: this.config.bool('spv') + }); + + this.rpc = new RPC(this); + + this.http = new HTTP({ + network: this.network, + logger: this.logger, + node: this, + prefix: this.config.prefix, + ssl: this.config.bool('ssl'), + keyFile: this.config.path('ssl-key'), + certFile: this.config.path('ssl-cert'), + host: this.config.str('http-host'), + port: this.config.uint('http-port'), + apiKey: this.config.str('api-key'), + walletAuth: this.config.bool('wallet-auth'), + noAuth: this.config.bool('no-auth'), + cors: this.config.bool('cors'), + adminToken: this.config.str('admin-token') + }); + + this.init(); + } + + /** + * Initialize the node. + * @private + */ + + init() { + this.wdb.on('error', err => this.error(err)); + this.http.on('error', err => this.error(err)); + + this.loadPlugins(); + } + + /** + * Open the node and all its child objects, + * wait for the database to load. + * @returns {Promise} + */ + + async open() { + assert(!this.opened, 'WalletNode is already open.'); + this.opened = true; + + await this.handlePreopen(); + await this.wdb.open(); + + this.rpc.wallet = this.wdb.primary; + + await this.openPlugins(); + + await this.http.open(); + await this.handleOpen(); + + this.logger.info('Wallet node is loaded.'); + } + + /** + * Close the node, wait for the database to close. + * @returns {Promise} + */ + + async close() { + assert(this.opened, 'WalletNode is not open.'); + this.opened = false; + + await this.handlePreclose(); + await this.http.close(); + + await this.closePlugins(); + + this.rpc.wallet = null; + + await this.wdb.close(); + await this.handleClose(); + } +} + +/* + * Expose + */ + +module.exports = WalletNode; diff --git a/lib/wallet/nodeclient.js b/lib/wallet/nodeclient.js index f22dca2ef..c88b8f972 100644 --- a/lib/wallet/nodeclient.js +++ b/lib/wallet/nodeclient.js @@ -6,184 +6,213 @@ 'use strict'; -const AsyncObject = require('../utils/asyncobject'); +const assert = require('bsert'); +const AsyncEmitter = require('bevent'); /** - * NodeClient - * Sort of a fake local client for separation of concerns. + * Node Client * @alias module:node.NodeClient - * @constructor */ -function NodeClient(node) { - if (!(this instanceof NodeClient)) - return new NodeClient(node); - - AsyncObject.call(this); - - this.node = node; - this.network = node.network; - this.filter = null; - this.listen = false; - - this._init(); +class NodeClient extends AsyncEmitter { + /** + * Create a node client. + * @constructor + */ + + constructor(node) { + super(); + + this.node = node; + this.network = node.network; + this.filter = null; + this.opened = false; + + this.init(); + } + + /** + * Initialize the client. + */ + + init() { + this.node.on('connect', (entry, block) => { + if (!this.opened) + return; + + this.emit('block connect', entry, block.txs); + }); + + this.node.on('disconnect', (entry, block) => { + if (!this.opened) + return; + + this.emit('block disconnect', entry); + }); + + this.node.on('tx', (tx) => { + if (!this.opened) + return; + + this.emit('tx', tx); + }); + + this.node.on('reset', (tip) => { + if (!this.opened) + return; + + this.emit('chain reset', tip); + }); + } + + /** + * Open the client. + * @returns {Promise} + */ + + async open(options) { + assert(!this.opened, 'NodeClient is already open.'); + this.opened = true; + setImmediate(() => this.emit('connect')); + } + + /** + * Close the client. + * @returns {Promise} + */ + + async close() { + assert(this.opened, 'NodeClient is not open.'); + this.opened = false; + setImmediate(() => this.emit('disconnect')); + } + + /** + * Add a listener. + * @param {String} type + * @param {Function} handler + */ + + bind(type, handler) { + return this.on(type, handler); + } + + /** + * Add a listener. + * @param {String} type + * @param {Function} handler + */ + + hook(type, handler) { + return this.on(type, handler); + } + + /** + * Get chain tip. + * @returns {Promise} + */ + + async getTip() { + return this.node.chain.tip; + } + + /** + * Get chain entry. + * @param {Hash} hash + * @returns {Promise} + */ + + async getEntry(hash) { + const entry = await this.node.chain.getEntry(hash); + + if (!entry) + return null; + + if (!await this.node.chain.isMainChain(entry)) + return null; + + return entry; + } + + /** + * Send a transaction. Do not wait for promise. + * @param {TX} tx + * @returns {Promise} + */ + + async send(tx) { + this.node.relay(tx); + } + + /** + * Set bloom filter. + * @param {Bloom} filter + * @returns {Promise} + */ + + async setFilter(filter) { + this.filter = filter; + this.node.pool.setFilter(filter); + } + + /** + * Add data to filter. + * @param {Buffer} data + * @returns {Promise} + */ + + async addFilter(data) { + this.node.pool.queueFilterLoad(); + } + + /** + * Reset filter. + * @returns {Promise} + */ + + async resetFilter() { + this.node.pool.queueFilterLoad(); + } + + /** + * Esimate smart fee. + * @param {Number?} blocks + * @returns {Promise} + */ + + async estimateFee(blocks) { + if (!this.node.fees) + return this.network.feeRate; + + return this.node.fees.estimateFee(blocks); + } + + /** + * Get hash range. + * @param {Number} start + * @param {Number} end + * @returns {Promise} + */ + + async getHashes(start = -1, end = -1) { + return this.node.chain.getHashes(start, end); + } + + /** + * Rescan for any missed transactions. + * @param {Number|Hash} start - Start block. + * @param {Bloom} filter + * @param {Function} iter - Iterator. + * @returns {Promise} + */ + + async rescan(start) { + return this.node.chain.scan(start, this.filter, (entry, txs) => { + return this.emitAsync('block rescan', entry, txs); + }); + } } -Object.setPrototypeOf(NodeClient.prototype, AsyncObject.prototype); - -/** - * Initialize the client. - * @returns {Promise} - */ - -NodeClient.prototype._init = function _init() { - this.node.on('connect', (entry, block) => { - if (!this.listen) - return; - - this.emit('block connect', entry, block.txs); - }); - - this.node.on('disconnect', (entry, block) => { - if (!this.listen) - return; - - this.emit('block disconnect', entry); - }); - - this.node.on('tx', (tx) => { - if (!this.listen) - return; - - this.emit('tx', tx); - }); - - this.node.on('reset', (tip) => { - if (!this.listen) - return; - - this.emit('chain reset', tip); - }); -}; - -/** - * Open the client. - * @returns {Promise} - */ - -NodeClient.prototype._open = function _open(options) { - this.listen = true; - return Promise.resolve(); -}; - -/** - * Close the client. - * @returns {Promise} - */ - -NodeClient.prototype._close = function _close() { - this.listen = false; - return Promise.resolve(); -}; - -/** - * Get chain tip. - * @returns {Promise} - */ - -NodeClient.prototype.getTip = function getTip() { - return Promise.resolve(this.node.chain.tip); -}; - -/** - * Get chain entry. - * @param {Hash} hash - * @returns {Promise} - */ - -NodeClient.prototype.getEntry = async function getEntry(hash) { - const entry = await this.node.chain.getEntry(hash); - - if (!entry) - return null; - - if (!await this.node.chain.isMainChain(entry)) - return null; - - return entry; -}; - -/** - * Send a transaction. Do not wait for promise. - * @param {TX} tx - * @returns {Promise} - */ - -NodeClient.prototype.send = function send(tx) { - this.node.relay(tx); - return Promise.resolve(); -}; - -/** - * Set bloom filter. - * @param {Bloom} filter - * @returns {Promise} - */ - -NodeClient.prototype.setFilter = function setFilter(filter) { - this.filter = filter; - this.node.pool.setFilter(filter); - return Promise.resolve(); -}; - -/** - * Add data to filter. - * @param {Buffer} data - * @returns {Promise} - */ - -NodeClient.prototype.addFilter = function addFilter(data) { - this.node.pool.queueFilterLoad(); - return Promise.resolve(); -}; - -/** - * Reset filter. - * @returns {Promise} - */ - -NodeClient.prototype.resetFilter = function resetFilter() { - this.node.pool.queueFilterLoad(); - return Promise.resolve(); -}; - -/** - * Esimate smart fee. - * @param {Number?} blocks - * @returns {Promise} - */ - -NodeClient.prototype.estimateFee = async function estimateFee(blocks) { - if (!this.node.fees) - return this.network.feeRate; - - return this.node.fees.estimateFee(blocks); -}; - -/** - * Rescan for any missed transactions. - * @param {Number|Hash} start - Start block. - * @param {Bloom} filter - * @param {Function} iter - Iterator. - * @returns {Promise} - */ - -NodeClient.prototype.rescan = function rescan(start) { - return this.node.chain.scan(start, this.filter, (entry, txs) => { - return this.fire('block rescan', entry, txs); - }); -}; - /* * Expose */ diff --git a/lib/wallet/nullclient.js b/lib/wallet/nullclient.js new file mode 100644 index 000000000..b9126d3f2 --- /dev/null +++ b/lib/wallet/nullclient.js @@ -0,0 +1,171 @@ +/*! + * nullclient.js - node client for bcoin + * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const assert = require('bsert'); +const EventEmitter = require('events'); + +/** + * Null Client + * Sort of a fake local client for separation of concerns. + * @alias module:node.NullClient + */ + +class NullClient extends EventEmitter { + /** + * Create a client. + * @constructor + */ + + constructor(wdb) { + super(); + + this.wdb = wdb; + this.network = wdb.network; + this.opened = false; + } + + /** + * Open the client. + * @returns {Promise} + */ + + async open(options) { + assert(!this.opened, 'NullClient is already open.'); + this.opened = true; + setImmediate(() => this.emit('connect')); + } + + /** + * Close the client. + * @returns {Promise} + */ + + async close() { + assert(this.opened, 'NullClient is not open.'); + this.opened = false; + setImmediate(() => this.emit('disconnect')); + } + + /** + * Add a listener. + * @param {String} type + * @param {Function} handler + */ + + bind(type, handler) { + return this.on(type, handler); + } + + /** + * Add a listener. + * @param {String} type + * @param {Function} handler + */ + + hook(type, handler) { + return this.on(type, handler); + } + + /** + * Get chain tip. + * @returns {Promise} + */ + + async getTip() { + const {hash, height, time} = this.network.genesis; + return { hash, height, time }; + } + + /** + * Get chain entry. + * @param {Hash} hash + * @returns {Promise} + */ + + async getEntry(hash) { + return { hash, height: 0, time: 0 }; + } + + /** + * Send a transaction. Do not wait for promise. + * @param {TX} tx + * @returns {Promise} + */ + + async send(tx) { + this.wdb.emit('send', tx); + } + + /** + * Set bloom filter. + * @param {Bloom} filter + * @returns {Promise} + */ + + async setFilter(filter) { + this.wdb.emit('set filter', filter); + } + + /** + * Add data to filter. + * @param {Buffer} data + * @returns {Promise} + */ + + async addFilter(data) { + this.wdb.emit('add filter', data); + } + + /** + * Reset filter. + * @returns {Promise} + */ + + async resetFilter() { + this.wdb.emit('reset filter'); + } + + /** + * Esimate smart fee. + * @param {Number?} blocks + * @returns {Promise} + */ + + async estimateFee(blocks) { + return this.network.feeRate; + } + + /** + * Get hash range. + * @param {Number} start + * @param {Number} end + * @returns {Promise} + */ + + async getHashes(start = -1, end = -1) { + return [this.network.genesis.hash]; + } + + /** + * Rescan for any missed transactions. + * @param {Number|Hash} start - Start block. + * @param {Bloom} filter + * @param {Function} iter - Iterator. + * @returns {Promise} + */ + + async rescan(start) { + ; + } +} + +/* + * Expose + */ + +module.exports = NullClient; diff --git a/lib/wallet/path.js b/lib/wallet/path.js index ca2bc3a69..b566bbacf 100644 --- a/lib/wallet/path.js +++ b/lib/wallet/path.js @@ -6,311 +6,324 @@ 'use strict'; -const assert = require('assert'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); -const encoding = require('../utils/encoding'); +const assert = require('bsert'); +const bio = require('bufio'); const Address = require('../primitives/address'); +const {encoding} = bio; +const {inspectSymbol} = require('../utils'); /** * Path * @alias module:wallet.Path - * @constructor - * @property {WalletID} wid * @property {String} name - Account name. * @property {Number} account - Account index. * @property {Number} branch - Branch index. * @property {Number} index - Address index. - * @property {Address|null} address */ -function Path(options) { - if (!(this instanceof Path)) - return new Path(options); +class Path { + /** + * Create a path. + * @constructor + * @param {Object?} options + */ - this.keyType = Path.types.HD; + constructor(options) { + this.keyType = Path.types.HD; - this.id = null; // Passed in by caller. - this.wid = -1; // Passed in by caller. - this.name = null; // Passed in by caller. - this.account = 0; - this.branch = -1; - this.index = -1; + this.name = null; // Passed in by caller. + this.account = 0; - this.encrypted = false; - this.data = null; + this.type = Address.types.PUBKEYHASH; + this.version = -1; - // Currently unused. - this.type = Address.types.PUBKEYHASH; - this.version = -1; - this.hash = null; // Passed in by caller. + this.branch = -1; + this.index = -1; - if (options) - this.fromOptions(options); -} + this.encrypted = false; + this.data = null; -/** - * Path types. - * @enum {Number} - * @default - */ + this.hash = null; // Passed in by caller. -Path.types = { - HD: 0, - KEY: 1, - ADDRESS: 2 -}; - -/** - * Instantiate path from options object. - * @private - * @param {Object} options - * @returns {Path} - */ - -Path.prototype.fromOptions = function fromOptions(options) { - this.keyType = options.keyType; + if (options) + this.fromOptions(options); + } - this.id = options.id; - this.wid = options.wid; - this.name = options.name; - this.account = options.account; - this.branch = options.branch; - this.index = options.index; + /** + * Instantiate path from options object. + * @private + * @param {Object} options + * @returns {Path} + */ - this.encrypted = options.encrypted; - this.data = options.data; + fromOptions(options) { + this.keyType = options.keyType; - this.type = options.type; - this.version = options.version; - this.hash = options.hash; + this.name = options.name; + this.account = options.account; + this.branch = options.branch; + this.index = options.index; - return this; -}; + this.encrypted = options.encrypted; + this.data = options.data; -/** - * Instantiate path from options object. - * @param {Object} options - * @returns {Path} - */ + this.type = options.type; + this.version = options.version; + this.hash = options.hash; -Path.fromOptions = function fromOptions(options) { - return new Path().fromOptions(options); -}; + return this; + } -/** - * Clone the path object. - * @returns {Path} - */ + /** + * Instantiate path from options object. + * @param {Object} options + * @returns {Path} + */ -Path.prototype.clone = function clone() { - const path = new Path(); + static fromOptions(options) { + return new this().fromOptions(options); + } - path.keyType = this.keyType; + /** + * Clone the path object. + * @returns {Path} + */ - path.id = this.id; - path.wid = this.wid; - path.name = this.name; - path.account = this.account; - path.branch = this.branch; - path.index = this.index; + clone() { + const path = new this.constructor(); - path.encrypted = this.encrypted; - path.data = this.data; + path.keyType = this.keyType; - path.type = this.type; - path.version = this.version; - path.hash = this.hash; + path.name = this.name; + path.account = this.account; + path.branch = this.branch; + path.index = this.index; - return path; -}; + path.encrypted = this.encrypted; + path.data = this.data; -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + path.type = this.type; + path.version = this.version; + path.hash = this.hash; -Path.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); - - this.account = br.readU32(); - this.keyType = br.readU8(); - - switch (this.keyType) { - case Path.types.HD: - this.branch = br.readU32(); - this.index = br.readU32(); - break; - case Path.types.KEY: - this.encrypted = br.readU8() === 1; - this.data = br.readVarBytes(); - break; - case Path.types.ADDRESS: - // Hash will be passed in by caller. - break; - default: - assert(false); - break; + return path; } - this.version = br.readI8(); - this.type = br.readU8(); - - if (this.type === 129 || this.type === 130) - this.type = 4; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + const br = bio.read(data); + + this.account = br.readU32(); + this.keyType = br.readU8(); + + const flags = br.readU8(); + + this.type = flags & 7; + this.version = flags >>> 3; + + if (this.version === 0x1f) + this.version = -1; + + switch (this.keyType) { + case Path.types.HD: + this.branch = br.readU32(); + this.index = br.readU32(); + break; + case Path.types.KEY: + this.encrypted = br.readU8() === 1; + this.data = br.readVarBytes(); + break; + case Path.types.ADDRESS: + // Hash will be passed in by caller. + break; + default: + assert(false); + break; + } + + return this; + } - return this; -}; + /** + * Instantiate path from serialized data. + * @param {Buffer} data + * @returns {Path} + */ -/** - * Instantiate path from serialized data. - * @param {Buffer} data - * @returns {Path} - */ + static fromRaw(data) { + return new this().fromRaw(data); + } -Path.fromRaw = function fromRaw(data) { - return new Path().fromRaw(data); -}; + /** + * Calculate serialization size. + * @returns {Number} + */ -/** - * Calculate serialization size. - * @returns {Number} - */ + getSize() { + let size = 0; -Path.prototype.getSize = function getSize() { - let size = 0; + size += 6; - size += 5; + switch (this.keyType) { + case Path.types.HD: + size += 8; + break; + case Path.types.KEY: + size += 1; + size += encoding.sizeVarBytes(this.data); + break; + } - switch (this.keyType) { - case Path.types.HD: - size += 8; - break; - case Path.types.KEY: - size += 1; - size += encoding.sizeVarBytes(this.data); - break; + return size; } - size += 2; - - return size; -}; - -/** - * Serialize path. - * @returns {Buffer} - */ - -Path.prototype.toRaw = function toRaw() { - const size = this.getSize(); - const bw = new StaticWriter(size); - - bw.writeU32(this.account); - bw.writeU8(this.keyType); - - switch (this.keyType) { - case Path.types.HD: - assert(!this.data); - assert(this.index !== -1); - bw.writeU32(this.branch); - bw.writeU32(this.index); - break; - case Path.types.KEY: - assert(this.data); - assert(this.index === -1); - bw.writeU8(this.encrypted ? 1 : 0); - bw.writeVarBytes(this.data); - break; - case Path.types.ADDRESS: - assert(!this.data); - assert(this.index === -1); - break; - default: - assert(false); - break; + /** + * Serialize path. + * @returns {Buffer} + */ + + toRaw() { + const size = this.getSize(); + const bw = bio.write(size); + + bw.writeU32(this.account); + bw.writeU8(this.keyType); + + let version = this.version; + + if (version === -1) + version = 0x1f; + + const flags = (version << 3) | this.type; + + bw.writeU8(flags); + + switch (this.keyType) { + case Path.types.HD: + assert(!this.data); + assert(this.index !== -1); + bw.writeU32(this.branch); + bw.writeU32(this.index); + break; + case Path.types.KEY: + assert(this.data); + assert(this.index === -1); + bw.writeU8(this.encrypted ? 1 : 0); + bw.writeVarBytes(this.data); + break; + case Path.types.ADDRESS: + assert(!this.data); + assert(this.index === -1); + break; + default: + assert(false); + break; + } + + return bw.render(); } - bw.writeI8(this.version); - bw.writeU8(this.type); + /** + * Inject properties from address. + * @private + * @param {Account} account + * @param {Address} address + */ + + fromAddress(account, address) { + this.keyType = Path.types.ADDRESS; + this.name = account.name; + this.account = account.accountIndex; + this.version = address.version; + this.type = address.type; + this.hash = address.getHash(); + return this; + } - return bw.render(); -}; + /** + * Instantiate path from address. + * @param {Account} account + * @param {Address} address + * @returns {Path} + */ -/** - * Inject properties from address. - * @private - * @param {Account} account - * @param {Address} address - */ + static fromAddress(account, address) { + return new this().fromAddress(account, address); + } -Path.prototype.fromAddress = function fromAddress(account, address) { - this.keyType = Path.types.ADDRESS; - this.id = account.id; - this.wid = account.wid; - this.name = account.name; - this.account = account.accountIndex; - this.version = address.version; - this.type = address.type; - this.hash = address.getHash('hex'); - return this; -}; + /** + * Convert path object to string derivation path. + * @returns {String} + */ -/** - * Instantiate path from address. - * @param {Account} account - * @param {Address} address - * @returns {Path} - */ + toPath() { + if (this.keyType !== Path.types.HD) + return null; -Path.fromAddress = function fromAddress(account, address) { - return new Path().fromAddress(account, address); -}; + return `m/${this.account}'/${this.branch}/${this.index}`; + } -/** - * Convert path object to string derivation path. - * @returns {String} - */ + /** + * Convert path object to an address (currently unused). + * @returns {Address} + */ -Path.prototype.toPath = function toPath() { - if (this.keyType !== Path.types.HD) - return null; + toAddress() { + return Address.fromHash(this.hash, this.type, this.version); + } - return `m/${this.account}'/${this.branch}/${this.index}`; -}; + /** + * Convert path to a json-friendly object. + * @returns {Object} + */ + + toJSON() { + return { + name: this.name, + account: this.account, + change: this.branch === 1, + derivation: this.toPath() + }; + } -/** - * Convert path object to an address (currently unused). - * @returns {Address} - */ + /** + * Inspect the path. + * @returns {String} + */ -Path.prototype.toAddress = function toAddress(network) { - return Address.fromHash(this.hash, this.type, this.version, network); -}; + [inspectSymbol]() { + return ``; + } +} /** - * Convert path to a json-friendly object. - * @returns {Object} + * Path types. + * @enum {Number} + * @default */ -Path.prototype.toJSON = function toJSON() { - return { - name: this.name, - account: this.account, - change: this.branch === 1, - derivation: this.toPath() - }; +Path.types = { + HD: 0, + KEY: 1, + ADDRESS: 2 }; /** - * Inspect the path. - * @returns {String} + * Path types. + * @enum {Number} + * @default */ -Path.prototype.inspect = function inspect() { - return ``; -}; +Path.typesByVal = [ + 'HD', + 'KEY', + 'ADDRESS' +]; /** * Expose diff --git a/lib/wallet/plugin.js b/lib/wallet/plugin.js index 4334e6e3c..f2e6df198 100644 --- a/lib/wallet/plugin.js +++ b/lib/wallet/plugin.js @@ -6,8 +6,11 @@ 'use strict'; +const EventEmitter = require('events'); const WalletDB = require('./walletdb'); const NodeClient = require('./nodeclient'); +const HTTP = require('./http'); +const RPC = require('./rpc'); /** * @exports wallet/plugin @@ -15,6 +18,85 @@ const NodeClient = require('./nodeclient'); const plugin = exports; +/** + * Plugin + * @extends EventEmitter + */ + +class Plugin extends EventEmitter { + /** + * Create a plugin. + * @constructor + * @param {Node} node + */ + + constructor(node) { + super(); + + this.config = node.config.filter('wallet'); + + if (node.config.options.file) + this.config.open('wallet.conf'); + + this.network = node.network; + this.logger = node.logger; + + this.client = new NodeClient(node); + + this.wdb = new WalletDB({ + network: this.network, + logger: this.logger, + workers: this.workers, + client: this.client, + prefix: this.config.prefix, + memory: this.config.bool('memory', node.memory), + maxFiles: this.config.uint('max-files'), + cacheSize: this.config.mb('cache-size'), + witness: this.config.bool('witness'), + checkpoints: this.config.bool('checkpoints'), + wipeNoReally: this.config.bool('wipe-no-really'), + spv: node.spv + }); + + this.rpc = new RPC(this); + + this.http = new HTTP({ + network: this.network, + logger: this.logger, + node: this, + ssl: this.config.bool('ssl'), + keyFile: this.config.path('ssl-key'), + certFile: this.config.path('ssl-cert'), + host: this.config.str('http-host'), + port: this.config.uint('http-port'), + apiKey: this.config.str('api-key', node.config.str('api-key')), + walletAuth: this.config.bool('wallet-auth'), + noAuth: this.config.bool('no-auth'), + cors: this.config.bool('cors'), + adminToken: this.config.str('admin-token') + }); + + this.init(); + } + + init() { + this.wdb.on('error', err => this.emit('error', err)); + this.http.on('error', err => this.emit('error', err)); + } + + async open() { + await this.wdb.open(); + this.rpc.wallet = this.wdb.primary; + await this.http.open(); + } + + async close() { + await this.http.close(); + this.rpc.wallet = null; + await this.wdb.close(); + } +} + /** * Plugin name. * @const {String} @@ -29,37 +111,5 @@ plugin.id = 'walletdb'; */ plugin.init = function init(node) { - const config = node.config; - const client = new NodeClient(node); - - const wdb = new WalletDB({ - network: node.network, - logger: node.logger, - workers: node.workers, - client: client, - prefix: config.prefix, - db: config.str(['wallet-db', 'db']), - maxFiles: config.uint('wallet-max-files'), - cacheSize: config.mb('wallet-cache-size'), - witness: config.bool('wallet-witness'), - checkpoints: config.bool('wallet-checkpoints'), - startHeight: config.uint('wallet-start-height'), - wipeNoReally: config.bool('wallet-wipe-no-really'), - apiKey: config.str(['wallet-api-key', 'api-key']), - walletAuth: config.bool('wallet-auth'), - noAuth: config.bool(['wallet-no-auth', 'no-auth']), - ssl: config.str('wallet-ssl'), - host: config.str('wallet-host'), - port: config.uint('wallet-port'), - spv: node.spv, - verify: node.spv, - listen: false - }); - - if (node.http && wdb.http) - wdb.http.attach(node.http); - - wdb.rpc.attach(node.rpc); - - return wdb; + return new Plugin(node); }; diff --git a/lib/wallet/records.js b/lib/wallet/records.js index 2b78c808b..778e7b397 100644 --- a/lib/wallet/records.js +++ b/lib/wallet/records.js @@ -10,734 +10,485 @@ * @module wallet/records */ -const assert = require('assert'); +const assert = require('bsert'); +const bio = require('bufio'); const util = require('../utils/util'); -const encoding = require('../utils/encoding'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); const TX = require('../primitives/tx'); +const consensus = require('../protocol/consensus'); /** * Chain State - * @constructor */ -function ChainState() { - if (!(this instanceof ChainState)) - return new ChainState(); +class ChainState { + /** + * Create a chain state. + * @constructor + */ - this.startHeight = -1; - this.startHash = encoding.NULL_HASH; - this.height = -1; - this.marked = false; -} - -/** - * Clone the state. - * @returns {ChainState} - */ - -ChainState.prototype.clone = function clone() { - const state = new ChainState(); - state.startHeight = this.startHeight; - state.startHash = this.startHash; - state.height = this.height; - state.marked = this.marked; - return state; -}; + constructor() { + this.startHeight = 0; + this.startHash = consensus.ZERO_HASH; + this.height = 0; + this.marked = false; + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + /** + * Clone the state. + * @returns {ChainState} + */ + + clone() { + const state = new ChainState(); + state.startHeight = this.startHeight; + state.startHash = this.startHash; + state.height = this.height; + state.marked = this.marked; + return state; + } -ChainState.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ - this.startHeight = br.readU32(); - this.startHash = br.readHash('hex'); - this.height = br.readU32(); - this.marked = true; + fromRaw(data) { + const br = bio.read(data); - if (br.left() > 0) + this.startHeight = br.readU32(); + this.startHash = br.readHash(); + this.height = br.readU32(); this.marked = br.readU8() === 1; - return this; -}; - -/** - * Instantiate chain state from serialized data. - * @param {Hash} hash - * @param {Buffer} data - * @returns {ChainState} - */ - -ChainState.fromRaw = function fromRaw(data) { - return new ChainState().fromRaw(data); -}; - -/** - * Serialize the chain state. - * @returns {Buffer} - */ - -ChainState.prototype.toRaw = function toRaw() { - const bw = new StaticWriter(41); - - bw.writeU32(this.startHeight); - bw.writeHash(this.startHash); - bw.writeU32(this.height); - bw.writeU8(this.marked ? 1 : 0); - - return bw.render(); -}; - -/** - * Block Meta - * @constructor - * @param {Hash} hash - * @param {Number} height - * @param {Number} time - */ - -function BlockMeta(hash, height, time) { - if (!(this instanceof BlockMeta)) - return new BlockMeta(hash, height, time); - - this.hash = hash || encoding.NULL_HASH; - this.height = height != null ? height : -1; - this.time = time || 0; -} - -/** - * Clone the block. - * @returns {BlockMeta} - */ - -BlockMeta.prototype.clone = function clone() { - return new BlockMeta(this.hash, this.height, this.time); -}; - -/** - * Get block meta hash as a buffer. - * @returns {Buffer} - */ - -BlockMeta.prototype.toHash = function toHash() { - return Buffer.from(this.hash, 'hex'); -}; - -/** - * Instantiate block meta from chain entry. - * @private - * @param {ChainEntry} entry - */ - -BlockMeta.prototype.fromEntry = function fromEntry(entry) { - this.hash = entry.hash; - this.height = entry.height; - this.time = entry.time; - return this; -}; - -/** - * Instantiate block meta from json object. - * @private - * @param {Object} json - */ - -BlockMeta.prototype.fromJSON = function fromJSON(json) { - this.hash = util.revHex(json.hash); - this.height = json.height; - this.time = json.time; - return this; -}; - -/** - * Instantiate block meta from serialized tip data. - * @private - * @param {Buffer} data - */ - -BlockMeta.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); - this.hash = br.readHash('hex'); - this.height = br.readU32(); - this.time = br.readU32(); - return this; -}; - -/** - * Instantiate block meta from chain entry. - * @param {ChainEntry} entry - * @returns {BlockMeta} - */ - -BlockMeta.fromEntry = function fromEntry(entry) { - return new BlockMeta().fromEntry(entry); -}; - -/** - * Instantiate block meta from json object. - * @param {Object} json - * @returns {BlockMeta} - */ - -BlockMeta.fromJSON = function fromJSON(json) { - return new BlockMeta().fromJSON(json); -}; - -/** - * Instantiate block meta from serialized data. - * @param {Hash} hash - * @param {Buffer} data - * @returns {BlockMeta} - */ - -BlockMeta.fromRaw = function fromRaw(data) { - return new BlockMeta().fromRaw(data); -}; - -/** - * Serialize the block meta. - * @returns {Buffer} - */ + return this; + } -BlockMeta.prototype.toRaw = function toRaw() { - const bw = new StaticWriter(42); - bw.writeHash(this.hash); - bw.writeU32(this.height); - bw.writeU32(this.time); - return bw.render(); -}; + /** + * Instantiate chain state from serialized data. + * @param {Hash} hash + * @param {Buffer} data + * @returns {ChainState} + */ -/** - * Convert the block meta to a more json-friendly object. - * @returns {Object} - */ + static fromRaw(data) { + return new this().fromRaw(data); + } -BlockMeta.prototype.toJSON = function toJSON() { - return { - hash: util.revHex(this.hash), - height: this.height, - time: this.time - }; -}; + /** + * Serialize the chain state. + * @returns {Buffer} + */ -/** - * Wallet Block - * @constructor - * @param {Hash} hash - * @param {Number} height - */ + toRaw() { + const bw = bio.write(41); -function BlockMapRecord(height) { - if (!(this instanceof BlockMapRecord)) - return new BlockMapRecord(height); + bw.writeU32(this.startHeight); + bw.writeHash(this.startHash); + bw.writeU32(this.height); + bw.writeU8(this.marked ? 1 : 0); - this.height = height != null ? height : -1; - this.txs = new Map(); + return bw.render(); + } } /** - * Instantiate wallet block from serialized data. - * @private - * @param {Hash} hash - * @param {Buffer} data + * Block Meta */ -BlockMapRecord.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); - const count = br.readU32(); +class BlockMeta { + /** + * Create block meta. + * @constructor + * @param {Hash} hash + * @param {Number} height + * @param {Number} time + */ - for (let i = 0; i < count; i++) { - const hash = br.readHash('hex'); - const tx = TXMapRecord.fromReader(hash, br); - this.txs.set(tx.hash, tx); + constructor(hash, height, time) { + this.hash = hash || consensus.ZERO_HASH; + this.height = height != null ? height : -1; + this.time = time || 0; } - return this; -}; - -/** - * Instantiate wallet block from serialized data. - * @param {Hash} hash - * @param {Buffer} data - * @returns {BlockMapRecord} - */ - -BlockMapRecord.fromRaw = function fromRaw(height, data) { - return new BlockMapRecord(height).fromRaw(data); -}; + /** + * Clone the block. + * @returns {BlockMeta} + */ -/** - * Calculate serialization size. - * @returns {Number} - */ - -BlockMapRecord.prototype.getSize = function getSize() { - let size = 0; - - size += 4; - - for (const tx of this.txs.values()) { - size += 32; - size += tx.getSize(); + clone() { + return new this.constructor(this.hash, this.height, this.time); } - return size; -}; + /** + * Get block meta hash as a buffer. + * @returns {Buffer} + */ -/** - * Serialize the wallet block as a block. - * Contains matching transaction hashes. - * @returns {Buffer} - */ - -BlockMapRecord.prototype.toRaw = function toRaw() { - const size = this.getSize(); - const bw = new StaticWriter(size); - - bw.writeU32(this.txs.size); - - for (const [hash, tx] of this.txs) { - bw.writeHash(hash); - tx.toWriter(bw); + toHash() { + return this.hash; } - return bw.render(); -}; - -/** - * Add a hash and wid pair to the block. - * @param {Hash} hash - * @param {WalletID} wid - * @returns {Boolean} - */ - -BlockMapRecord.prototype.add = function add(hash, wid) { - let tx = this.txs.get(hash); - - if (!tx) { - tx = new TXMapRecord(hash); - this.txs.set(hash, tx); + /** + * Instantiate block meta from chain entry. + * @private + * @param {ChainEntry} entry + */ + + fromEntry(entry) { + this.hash = entry.hash; + this.height = entry.height; + this.time = entry.time; + return this; } - return tx.add(wid); -}; - -/** - * Remove a hash and wid pair from the block. - * @param {Hash} hash - * @param {WalletID} wid - * @returns {Boolean} - */ + /** + * Instantiate block meta from json object. + * @private + * @param {Object} json + */ + + fromJSON(json) { + this.hash = util.revHex(json.hash); + this.height = json.height; + this.time = json.time; + return this; + } -BlockMapRecord.prototype.remove = function remove(hash, wid) { - const tx = this.txs.get(hash); + /** + * Instantiate block meta from serialized tip data. + * @private + * @param {Buffer} data + */ - if (!tx) - return false; + fromRaw(data) { + const br = bio.read(data); + this.hash = br.readHash(); + this.height = br.readU32(); + this.time = br.readU32(); + return this; + } - if (!tx.remove(wid)) - return false; + /** + * Instantiate block meta from chain entry. + * @param {ChainEntry} entry + * @returns {BlockMeta} + */ - if (tx.wids.size === 0) - this.txs.delete(tx.hash); + static fromEntry(entry) { + return new this().fromEntry(entry); + } - return true; -}; + /** + * Instantiate block meta from json object. + * @param {Object} json + * @returns {BlockMeta} + */ -/** - * Convert tx map to an array. - * @returns {Array} - */ + static fromJSON(json) { + return new this().fromJSON(json); + } -BlockMapRecord.prototype.toArray = function toArray() { - const txs = []; + /** + * Instantiate block meta from serialized data. + * @param {Hash} hash + * @param {Buffer} data + * @returns {BlockMeta} + */ - for (const tx of this.txs.values()) - txs.push(tx); + static fromRaw(data) { + return new this().fromRaw(data); + } - return txs; -}; + /** + * Serialize the block meta. + * @returns {Buffer} + */ -/** - * TX Hash - * @constructor - */ + toRaw() { + const bw = bio.write(42); + bw.writeHash(this.hash); + bw.writeU32(this.height); + bw.writeU32(this.time); + return bw.render(); + } -function TXMapRecord(hash, wids) { - this.hash = hash || encoding.NULL_HASH; - this.wids = wids || new Set(); + /** + * Convert the block meta to a more json-friendly object. + * @returns {Object} + */ + + toJSON() { + return { + hash: util.revHex(this.hash), + height: this.height, + time: this.time + }; + } } -TXMapRecord.prototype.add = function add(wid) { - if (this.wids.has(wid)) - return false; - - this.wids.add(wid); - return true; -}; - -TXMapRecord.prototype.remove = function remove(wid) { - return this.wids.delete(wid); -}; - -TXMapRecord.prototype.toWriter = function toWriter(bw) { - return serializeWallets(bw, this.wids); -}; - -TXMapRecord.prototype.getSize = function getSize() { - return sizeWallets(this.wids); -}; - -TXMapRecord.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; - -TXMapRecord.prototype.fromReader = function fromReader(br) { - this.wids = parseWallets(br); - return this; -}; - -TXMapRecord.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -TXMapRecord.fromReader = function fromReader(hash, br) { - return new TXMapRecord(hash).fromReader(br); -}; - -TXMapRecord.fromRaw = function fromRaw(hash, data) { - return new TXMapRecord(hash).fromRaw(data); -}; - /** - * Outpoint Map - * @constructor + * TX Record */ -function OutpointMapRecord(hash, index, wids) { - this.hash = hash || encoding.NULL_HASH; - this.index = index != null ? index : -1; - this.wids = wids || new Set(); -} - -OutpointMapRecord.prototype.add = function add(wid) { - if (this.wids.has(wid)) - return false; - - this.wids.add(wid); - return true; -}; +class TXRecord { + /** + * Create tx record. + * @constructor + * @param {TX} tx + * @param {BlockMeta?} block + */ -OutpointMapRecord.prototype.remove = function remove(wid) { - return this.wids.delete(wid); -}; + constructor(tx, block) { + this.tx = null; + this.hash = null; + this.mtime = util.now(); + this.height = -1; + this.block = null; + this.index = -1; + this.time = 0; -OutpointMapRecord.prototype.toWriter = function toWriter(bw) { - return serializeWallets(bw, this.wids); -}; - -OutpointMapRecord.prototype.getSize = function getSize() { - return sizeWallets(this.wids); -}; - -OutpointMapRecord.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; - -OutpointMapRecord.prototype.fromReader = function fromReader(br) { - this.wids = parseWallets(br); - return this; -}; + if (tx) + this.fromTX(tx, block); + } -OutpointMapRecord.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + /** + * Inject properties from tx and block. + * @private + * @param {TX} tx + * @param {Block?} block + * @returns {TXRecord} + */ -OutpointMapRecord.fromReader = function fromReader(hash, index, br) { - return new OutpointMapRecord(hash, index).fromReader(br); -}; + fromTX(tx, block) { + this.tx = tx; + this.hash = tx.hash(); -OutpointMapRecord.fromRaw = function fromRaw(hash, index, data) { - return new OutpointMapRecord(hash, index).fromRaw(data); -}; + if (block) + this.setBlock(block); -/** - * Path Record - * @constructor - */ - -function PathMapRecord(hash, wids) { - this.hash = hash || encoding.NULL_HASH; - this.wids = wids || new Set(); -} - -PathMapRecord.prototype.add = function add(wid) { - if (this.wids.has(wid)) - return false; + return this; + } - this.wids.add(wid); - return true; -}; + /** + * Instantiate tx record from tx and block. + * @param {TX} tx + * @param {Block?} block + * @returns {TXRecord} + */ -PathMapRecord.prototype.remove = function remove(wid) { - return this.wids.delete(wid); -}; + static fromTX(tx, block) { + return new this().fromTX(tx, block); + } -PathMapRecord.prototype.toWriter = function toWriter(bw) { - return serializeWallets(bw, this.wids); -}; + /** + * Set block data (confirm). + * @param {BlockMeta} block + */ -PathMapRecord.prototype.getSize = function getSize() { - return sizeWallets(this.wids); -}; + setBlock(block) { + this.height = block.height; + this.block = block.hash; + this.time = block.time; + } -PathMapRecord.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; + /** + * Unset block (unconfirm). + */ -PathMapRecord.prototype.fromReader = function fromReader(br) { - this.wids = parseWallets(br); - return this; -}; + unsetBlock() { + this.height = -1; + this.block = null; + this.time = 0; + } -PathMapRecord.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + /** + * Convert tx record to a block meta. + * @returns {BlockMeta} + */ -PathMapRecord.fromReader = function fromReader(hash, br) { - return new PathMapRecord(hash).fromReader(br); -}; + getBlock() { + if (this.height === -1) + return null; -PathMapRecord.fromRaw = function fromRaw(hash, data) { - return new PathMapRecord(hash).fromRaw(data); -}; + return new BlockMeta(this.block, this.height, this.time); + } -/** - * TXRecord - * @constructor - * @param {TX} tx - * @param {BlockMeta?} block - */ + /** + * Calculate current number of transaction confirmations. + * @param {Number} height - Current chain height. + * @returns {Number} confirmations + */ -function TXRecord(tx, block) { - if (!(this instanceof TXRecord)) - return new TXRecord(tx, block); + getDepth(height) { + assert(typeof height === 'number', 'Must pass in height.'); - this.tx = null; - this.hash = null; - this.mtime = util.now(); - this.height = -1; - this.block = null; - this.index = -1; - this.time = 0; + if (this.height === -1) + return 0; - if (tx) - this.fromTX(tx, block); -} + if (height < this.height) + return 0; -/** - * Inject properties from tx and block. - * @private - * @param {TX} tx - * @param {Block?} block - * @returns {TXRecord} - */ + return height - this.height + 1; + } -TXRecord.prototype.fromTX = function fromTX(tx, block) { - this.tx = tx; - this.hash = tx.hash('hex'); + /** + * Get serialization size. + * @returns {Number} + */ - if (block) - this.setBlock(block); + getSize() { + let size = 0; - return this; -}; + size += this.tx.getSize(); + size += 4; -/** - * Instantiate tx record from tx and block. - * @param {TX} tx - * @param {Block?} block - * @returns {TXRecord} - */ + if (this.block) { + size += 1; + size += 32; + size += 4 * 3; + } else { + size += 1; + } -TXRecord.fromTX = function fromTX(tx, block) { - return new TXRecord().fromTX(tx, block); -}; + return size; + } -/** - * Set block data (confirm). - * @param {BlockMeta} block - */ + /** + * Serialize a transaction to "extended format". + * @returns {Buffer} + */ -TXRecord.prototype.setBlock = function setBlock(block) { - this.height = block.height; - this.block = block.hash; - this.time = block.time; -}; + toRaw() { + const size = this.getSize(); + const bw = bio.write(size); -/** - * Unset block (unconfirm). - */ + let index = this.index; -TXRecord.prototype.unsetBlock = function unsetBlock() { - this.height = -1; - this.block = null; - this.time = 0; -}; + this.tx.toWriter(bw); -/** - * Convert tx record to a block meta. - * @returns {BlockMeta} - */ + bw.writeU32(this.mtime); -TXRecord.prototype.getBlock = function getBlock() { - if (this.height === -1) - return null; + if (this.block) { + if (index === -1) + index = 0x7fffffff; - return new BlockMeta(this.block, this.height, this.time); -}; + bw.writeU8(1); + bw.writeHash(this.block); + bw.writeU32(this.height); + bw.writeU32(this.time); + bw.writeU32(index); + } else { + bw.writeU8(0); + } -/** - * Calculate current number of transaction confirmations. - * @param {Number} height - Current chain height. - * @returns {Number} confirmations - */ + return bw.render(); + } -TXRecord.prototype.getDepth = function getDepth(height) { - assert(typeof height === 'number', 'Must pass in height.'); + /** + * Inject properties from "extended" format. + * @private + * @param {Buffer} data + */ - if (this.height === -1) - return 0; + fromRaw(data) { + const br = bio.read(data); - if (height < this.height) - return 0; + this.tx = new TX(); + this.tx.fromReader(br); - return height - this.height + 1; -}; + this.hash = this.tx.hash(); + this.mtime = br.readU32(); -/** - * Get serialization size. - * @returns {Number} - */ + if (br.readU8() === 1) { + this.block = br.readHash(); + this.height = br.readU32(); + this.time = br.readU32(); + this.index = br.readU32(); + if (this.index === 0x7fffffff) + this.index = -1; + } -TXRecord.prototype.getSize = function getSize() { - let size = 0; + return this; + } - size += this.tx.getSize(); - size += 4; + /** + * Instantiate a transaction from a buffer + * in "extended" serialization format. + * @param {Buffer} data + * @returns {TX} + */ - if (this.block) { - size += 1; - size += 32; - size += 4 * 3; - } else { - size += 1; + static fromRaw(data) { + return new this().fromRaw(data); } - - return size; -}; +} /** - * Serialize a transaction to "extended format". - * @returns {Buffer} + * Map Record */ -TXRecord.prototype.toRaw = function toRaw() { - const size = this.getSize(); - const bw = new StaticWriter(size); - let index = this.index; - - this.tx.toWriter(bw); - - bw.writeU32(this.mtime); - - if (this.block) { - if (index === -1) - index = 0x7fffffff; +class MapRecord { + /** + * Create map record. + * @constructor + */ - bw.writeU8(1); - bw.writeHash(this.block); - bw.writeU32(this.height); - bw.writeU32(this.time); - bw.writeU32(index); - } else { - bw.writeU8(0); + constructor() { + this.wids = new Set(); } - return bw.render(); -}; - -/** - * Inject properties from "extended" format. - * @private - * @param {Buffer} data - */ - -TXRecord.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); + add(wid) { + if (this.wids.has(wid)) + return false; - this.tx = new TX(); - this.tx.fromReader(br); + this.wids.add(wid); - this.hash = this.tx.hash('hex'); - this.mtime = br.readU32(); + return true; + } - if (br.readU8() === 1) { - this.block = br.readHash('hex'); - this.height = br.readU32(); - this.time = br.readU32(); - this.index = br.readU32(); - if (this.index === 0x7fffffff) - this.index = -1; + remove(wid) { + return this.wids.delete(wid); } - return this; -}; + toWriter(bw) { + bw.writeU32(this.wids.size); -/** - * Instantiate a transaction from a buffer - * in "extended" serialization format. - * @param {Buffer} data - * @returns {TX} - */ + for (const wid of this.wids) + bw.writeU32(wid); -TXRecord.fromRaw = function fromRaw(data) { - return new TXRecord().fromRaw(data); -}; + return bw; + } -/* - * Helpers - */ + getSize() { + return 4 + this.wids.size * 4; + } -function parseWallets(br) { - const count = br.readU32(); - const wids = new Set(); + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } - for (let i = 0; i < count; i++) - wids.add(br.readU32()); + fromReader(br) { + const count = br.readU32(); - return wids; -} + for (let i = 0; i < count; i++) + this.wids.add(br.readU32()); -function sizeWallets(wids) { - return 4 + wids.size * 4; -} + return this; + } -function serializeWallets(bw, wids) { - bw.writeU32(wids.size); + fromRaw(data) { + return this.fromReader(bio.read(data)); + } - for (const wid of wids) - bw.writeU32(wid); + static fromReader(br) { + return new this().fromReader(br); + } - return bw; + static fromRaw(data) { + return new this().fromRaw(data); + } } /* @@ -746,10 +497,7 @@ function serializeWallets(bw, wids) { exports.ChainState = ChainState; exports.BlockMeta = BlockMeta; -exports.BlockMapRecord = BlockMapRecord; -exports.TXMapRecord = TXMapRecord; -exports.OutpointMapRecord = OutpointMapRecord; -exports.PathMapRecord = PathMapRecord; exports.TXRecord = TXRecord; +exports.MapRecord = MapRecord; module.exports = exports; diff --git a/lib/wallet/rpc.js b/lib/wallet/rpc.js index bfb67df26..0b1502102 100644 --- a/lib/wallet/rpc.js +++ b/lib/wallet/rpc.js @@ -6,10 +6,15 @@ 'use strict'; -const assert = require('assert'); -const fs = require('../utils/fs'); +const assert = require('bsert'); +const {format} = require('util'); +const bweb = require('bweb'); +const {Lock} = require('bmutex'); +const fs = require('bfile'); +const Validator = require('bval'); +const hash256 = require('bcrypto/lib/hash256'); +const {BufferMap, BufferSet} = require('buffer-map'); const util = require('../utils/util'); -const digest = require('../crypto/digest'); const Amount = require('../btc/amount'); const Script = require('../script/script'); const Address = require('../primitives/address'); @@ -19,1549 +24,1674 @@ const MTX = require('../primitives/mtx'); const Outpoint = require('../primitives/outpoint'); const Output = require('../primitives/output'); const TX = require('../primitives/tx'); -const encoding = require('../utils/encoding'); -const RPCBase = require('../http/rpcbase'); +const consensus = require('../protocol/consensus'); const pkg = require('../pkg'); -const Validator = require('../utils/validator'); const common = require('./common'); -const RPCError = RPCBase.RPCError; -const errs = RPCBase.errors; -const MAGIC_STRING = RPCBase.MAGIC_STRING; +const RPCBase = bweb.RPC; +const RPCError = bweb.RPCError; + +/* + * Constants + */ + +const errs = { + // Standard JSON-RPC 2.0 errors + INVALID_REQUEST: bweb.errors.INVALID_REQUEST, + METHOD_NOT_FOUND: bweb.errors.METHOD_NOT_FOUND, + INVALID_PARAMS: bweb.errors.INVALID_PARAMS, + INTERNAL_ERROR: bweb.errors.INTERNAL_ERROR, + PARSE_ERROR: bweb.errors.PARSE_ERROR, + + // General application defined errors + MISC_ERROR: -1, + FORBIDDEN_BY_SAFE_MODE: -2, + TYPE_ERROR: -3, + INVALID_ADDRESS_OR_KEY: -5, + OUT_OF_MEMORY: -7, + INVALID_PARAMETER: -8, + DATABASE_ERROR: -20, + DESERIALIZATION_ERROR: -22, + VERIFY_ERROR: -25, + VERIFY_REJECTED: -26, + VERIFY_ALREADY_IN_CHAIN: -27, + IN_WARMUP: -28, + + // Wallet errors + WALLET_ERROR: -4, + WALLET_INSUFFICIENT_FUNDS: -6, + WALLET_INVALID_ACCOUNT_NAME: -11, + WALLET_KEYPOOL_RAN_OUT: -12, + WALLET_UNLOCK_NEEDED: -13, + WALLET_PASSPHRASE_INCORRECT: -14, + WALLET_WRONG_ENC_STATE: -15, + WALLET_ENCRYPTION_FAILED: -16, + WALLET_ALREADY_UNLOCKED: -17 +}; + +const MAGIC_STRING = 'Florincoin Signed Message:\n'; /** - * Bitcoin Core RPC + * Wallet RPC * @alias module:wallet.RPC - * @constructor - * @param {WalletDB} wdb + * @extends bweb.RPC */ -function RPC(wdb) { - if (!(this instanceof RPC)) - return new RPC(wdb); +class RPC extends RPCBase { + /** + * Create an RPC. + * @param {WalletDB} wdb + */ - RPCBase.call(this); + constructor(node) { + super(); - assert(wdb, 'RPC requires a WalletDB.'); + assert(node, 'RPC requires a WalletDB.'); - this.wdb = wdb; - this.network = wdb.network; - this.logger = wdb.logger.context('rpc'); - this.client = wdb.client; + this.wdb = node.wdb; + this.network = node.network; + this.logger = node.logger.context('wallet-rpc'); + this.client = node.client; + this.locker = new Lock(); - this.wallet = null; + this.wallet = null; - this.init(); -} + this.init(); + } -Object.setPrototypeOf(RPC.prototype, RPCBase.prototype); - -RPC.prototype.init = function init() { - this.add('help', this.help); - this.add('stop', this.stop); - this.add('fundrawtransaction', this.fundRawTransaction); - this.add('resendwallettransactions', this.resendWalletTransactions); - this.add('abandontransaction', this.abandonTransaction); - this.add('addmultisigaddress', this.addMultisigAddress); - this.add('addwitnessaddress', this.addWitnessAddress); - this.add('backupwallet', this.backupWallet); - this.add('dumpprivkey', this.dumpPrivKey); - this.add('dumpwallet', this.dumpWallet); - this.add('encryptwallet', this.encryptWallet); - this.add('getaccountaddress', this.getAccountAddress); - this.add('getaccount', this.getAccount); - this.add('getaddressesbyaccount', this.getAddressesByAccount); - this.add('getbalance', this.getBalance); - this.add('getnewaddress', this.getNewAddress); - this.add('getrawchangeaddress', this.getRawChangeAddress); - this.add('getreceivedbyaccount', this.getReceivedByAccount); - this.add('getreceivedbyaddress', this.getReceivedByAddress); - this.add('gettransaction', this.getTransaction); - this.add('getunconfirmedbalance', this.getUnconfirmedBalance); - this.add('getwalletinfo', this.getWalletInfo); - this.add('importprivkey', this.importPrivKey); - this.add('importwallet', this.importWallet); - this.add('importaddress', this.importAddress); - this.add('importprunedfunds', this.importPrunedFunds); - this.add('importpubkey', this.importPubkey); - this.add('keypoolrefill', this.keyPoolRefill); - this.add('listaccounts', this.listAccounts); - this.add('listaddressgroupings', this.listAddressGroupings); - this.add('listlockunspent', this.listLockUnspent); - this.add('listreceivedbyaccount', this.listReceivedByAccount); - this.add('listreceivedbyaddress', this.listReceivedByAddress); - this.add('listsinceblock', this.listSinceBlock); - this.add('listtransactions', this.listTransactions); - this.add('listunspent', this.listUnspent); - this.add('lockunspent', this.lockUnspent); - this.add('move', this.move); - this.add('sendfrom', this.sendFrom); - this.add('sendmany', this.sendMany); - this.add('sendtoaddress', this.sendToAddress); - this.add('setaccount', this.setAccount); - this.add('settxfee', this.setTXFee); - this.add('signmessage', this.signMessage); - this.add('walletlock', this.walletLock); - this.add('walletpassphrasechange', this.walletPassphraseChange); - this.add('walletpassphrase', this.walletPassphrase); - this.add('removeprunedfunds', this.removePrunedFunds); - this.add('selectwallet', this.selectWallet); - this.add('getmemoryinfo', this.getMemoryInfo); - this.add('setloglevel', this.setLogLevel); -}; + getCode(err) { + switch (err.type) { + case 'RPCError': + return err.code; + case 'ValidationError': + return errs.TYPE_ERROR; + case 'EncodingError': + return errs.DESERIALIZATION_ERROR; + case 'FundingError': + return errs.WALLET_INSUFFICIENT_FUNDS; + default: + return errs.INTERNAL_ERROR; + } + } -RPC.prototype.help = async function help(args, _help) { - if (args.length === 0) - return 'Select a command.'; + handleCall(cmd, query) { + this.logger.debug('Handling RPC call: %s.', cmd.method); + } - const json = { - method: args[0], - params: [] - }; + init() { + this.add('help', this.help); + this.add('stop', this.stop); + this.add('fundrawtransaction', this.fundRawTransaction); + this.add('resendwallettransactions', this.resendWalletTransactions); + this.add('abandontransaction', this.abandonTransaction); + this.add('addmultisigaddress', this.addMultisigAddress); + this.add('addwitnessaddress', this.addWitnessAddress); + this.add('backupwallet', this.backupWallet); + this.add('dumpprivkey', this.dumpPrivKey); + this.add('dumpwallet', this.dumpWallet); + this.add('encryptwallet', this.encryptWallet); + this.add('getaddressinfo', this.getAddressInfo); + this.add('getaccountaddress', this.getAccountAddress); + this.add('getaccount', this.getAccount); + this.add('getaddressesbyaccount', this.getAddressesByAccount); + this.add('getbalance', this.getBalance); + this.add('getnewaddress', this.getNewAddress); + this.add('getrawchangeaddress', this.getRawChangeAddress); + this.add('getreceivedbyaccount', this.getReceivedByAccount); + this.add('getreceivedbyaddress', this.getReceivedByAddress); + this.add('gettransaction', this.getTransaction); + this.add('getunconfirmedbalance', this.getUnconfirmedBalance); + this.add('getwalletinfo', this.getWalletInfo); + this.add('importprivkey', this.importPrivKey); + this.add('importwallet', this.importWallet); + this.add('importaddress', this.importAddress); + this.add('importprunedfunds', this.importPrunedFunds); + this.add('importpubkey', this.importPubkey); + this.add('keypoolrefill', this.keyPoolRefill); + this.add('listaccounts', this.listAccounts); + this.add('listaddressgroupings', this.listAddressGroupings); + this.add('listlockunspent', this.listLockUnspent); + this.add('listreceivedbyaccount', this.listReceivedByAccount); + this.add('listreceivedbyaddress', this.listReceivedByAddress); + this.add('listsinceblock', this.listSinceBlock); + this.add('listtransactions', this.listTransactions); + this.add('listunspent', this.listUnspent); + this.add('lockunspent', this.lockUnspent); + this.add('move', this.move); + this.add('sendfrom', this.sendFrom); + this.add('sendmany', this.sendMany); + this.add('sendtoaddress', this.sendToAddress); + this.add('setaccount', this.setAccount); + this.add('settxfee', this.setTXFee); + this.add('signmessage', this.signMessage); + this.add('walletlock', this.walletLock); + this.add('walletpassphrasechange', this.walletPassphraseChange); + this.add('walletpassphrase', this.walletPassphrase); + this.add('removeprunedfunds', this.removePrunedFunds); + this.add('selectwallet', this.selectWallet); + this.add('getmemoryinfo', this.getMemoryInfo); + this.add('setloglevel', this.setLogLevel); + } - return await this.execute(json, true); -}; + async help(args, _help) { + if (args.length === 0) + return `Select a command:\n${Object.keys(this.calls).join('\n')}`; -RPC.prototype.stop = async function stop(args, help) { - if (help || args.length !== 0) - throw new RPCError(errs.MISC_ERROR, 'stop'); + const json = { + method: args[0], + params: [] + }; - this.wdb.close(); + return await this.execute(json, true); + } - return 'Stopping.'; -}; + async stop(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'stop'); -RPC.prototype.fundRawTransaction = async function fundRawTransaction(args, help) { - if (help || args.length < 1 || args.length > 2) { - throw new RPCError(errs.MISC_ERROR, - 'fundrawtransaction "hexstring" ( options )'); + this.wdb.close(); + + return 'Stopping.'; } - const wallet = this.wallet; - const valid = new Validator([args]); - const data = valid.buf(0); - const options = valid.obj(1); + async fundRawTransaction(args, help) { + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'fundrawtransaction "hexstring" ( options )'); + } - if (!data) - throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.'); + const wallet = this.wallet; + const valid = new Validator(args); + const data = valid.buf(0); + const options = valid.obj(1); - const tx = MTX.fromRaw(data); + if (!data) + throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.'); - if (tx.outputs.length === 0) { - throw new RPCError(errs.INVALID_PARAMETER, - 'TX must have at least one output.'); - } + const tx = MTX.fromRaw(data); - let rate = null; - let change = null; + if (tx.outputs.length === 0) { + throw new RPCError(errs.INVALID_PARAMETER, + 'TX must have at least one output.'); + } - if (options) { - const valid = new Validator([options]); + let rate = null; + let change = null; - rate = valid.ufixed('feeRate', 8); - change = valid.str('changeAddress'); + if (options) { + const valid = new Validator(options); - if (change) - change = parseAddress(change, this.network); - } + rate = valid.ufixed('feeRate', 8); + change = valid.str('changeAddress'); - await wallet.fund(tx, { - rate: rate, - changeAddress: change - }); + if (change) + change = parseAddress(change, this.network); + } - return { - hex: tx.toRaw().toString('hex'), - changepos: tx.changeIndex, - fee: Amount.btc(tx.getFee(), true) - }; -}; + await wallet.fund(tx, { + rate: rate, + changeAddress: change + }); -/* - * Wallet - */ + return { + hex: tx.toRaw().toString('hex'), + changepos: tx.changeIndex, + fee: Amount.btc(tx.getFee(), true) + }; + } -RPC.prototype.resendWalletTransactions = async function resendWalletTransactions(args, help) { - if (help || args.length !== 0) - throw new RPCError(errs.MISC_ERROR, 'resendwallettransactions'); + /* + * Wallet + */ - const wallet = this.wallet; - const txs = await wallet.resend(); - const hashes = []; + async resendWalletTransactions(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'resendwallettransactions'); - for (const tx of txs) - hashes.push(tx.txid()); + const wallet = this.wallet; + const txs = await wallet.resend(); + const hashes = []; - return hashes; -}; + for (const tx of txs) + hashes.push(tx.txid()); -RPC.prototype.addMultisigAddress = async function addMultisigAddress(args, help) { - if (help || args.length < 2 || args.length > 3) { - throw new RPCError(errs.MISC_ERROR, - 'addmultisigaddress nrequired ["key",...] ( "account" )'); + return hashes; } - // Impossible to implement in bcoin (no address book). - throw new Error('Not implemented.'); -}; + async addMultisigAddress(args, help) { + // Impossible to implement in bcoin (no address book). + throw new Error('Not implemented.'); + } -RPC.prototype.addWitnessAddress = async function addWitnessAddress(args, help) { - if (help || args.length < 1 || args.length > 1) - throw new RPCError(errs.MISC_ERROR, 'addwitnessaddress "address"'); + async addWitnessAddress(args, help) { + // Unlikely to be implemented. + throw new Error('Not implemented.'); + } - // Unlikely to be implemented. - throw new Error('Not implemented.'); -}; + async backupWallet(args, help) { + const valid = new Validator(args); + const dest = valid.str(0); -RPC.prototype.backupWallet = async function backupWallet(args, help) { - const valid = new Validator([args]); - const dest = valid.str(0); + if (help || args.length !== 1 || !dest) + throw new RPCError(errs.MISC_ERROR, 'backupwallet "destination"'); - if (help || args.length !== 1 || !dest) - throw new RPCError(errs.MISC_ERROR, 'backupwallet "destination"'); + await this.wdb.backup(dest); - await this.wdb.backup(dest); + return null; + } - return null; -}; + async dumpPrivKey(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'dumpprivkey "bitcoinaddress"'); -RPC.prototype.dumpPrivKey = async function dumpPrivKey(args, help) { - if (help || args.length !== 1) - throw new RPCError(errs.MISC_ERROR, 'dumpprivkey "bitcoinaddress"'); + const wallet = this.wallet; + const valid = new Validator(args); + const addr = valid.str(0, ''); - const wallet = this.wallet; - const valid = new Validator([args]); - const addr = valid.str(0, ''); + const hash = parseHash(addr, this.network); + const ring = await wallet.getPrivateKey(hash); - const hash = parseHash(addr, this.network); - const ring = await wallet.getPrivateKey(hash); + if (!ring) + throw new RPCError(errs.MISC_ERROR, 'Key not found.'); - if (!ring) - throw new RPCError(errs.MISC_ERROR, 'Key not found.'); + return ring.toSecret(this.network); + } - return ring.toSecret(); -}; + async dumpWallet(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'dumpwallet "filename"'); -RPC.prototype.dumpWallet = async function dumpWallet(args, help) { - if (help || args.length !== 1) - throw new RPCError(errs.MISC_ERROR, 'dumpwallet "filename"'); + const wallet = this.wallet; + const valid = new Validator(args); + const file = valid.str(0); - const wallet = this.wallet; - const valid = new Validator([args]); - const file = valid.str(0); + if (!file) + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); - if (!file) - throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); + const tip = await this.wdb.getTip(); + const time = util.date(); - const tip = await this.wdb.getTip(); - const time = util.date(); + const out = [ + format('# Wallet Dump created by Bcoin %s', pkg.version), + format('# * Created on %s', time), + format('# * Best block at time of backup was %d (%s).', + tip.height, util.revHex(tip.hash)), + format('# * File: %s', file), + '' + ]; - const out = [ - util.fmt('# Wallet Dump created by Bcoin %s', pkg.version), - util.fmt('# * Created on %s', time), - util.fmt('# * Best block at time of backup was %d (%s).', - tip.height, util.revHex(tip.hash)), - util.fmt('# * File: %s', file), - '' - ]; + const hashes = await wallet.getAddressHashes(); - const hashes = await wallet.getAddressHashes(); + for (const hash of hashes) { + const ring = await wallet.getPrivateKey(hash); - for (const hash of hashes) { - const ring = await wallet.getPrivateKey(hash); + if (!ring) + continue; - if (!ring) - continue; + const addr = ring.getAddress('string'); - const addr = ring.getAddress('string'); + let fmt = '%s %s label= addr=%s'; - let fmt = '%s %s label= addr=%s'; + if (ring.branch === 1) + fmt = '%s %s change=1 addr=%s'; - if (ring.branch === 1) - fmt = '%s %s change=1 addr=%s'; + const str = format(fmt, ring.toSecret(this.network), time, addr); - const str = util.fmt(fmt, ring.toSecret(), time, addr); + out.push(str); + } - out.push(str); - } + out.push(''); + out.push('# End of dump'); + out.push(''); - out.push(''); - out.push('# End of dump'); - out.push(''); + const dump = out.join('\n'); - const dump = out.join('\n'); + if (fs.unsupported) + return dump; - if (fs.unsupported) - return dump; + await fs.writeFile(file, dump, 'utf8'); - await fs.writeFile(file, dump, 'utf8'); + return null; + } - return null; -}; + async encryptWallet(args, help) { + const wallet = this.wallet; -RPC.prototype.encryptWallet = async function encryptWallet(args, help) { - const wallet = this.wallet; + if (!wallet.master.encrypted && (help || args.length !== 1)) + throw new RPCError(errs.MISC_ERROR, 'encryptwallet "passphrase"'); - if (!wallet.master.encrypted && (help || args.length !== 1)) - throw new RPCError(errs.MISC_ERROR, 'encryptwallet "passphrase"'); + const valid = new Validator(args); + const passphrase = valid.str(0, ''); - const valid = new Validator([args]); - const passphrase = valid.str(0, ''); + if (wallet.master.encrypted) { + throw new RPCError(errs.WALLET_WRONG_ENC_STATE, + 'Already running with an encrypted wallet.'); + } - if (wallet.master.encrypted) { - throw new RPCError(errs.WALLET_WRONG_ENC_STATE, - 'Already running with an encrypted wallet.'); - } + if (passphrase.length < 1) + throw new RPCError(errs.MISC_ERROR, 'encryptwallet "passphrase"'); - if (passphrase.length < 1) - throw new RPCError(errs.MISC_ERROR, 'encryptwallet "passphrase"'); + try { + await wallet.encrypt(passphrase); + } catch (e) { + throw new RPCError(errs.WALLET_ENCRYPTION_FAILED, 'Encryption failed.'); + } - try { - await wallet.setPassphrase(passphrase); - } catch (e) { - throw new RPCError(errs.WALLET_ENCRYPTION_FAILED, 'Encryption failed.'); + return 'wallet encrypted; we do not need to stop!'; } - return 'wallet encrypted; we do not need to stop!'; -}; + async getAccountAddress(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'getaccountaddress "account"'); + + const wallet = this.wallet; + const valid = new Validator(args); + let name = valid.str(0, ''); -RPC.prototype.getAccountAddress = async function getAccountAddress(args, help) { - if (help || args.length !== 1) - throw new RPCError(errs.MISC_ERROR, 'getaccountaddress "account"'); + if (!name) + name = 'default'; - const wallet = this.wallet; - const valid = new Validator([args]); - let name = valid.str(0, ''); + const addr = await wallet.receiveAddress(name); - if (!name) - name = 'default'; + if (!addr) + return ''; - const account = await wallet.getAccount(name); + return addr.toString(this.network); + } - if (!account) - return ''; + async getAccount(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'getaccount "bitcoinaddress"'); - return account.receive.getAddress('string'); -}; + const wallet = this.wallet; + const valid = new Validator(args); + const addr = valid.str(0, ''); -RPC.prototype.getAccount = async function getAccount(args, help) { - if (help || args.length !== 1) - throw new RPCError(errs.MISC_ERROR, 'getaccount "bitcoinaddress"'); + const hash = parseHash(addr, this.network); + const path = await wallet.getPath(hash); - const wallet = this.wallet; - const valid = new Validator([args]); - const addr = valid.str(0, ''); + if (!path) + return ''; - const hash = parseHash(addr, this.network); - const path = await wallet.getPath(hash); + return path.name; + } - if (!path) - return ''; + async getAddressesByAccount(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'getaddressesbyaccount "account"'); + + const wallet = this.wallet; + const valid = new Validator(args); + let name = valid.str(0, ''); + const addrs = []; + + if (name === '') + name = 'default'; + + let paths; + try { + paths = await wallet.getPaths(name); + } catch (e) { + if (e.message === 'Account not found.') + return []; + throw e; + } - return path.name; -}; + for (const path of paths) { + const addr = path.toAddress(); + addrs.push(addr.toString(this.network)); + } -RPC.prototype.getAddressesByAccount = async function getAddressesByAccount(args, help) { - if (help || args.length !== 1) - throw new RPCError(errs.MISC_ERROR, 'getaddressesbyaccount "account"'); + return addrs; + } - const wallet = this.wallet; - const valid = new Validator([args]); - let name = valid.str(0, ''); - const addrs = []; + async getAddressInfo(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'getaddressinfo "address"'); - if (name === '') - name = 'default'; + const valid = new Validator(args); + const addr = valid.str(0, ''); - const paths = await wallet.getPaths(name); + const address = parseAddress(addr, this.network); + const script = Script.fromAddress(address); + const wallet = this.wallet.toJSON(); - for (const path of paths) { - const addr = path.toAddress(); - addrs.push(addr.toString(this.network)); - } + const path = await this.wallet.getPath(address); - return addrs; -}; + const isScript = script.isScripthash() || script.isWitnessScripthash(); + const isWitness = address.isProgram(); + + const result = { + address: address.toString(this.network), + scriptPubKey: script ? script.toJSON() : undefined, + ismine: path != null, + ischange: path ? path.branch === 1 : false, + iswatchonly: wallet.watchOnly, + isscript: isScript, + iswitness: isWitness + }; + + if (isWitness) { + result.witness_version = address.version; + result.witness_program = address.hash.toString('hex'); + } -RPC.prototype.getBalance = async function getBalance(args, help) { - if (help || args.length > 3) { - throw new RPCError(errs.MISC_ERROR, - 'getbalance ( "account" minconf includeWatchonly )'); + return result; } - const wallet = this.wallet; - const valid = new Validator([args]); - let name = valid.str(0); - const minconf = valid.u32(1, 1); - const watchOnly = valid.bool(2, false); + async getBalance(args, help) { + if (help || args.length > 3) { + throw new RPCError(errs.MISC_ERROR, + 'getbalance ( "account" minconf includeWatchonly )'); + } - if (name === '') - name = 'default'; + const wallet = this.wallet; + const valid = new Validator(args); + let name = valid.str(0); + const minconf = valid.u32(1, 1); + const watchOnly = valid.bool(2, false); - if (name === '*') - name = null; + if (name === '') + name = 'default'; - if (wallet.watchOnly !== watchOnly) - return 0; + if (name === '*') + name = null; - const balance = await wallet.getBalance(name); + if (wallet.watchOnly !== watchOnly) + return 0; - let value; - if (minconf > 0) - value = balance.confirmed; - else - value = balance.unconfirmed; + const balance = await wallet.getBalance(name); - return Amount.btc(value, true); -}; + let value; + if (minconf > 0) + value = balance.confirmed; + else + value = balance.unconfirmed; -RPC.prototype.getNewAddress = async function getNewAddress(args, help) { - if (help || args.length > 1) - throw new RPCError(errs.MISC_ERROR, 'getnewaddress ( "account" )'); + return Amount.btc(value, true); + } - const wallet = this.wallet; - const valid = new Validator([args]); - let name = valid.str(0); + async getNewAddress(args, help) { + if (help || args.length > 1) + throw new RPCError(errs.MISC_ERROR, 'getnewaddress ( "account" )'); - if (name === '') - name = 'default'; + const wallet = this.wallet; + const valid = new Validator(args); + let name = valid.str(0); - const addr = await wallet.createReceive(name); + if (name === '' || args.length === 0) + name = 'default'; - return addr.getAddress('string'); -}; + const addr = await wallet.createReceive(name); -RPC.prototype.getRawChangeAddress = async function getRawChangeAddress(args, help) { - if (help || args.length > 1) - throw new RPCError(errs.MISC_ERROR, 'getrawchangeaddress'); + return addr.getAddress('string', this.network); + } - const wallet = this.wallet; - const addr = await wallet.createChange(); + async getRawChangeAddress(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getrawchangeaddress'); - return addr.getAddress('string'); -}; + const wallet = this.wallet; + const addr = await wallet.createChange(); -RPC.prototype.getReceivedByAccount = async function getReceivedByAccount(args, help) { - if (help || args.length < 1 || args.length > 2) { - throw new RPCError(errs.MISC_ERROR, - 'getreceivedbyaccount "account" ( minconf )'); + return addr.getAddress('string', this.network); } - const wallet = this.wallet; - const valid = new Validator([args]); - let name = valid.str(0); - const minconf = valid.u32(1, 0); - const height = this.wdb.state.height; + async getReceivedByAccount(args, help) { + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'getreceivedbyaccount "account" ( minconf )'); + } - if (name === '') - name = 'default'; + const wallet = this.wallet; + const valid = new Validator(args); + let name = valid.str(0); + const minconf = valid.u32(1, 0); + const height = this.wdb.state.height; - const paths = await wallet.getPaths(name); - const filter = new Set(); + if (name === '') + name = 'default'; - for (const path of paths) - filter.add(path.hash); + const paths = await wallet.getPaths(name); + const filter = new BufferSet(); - const txs = await wallet.getHistory(name); + for (const path of paths) + filter.add(path.hash); - let total = 0; - let lastConf = -1; + const txs = await wallet.getHistory(name); - for (const wtx of txs) { - const conf = wtx.getDepth(height); + let total = 0; + let lastConf = -1; - if (conf < minconf) - continue; + for (const wtx of txs) { + const conf = wtx.getDepth(height); - if (lastConf === -1 || conf < lastConf) - lastConf = conf; + if (conf < minconf) + continue; - for (const output of wtx.tx.outputs) { - const hash = output.getHash('hex'); - if (hash && filter.has(hash)) - total += output.value; - } - } + if (lastConf === -1 || conf < lastConf) + lastConf = conf; - return Amount.btc(total, true); -}; + for (const output of wtx.tx.outputs) { + const hash = output.getHash(); + if (hash && filter.has(hash)) + total += output.value; + } + } -RPC.prototype.getReceivedByAddress = async function getReceivedByAddress(args, help) { - if (help || args.length < 1 || args.length > 2) { - throw new RPCError(errs.MISC_ERROR, - 'getreceivedbyaddress "bitcoinaddress" ( minconf )'); + return Amount.btc(total, true); } - const wallet = this.wallet; - const valid = new Validator([args]); - const addr = valid.str(0, ''); - const minconf = valid.u32(1, 0); - const height = this.wdb.state.height; + async getReceivedByAddress(args, help) { + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'getreceivedbyaddress "bitcoinaddress" ( minconf )'); + } + + const wallet = this.wallet; + const valid = new Validator(args); + const addr = valid.str(0, ''); + const minconf = valid.u32(1, 0); + const height = this.wdb.state.height; - const hash = parseHash(addr, this.network); - const txs = await wallet.getHistory(); + const hash = parseHash(addr, this.network); + const txs = await wallet.getHistory(); - let total = 0; + let total = 0; - for (const wtx of txs) { - if (wtx.getDepth(height) < minconf) - continue; + for (const wtx of txs) { + if (wtx.getDepth(height) < minconf) + continue; - for (const output of wtx.tx.outputs) { - if (output.getHash('hex') === hash) - total += output.value; + for (const output of wtx.tx.outputs) { + if (output.getHash().equals(hash)) + total += output.value; + } } - } - return Amount.btc(total, true); -}; + return Amount.btc(total, true); + } -RPC.prototype._toWalletTX = async function _toWalletTX(wtx) { - const wallet = this.wallet; - const details = await wallet.toDetails(wtx); + async _toWalletTX(wtx) { + const wallet = this.wallet; + const details = await wallet.toDetails(wtx); - if (!details) - throw new RPCError(errs.WALLET_ERROR, 'TX not found.'); + if (!details) + throw new RPCError(errs.WALLET_ERROR, 'TX not found.'); - let receive = true; - for (const member of details.inputs) { - if (member.path) { - receive = false; - break; + let receive = true; + for (const member of details.inputs) { + if (member.path) { + receive = false; + break; + } } - } - const det = []; - let sent = 0; - let received = 0; + const det = []; + let sent = 0; + let received = 0; + + for (let i = 0; i < details.outputs.length; i++) { + const member = details.outputs[i]; - for (let i = 0; i < details.outputs.length; i++) { - const member = details.outputs[i]; + if (member.path) { + if (member.path.branch === 1) + continue; + + det.push({ + account: member.path.name, + address: member.address.toString(this.network), + category: 'receive', + amount: Amount.btc(member.value, true), + label: member.path.name, + vout: i + }); + + received += member.value; - if (member.path) { - if (member.path.branch === 1) + continue; + } + + if (receive) continue; det.push({ - account: member.path.name, - address: member.address.toString(this.network), - category: 'receive', - amount: Amount.btc(member.value, true), - label: member.path.name, + account: '', + address: member.address + ? member.address.toString(this.network) + : null, + category: 'send', + amount: -(Amount.btc(member.value, true)), + fee: -(Amount.btc(details.fee, true)), vout: i }); - received += member.value; + sent += member.value; + } + + return { + amount: Amount.btc(receive ? received : -sent, true), + confirmations: details.confirmations, + blockhash: details.block ? util.revHex(details.block) : null, + blockindex: details.index, + blocktime: details.time, + txid: util.revHex(details.hash), + walletconflicts: [], + time: details.mtime, + timereceived: details.mtime, + 'bip125-replaceable': 'no', + details: det, + hex: details.tx.toRaw().toString('hex') + }; + } - continue; + async getTransaction(args, help) { + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'gettransaction "txid" ( includeWatchonly )'); } - if (receive) - continue; + const wallet = this.wallet; + const valid = new Validator(args); + const hash = valid.brhash(0); + const watchOnly = valid.bool(1, false); - det.push({ - account: '', - address: member.address - ? member.address.toString(this.network) - : null, - category: 'send', - amount: -(Amount.btc(member.value, true)), - fee: -(Amount.btc(details.fee, true)), - vout: i - }); + if (!hash) + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter'); - sent += member.value; - } + const wtx = await wallet.getTX(hash); - return { - amount: Amount.btc(receive ? received : -sent, true), - confirmations: details.confirmations, - blockhash: details.block ? util.revHex(details.block) : null, - blockindex: details.index, - blocktime: details.time, - txid: util.revHex(details.hash), - walletconflicts: [], - time: details.mtime, - timereceived: details.mtime, - 'bip125-replaceable': 'no', - details: det, - hex: details.tx.toRaw().toString('hex') - }; -}; + if (!wtx) + throw new RPCError(errs.WALLET_ERROR, 'TX not found.'); -RPC.prototype.getTransaction = async function getTransaction(args, help) { - if (help || args.length < 1 || args.length > 2) { - throw new RPCError(errs.MISC_ERROR, - 'gettransaction "txid" ( includeWatchonly )'); + return await this._toWalletTX(wtx, watchOnly); } - const wallet = this.wallet; - const valid = new Validator([args]); - const hash = valid.hash(0); - const watchOnly = valid.bool(1, false); + async abandonTransaction(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'abandontransaction "txid"'); - if (!hash) - throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter'); + const wallet = this.wallet; + const valid = new Validator(args); + const hash = valid.brhash(0); - const wtx = await wallet.getTX(hash); + if (!hash) + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); - if (!wtx) - throw new RPCError(errs.WALLET_ERROR, 'TX not found.'); + const result = await wallet.abandon(hash); - return await this._toWalletTX(wtx, watchOnly); -}; + if (!result) + throw new RPCError(errs.WALLET_ERROR, 'Transaction not in wallet.'); -RPC.prototype.abandonTransaction = async function abandonTransaction(args, help) { - if (help || args.length !== 1) - throw new RPCError(errs.MISC_ERROR, 'abandontransaction "txid"'); + return null; + } - const wallet = this.wallet; - const valid = new Validator([args]); - const hash = valid.hash(0); + async getUnconfirmedBalance(args, help) { + if (help || args.length > 0) + throw new RPCError(errs.MISC_ERROR, 'getunconfirmedbalance'); - if (!hash) - throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); + const wallet = this.wallet; + const balance = await wallet.getBalance(); - const result = await wallet.abandon(hash); + return Amount.btc(balance.unconfirmed, true); + } - if (!result) - throw new RPCError(errs.WALLET_ERROR, 'Transaction not in wallet.'); + async getWalletInfo(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getwalletinfo'); + + const wallet = this.wallet; + const balance = await wallet.getBalance(); + + return { + walletid: wallet.id, + walletversion: 6, + balance: Amount.btc(balance.unconfirmed, true), + unconfirmed_balance: Amount.btc(balance.unconfirmed, true), + txcount: balance.tx, + keypoololdest: 0, + keypoolsize: 0, + unlocked_until: wallet.master.until, + paytxfee: Amount.btc(this.wdb.feeRate, true) + }; + } - return null; -}; + async importPrivKey(args, help) { + if (help || args.length < 1 || args.length > 3) { + throw new RPCError(errs.MISC_ERROR, + 'importprivkey "bitcoinprivkey" ( "label" rescan )'); + } -RPC.prototype.getUnconfirmedBalance = async function getUnconfirmedBalance(args, help) { - if (help || args.length > 0) - throw new RPCError(errs.MISC_ERROR, 'getunconfirmedbalance'); + const wallet = this.wallet; + const valid = new Validator(args); + const secret = valid.str(0); + const rescan = valid.bool(2, false); - const wallet = this.wallet; - const balance = await wallet.getBalance(); + const key = parseSecret(secret, this.network); - return Amount.btc(balance.unconfirmed, true); -}; + await wallet.importKey(0, key); -RPC.prototype.getWalletInfo = async function getWalletInfo(args, help) { - if (help || args.length !== 0) - throw new RPCError(errs.MISC_ERROR, 'getwalletinfo'); - - const wallet = this.wallet; - const balance = await wallet.getBalance(); - - return { - walletid: wallet.id, - walletversion: 6, - balance: Amount.btc(balance.unconfirmed, true), - unconfirmed_balance: Amount.btc(balance.unconfirmed, true), - txcount: wallet.txdb.state.tx, - keypoololdest: 0, - keypoolsize: 0, - unlocked_until: wallet.master.until, - paytxfee: Amount.btc(this.wdb.feeRate, true) - }; -}; + if (rescan) + await this.wdb.rescan(0); -RPC.prototype.importPrivKey = async function importPrivKey(args, help) { - if (help || args.length < 1 || args.length > 3) { - throw new RPCError(errs.MISC_ERROR, - 'importprivkey "bitcoinprivkey" ( "label" rescan )'); + return null; } - const wallet = this.wallet; - const valid = new Validator([args]); - const secret = valid.str(0); - const rescan = valid.bool(2, false); + async importWallet(args, help) { + if (help || args.length < 1 || args.length > 2) + throw new RPCError(errs.MISC_ERROR, 'importwallet "filename" ( rescan )'); - const key = parseSecret(secret, this.network); + const wallet = this.wallet; + const valid = new Validator(args); + const file = valid.str(0); + const rescan = valid.bool(1, false); - await wallet.importKey(0, key); + if (fs.unsupported) + throw new RPCError(errs.INTERNAL_ERROR, 'FS not available.'); - if (rescan) - await this.wdb.rescan(0); - - return null; -}; + let data; + try { + data = await fs.readFile(file, 'utf8'); + } catch (e) { + throw new RPCError(errs.INTERNAL_ERROR, e.code || ''); + } -RPC.prototype.importWallet = async function importWallet(args, help) { - if (help || args.length !== 1) - throw new RPCError(errs.MISC_ERROR, 'importwallet "filename" ( rescan )'); + const lines = data.split(/\n+/); + const keys = []; - const wallet = this.wallet; - const valid = new Validator([args]); - const file = valid.str(0); - const rescan = valid.bool(1, false); + for (let line of lines) { + line = line.trim(); - if (fs.unsupported) - throw new RPCError(errs.INTERNAL_ERROR, 'FS not available.'); + if (line.length === 0) + continue; - const data = await fs.readFile(file, 'utf8'); - const lines = data.split(/\n+/); - const keys = []; + if (/^\s*#/.test(line)) + continue; - for (let line of lines) { - line = line.trim(); + const parts = line.split(/\s+/); - if (line.length === 0) - continue; + if (parts.length < 4) + throw new RPCError(errs.DESERIALIZATION_ERROR, 'Malformed wallet.'); - if (/^\s*#/.test(line)) - continue; + const secret = parseSecret(parts[0], this.network); - const parts = line.split(/\s+/); + keys.push(secret); + } - if (parts.length < 4) - throw new RPCError(errs.DESERIALIZATION_ERROR, 'Malformed wallet.'); + for (const key of keys) + await wallet.importKey(0, key); - const secret = parseSecret(parts[0], this.network); + if (rescan) + await this.wdb.rescan(0); - keys.push(secret); + return null; } - for (const key of keys) - await wallet.importKey(0, key); + async importAddress(args, help) { + if (help || args.length < 1 || args.length > 4) { + throw new RPCError(errs.MISC_ERROR, + 'importaddress "address" ( "label" rescan p2sh )'); + } - if (rescan) - await this.wdb.rescan(0); + const wallet = this.wallet; + const valid = new Validator(args); + let addr = valid.str(0, ''); + const rescan = valid.bool(2, false); + const p2sh = valid.bool(3, false); - return null; -}; + if (p2sh) { + let script = valid.buf(0); -RPC.prototype.importAddress = async function importAddress(args, help) { - if (help || args.length < 1 || args.length > 4) { - throw new RPCError(errs.MISC_ERROR, - 'importaddress "address" ( "label" rescan p2sh )'); - } + if (!script) + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameters.'); - const wallet = this.wallet; - const valid = new Validator([args]); - let addr = valid.str(0, ''); - const rescan = valid.bool(2, false); - const p2sh = valid.bool(3, false); + script = Script.fromRaw(script); + script = Script.fromScripthash(script.hash160()); - if (p2sh) { - let script = valid.buf(0); + addr = script.getAddress(); + } else { + addr = parseAddress(addr, this.network); + } - if (!script) - throw new RPCError(errs.TYPE_ERROR, 'Invalid parameters.'); + try { + await wallet.importAddress(0, addr); + } catch (e) { + if (e.message !== 'Address already exists.') + throw e; + } - script = Script.fromRaw(script); - script = Script.fromScripthash(script.hash160()); + if (rescan) + await this.wdb.rescan(0); - addr = script.getAddress(); - } else { - addr = parseAddress(addr, this.network); + return null; } - await wallet.importAddress(0, addr); - - if (rescan) - await this.wdb.rescan(0); - - return null; -}; + async importPubkey(args, help) { + if (help || args.length < 1 || args.length > 3) { + throw new RPCError(errs.MISC_ERROR, + 'importpubkey "pubkey" ( "label" rescan )'); + } -RPC.prototype.importPubkey = async function importPubkey(args, help) { - if (help || args.length < 1 || args.length > 4) { - throw new RPCError(errs.MISC_ERROR, - 'importpubkey "pubkey" ( "label" rescan )'); - } + const wallet = this.wallet; + const valid = new Validator(args); + const data = valid.buf(0); + const rescan = valid.bool(2, false); - const wallet = this.wallet; - const valid = new Validator([args]); - const data = valid.buf(0); - const rescan = valid.bool(2, false); + if (!data) + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); - if (!data) - throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); + const key = KeyRing.fromPublic(data, this.network); - const key = KeyRing.fromPublic(data, this.network); + await wallet.importKey(0, key); - await wallet.importKey(0, key); + if (rescan) + await this.wdb.rescan(0); - if (rescan) - await this.wdb.rescan(0); + return null; + } - return null; -}; + async keyPoolRefill(args, help) { + if (help || args.length > 1) + throw new RPCError(errs.MISC_ERROR, 'keypoolrefill ( newsize )'); + return null; + } -RPC.prototype.keyPoolRefill = async function keyPoolRefill(args, help) { - if (help || args.length > 1) - throw new RPCError(errs.MISC_ERROR, 'keypoolrefill ( newsize )'); - return null; -}; + async listAccounts(args, help) { + if (help || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'listaccounts ( minconf includeWatchonly)'); + } -RPC.prototype.listAccounts = async function listAccounts(args, help) { - if (help || args.length > 2) { - throw new RPCError(errs.MISC_ERROR, - 'listaccounts ( minconf includeWatchonly)'); - } + const wallet = this.wallet; + const valid = new Validator(args); + const minconf = valid.u32(0, 0); + const watchOnly = valid.bool(1, false); - const wallet = this.wallet; - const valid = new Validator([args]); - const minconf = valid.u32(0, 0); - const watchOnly = valid.bool(1, false); + const accounts = await wallet.getAccounts(); + const map = {}; - const accounts = await wallet.getAccounts(); - const map = {}; + for (const account of accounts) { + const balance = await wallet.getBalance(account); + let value = balance.unconfirmed; - for (const account of accounts) { - const balance = await wallet.getBalance(account); - let value = balance.unconfirmed; + if (minconf > 0) + value = balance.confirmed; - if (minconf > 0) - value = balance.confirmed; + if (wallet.watchOnly !== watchOnly) + value = 0; - if (wallet.watchOnly !== watchOnly) - value = 0; + map[account] = Amount.btc(value, true); + } - map[account] = Amount.btc(value, true); + return map; } - return map; -}; + async listAddressGroupings(args, help) { + throw new Error('Not implemented.'); + } -RPC.prototype.listAddressGroupings = async function listAddressGroupings(args, help) { - if (help) - throw new RPCError(errs.MISC_ERROR, 'listaddressgroupings'); - throw new Error('Not implemented.'); -}; + async listLockUnspent(args, help) { + if (help || args.length > 0) + throw new RPCError(errs.MISC_ERROR, 'listlockunspent'); -RPC.prototype.listLockUnspent = async function listLockUnspent(args, help) { - if (help || args.length > 0) - throw new RPCError(errs.MISC_ERROR, 'listlockunspent'); + const wallet = this.wallet; + const outpoints = wallet.getLocked(); + const out = []; - const wallet = this.wallet; - const outpoints = wallet.getLocked(); - const out = []; + for (const outpoint of outpoints) { + out.push({ + txid: outpoint.txid(), + vout: outpoint.index + }); + } - for (const outpoint of outpoints) { - out.push({ - txid: outpoint.txid(), - vout: outpoint.index - }); + return out; } - return out; -}; + async listReceivedByAccount(args, help) { + if (help || args.length > 3) { + throw new RPCError(errs.MISC_ERROR, + 'listreceivedbyaccount ( minconf includeempty includeWatchonly )'); + } + + const valid = new Validator(args); + const minconf = valid.u32(0, 0); + const includeEmpty = valid.bool(1, false); + const watchOnly = valid.bool(2, false); -RPC.prototype.listReceivedByAccount = async function listReceivedByAccount(args, help) { - if (help || args.length > 3) { - throw new RPCError(errs.MISC_ERROR, - 'listreceivedbyaccount ( minconf includeempty includeWatchonly )'); + return await this._listReceived(minconf, includeEmpty, watchOnly, true); } - const valid = new Validator([args]); - const minconf = valid.u32(0, 0); - const includeEmpty = valid.bool(1, false); - const watchOnly = valid.bool(2, false); + async listReceivedByAddress(args, help) { + if (help || args.length > 3) { + throw new RPCError(errs.MISC_ERROR, + 'listreceivedbyaddress ( minconf includeempty includeWatchonly )'); + } - return await this._listReceived(minconf, includeEmpty, watchOnly, true); -}; + const valid = new Validator(args); + const minconf = valid.u32(0, 0); + const includeEmpty = valid.bool(1, false); + const watchOnly = valid.bool(2, false); -RPC.prototype.listReceivedByAddress = async function listReceivedByAddress(args, help) { - if (help || args.length > 3) { - throw new RPCError(errs.MISC_ERROR, - 'listreceivedbyaddress ( minconf includeempty includeWatchonly )'); + return await this._listReceived(minconf, includeEmpty, watchOnly, false); } - const valid = new Validator([args]); - const minconf = valid.u32(0, 0); - const includeEmpty = valid.bool(1, false); - const watchOnly = valid.bool(2, false); + async _listReceived(minconf, empty, watchOnly, account) { + const wallet = this.wallet; + const paths = await wallet.getPaths(); + const height = this.wdb.state.height; + + const map = new BufferMap(); + for (const path of paths) { + const addr = path.toAddress(); + map.set(path.hash, { + involvesWatchonly: wallet.watchOnly, + address: addr.toString(this.network), + account: path.name, + amount: 0, + confirmations: -1, + label: '' + }); + } - return await this._listReceived(minconf, includeEmpty, watchOnly, false); -}; + const txs = await wallet.getHistory(); -RPC.prototype._listReceived = async function _listReceived(minconf, empty, watchOnly, account) { - const wallet = this.wallet; - const paths = await wallet.getPaths(); - const height = this.wdb.state.height; - - const map = new Map(); - for (const path of paths) { - const addr = path.toAddress(); - map.set(path.hash, { - involvesWatchonly: wallet.watchOnly, - address: addr.toString(this.network), - account: path.name, - amount: 0, - confirmations: -1, - label: '' - }); - } + for (const wtx of txs) { + const conf = wtx.getDepth(height); - const txs = await wallet.getHistory(); + if (conf < minconf) + continue; - for (const wtx of txs) { - const conf = wtx.getDepth(height); + for (const output of wtx.tx.outputs) { + const addr = output.getAddress(); - if (conf < minconf) - continue; + if (!addr) + continue; - for (const output of wtx.tx.outputs) { - const addr = output.getAddress(); + const hash = addr.getHash(); + const entry = map.get(hash); - if (!addr) - continue; + if (entry) { + if (entry.confirmations === -1 || conf < entry.confirmations) + entry.confirmations = conf; + entry.address = addr.toString(this.network); + entry.amount += output.value; + } + } + } - const hash = addr.getHash('hex'); - const entry = map.get(hash); + let out = []; + for (const entry of map.values()) + out.push(entry); - if (entry) { - if (entry.confirmations === -1 || conf < entry.confirmations) - entry.confirmations = conf; - entry.address = addr.toString(this.network); - entry.amount += output.value; + if (account) { + const map = new Map(); + + for (const entry of out) { + const item = map.get(entry.account); + if (!item) { + map.set(entry.account, entry); + entry.address = undefined; + continue; + } + item.amount += entry.amount; } - } - } - let out = []; - for (const entry of map.values()) - out.push(entry); + out = []; - if (account) { - const map = new Map(); + for (const entry of map.values()) + out.push(entry); + } + const result = []; for (const entry of out) { - const item = map.get(entry.account); - if (!item) { - map.set(entry.account, entry); - entry.address = undefined; + if (!empty && entry.amount === 0) continue; - } - item.amount += entry.amount; - } - out = []; + if (entry.confirmations === -1) + entry.confirmations = 0; - for (const entry of map.values()) - out.push(entry); + entry.amount = Amount.btc(entry.amount, true); + result.push(entry); + } + + return result; } - const result = []; - for (const entry of out) { - if (!empty && entry.amount === 0) - continue; + async listSinceBlock(args, help) { + if (help || args.length > 3) { + throw new RPCError(errs.MISC_ERROR, + 'listsinceblock ( "blockhash" target-confirmations includeWatchonly)'); + } - if (entry.confirmations === -1) - entry.confirmations = 0; + const wallet = this.wallet; + const chainHeight = this.wdb.state.height; + const valid = new Validator(args); + const block = valid.brhash(0); + const minconf = valid.u32(1, 0); + const watchOnly = valid.bool(2, false); - entry.amount = Amount.btc(entry.amount, true); - result.push(entry); - } + if (wallet.watchOnly !== watchOnly) + return []; - return result; -}; + let height = -1; -RPC.prototype.listSinceBlock = async function listSinceBlock(args, help) { - const wallet = this.wallet; - const chainHeight = this.wdb.state.height; - const valid = new Validator([args]); - const block = valid.hash(0); - const minconf = valid.u32(1, 0); - const watchOnly = valid.bool(2, false); - - if (help) { - throw new RPCError(errs.MISC_ERROR, - 'listsinceblock ( "blockhash" target-confirmations includeWatchonly)'); - } + if (block) { + const entry = await this.client.getEntry(block); + if (entry) + height = entry.height; + else + throw new RPCError(errs.MISC_ERROR, 'Block not found.'); + } - if (wallet.watchOnly !== watchOnly) - return []; + if (height === -1) + height = chainHeight; - let height = -1; - if (block) { - const entry = await this.client.getEntry(block); - if (entry) - height = entry.height; - } + const txs = await wallet.getHistory(); + const out = []; - if (height === -1) - height = this.chain.height; + let highest = null; - const txs = await wallet.getHistory(); + for (const wtx of txs) { + if (wtx.height < height) + continue; - const out = []; - let highest; - for (const wtx of txs) { - if (wtx.height < height) - continue; + if (wtx.getDepth(chainHeight) < minconf) + continue; - if (wtx.getDepth(chainHeight) < minconf) - continue; + if (!highest || wtx.height > highest) + highest = wtx; - if (!highest || wtx.height > highest) - highest = wtx; + const json = await this._toListTX(wtx); - const json = await this._toListTX(wtx); + out.push(json); + } - out.push(json); + return { + transactions: out, + lastblock: highest && highest.block + ? util.revHex(highest.block) + : util.revHex(consensus.ZERO_HASH) + }; } - return { - transactions: out, - lastblock: highest && highest.block - ? util.revHex(highest.block) - : encoding.NULL_HASH - }; -}; - -RPC.prototype._toListTX = async function _toListTX(wtx) { - const wallet = this.wallet; - const details = await wallet.toDetails(wtx); + async _toListTX(wtx) { + const wallet = this.wallet; + const details = await wallet.toDetails(wtx); - if (!details) - throw new RPCError(errs.WALLET_ERROR, 'TX not found.'); + if (!details) + throw new RPCError(errs.WALLET_ERROR, 'TX not found.'); - let receive = true; - for (const member of details.inputs) { - if (member.path) { - receive = false; - break; + let receive = true; + for (const member of details.inputs) { + if (member.path) { + receive = false; + break; + } } - } - - let sent = 0; - let received = 0; - let sendMember, recMember, sendIndex, recIndex; - for (let i = 0; i < details.outputs.length; i++) { - const member = details.outputs[i]; - if (member.path) { - if (member.path.branch === 1) + let sent = 0; + let received = 0; + let sendMember = null; + let recMember = null; + let sendIndex = -1; + let recIndex = -1; + + for (let i = 0; i < details.outputs.length; i++) { + const member = details.outputs[i]; + + if (member.path) { + if (member.path.branch === 1) + continue; + received += member.value; + recMember = member; + recIndex = i; continue; - received += member.value; - recMember = member; - recIndex = i; - continue; - } + } - sent += member.value; - sendMember = member; - sendIndex = i; - } + sent += member.value; + sendMember = member; + sendIndex = i; + } - let member, index; - if (receive) { - member = recMember; - index = recIndex; - } else { - member = sendMember; - index = sendIndex; - } + let member = null; + let index = -1; + + if (receive) { + assert(recMember); + member = recMember; + index = recIndex; + } else { + if (sendMember) { + member = sendMember; + index = sendIndex; + } else { + // In the odd case where we send to ourselves. + receive = true; + received = 0; + member = recMember; + index = recIndex; + } + } - // In the odd case where we send to ourselves. - if (!member) { - assert(!receive); - member = recMember; - index = recIndex; - } + let rbf = false; - return { - account: member.path ? member.path.name : '', - address: member.address - ? member.address.toString(this.network) - : null, - category: receive ? 'receive' : 'send', - amount: Amount.btc(receive ? received : -sent, true), - label: member.path ? member.path.name : undefined, - vout: index, - confirmations: details.getDepth(), - blockhash: details.block ? util.revHex(details.block) : null, - blockindex: details.index, - blocktime: details.time, - txid: util.revHex(details.hash), - walletconflicts: [], - time: details.mtime, - timereceived: details.mtime, - 'bip125-replaceable': 'no' - }; -}; + if (wtx.height === -1 && wtx.tx.isRBF()) + rbf = true; -RPC.prototype.listTransactions = async function listTransactions(args, help) { - if (help || args.length > 4) { - throw new RPCError(errs.MISC_ERROR, - 'listtransactions ( "account" count from includeWatchonly)'); + return { + account: member.path ? member.path.name : '', + address: member.address + ? member.address.toString(this.network) + : null, + category: receive ? 'receive' : 'send', + amount: Amount.btc(receive ? received : -sent, true), + label: member.path ? member.path.name : undefined, + vout: index, + confirmations: details.getDepth(this.wdb.height), + blockhash: details.block ? util.revHex(details.block) : null, + blockindex: -1, + blocktime: details.time, + blockheight: details.height, + txid: util.revHex(details.hash), + walletconflicts: [], + time: details.mtime, + timereceived: details.mtime, + 'bip125-replaceable': rbf ? 'yes' : 'no' + }; } - const wallet = this.wallet; - const valid = new Validator([args]); - let name = valid.str(0); - const count = valid.u32(1, 10); - const from = valid.u32(2, 0); - const watchOnly = valid.bool(3, false); + async listTransactions(args, help) { + if (help || args.length > 4) { + throw new RPCError(errs.MISC_ERROR, + 'listtransactions ( "account" count from includeWatchonly)'); + } - if (wallet.watchOnly !== watchOnly) - return []; + const wallet = this.wallet; + const valid = new Validator(args); + let name = valid.str(0); + const count = valid.u32(1, 10); + const from = valid.u32(2, 0); + const watchOnly = valid.bool(3, false); - if (name === '') - name = 'default'; + if (wallet.watchOnly !== watchOnly) + return []; - const txs = await wallet.getHistory(); + if (name === '') + name = 'default'; - common.sortTX(txs); + const txs = await wallet.getHistory(name); - const end = from + count; - const to = Math.min(end, txs.length); - const out = []; + common.sortTX(txs); - for (let i = from; i < to; i++) { - const wtx = txs[i]; - const json = await this._toListTX(wtx); - out.push(json); - } + const end = from + count; + const to = Math.min(end, txs.length); + const out = []; - return out; -}; + for (let i = from; i < to; i++) { + const wtx = txs[i]; + const json = await this._toListTX(wtx); + out.push(json); + } -RPC.prototype.listUnspent = async function listUnspent(args, help) { - if (help || args.length > 3) { - throw new RPCError(errs.MISC_ERROR, - 'listunspent ( minconf maxconf ["address",...] )'); + return out; } - const wallet = this.wallet; - const valid = new Validator([args]); - const minDepth = valid.u32(0, 1); - const maxDepth = valid.u32(1, 9999999); - const addrs = valid.array(2); - const height = this.wdb.state.height; + async listUnspent(args, help) { + if (help || args.length > 3) { + throw new RPCError(errs.MISC_ERROR, + 'listunspent ( minconf maxconf ["address",...] )'); + } + + const wallet = this.wallet; + const valid = new Validator(args); + const minDepth = valid.u32(0, 1); + const maxDepth = valid.u32(1, 9999999); + const addrs = valid.array(2); + const height = this.wdb.state.height; - const map = new Set(); + const map = new BufferSet(); - if (addrs) { - const valid = new Validator([addrs]); - for (let i = 0; i < addrs.length; i++) { - const addr = valid.str(i, ''); - const hash = parseHash(addr, this.network); + if (addrs) { + const valid = new Validator(addrs); + for (let i = 0; i < addrs.length; i++) { + const addr = valid.str(i, ''); + const hash = parseHash(addr, this.network); - if (map.has(hash)) - throw new RPCError(errs.INVALID_PARAMETER, 'Duplicate address.'); + if (map.has(hash)) + throw new RPCError(errs.INVALID_PARAMETER, 'Duplicate address.'); - map.add(hash); + map.add(hash); + } } - } - const coins = await wallet.getCoins(); + const coins = await wallet.getCoins(); - common.sortCoins(coins); + common.sortCoins(coins); - const out = []; + const out = []; - for (const coin of coins) { - const depth = coin.getDepth(height); + for (const coin of coins) { + const depth = coin.getDepth(height); - if (depth < minDepth || depth > maxDepth) - continue; + if (depth < minDepth || depth > maxDepth) + continue; - const addr = coin.getAddress(); + const addr = coin.getAddress(); - if (!addr) - continue; + if (!addr) + continue; - const hash = coin.getHash('hex'); + const hash = coin.getHash(); - if (addrs) { - if (!hash || !map.has(hash)) - continue; + if (addrs) { + if (!hash || !map.has(hash)) + continue; + } + + const ring = await wallet.getKey(hash); + + out.push({ + txid: coin.txid(), + vout: coin.index, + address: addr ? addr.toString(this.network) : null, + account: ring ? ring.name : undefined, + redeemScript: ring && ring.script + ? ring.script.toJSON() + : undefined, + scriptPubKey: coin.script.toJSON(), + amount: Amount.btc(coin.value, true), + confirmations: depth, + spendable: !wallet.isLocked(coin), + solvable: true + }); } - const ring = await wallet.getKey(hash); - - out.push({ - txid: coin.txid(), - vout: coin.index, - address: addr ? addr.toString(this.network) : null, - account: ring ? ring.name : undefined, - redeemScript: ring && ring.script - ? ring.script.toJSON() - : undefined, - scriptPubKey: coin.script.toJSON(), - amount: Amount.btc(coin.value, true), - confirmations: depth, - spendable: !wallet.isLocked(coin), - solvable: true - }); + return out; } - return out; -}; + async lockUnspent(args, help) { + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'lockunspent unlock ([{"txid":"txid","vout":n},...])'); + } -RPC.prototype.lockUnspent = async function lockUnspent(args, help) { - if (help || args.length < 1 || args.length > 2) { - throw new RPCError(errs.MISC_ERROR, - 'lockunspent unlock ([{"txid":"txid","vout":n},...])'); - } + const wallet = this.wallet; + const valid = new Validator(args); + const unlock = valid.bool(0, false); + const outputs = valid.array(1); - const wallet = this.wallet; - const valid = new Validator([args]); - const unlock = valid.bool(0, false); - const outputs = valid.array(1); + if (args.length === 1) { + if (unlock) + wallet.unlockCoins(); + return true; + } - if (args.length === 1) { - if (unlock) - wallet.unlockCoins(); - return true; - } + if (!outputs) + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); - if (!outputs) - throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); + for (const output of outputs) { + const valid = new Validator(output); + const hash = valid.brhash('txid'); + const index = valid.u32('vout'); - for (const output of outputs) { - const valid = new Validator([output]); - const hash = valid.hash('txid'); - const index = valid.u32('vout'); + if (hash == null || index == null) + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter.'); - if (hash == null || index == null) - throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter.'); + const outpoint = new Outpoint(hash, index); - const outpoint = new Outpoint(hash, index); + if (unlock) { + wallet.unlockCoin(outpoint); + continue; + } - if (unlock) { - wallet.unlockCoin(outpoint); - continue; + wallet.lockCoin(outpoint); } - wallet.lockCoin(outpoint); + return true; } - return true; -}; - -RPC.prototype.move = async function move(args, help) { - // Not implementing: stupid and deprecated. - throw new Error('Not implemented.'); -}; - -RPC.prototype.sendFrom = async function sendFrom(args, help) { - if (help || args.length < 3 || args.length > 6) { - throw new RPCError(errs.MISC_ERROR, - 'sendfrom "fromaccount" "tobitcoinaddress"' - + ' amount ( minconf "comment" "comment-to" )'); + async move(args, help) { + // Not implementing: stupid and deprecated. + throw new Error('Not implemented.'); } - const wallet = this.wallet; - const valid = new Validator([args]); - let name = valid.str(0); - const str = valid.str(1); - const value = valid.ufixed(2, 8); - const minconf = valid.u32(3, 0); + async sendFrom(args, help) { + if (help || args.length < 3 || args.length > 6) { + throw new RPCError(errs.MISC_ERROR, + 'sendfrom "fromaccount" "tobitcoinaddress"' + + ' amount ( minconf "comment" "comment-to" )'); + } - const addr = parseAddress(str, this.network); + const wallet = this.wallet; + const valid = new Validator(args); + let name = valid.str(0); + const str = valid.str(1); + const value = valid.ufixed(2, 8); + const minconf = valid.u32(3, 0); - if (!addr || value == null) - throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); + const addr = parseAddress(str, this.network); - if (name === '') - name = 'default'; + if (!addr || value == null) + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); - const options = { - account: name, - depth: minconf, - outputs: [{ - address: addr, - value: value - }] - }; + if (name === '') + name = 'default'; - const tx = await wallet.send(options); + const options = { + account: name, + depth: minconf, + outputs: [{ + address: addr, + value: value + }] + }; - return tx.txid(); -}; + const tx = await wallet.send(options); -RPC.prototype.sendMany = async function sendMany(args, help) { - if (help || args.length < 2 || args.length > 5) { - throw new RPCError(errs.MISC_ERROR, - 'sendmany "fromaccount" {"address":amount,...}' - + ' ( minconf "comment" ["address",...] )'); + return tx.txid(); } - const wallet = this.wallet; - const valid = new Validator([args]); - let name = valid.str(0); - const sendTo = valid.obj(1); - const minconf = valid.u32(2, 1); - const subtract = valid.bool(4, false); + async sendMany(args, help) { + if (help || args.length < 2 || args.length > 5) { + throw new RPCError(errs.MISC_ERROR, + 'sendmany "fromaccount" {"address":amount,...}' + + ' ( minconf "comment" subtractfee )'); + } - if (name === '') - name = 'default'; + const wallet = this.wallet; + const valid = new Validator(args); + let name = valid.str(0); + const sendTo = valid.obj(1); + const minconf = valid.u32(2, 1); + const subtract = valid.bool(4, false); - if (!sendTo) - throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); + if (name === '') + name = 'default'; - const to = new Validator([sendTo]); - const uniq = new Set(); - const outputs = []; + if (!sendTo) + throw new RPCError(errs.TYPE_ERROR, 'Invalid send-to address.'); - for (const key of Object.keys(sendTo)) { - const value = to.ufixed(key, 8); - const addr = parseAddress(key, this.network); - const hash = addr.getHash('hex'); + const to = new Validator(sendTo); + const uniq = new BufferSet(); + const outputs = []; - if (value == null) - throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter.'); + for (const key of Object.keys(sendTo)) { + const value = to.ufixed(key, 8); + const addr = parseAddress(key, this.network); + const hash = addr.getHash(); - if (uniq.has(hash)) - throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter.'); + if (value == null) + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid amount.'); - uniq.add(hash); + if (uniq.has(hash)) + throw new RPCError(errs.INVALID_PARAMETER, + 'Each send-to address must be unique.'); - const output = new Output(); - output.value = value; - output.script.fromAddress(addr); - outputs.push(output); - } + uniq.add(hash); - const options = { - outputs: outputs, - subtractFee: subtract, - account: name, - depth: minconf - }; + const output = new Output(); + output.value = value; + output.script.fromAddress(addr); + outputs.push(output); + } - const tx = await wallet.send(options); + const options = { + outputs: outputs, + subtractFee: subtract, + account: name, + depth: minconf + }; - return tx.txid(); -}; + const tx = await wallet.send(options); -RPC.prototype.sendToAddress = async function sendToAddress(args, help) { - if (help || args.length < 2 || args.length > 5) { - throw new RPCError(errs.MISC_ERROR, - 'sendtoaddress "bitcoinaddress" amount' - + ' ( "comment" "comment-to" subtractfeefromamount )'); + return tx.txid(); } - const wallet = this.wallet; - const valid = new Validator([args]); - const str = valid.str(0); - const value = valid.ufixed(1, 8); - const subtract = valid.bool(4, false); + async sendToAddress(args, help) { + if (help || args.length < 2 || args.length > 5) { + throw new RPCError(errs.MISC_ERROR, + 'sendtoaddress "bitcoinaddress" amount' + + ' ( "comment" "comment-to" subtractfeefromamount )'); + } - const addr = parseAddress(str, this.network); + const wallet = this.wallet; + const valid = new Validator(args); + const str = valid.str(0); + const value = valid.ufixed(1, 8); + const subtract = valid.bool(4, false); - if (!addr || value == null) - throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); + const addr = parseAddress(str, this.network); - const options = { - subtractFee: subtract, - outputs: [{ - address: addr, - value: value - }] - }; + if (!addr || value == null) + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); - const tx = await wallet.send(options); + const options = { + subtractFee: subtract, + outputs: [{ + address: addr, + value: value + }] + }; - return tx.txid(); -}; + const tx = await wallet.send(options); -RPC.prototype.setAccount = async function setAccount(args, help) { - if (help || args.length < 1 || args.length > 2) { - throw new RPCError(errs.MISC_ERROR, - 'setaccount "bitcoinaddress" "account"'); + return tx.txid(); } - // Impossible to implement in bcoin: - throw new Error('Not implemented.'); -}; + async setAccount(args, help) { + // Impossible to implement in bcoin: + throw new Error('Not implemented.'); + } -RPC.prototype.setTXFee = async function setTXFee(args, help) { - const valid = new Validator([args]); - const rate = valid.ufixed(0, 8); + async setTXFee(args, help) { + const valid = new Validator(args); + const rate = valid.ufixed(0, 8); - if (help || args.length < 1 || args.length > 1) - throw new RPCError(errs.MISC_ERROR, 'settxfee amount'); + if (help || args.length < 1 || args.length > 1) + throw new RPCError(errs.MISC_ERROR, 'settxfee amount'); - if (rate == null) - throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); + if (rate == null) + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); - this.wdb.feeRate = rate; + this.wdb.feeRate = rate; - return true; -}; - -RPC.prototype.signMessage = async function signMessage(args, help) { - if (help || args.length !== 2) { - throw new RPCError(errs.MISC_ERROR, - 'signmessage "bitcoinaddress" "message"'); + return true; } - const wallet = this.wallet; - const valid = new Validator([args]); - const b58 = valid.str(0, ''); - const str = valid.str(1, ''); - - const addr = parseHash(b58, this.network); + async signMessage(args, help) { + if (help || args.length !== 2) { + throw new RPCError(errs.MISC_ERROR, + 'signmessage "bitcoinaddress" "message"'); + } - const ring = await wallet.getKey(addr); + const wallet = this.wallet; + const valid = new Validator(args); + const b58 = valid.str(0, ''); + const str = valid.str(1, ''); - if (!ring) - throw new RPCError(errs.WALLET_ERROR, 'Address not found.'); + const addr = parseHash(b58, this.network); - if (!wallet.master.key) - throw new RPCError(errs.WALLET_UNLOCK_NEEDED, 'Wallet is locked.'); + const ring = await wallet.getKey(addr); - const msg = Buffer.from(MAGIC_STRING + str, 'utf8'); - const hash = digest.hash256(msg); + if (!ring) + throw new RPCError(errs.WALLET_ERROR, 'Address not found.'); - const sig = ring.sign(hash); + if (!wallet.master.key) + throw new RPCError(errs.WALLET_UNLOCK_NEEDED, 'Wallet is locked.'); - return sig.toString('base64'); -}; + const msg = Buffer.from(MAGIC_STRING + str, 'utf8'); + const hash = hash256.digest(msg); -RPC.prototype.walletLock = async function walletLock(args, help) { - const wallet = this.wallet; + const sig = ring.sign(hash); - if (help || (wallet.master.encrypted && args.length !== 0)) - throw new RPCError(errs.MISC_ERROR, 'walletlock'); + return sig.toString('base64'); + } - if (!wallet.master.encrypted) - throw new RPCError(errs.WALLET_WRONG_ENC_STATE, 'Wallet is not encrypted.'); + async walletLock(args, help) { + const wallet = this.wallet; - await wallet.lock(); + if (help || (wallet.master.encrypted && args.length !== 0)) + throw new RPCError(errs.MISC_ERROR, 'walletlock'); - return null; -}; + if (!wallet.master.encrypted) { + throw new RPCError( + errs.WALLET_WRONG_ENC_STATE, + 'Wallet is not encrypted.'); + } -RPC.prototype.walletPassphraseChange = async function walletPassphraseChange(args, help) { - const wallet = this.wallet; + await wallet.lock(); - if (help || (wallet.master.encrypted && args.length !== 2)) { - throw new RPCError(errs.MISC_ERROR, 'walletpassphrasechange' - + ' "oldpassphrase" "newpassphrase"'); + return null; } - const valid = new Validator([args]); - const old = valid.str(0, ''); - const new_ = valid.str(1, ''); + async walletPassphraseChange(args, help) { + const wallet = this.wallet; - if (!wallet.master.encrypted) - throw new RPCError(errs.WALLET_WRONG_ENC_STATE, 'Wallet is not encrypted.'); + if (help || (wallet.master.encrypted && args.length !== 2)) { + throw new RPCError(errs.MISC_ERROR, 'walletpassphrasechange' + + ' "oldpassphrase" "newpassphrase"'); + } - if (old.length < 1 || new_.length < 1) - throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter'); + const valid = new Validator(args); + const old = valid.str(0, ''); + const passphrase = valid.str(1, ''); - await wallet.setPassphrase(old, new_); + if (!wallet.master.encrypted) { + throw new RPCError( + errs.WALLET_WRONG_ENC_STATE, + 'Wallet is not encrypted.'); + } - return null; -}; + if (old.length < 1 || passphrase.length < 1) + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter'); -RPC.prototype.walletPassphrase = async function walletPassphrase(args, help) { - const wallet = this.wallet; - const valid = new Validator([args]); - const passphrase = valid.str(0, ''); - const timeout = valid.u32(1); + await wallet.setPassphrase(passphrase, old); - if (help || (wallet.master.encrypted && args.length !== 2)) { - throw new RPCError(errs.MISC_ERROR, - 'walletpassphrase "passphrase" timeout'); + return null; } - if (!wallet.master.encrypted) - throw new RPCError(errs.WALLET_WRONG_ENC_STATE, 'Wallet is not encrypted.'); + async walletPassphrase(args, help) { + const wallet = this.wallet; + const valid = new Validator(args); + const passphrase = valid.str(0, ''); + const timeout = valid.u32(1); - if (passphrase.length < 1) - throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter'); + if (help || (wallet.master.encrypted && args.length !== 2)) { + throw new RPCError(errs.MISC_ERROR, + 'walletpassphrase "passphrase" timeout'); + } - if (timeout == null) - throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter'); + if (!wallet.master.encrypted) { + throw new RPCError( + errs.WALLET_WRONG_ENC_STATE, + 'Wallet is not encrypted.'); + } - await wallet.unlock(passphrase, timeout); + if (passphrase.length < 1) + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter'); - return null; -}; + if (timeout == null) + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter'); -RPC.prototype.importPrunedFunds = async function importPrunedFunds(args, help) { - if (help || args.length < 2 || args.length > 3) { - throw new RPCError(errs.MISC_ERROR, - 'importprunedfunds "rawtransaction" "txoutproof" ( "label" )'); + await wallet.unlock(passphrase, timeout); + + return null; } - const valid = new Validator([args]); - const txRaw = valid.buf(0); - const blockRaw = valid.buf(1); + async importPrunedFunds(args, help) { + if (help || args.length !== 2) { + throw new RPCError(errs.MISC_ERROR, + 'importprunedfunds "rawtransaction" "txoutproof"'); + } - if (!txRaw || !blockRaw) - throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); + const valid = new Validator(args); + const txRaw = valid.buf(0); + const blockRaw = valid.buf(1); - const tx = TX.fromRaw(txRaw); - const block = MerkleBlock.fromRaw(blockRaw); - const hash = block.hash('hex'); + if (!txRaw || !blockRaw) + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); - if (!block.verify()) - throw new RPCError(errs.VERIFY_ERROR, 'Invalid proof.'); + const tx = TX.fromRaw(txRaw); + const block = MerkleBlock.fromRaw(blockRaw); + const hash = block.hash(); - if (!block.hasTX(tx.hash('hex'))) - throw new RPCError(errs.VERIFY_ERROR, 'Invalid proof.'); + if (!block.verify()) + throw new RPCError(errs.VERIFY_ERROR, 'Invalid proof.'); - const height = await this.client.getEntry(hash); + if (!block.hasTX(tx.hash())) + throw new RPCError(errs.VERIFY_ERROR, 'Invalid proof.'); - if (height === -1) - throw new RPCError(errs.VERIFY_ERROR, 'Invalid proof.'); + const height = await this.client.getEntry(hash); - const entry = { - hash: hash, - time: block.time, - height: height - }; + if (height === -1) + throw new RPCError(errs.VERIFY_ERROR, 'Invalid proof.'); - if (!await this.wdb.addTX(tx, entry)) - throw new RPCError(errs.WALLET_ERROR, 'No tracked address for TX.'); + const entry = { + hash: hash, + time: block.time, + height: height + }; - return null; -}; + if (!await this.wdb.addTX(tx, entry)) + throw new RPCError(errs.WALLET_ERROR, + 'Address for TX not in wallet, or TX already in wallet'); -RPC.prototype.removePrunedFunds = async function removePrunedFunds(args, help) { - if (help || args.length !== 1) - throw new RPCError(errs.MISC_ERROR, 'removeprunedfunds "txid"'); + return null; + } - const wallet = this.wallet; - const valid = new Validator([args]); - const hash = valid.hash(0); + async removePrunedFunds(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'removeprunedfunds "txid"'); - if (!hash) - throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); + const wallet = this.wallet; + const valid = new Validator(args); + const hash = valid.brhash(0); - if (!await wallet.remove(hash)) - throw new RPCError(errs.WALLET_ERROR, 'Transaction not in wallet.'); + if (!hash) + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); - return null; -}; + if (!await wallet.remove(hash)) + throw new RPCError(errs.WALLET_ERROR, 'Transaction not in wallet.'); -RPC.prototype.selectWallet = async function selectWallet(args, help) { - const valid = new Validator([args]); - const id = valid.str(0); + return null; + } - if (help || args.length !== 1) - throw new RPCError(errs.MISC_ERROR, 'selectwallet "id"'); + async selectWallet(args, help) { + const valid = new Validator(args); + const id = valid.str(0); - const wallet = await this.wdb.get(id); + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'selectwallet "id"'); - if (!wallet) - throw new RPCError(errs.WALLET_ERROR, 'Wallet not found.'); + const wallet = await this.wdb.get(id); - this.wallet = wallet; + if (!wallet) + throw new RPCError(errs.WALLET_ERROR, 'Wallet not found.'); - return null; -}; + this.wallet = wallet; + + return null; + } -RPC.prototype.getMemoryInfo = async function getMemoryInfo(args, help) { - if (help || args.length !== 0) - throw new RPCError(errs.MISC_ERROR, 'getmemoryinfo'); + async getMemoryInfo(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getmemoryinfo'); - return util.memoryUsage(); -}; + return this.logger.memoryUsage(); + } -RPC.prototype.setLogLevel = async function setLogLevel(args, help) { - if (help || args.length !== 1) - throw new RPCError(errs.MISC_ERROR, 'setloglevel "level"'); + async setLogLevel(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'setloglevel "level"'); - const valid = new Validator([args]); - const level = valid.str(0, ''); + const valid = new Validator(args); + const level = valid.str(0, ''); - this.logger.setLevel(level); + this.logger.setLevel(level); - return null; -}; + return null; + } +} /* * Helpers @@ -1569,7 +1699,7 @@ RPC.prototype.setLogLevel = async function setLogLevel(args, help) { function parseHash(raw, network) { const addr = parseAddress(raw, network); - return addr.getHash('hex'); + return addr.getHash(); } function parseAddress(raw, network) { diff --git a/lib/wallet/server-browser.js b/lib/wallet/server-browser.js deleted file mode 100644 index 212272704..000000000 --- a/lib/wallet/server-browser.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -exports.unsupported = true; diff --git a/lib/wallet/server.js b/lib/wallet/server.js index f6e863856..e69de29bb 100644 --- a/lib/wallet/server.js +++ b/lib/wallet/server.js @@ -1,109 +0,0 @@ -/*! - * server.js - wallet server for bcoin - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const WalletDB = require('./walletdb'); -const WorkerPool = require('../workers/workerpool'); -const Config = require('../node/config'); -const Logger = require('../node/logger'); -const Client = require('./client'); - -/** - * @exports wallet/server - */ - -const server = exports; - -/** - * Create a wallet server. - * @param {Object} options - * @returns {WalletDB} - */ - -server.create = function create(options) { - const config = new Config('fcoin'); - let logger = new Logger('debug'); - - config.inject(options); - config.load(options); - - if (options.config) - config.open('wallet.conf'); - - if (config.has('logger')) - logger = config.obj('logger'); - - const client = new Client({ - network: config.network, - uri: config.str('node-uri'), - apiKey: config.str('node-api-key') - }); - - logger.set({ - filename: config.bool('log-file') - ? config.location('wallet.log') - : null, - level: config.str('log-level'), - console: config.bool('log-console'), - shrink: config.bool('log-shrink') - }); - - const workers = new WorkerPool({ - enabled: config.str('workers-enabled'), - size: config.uint('workers-size'), - timeout: config.uint('workers-timeout') - }); - - const wdb = new WalletDB({ - network: config.network, - logger: logger, - workers: workers, - client: client, - prefix: config.prefix, - db: config.str('db'), - maxFiles: config.uint('max-files'), - cacheSize: config.mb('cache-size'), - witness: config.bool('witness'), - checkpoints: config.bool('checkpoints'), - startHeight: config.uint('start-height'), - wipeNoReally: config.bool('wipe-no-really'), - apiKey: config.str('api-key'), - walletAuth: config.bool('auth'), - noAuth: config.bool('no-auth'), - ssl: config.str('ssl'), - host: config.str('host'), - port: config.uint('port'), - spv: config.bool('spv'), - verify: config.bool('spv'), - listen: true - }); - - wdb.on('error', () => {}); - - workers.on('spawn', (child) => { - logger.info('Spawning worker process: %d.', child.id); - }); - - workers.on('exit', (code, child) => { - logger.warning('Worker %d exited: %s.', child.id, code); - }); - - workers.on('log', (text, child) => { - logger.debug('Worker %d says:', child.id); - logger.debug(text); - }); - - workers.on('error', (err, child) => { - if (child) { - logger.error('Worker %d error: %s', child.id, err.message); - return; - } - wdb.emit('error', err); - }); - - return wdb; -}; diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 079480e4c..1f9e733aa 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -7,3197 +7,2709 @@ 'use strict'; +const assert = require('bsert'); +const bio = require('bufio'); +const {BufferSet} = require('buffer-map'); const util = require('../utils/util'); -const LRU = require('../utils/lru'); -const assert = require('assert'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); const Amount = require('../btc/amount'); const CoinView = require('../coins/coinview'); const Coin = require('../primitives/coin'); const Outpoint = require('../primitives/outpoint'); const records = require('./records'); const layout = require('./layout').txdb; -const encoding = require('../utils/encoding'); +const consensus = require('../protocol/consensus'); const policy = require('../protocol/policy'); -const Script = require('../script/script'); -const BlockMapRecord = records.BlockMapRecord; -const OutpointMapRecord = records.OutpointMapRecord; -const TXRecord = records.TXRecord; +const {TXRecord} = records; +const {inspectSymbol} = require('../utils'); /** * TXDB * @alias module:wallet.TXDB - * @constructor - * @param {Wallet} wallet */ -function TXDB(wallet) { - if (!(this instanceof TXDB)) - return new TXDB(wallet); - - this.wallet = wallet; - this.walletdb = wallet.db; - this.db = wallet.db.db; - this.logger = wallet.db.logger; - this.network = wallet.db.network; - this.options = wallet.db.options; - this.coinCache = new LRU(10000); - - this.locked = new Set(); - this.state = null; - this.pending = null; - this.events = []; -} +class TXDB { + /** + * Create a TXDB. + * @constructor + * @param {WalletDB} wdb + */ -/** - * Database layout. - * @type {Object} - */ + constructor(wdb, wid) { + this.wdb = wdb; + this.db = wdb.db; + this.logger = wdb.logger; -TXDB.layout = layout; + this.wid = wid || 0; + this.bucket = null; + this.wallet = null; + this.locked = new BufferSet(); + } -/** - * Open TXDB. - * @returns {Promise} - */ + /** + * Open TXDB. + * @returns {Promise} + */ -TXDB.prototype.open = async function open() { - const state = await this.getState(); + async open(wallet) { + const prefix = layout.prefix.encode(wallet.wid); - if (state) { - this.state = state; - this.logger.info('TXDB loaded for %s.', this.wallet.id); - } else { - this.state = new TXDBState(this.wallet.wid, this.wallet.id); - this.logger.info('TXDB created for %s.', this.wallet.id); + this.wid = wallet.wid; + this.bucket = this.db.bucket(prefix); + this.wallet = wallet; } - this.logger.info('TXDB State: tx=%d coin=%s.', - this.state.tx, this.state.coin); - - this.logger.info( - 'Balance: unconfirmed=%s confirmed=%s.', - Amount.btc(this.state.unconfirmed), - Amount.btc(this.state.confirmed)); -}; + /** + * Emit transaction event. + * @private + * @param {String} event + * @param {Object} data + * @param {Details} details + */ + + emit(event, data, details) { + this.wdb.emit(event, this.wallet, data, details); + this.wallet.emit(event, data, details); + } -/** - * Start batch. - * @private - */ + /** + * Get wallet path for output. + * @param {Output} output + * @returns {Promise} - Returns {@link Path}. + */ -TXDB.prototype.start = function start() { - this.pending = this.state.clone(); - this.coinCache.start(); - return this.wallet.start(); -}; + getPath(output) { + const hash = output.getHash(); -/** - * Drop batch. - * @private - */ + if (!hash) + return null; -TXDB.prototype.drop = function drop() { - this.pending = null; - this.events.length = 0; - this.coinCache.drop(); - return this.wallet.drop(); -}; + return this.wdb.getPath(this.wid, hash); + } -/** - * Clear batch. - * @private - */ + /** + * Test whether path exists for output. + * @param {Output} output + * @returns {Promise} - Returns Boolean. + */ -TXDB.prototype.clear = function clear() { - this.pending = this.state.clone(); - this.events.length = 0; - this.coinCache.clear(); - return this.wallet.clear(); -}; + hasPath(output) { + const hash = output.getHash(); -/** - * Save batch. - * @returns {Promise} - */ + if (!hash) + return false; -TXDB.prototype.commit = async function commit() { - try { - await this.wallet.commit(); - } catch (e) { - this.pending = null; - this.events.length = 0; - this.coinCache.drop(); - throw e; - } - - // Overwrite the entire state - // with our new committed state. - if (this.pending.committed) { - this.state = this.pending; - - // Emit buffered events now that - // we know everything is written. - for (const [event, data, details] of this.events) { - this.walletdb.emit(event, this.wallet.id, data, details); - this.wallet.emit(event, data, details); - } + return this.wdb.hasPath(this.wid, hash); } - this.pending = null; - this.events.length = 0; - this.coinCache.commit(); -}; + /** + * Save credit. + * @param {Credit} credit + * @param {Path} path + */ -/** - * Emit transaction event. - * @private - * @param {String} event - * @param {Object} data - * @param {Details} details - */ + async saveCredit(b, credit, path) { + const {coin} = credit; -TXDB.prototype.emit = function emit(event, data, details) { - this.events.push([event, data, details]); -}; + b.put(layout.c.encode(coin.hash, coin.index), credit.toRaw()); + b.put(layout.C.encode(path.account, coin.hash, coin.index), null); -/** - * Prefix a key. - * @param {Buffer} key - * @returns {Buffer} Prefixed key. - */ + return this.addOutpointMap(b, coin.hash, coin.index); + } -TXDB.prototype.prefix = function prefix(key) { - assert(this.wallet.wid); - return layout.prefix(this.wallet.wid, key); -}; + /** + * Remove credit. + * @param {Credit} credit + * @param {Path} path + */ -/** - * Put key and value to current batch. - * @param {String} key - * @param {Buffer} value - */ + async removeCredit(b, credit, path) { + const {coin} = credit; -TXDB.prototype.put = function put(key, value) { - assert(this.wallet.current); - this.wallet.current.put(this.prefix(key), value); -}; + b.del(layout.c.encode(coin.hash, coin.index)); + b.del(layout.C.encode(path.account, coin.hash, coin.index)); -/** - * Delete key from current batch. - * @param {String} key - */ + return this.removeOutpointMap(b, coin.hash, coin.index); + } -TXDB.prototype.del = function del(key) { - assert(this.wallet.current); - this.wallet.current.del(this.prefix(key)); -}; + /** + * Spend credit. + * @param {Credit} credit + * @param {TX} tx + * @param {Number} index + */ + + spendCredit(b, credit, tx, index) { + const prevout = tx.inputs[index].prevout; + const spender = Outpoint.fromTX(tx, index); + b.put(layout.s.encode(prevout.hash, prevout.index), spender.toRaw()); + b.put(layout.d.encode(spender.hash, spender.index), credit.coin.toRaw()); + } -/** - * Get. - * @param {String} key - */ + /** + * Unspend credit. + * @param {TX} tx + * @param {Number} index + */ + + unspendCredit(b, tx, index) { + const prevout = tx.inputs[index].prevout; + const spender = Outpoint.fromTX(tx, index); + b.del(layout.s.encode(prevout.hash, prevout.index)); + b.del(layout.d.encode(spender.hash, spender.index)); + } -TXDB.prototype.get = function get(key) { - return this.db.get(this.prefix(key)); -}; + /** + * Write input record. + * @param {TX} tx + * @param {Number} index + */ + + async writeInput(b, tx, index) { + const prevout = tx.inputs[index].prevout; + const spender = Outpoint.fromTX(tx, index); + b.put(layout.s.encode(prevout.hash, prevout.index), spender.toRaw()); + return this.addOutpointMap(b, prevout.hash, prevout.index); + } -/** - * Has. - * @param {String} key - */ + /** + * Remove input record. + * @param {TX} tx + * @param {Number} index + */ -TXDB.prototype.has = function has(key) { - return this.db.has(this.prefix(key)); -}; + async removeInput(b, tx, index) { + const prevout = tx.inputs[index].prevout; + b.del(layout.s.encode(prevout.hash, prevout.index)); + return this.removeOutpointMap(b, prevout.hash, prevout.index); + } -/** - * Iterate. - * @param {Object} options - * @returns {Promise} - */ + /** + * Update wallet balance. + * @param {BalanceDelta} state + */ -TXDB.prototype.range = function range(options) { - if (options.gte) - options.gte = this.prefix(options.gte); + async updateBalance(b, state) { + const balance = await this.getWalletBalance(); + state.applyTo(balance); + b.put(layout.R.encode(), balance.toRaw()); + return balance; + } - if (options.lte) - options.lte = this.prefix(options.lte); + /** + * Update account balance. + * @param {Number} acct + * @param {Balance} delta + */ + + async updateAccountBalance(b, acct, delta) { + const balance = await this.getAccountBalance(acct); + delta.applyTo(balance); + b.put(layout.r.encode(acct), balance.toRaw()); + return balance; + } - return this.db.range(options); -}; + /** + * Test a whether a coin has been spent. + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} - Returns Boolean. + */ -/** - * Iterate. - * @param {Object} options - * @returns {Promise} - */ + async getSpent(hash, index) { + const data = await this.bucket.get(layout.s.encode(hash, index)); -TXDB.prototype.keys = function keys(options) { - if (options.gte) - options.gte = this.prefix(options.gte); + if (!data) + return null; - if (options.lte) - options.lte = this.prefix(options.lte); + return Outpoint.fromRaw(data); + } - return this.db.keys(options); -}; + /** + * Test a whether a coin has been spent. + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} - Returns Boolean. + */ -/** - * Iterate. - * @param {Object} options - * @returns {Promise} - */ + isSpent(hash, index) { + return this.bucket.has(layout.s.encode(hash, index)); + } -TXDB.prototype.values = function values(options) { - if (options.gte) - options.gte = this.prefix(options.gte); + /** + * Append to global map. + * @param {Number} height + * @returns {Promise} + */ - if (options.lte) - options.lte = this.prefix(options.lte); + addBlockMap(b, height) { + return this.wdb.addBlockMap(b.root(), height, this.wid); + } - return this.db.values(options); -}; + /** + * Remove from global map. + * @param {Number} height + * @returns {Promise} + */ -/** - * Get wallet path for output. - * @param {Output} output - * @returns {Promise} - Returns {@link Path}. - */ + removeBlockMap(b, height) { + return this.wdb.removeBlockMap(b.root(), height, this.wid); + } -TXDB.prototype.getPath = async function getPath(output) { - const addr = output.getAddress(); + /** + * Append to global map. + * @param {Hash} hash + * @returns {Promise} + */ - if (!addr) - return null; + addTXMap(b, hash) { + return this.wdb.addTXMap(b.root(), hash, this.wid); + } - return await this.wallet.getPath(addr); -}; + /** + * Remove from global map. + * @param {Hash} hash + * @returns {Promise} + */ -/** - * Test whether path exists for output. - * @param {Output} output - * @returns {Promise} - Returns Boolean. - */ + removeTXMap(b, hash) { + return this.wdb.removeTXMap(b.root(), hash, this.wid); + } -TXDB.prototype.hasPath = async function hasPath(output) { - const addr = output.getAddress(); + /** + * Append to global map. + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} + */ - if (!addr) - return false; + addOutpointMap(b, hash, index) { + return this.wdb.addOutpointMap(b.root(), hash, index, this.wid); + } - return await this.wallet.hasPath(addr); -}; + /** + * Remove from global map. + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} + */ -/** - * Save credit. - * @param {Credit} credit - * @param {Path} path - */ + removeOutpointMap(b, hash, index) { + return this.wdb.removeOutpointMap(b.root(), hash, index, this.wid); + } -TXDB.prototype.saveCredit = async function saveCredit(credit, path) { - const coin = credit.coin; - const key = coin.toKey(); - const raw = credit.toRaw(); + /** + * List block records. + * @returns {Promise} + */ + + getBlocks() { + return this.bucket.keys({ + gte: layout.b.min(), + lte: layout.b.max(), + parse: key => layout.b.decode(key)[0] + }); + } - await this.addOutpointMap(coin.hash, coin.index); + /** + * Get block record. + * @param {Number} height + * @returns {Promise} + */ - this.put(layout.c(coin.hash, coin.index), raw); - this.put(layout.C(path.account, coin.hash, coin.index), null); + async getBlock(height) { + const data = await this.bucket.get(layout.b.encode(height)); - this.coinCache.push(key, raw); -}; + if (!data) + return null; -/** - * Remove credit. - * @param {Credit} credit - * @param {Path} path - */ + return BlockRecord.fromRaw(data); + } -TXDB.prototype.removeCredit = async function removeCredit(credit, path) { - const coin = credit.coin; - const key = coin.toKey(); + /** + * Append to the global block record. + * @param {Hash} hash + * @param {BlockMeta} block + * @returns {Promise} + */ + + async addBlock(b, hash, block) { + const key = layout.b.encode(block.height); + const data = await this.bucket.get(key); + + if (!data) { + const blk = BlockRecord.fromMeta(block); + blk.add(hash); + b.put(key, blk.toRaw()); + return; + } - await this.removeOutpointMap(coin.hash, coin.index); + const raw = Buffer.allocUnsafe(data.length + 32); + data.copy(raw, 0); - this.del(layout.c(coin.hash, coin.index)); - this.del(layout.C(path.account, coin.hash, coin.index)); + const size = raw.readUInt32LE(40, true); + raw.writeUInt32LE(size + 1, 40, true); + hash.copy(raw, data.length); - this.coinCache.unpush(key); -}; + b.put(key, raw); + } -/** - * Spend credit. - * @param {Credit} credit - * @param {TX} tx - * @param {Number} index - */ + /** + * Remove from the global block record. + * @param {Hash} hash + * @param {Number} height + * @returns {Promise} + */ -TXDB.prototype.spendCredit = function spendCredit(credit, tx, index) { - const prevout = tx.inputs[index].prevout; - const spender = Outpoint.fromTX(tx, index); - this.put(layout.s(prevout.hash, prevout.index), spender.toRaw()); - this.put(layout.d(spender.hash, spender.index), credit.coin.toRaw()); -}; + async removeBlock(b, hash, height) { + const key = layout.b.encode(height); + const data = await this.bucket.get(key); -/** - * Unspend credit. - * @param {TX} tx - * @param {Number} index - */ + if (!data) + return; -TXDB.prototype.unspendCredit = function unspendCredit(tx, index) { - const prevout = tx.inputs[index].prevout; - const spender = Outpoint.fromTX(tx, index); - this.del(layout.s(prevout.hash, prevout.index)); - this.del(layout.d(spender.hash, spender.index)); -}; + const size = data.readUInt32LE(40, true); -/** - * Write input record. - * @param {TX} tx - * @param {Number} index - */ + assert(size > 0); + assert(data.slice(-32).equals(hash)); -TXDB.prototype.writeInput = function writeInput(tx, index) { - const prevout = tx.inputs[index].prevout; - const spender = Outpoint.fromTX(tx, index); - this.put(layout.s(prevout.hash, prevout.index), spender.toRaw()); -}; + if (size === 1) { + b.del(key); + return; + } -/** - * Remove input record. - * @param {TX} tx - * @param {Number} index - */ + const raw = data.slice(0, -32); + raw.writeUInt32LE(size - 1, 40, true); -TXDB.prototype.removeInput = function removeInput(tx, index) { - const prevout = tx.inputs[index].prevout; - this.del(layout.s(prevout.hash, prevout.index)); -}; + b.put(key, raw); + } -/** - * Resolve orphan input. - * @param {TX} tx - * @param {Number} index - * @param {Number} height - * @param {Path} path - * @returns {Boolean} - */ + /** + * Remove from the global block record. + * @param {Hash} hash + * @param {Number} height + * @returns {Promise} + */ -TXDB.prototype.resolveInput = async function resolveInput(tx, index, height, path, own) { - const hash = tx.hash('hex'); - const spent = await this.getSpent(hash, index); + async spliceBlock(b, hash, height) { + const block = await this.getBlock(height); - if (!spent) - return false; + if (!block) + return; - // If we have an undo coin, we - // already knew about this input. - if (await this.hasSpentCoin(spent)) - return false; + if (!block.remove(hash)) + return; - // Get the spending transaction so - // we can properly add the undo coin. - const stx = await this.getTX(spent.hash); - assert(stx); + if (block.hashes.size === 0) { + b.del(layout.b.encode(height)); + return; + } - // Crete the credit and add the undo coin. - const credit = Credit.fromTX(tx, index, height); - credit.own = own; + b.put(layout.b.encode(height), block.toRaw()); + } - this.spendCredit(credit, stx.tx, spent.index); + /** + * Add transaction without a batch. + * @private + * @param {TX} tx + * @returns {Promise} + */ - // If the spender is unconfirmed, save - // the credit as well, and mark it as - // unspent in the mempool. This is the - // same behavior `insert` would have - // done for inputs. We're just doing - // it retroactively. - if (stx.height === -1) { - credit.spent = true; - await this.saveCredit(credit, path); - if (height !== -1) - this.pending.confirmed += credit.coin.value; - } + async add(tx, block) { + const hash = tx.hash(); + const existing = await this.getTX(hash); - return true; -}; + assert(!tx.mutable, 'Cannot add mutable TX to wallet.'); -/** - * Test an entire transaction to see - * if any of its outpoints are a double-spend. - * @param {TX} tx - * @returns {Promise} - Returns Boolean. - */ + if (existing) { + // Existing tx is already confirmed. Ignore. + if (existing.height !== -1) + return null; -TXDB.prototype.isDoubleSpend = async function isDoubleSpend(tx) { - for (const {prevout} of tx.inputs) { - const spent = await this.isSpent(prevout.hash, prevout.index); - if (spent) - return true; - } + // The incoming tx won't confirm the + // existing one anyway. Ignore. + if (!block) + return null; - return false; -}; + // Confirm transaction. + return this.confirm(existing, block); + } -/** - * Test an entire transaction to see - * if any of its outpoints are replace by fee. - * @param {TX} tx - * @returns {Promise} - Returns Boolean. - */ + const wtx = TXRecord.fromTX(tx, block); -TXDB.prototype.isRBF = async function isRBF(tx) { - if (tx.isRBF()) - return true; + if (!block) { + // Potentially remove double-spenders. + // Only remove if they're not confirmed. + if (!await this.removeConflicts(tx, true)) + return null; + } else { + // Potentially remove double-spenders. + await this.removeConflicts(tx, false); + } - for (const {prevout} of tx.inputs) { - const key = layout.r(prevout.hash); - if (await this.has(key)) - return true; + // Finally we can do a regular insertion. + return this.insert(wtx, block); } - return false; -}; + /** + * Insert transaction. + * @private + * @param {TXRecord} wtx + * @param {BlockMeta} block + * @returns {Promise} + */ + + async insert(wtx, block) { + const b = this.bucket.batch(); + const {tx, hash} = wtx; + const height = block ? block.height : -1; + const details = new Details(wtx, block); + const state = new BalanceDelta(); + + let own = false; + + if (!tx.isCoinbase()) { + // We need to potentially spend some coins here. + for (let i = 0; i < tx.inputs.length; i++) { + const input = tx.inputs[i]; + const {hash, index} = input.prevout; + const credit = await this.getCredit(hash, index); + + if (!credit) { + // Watch all inputs for incoming txs. + // This allows us to check for double spends. + if (!block) + await this.writeInput(b, tx, i); + continue; + } -/** - * Test a whether a coin has been spent. - * @param {Hash} hash - * @param {Number} index - * @returns {Promise} - Returns Boolean. - */ + const coin = credit.coin; + const path = await this.getPath(coin); + assert(path); + + // Build the tx details object + // as we go, for speed. + details.setInput(i, path, coin); + + // Write an undo coin for the credit + // and add it to the stxo set. + this.spendCredit(b, credit, tx, i); + + // Unconfirmed balance should always + // be updated as it reflects the on-chain + // balance _and_ mempool balance assuming + // everything in the mempool were to confirm. + state.tx(path, 1); + state.coin(path, -1); + state.unconfirmed(path, -coin.value); + + if (!block) { + // If the tx is not mined, we do not + // disconnect the coin, we simply mark + // a `spent` flag on the credit. This + // effectively prevents the mempool + // from altering our utxo state + // permanently. It also makes it + // possible to compare the on-chain + // state vs. the mempool state. + credit.spent = true; + await this.saveCredit(b, credit, path); + } else { + // If the tx is mined, we can safely + // remove the coin being spent. This + // coin will be indexed as an undo + // coin so it can be reconnected + // later during a reorg. + state.confirmed(path, -coin.value); + await this.removeCredit(b, credit, path); + } -TXDB.prototype.getSpent = async function getSpent(hash, index) { - const data = await this.get(layout.s(hash, index)); + own = true; + } + } - if (!data) - return null; + // Potentially add coins to the utxo set. + for (let i = 0; i < tx.outputs.length; i++) { + const output = tx.outputs[i]; + const path = await this.getPath(output); - return Outpoint.fromRaw(data); -}; + if (!path) + continue; -/** - * Test a whether a coin has been spent. - * @param {Hash} hash - * @param {Number} index - * @returns {Promise} - Returns Boolean. - */ + details.setOutput(i, path); -TXDB.prototype.isSpent = function isSpent(hash, index) { - return this.has(layout.s(hash, index)); -}; + const credit = Credit.fromTX(tx, i, height); + credit.own = own; -/** - * Append to the global unspent record. - * @param {Hash} hash - * @param {Number} index - * @returns {Promise} - */ + state.tx(path, 1); + state.coin(path, 1); + state.unconfirmed(path, output.value); -TXDB.prototype.addOutpointMap = async function addOutpointMap(hash, i) { - let map = await this.walletdb.getOutpointMap(hash, i); + if (block) + state.confirmed(path, output.value); - if (!map) - map = new OutpointMapRecord(hash, i); + await this.saveCredit(b, credit, path); + } - if (!map.add(this.wallet.wid)) - return; + // If this didn't update any coins, + // it's not our transaction. + if (!state.updated()) + return null; - this.walletdb.writeOutpointMap(this.wallet, hash, i, map); -}; + // Save and index the transaction record. + b.put(layout.t.encode(hash), wtx.toRaw()); + b.put(layout.m.encode(wtx.mtime, hash), null); -/** - * Remove from the global unspent record. - * @param {Hash} hash - * @param {Number} index - * @returns {Promise} - */ + if (!block) + b.put(layout.p.encode(hash), null); + else + b.put(layout.h.encode(height, hash), null); -TXDB.prototype.removeOutpointMap = async function removeOutpointMap(hash, i) { - const map = await this.walletdb.getOutpointMap(hash, i); + // Do some secondary indexing for account-based + // queries. This saves us a lot of time for + // queries later. + for (const [acct, delta] of state.accounts) { + await this.updateAccountBalance(b, acct, delta); - if (!map) - return; + b.put(layout.T.encode(acct, hash), null); + b.put(layout.M.encode(acct, wtx.mtime, hash), null); - if (!map.remove(this.wallet.wid)) - return; + if (!block) + b.put(layout.P.encode(acct, hash), null); + else + b.put(layout.H.encode(acct, height, hash), null); + } - if (map.wids.size === 0) { - this.walletdb.unwriteOutpointMap(this.wallet, hash, i); - return; - } + // Update block records. + if (block) { + await this.addBlockMap(b, height); + await this.addBlock(b, tx.hash(), block); + } else { + await this.addTXMap(b, hash); + } - this.walletdb.writeOutpointMap(this.wallet, hash, i, map); -}; + // Commit the new state. + const balance = await this.updateBalance(b, state); -/** - * Append to the global block record. - * @param {Hash} hash - * @param {Number} height - * @returns {Promise} - */ + await b.write(); -TXDB.prototype.addBlockMap = async function addBlockMap(hash, height) { - let block = await this.walletdb.getBlockMap(height); + // This transaction may unlock some + // coins now that we've seen it. + this.unlockTX(tx); - if (!block) - block = new BlockMapRecord(height); + // Emit events for potential local and + // websocket listeners. Note that these + // will only be emitted if the batch is + // successfully written to disk. + this.emit('tx', tx, details); + this.emit('balance', balance); - if (!block.add(hash, this.wallet.wid)) - return; + return details; + } - this.walletdb.writeBlockMap(this.wallet, height, block); -}; + /** + * Attempt to confirm a transaction. + * @private + * @param {TXRecord} wtx + * @param {BlockMeta} block + * @returns {Promise} + */ + + async confirm(wtx, block) { + const b = this.bucket.batch(); + const {tx, hash} = wtx; + const height = block.height; + const details = new Details(wtx, block); + const state = new BalanceDelta(); + + wtx.setBlock(block); + + if (!tx.isCoinbase()) { + const credits = await this.getSpentCredits(tx); + + // Potentially spend coins. Now that the tx + // is mined, we can actually _remove_ coins + // from the utxo state. + for (let i = 0; i < tx.inputs.length; i++) { + const input = tx.inputs[i]; + const {hash, index} = input.prevout; + + let resolved = false; + + // There may be new credits available + // that we haven't seen yet. + if (!credits[i]) { + await this.removeInput(b, tx, i); + + const credit = await this.getCredit(hash, index); + + if (!credit) + continue; + + // Add a spend record and undo coin + // for the coin we now know is ours. + // We don't need to remove the coin + // since it was never added in the + // first place. + this.spendCredit(b, credit, tx, i); + + credits[i] = credit; + resolved = true; + } -/** - * Remove from the global block record. - * @param {Hash} hash - * @param {Number} height - * @returns {Promise} - */ + const credit = credits[i]; + const coin = credit.coin; -TXDB.prototype.removeBlockMap = async function removeBlockMap(hash, height) { - const block = await this.walletdb.getBlockMap(height); + assert(coin.height !== -1); - if (!block) - return; + const path = await this.getPath(coin); + assert(path); - if (!block.remove(hash, this.wallet.wid)) - return; + details.setInput(i, path, coin); - if (block.txs.size === 0) { - this.walletdb.unwriteBlockMap(this.wallet, height); - return; - } + if (resolved) { + state.coin(path, -1); + state.unconfirmed(path, -coin.value); + } - this.walletdb.writeBlockMap(this.wallet, height, block); -}; + // We can now safely remove the credit + // entirely, now that we know it's also + // been removed on-chain. + state.confirmed(path, -coin.value); -/** - * List block records. - * @returns {Promise} - */ + await this.removeCredit(b, credit, path); + } + } -TXDB.prototype.getBlocks = function getBlocks() { - return this.keys({ - gte: layout.b(0), - lte: layout.b(0xffffffff), - parse: key => layout.bb(key) - }); -}; + // Update credit heights, including undo coins. + for (let i = 0; i < tx.outputs.length; i++) { + const output = tx.outputs[i]; + const path = await this.getPath(output); -/** - * Get block record. - * @param {Number} height - * @returns {Promise} - */ + if (!path) + continue; -TXDB.prototype.getBlock = async function getBlock(height) { - const data = await this.get(layout.b(height)); + details.setOutput(i, path); - if (!data) - return null; + const credit = await this.getCredit(hash, i); + assert(credit); - return BlockRecord.fromRaw(data); -}; + // Credits spent in the mempool add an + // undo coin for ease. If this credit is + // spent in the mempool, we need to + // update the undo coin's height. + if (credit.spent) + await this.updateSpentCoin(b, tx, i, height); -/** - * Append to the global block record. - * @param {Hash} hash - * @param {BlockMeta} meta - * @returns {Promise} - */ + // Update coin height and confirmed + // balance. Save once again. + state.confirmed(path, output.value); + credit.coin.height = height; -TXDB.prototype.addBlock = async function addBlock(hash, meta) { - const key = layout.b(meta.height); - let data = await this.get(key); - let block; + await this.saveCredit(b, credit, path); + } - if (!data) { - block = BlockRecord.fromMeta(meta); - data = block.toRaw(); - } + // Save the new serialized transaction as + // the block-related properties have been + // updated. Also reindex for height. + b.put(layout.t.encode(hash), wtx.toRaw()); + b.del(layout.p.encode(hash)); + b.put(layout.h.encode(height, hash), null); + + // Secondary indexing also needs to change. + for (const [acct, delta] of state.accounts) { + await this.updateAccountBalance(b, acct, delta); + b.del(layout.P.encode(acct, hash)); + b.put(layout.H.encode(acct, height, hash), null); + } - block = Buffer.allocUnsafe(data.length + 32); - data.copy(block, 0); + await this.removeTXMap(b, hash); + await this.addBlockMap(b, height); + await this.addBlock(b, tx.hash(), block); - const size = block.readUInt32LE(40, true); - block.writeUInt32LE(size + 1, 40, true); - hash.copy(block, data.length); + // Commit the new state. The balance has updated. + const balance = await this.updateBalance(b, state); - this.put(key, block); -}; + await b.write(); -/** - * Remove from the global block record. - * @param {Hash} hash - * @param {Number} height - * @returns {Promise} - */ + this.unlockTX(tx); -TXDB.prototype.removeBlock = async function removeBlock(hash, height) { - const key = layout.b(height); - const data = await this.get(key); + this.emit('confirmed', tx, details); + this.emit('balance', balance); + + return details; + } - if (!data) - return; + /** + * Recursively remove a transaction + * from the database. + * @param {Hash} hash + * @returns {Promise} + */ - const size = data.readUInt32LE(40, true); + async remove(hash) { + const wtx = await this.getTX(hash); - assert(size > 0); - assert(data.slice(-32).equals(hash)); + if (!wtx) + return null; - if (size === 1) { - this.del(key); - return; + return this.removeRecursive(wtx); } - const block = data.slice(0, -32); - block.writeUInt32LE(size - 1, 40, true); + /** + * Remove a transaction from the + * database. Disconnect inputs. + * @private + * @param {TXRecord} wtx + * @returns {Promise} + */ + + async erase(wtx, block) { + const b = this.bucket.batch(); + const {tx, hash} = wtx; + const height = block ? block.height : -1; + const details = new Details(wtx, block); + const state = new BalanceDelta(); + + if (!tx.isCoinbase()) { + // We need to undo every part of the + // state this transaction ever touched. + // Start by getting the undo coins. + const credits = await this.getSpentCredits(tx); + + for (let i = 0; i < tx.inputs.length; i++) { + const credit = credits[i]; + + if (!credit) { + if (!block) + await this.removeInput(b, tx, i); + continue; + } - this.put(key, block); -}; + const coin = credit.coin; + const path = await this.getPath(coin); + assert(path); -/** - * Append to the global block record. - * @param {Hash} hash - * @param {BlockMeta} meta - * @returns {Promise} - */ + details.setInput(i, path, coin); -TXDB.prototype.addBlockSlow = async function addBlockSlow(hash, meta) { - let block = await this.getBlock(meta.height); + // Recalculate the balance, remove + // from stxo set, remove the undo + // coin, and resave the credit. + state.tx(path, -1); + state.coin(path, 1); + state.unconfirmed(path, coin.value); - if (!block) - block = BlockRecord.fromMeta(meta); + if (block) + state.confirmed(path, coin.value); - if (!block.add(hash)) - return; + this.unspendCredit(b, tx, i); - this.put(layout.b(meta.height), block.toRaw()); -}; + credit.spent = false; + await this.saveCredit(b, credit, path); + } + } -/** - * Remove from the global block record. - * @param {Hash} hash - * @param {Number} height - * @returns {Promise} - */ + // We need to remove all credits + // this transaction created. + for (let i = 0; i < tx.outputs.length; i++) { + const output = tx.outputs[i]; + const path = await this.getPath(output); -TXDB.prototype.removeBlockSlow = async function removeBlockSlow(hash, height) { - const block = await this.getBlock(height); + if (!path) + continue; - if (!block) - return; + details.setOutput(i, path); - if (!block.remove(hash)) - return; + const credit = Credit.fromTX(tx, i, height); - if (block.hashes.length === 0) { - this.del(layout.b(height)); - return; - } + state.tx(path, -1); + state.coin(path, -1); + state.unconfirmed(path, -output.value); - this.put(layout.b(height), block.toRaw()); -}; + if (block) + state.confirmed(path, -output.value); -/** - * Add transaction, potentially runs - * `confirm()` and `removeConflicts()`. - * @param {TX} tx - * @param {BlockMeta} block - * @returns {Promise} - */ + await this.removeCredit(b, credit, path); + } -TXDB.prototype.add = async function add(tx, block) { - this.start(); + // Remove the transaction data + // itself as well as unindex. + b.del(layout.t.encode(hash)); + b.del(layout.m.encode(wtx.mtime, hash)); - let result; - try { - result = await this._add(tx, block); - } catch (e) { - this.drop(); - throw e; - } + if (!block) + b.del(layout.p.encode(hash)); + else + b.del(layout.h.encode(height, hash)); - await this.commit(); + // Remove all secondary indexing. + for (const [acct, delta] of state.accounts) { + await this.updateAccountBalance(b, acct, delta); - return result; -}; + b.del(layout.T.encode(acct, hash)); + b.del(layout.M.encode(acct, wtx.mtime, hash)); -/** - * Add transaction without a batch. - * @private - * @param {TX} tx - * @returns {Promise} - */ + if (!block) + b.del(layout.P.encode(acct, hash)); + else + b.del(layout.H.encode(acct, height, hash)); + } -TXDB.prototype._add = async function _add(tx, block) { - const hash = tx.hash('hex'); - const existing = await this.getTX(hash); + // Update block records. + if (block) { + await this.removeBlockMap(b, height); + await this.spliceBlock(b, hash, height); + } else { + await this.removeTXMap(b, hash); + } - assert(!tx.mutable, 'Cannot add mutable TX to wallet.'); + // Update the transaction counter + // and commit new state due to + // balance change. + const balance = await this.updateBalance(b, state); - if (existing) { - // Existing tx is already confirmed. Ignore. - if (existing.height !== -1) - return null; + await b.write(); - // The incoming tx won't confirm the - // existing one anyway. Ignore. - if (!block) - return null; + this.emit('remove tx', tx, details); + this.emit('balance', balance); - // Confirm transaction. - return await this._confirm(existing, block); + return details; } - const wtx = TXRecord.fromTX(tx, block); + /** + * Remove a transaction and recursively + * remove all of its spenders. + * @private + * @param {TXRecord} wtx + * @returns {Promise} + */ - if (!block) { - // We ignore any unconfirmed txs - // that are replace-by-fee. - if (await this.isRBF(tx)) { - // We need to index every spender - // hash to detect "passive" - // replace-by-fee. - this.put(layout.r(hash), null); - return null; - } + async removeRecursive(wtx) { + const {tx, hash} = wtx; - // Potentially remove double-spenders. - // Only remove if they're not confirmed. - if (!await this.removeConflicts(tx, true)) + if (!await this.hasTX(hash)) return null; - } else { - // Potentially remove double-spenders. - await this.removeConflicts(tx, false); - - // Delete the replace-by-fee record. - this.del(layout.r(hash)); - } - - // Finally we can do a regular insertion. - return await this.insert(wtx, block); -}; - -/** - * Insert transaction. - * @private - * @param {TXRecord} wtx - * @param {BlockMeta} block - * @returns {Promise} - */ -TXDB.prototype.insert = async function insert(wtx, block) { - const tx = wtx.tx; - const hash = wtx.hash; - const height = block ? block.height : -1; - const details = new Details(this, wtx, block); - const accounts = new Set(); - let own = false; - let updated = false; - - if (!tx.isCoinbase()) { - // We need to potentially spend some coins here. - for (let i = 0; i < tx.inputs.length; i++) { - const input = tx.inputs[i]; - const prevout = input.prevout; - const credit = await this.getCredit(prevout.hash, prevout.index); + for (let i = 0; i < tx.outputs.length; i++) { + const spent = await this.getSpent(hash, i); - if (!credit) { - // Maintain an stxo list for every - // spent input (even ones we don't - // recognize). This is used for - // detecting double-spends (as best - // we can), as well as resolving - // inputs we didn't know were ours - // at the time. This built-in error - // correction is not technically - // necessary assuming no messages - // are ever missed from the mempool, - // but shit happens. - this.writeInput(tx, i); + if (!spent) continue; - } - const coin = credit.coin; + // Remove all of the spender's spenders first. + const stx = await this.getTX(spent.hash); - // Do some verification. - if (!block) { - if (!await this.verifyInput(tx, i, coin)) { - this.clear(); - return null; - } - } - - const path = await this.getPath(coin); - assert(path); - - // Build the tx details object - // as we go, for speed. - details.setInput(i, path, coin); - accounts.add(path.account); - - // Write an undo coin for the credit - // and add it to the stxo set. - this.spendCredit(credit, tx, i); - - // Unconfirmed balance should always - // be updated as it reflects the on-chain - // balance _and_ mempool balance assuming - // everything in the mempool were to confirm. - this.pending.coin--; - this.pending.unconfirmed -= coin.value; - - if (!block) { - // If the tx is not mined, we do not - // disconnect the coin, we simply mark - // a `spent` flag on the credit. This - // effectively prevents the mempool - // from altering our utxo state - // permanently. It also makes it - // possible to compare the on-chain - // state vs. the mempool state. - credit.spent = true; - await this.saveCredit(credit, path); - } else { - // If the tx is mined, we can safely - // remove the coin being spent. This - // coin will be indexed as an undo - // coin so it can be reconnected - // later during a reorg. - this.pending.confirmed -= coin.value; - await this.removeCredit(credit, path); - } + assert(stx); - updated = true; - own = true; + await this.removeRecursive(stx); } + + // Remove the spender. + return this.erase(wtx, wtx.getBlock()); } - // Potentially add coins to the utxo set. - for (let i = 0; i < tx.outputs.length; i++) { - const output = tx.outputs[i]; - const path = await this.getPath(output); + /** + * Revert a block. + * @param {Number} height + * @returns {Promise} + */ - if (!path) - continue; + async revert(height) { + const block = await this.getBlock(height); - details.setOutput(i, path); - accounts.add(path.account); + if (!block) + return 0; - // Attempt to resolve an input we - // did not know was ours at the time. - if (await this.resolveInput(tx, i, height, path, own)) { - updated = true; - continue; + this.logger.debug('Rescan: reverting block %d', height); + const hashes = block.toArray(); + + for (let i = hashes.length - 1; i >= 0; i--) { + const hash = hashes[i]; + await this.unconfirm(hash); } - const credit = Credit.fromTX(tx, i, height); - credit.own = own; + return hashes.length; + } - this.pending.coin++; - this.pending.unconfirmed += output.value; + /** + * Unconfirm a transaction without a batch. + * @private + * @param {Hash} hash + * @returns {Promise} + */ - if (block) - this.pending.confirmed += output.value; + async unconfirm(hash) { + const wtx = await this.getTX(hash); - await this.saveCredit(credit, path); + if (!wtx) + return null; - updated = true; - } + if (wtx.height === -1) + return null; - // If this didn't update any coins, - // it's not our transaction. - if (!updated) { - // Clear the spent list inserts. - this.clear(); - return null; + return this.disconnect(wtx, wtx.getBlock()); } - // Save and index the transaction record. - this.put(layout.t(hash), wtx.toRaw()); - this.put(layout.m(wtx.mtime, hash), null); + /** + * Unconfirm a transaction. Necessary after a reorg. + * @param {TXRecord} wtx + * @returns {Promise} + */ - if (!block) - this.put(layout.p(hash), null); - else - this.put(layout.h(height, hash), null); + async disconnect(wtx, block) { + const b = this.bucket.batch(); + const {tx, hash, height} = wtx; + const details = new Details(wtx, block); + const state = new BalanceDelta(); - // Do some secondary indexing for account-based - // queries. This saves us a lot of time for - // queries later. - for (const account of accounts) { - this.put(layout.T(account, hash), null); - this.put(layout.M(account, wtx.mtime, hash), null); + assert(block); - if (!block) - this.put(layout.P(account, hash), null); - else - this.put(layout.H(account, height, hash), null); - } + wtx.unsetBlock(); - // Update block records. - if (block) { - await this.addBlockMap(hash, height); - await this.addBlock(tx.hash(), block); - } + if (!tx.isCoinbase()) { + // We need to reconnect the coins. Start + // by getting all of the undo coins we know + // about. + const credits = await this.getSpentCredits(tx); - // Update the transaction counter and - // commit the new state. This state will - // only overwrite the best state once - // the batch has actually been written - // to disk. - this.pending.tx++; - this.put(layout.R, this.pending.commit()); + for (let i = 0; i < tx.inputs.length; i++) { + const credit = credits[i]; - // This transaction may unlock some - // coins now that we've seen it. - this.unlockTX(tx); + if (!credit) { + await this.writeInput(b, tx, i); + continue; + } - // Emit events for potential local and - // websocket listeners. Note that these - // will only be emitted if the batch is - // successfully written to disk. - this.emit('tx', tx, details); - this.emit('balance', this.pending.toBalance(), details); + const coin = credit.coin; - return details; -}; + assert(coin.height !== -1); -/** - * Attempt to confirm a transaction. - * @private - * @param {TX} tx - * @param {BlockMeta} block - * @returns {Promise} - */ + const path = await this.getPath(coin); + assert(path); -TXDB.prototype.confirm = async function confirm(hash, block) { - const wtx = await this.getTX(hash); + details.setInput(i, path, coin); - if (!wtx) - return null; + state.confirmed(path, coin.value); - if (wtx.height !== -1) - throw new Error('TX is already confirmed.'); + // Resave the credit and mark it + // as spent in the mempool instead. + credit.spent = true; + await this.saveCredit(b, credit, path); + } + } - assert(block); + // We need to remove heights on + // the credits and undo coins. + for (let i = 0; i < tx.outputs.length; i++) { + const output = tx.outputs[i]; + const path = await this.getPath(output); - this.start(); + if (!path) + continue; - let details; + const credit = await this.getCredit(hash, i); - try { - details = await this._confirm(wtx, block); - } catch (e) { - this.drop(); - throw e; - } + // Potentially update undo coin height. + if (!credit) { + await this.updateSpentCoin(b, tx, i, height); + continue; + } - await this.commit(); + if (credit.spent) + await this.updateSpentCoin(b, tx, i, height); - return details; -}; + details.setOutput(i, path); -/** - * Attempt to confirm a transaction. - * @private - * @param {TXRecord} wtx - * @param {BlockMeta} block - * @returns {Promise} - */ + // Update coin height and confirmed + // balance. Save once again. + credit.coin.height = -1; -TXDB.prototype._confirm = async function _confirm(wtx, block) { - const tx = wtx.tx; - const hash = wtx.hash; - const height = block.height; - const details = new Details(this, wtx, block); - const accounts = new Set(); + state.confirmed(path, -output.value); - wtx.setBlock(block); + await this.saveCredit(b, credit, path); + } - if (!tx.isCoinbase()) { - const credits = await this.getSpentCredits(tx); + await this.addTXMap(b, hash); + await this.removeBlockMap(b, height); + await this.removeBlock(b, tx.hash(), height); + + // We need to update the now-removed + // block properties and reindex due + // to the height change. + b.put(layout.t.encode(hash), wtx.toRaw()); + b.put(layout.p.encode(hash), null); + b.del(layout.h.encode(height, hash)); + + // Secondary indexing also needs to change. + for (const [acct, delta] of state.accounts) { + await this.updateAccountBalance(b, acct, delta); + b.put(layout.P.encode(acct, hash), null); + b.del(layout.H.encode(acct, height, hash)); + } - // Potentially spend coins. Now that the tx - // is mined, we can actually _remove_ coins - // from the utxo state. - for (let i = 0; i < tx.inputs.length; i++) { - const input = tx.inputs[i]; - const prevout = input.prevout; - let credit = credits[i]; + // Commit state due to unconfirmed + // vs. confirmed balance change. + const balance = await this.updateBalance(b, state); - // There may be new credits available - // that we haven't seen yet. - if (!credit) { - credit = await this.getCredit(prevout.hash, prevout.index); + await b.write(); - if (!credit) - continue; + this.emit('unconfirmed', tx, details); + this.emit('balance', balance); - // Add a spend record and undo coin - // for the coin we now know is ours. - // We don't need to remove the coin - // since it was never added in the - // first place. - this.spendCredit(credit, tx, i); + return details; + } - this.pending.coin--; - this.pending.unconfirmed -= credit.coin.value; - } + /** + * Remove spenders that have not been confirmed. We do this in the + * odd case of stuck transactions or when a coin is double-spent + * by a newer transaction. All previously-spending transactions + * of that coin that are _not_ confirmed will be removed from + * the database. + * @private + * @param {Hash} hash + * @param {TX} ref - Reference tx, the tx that double-spent. + * @returns {Promise} - Returns Boolean. + */ - const coin = credit.coin; + async removeConflict(wtx) { + const tx = wtx.tx; - assert(coin.height !== -1); + this.logger.warning('Handling conflicting tx: %h.', tx.hash()); - const path = await this.getPath(coin); - assert(path); + const details = await this.removeRecursive(wtx); - details.setInput(i, path, coin); - accounts.add(path.account); + this.logger.warning('Removed conflict: %h.', tx.hash()); - // We can now safely remove the credit - // entirely, now that we know it's also - // been removed on-chain. - this.pending.confirmed -= coin.value; + // Emit the _removed_ transaction. + this.emit('conflict', tx, details); - await this.removeCredit(credit, path); - } + return details; } - // Update credit heights, including undo coins. - for (let i = 0; i < tx.outputs.length; i++) { - const output = tx.outputs[i]; - const path = await this.getPath(output); + /** + * Retrieve coins for own inputs, remove + * double spenders, and verify inputs. + * @private + * @param {TX} tx + * @returns {Promise} + */ - if (!path) - continue; + async removeConflicts(tx, conf) { + if (tx.isCoinbase()) + return true; - details.setOutput(i, path); - accounts.add(path.account); + const txid = tx.hash(); + const spends = []; - const credit = await this.getCredit(hash, i); - assert(credit); + // Gather all spent records first. + for (const {prevout} of tx.inputs) { + const {hash, index} = prevout; - // Credits spent in the mempool add an - // undo coin for ease. If this credit is - // spent in the mempool, we need to - // update the undo coin's height. - if (credit.spent) - await this.updateSpentCoin(tx, i, height); + // Is it already spent? + const spent = await this.getSpent(hash, index); - // Update coin height and confirmed - // balance. Save once again. - const coin = credit.coin; - coin.height = height; + if (!spent) + continue; - this.pending.confirmed += output.value; + // Did _we_ spend it? + if (spent.hash.equals(txid)) + continue; - await this.saveCredit(credit, path); - } + const spender = await this.getTX(spent.hash); + assert(spender); - // Remove the RBF index if we have one. - this.del(layout.r(hash)); + if (conf && spender.height !== -1) + return false; - // Save the new serialized transaction as - // the block-related properties have been - // updated. Also reindex for height. - this.put(layout.t(hash), wtx.toRaw()); - this.del(layout.p(hash)); - this.put(layout.h(height, hash), null); + spends.push(spender); + } - // Secondary indexing also needs to change. - for (const account of accounts) { - this.del(layout.P(account, hash)); - this.put(layout.H(account, height, hash), null); - } + // Once we know we're not going to + // screw things up, remove the double + // spenders. + for (const spender of spends) { + // Remove the double spender. + await this.removeConflict(spender); + } - if (block) { - await this.addBlockMap(hash, height); - await this.addBlock(tx.hash(), block); + return true; } - // Commit the new state. The balance has updated. - this.put(layout.R, this.pending.commit()); + /** + * Lock all coins in a transaction. + * @param {TX} tx + */ - this.unlockTX(tx); + lockTX(tx) { + if (tx.isCoinbase()) + return; - this.emit('confirmed', tx, details); - this.emit('balance', this.pending.toBalance(), details); + for (const input of tx.inputs) + this.lockCoin(input.prevout); + } - return details; -}; + /** + * Unlock all coins in a transaction. + * @param {TX} tx + */ -/** - * Recursively remove a transaction - * from the database. - * @param {Hash} hash - * @returns {Promise} - */ + unlockTX(tx) { + if (tx.isCoinbase()) + return; -TXDB.prototype.remove = async function remove(hash) { - const wtx = await this.getTX(hash); + for (const input of tx.inputs) + this.unlockCoin(input.prevout); + } - if (!wtx) - return null; + /** + * Lock a single coin. + * @param {Coin|Outpoint} coin + */ - return await this.removeRecursive(wtx); -}; + lockCoin(coin) { + const key = coin.toKey(); + this.locked.add(key); + } -/** - * Remove a transaction from the - * database. Disconnect inputs. - * @private - * @param {TXRecord} wtx - * @returns {Promise} - */ + /** + * Unlock a single coin. + * @param {Coin|Outpoint} coin + */ -TXDB.prototype.erase = async function erase(wtx, block) { - const tx = wtx.tx; - const hash = wtx.hash; - const height = block ? block.height : -1; - const details = new Details(this, wtx, block); - const accounts = new Set(); - - if (!tx.isCoinbase()) { - // We need to undo every part of the - // state this transaction ever touched. - // Start by getting the undo coins. - const credits = await this.getSpentCredits(tx); + unlockCoin(coin) { + const key = coin.toKey(); + return this.locked.delete(key); + } - for (let i = 0; i < tx.inputs.length; i++) { - const credit = credits[i]; + /** + * Unlock all coins. + */ - if (!credit) { - // This input never had an undo - // coin, but remove it from the - // stxo set. - this.removeInput(tx, i); - continue; - } + unlockCoins() { + for (const coin of this.getLocked()) + this.unlockCoin(coin); + } - const coin = credit.coin; - const path = await this.getPath(coin); - assert(path); + /** + * Test locked status of a single coin. + * @param {Coin|Outpoint} coin + */ - details.setInput(i, path, coin); - accounts.add(path.account); + isLocked(coin) { + const key = coin.toKey(); + return this.locked.has(key); + } - // Recalculate the balance, remove - // from stxo set, remove the undo - // coin, and resave the credit. - this.pending.coin++; - this.pending.unconfirmed += coin.value; + /** + * Filter array of coins or outpoints + * for only unlocked ones. + * @param {Coin[]|Outpoint[]} + * @returns {Array} + */ - if (block) - this.pending.confirmed += coin.value; + filterLocked(coins) { + const out = []; - this.unspendCredit(tx, i); - await this.saveCredit(credit, path); + for (const coin of coins) { + if (!this.isLocked(coin)) + out.push(coin); } + + return out; } - // We need to remove all credits - // this transaction created. - for (let i = 0; i < tx.outputs.length; i++) { - const output = tx.outputs[i]; - const path = await this.getPath(output); + /** + * Return an array of all locked outpoints. + * @returns {Outpoint[]} + */ + + getLocked() { + const outpoints = []; + + for (const key of this.locked.keys()) + outpoints.push(Outpoint.fromKey(key)); - if (!path) - continue; + return outpoints; + } - details.setOutput(i, path); - accounts.add(path.account); + /** + * Get hashes of all transactions in the database. + * @param {Number} acct + * @returns {Promise} - Returns {@link Hash}[]. + */ + + getAccountHistoryHashes(acct) { + assert(typeof acct === 'number'); + return this.bucket.keys({ + gte: layout.T.min(acct), + lte: layout.T.max(acct), + parse: (key) => { + const [, hash] = layout.T.decode(key); + return hash; + } + }); + } - const credit = Credit.fromTX(tx, i, height); + /** + * Get hashes of all transactions in the database. + * @param {Number} acct + * @returns {Promise} - Returns {@link Hash}[]. + */ - this.pending.coin--; - this.pending.unconfirmed -= output.value; + getHistoryHashes(acct) { + assert(typeof acct === 'number'); - if (block) - this.pending.confirmed -= output.value; + if (acct !== -1) + return this.getAccountHistoryHashes(acct); - await this.removeCredit(credit, path); + return this.bucket.keys({ + gte: layout.t.min(), + lte: layout.t.max(), + parse: key => layout.t.decode(key)[0] + }); } - // Remove the RBF index if we have one. - this.del(layout.r(hash)); + /** + * Get hashes of all unconfirmed transactions in the database. + * @param {Number} acct + * @returns {Promise} - Returns {@link Hash}[]. + */ + + getAccountPendingHashes(acct) { + assert(typeof acct === 'number'); + return this.bucket.keys({ + gte: layout.P.min(acct), + lte: layout.P.max(acct), + parse: (key) => { + const [, hash] = layout.P.decode(key); + return hash; + } + }); + } - // Remove the transaction data - // itself as well as unindex. - this.del(layout.t(hash)); - this.del(layout.m(wtx.mtime, hash)); + /** + * Get hashes of all unconfirmed transactions in the database. + * @param {Number} acct + * @returns {Promise} - Returns {@link Hash}[]. + */ - if (!block) - this.del(layout.p(hash)); - else - this.del(layout.h(height, hash)); + getPendingHashes(acct) { + assert(typeof acct === 'number'); - // Remove all secondary indexing. - for (const account of accounts) { - this.del(layout.T(account, hash)); - this.del(layout.M(account, wtx.mtime, hash)); + if (acct !== -1) + return this.getAccountPendingHashes(acct); - if (!block) - this.del(layout.P(account, hash)); - else - this.del(layout.H(account, height, hash)); + return this.bucket.keys({ + gte: layout.p.min(), + lte: layout.p.max(), + parse: key => layout.p.decode(key)[0] + }); } - // Update block records. - if (block) { - await this.removeBlockMap(hash, height); - await this.removeBlockSlow(hash, height); + /** + * Get all coin hashes in the database. + * @param {Number} acct + * @returns {Promise} - Returns {@link Hash}[]. + */ + + getAccountOutpoints(acct) { + assert(typeof acct === 'number'); + return this.bucket.keys({ + gte: layout.C.min(acct), + lte: layout.C.max(acct), + parse: (key) => { + const [, hash, index] = layout.C.decode(key); + return new Outpoint(hash, index); + } + }); } - // Update the transaction counter - // and commit new state due to - // balance change. - this.pending.tx--; - this.put(layout.R, this.pending.commit()); + /** + * Get all coin hashes in the database. + * @param {Number} acct + * @returns {Promise} - Returns {@link Hash}[]. + */ - this.emit('remove tx', tx, details); - this.emit('balance', this.pending.toBalance(), details); + getOutpoints(acct) { + assert(typeof acct === 'number'); - return details; -}; + if (acct !== -1) + return this.getAccountOutpoints(acct); -/** - * Remove a transaction and recursively - * remove all of its spenders. - * @private - * @param {TXRecord} wtx - * @returns {Promise} - */ - -TXDB.prototype.removeRecursive = async function removeRecursive(wtx) { - const tx = wtx.tx; - const hash = wtx.hash; - - for (let i = 0; i < tx.outputs.length; i++) { - const spent = await this.getSpent(hash, i); - - if (!spent) - continue; - - // Remove all of the spender's spenders first. - const stx = await this.getTX(spent.hash); - - assert(stx); - - await this.removeRecursive(stx); + return this.bucket.keys({ + gte: layout.c.min(), + lte: layout.c.max(), + parse: (key) => { + const [hash, index] = layout.c.decode(key); + return new Outpoint(hash, index); + } + }); } - this.start(); - - // Remove the spender. - const details = await this.erase(wtx, wtx.getBlock()); - - assert(details); - - await this.commit(); - - return details; -}; - -/** - * Unconfirm a transaction. Necessary after a reorg. - * @param {Hash} hash - * @returns {Promise} - */ - -TXDB.prototype.unconfirm = async function unconfirm(hash) { - this.start(); - - let details; - try { - details = await this._unconfirm(hash); - } catch (e) { - this.drop(); - throw e; + /** + * Get TX hashes by height range. + * @param {Number} acct + * @param {Object} options + * @param {Number} options.start - Start height. + * @param {Number} options.end - End height. + * @param {Number?} options.limit - Max number of records. + * @param {Boolean?} options.reverse - Reverse order. + * @returns {Promise} - Returns {@link Hash}[]. + */ + + getAccountHeightRangeHashes(acct, options) { + assert(typeof acct === 'number'); + + const start = options.start || 0; + const end = options.end || 0xffffffff; + + return this.bucket.keys({ + gte: layout.H.min(acct, start), + lte: layout.H.max(acct, end), + limit: options.limit, + reverse: options.reverse, + parse: (key) => { + const [,, hash] = layout.H.decode(key); + return hash; + } + }); } - await this.commit(); - - return details; -}; - -/** - * Unconfirm a transaction without a batch. - * @private - * @param {Hash} hash - * @returns {Promise} - */ - -TXDB.prototype._unconfirm = async function _unconfirm(hash) { - const wtx = await this.getTX(hash); - - if (!wtx) - return null; - - if (wtx.height === -1) - return null; - - return await this.disconnect(wtx, wtx.getBlock()); -}; - -/** - * Unconfirm a transaction. Necessary after a reorg. - * @param {TXRecord} wtx - * @returns {Promise} - */ - -TXDB.prototype.disconnect = async function disconnect(wtx, block) { - const tx = wtx.tx; - const hash = wtx.hash; - const height = block.height; - const details = new Details(this, wtx, block); - const accounts = new Set(); - - assert(block); - - wtx.unsetBlock(); - - if (!tx.isCoinbase()) { - // We need to reconnect the coins. Start - // by getting all of the undo coins we know - // about. - const credits = await this.getSpentCredits(tx); - - for (let i = 0; i < tx.inputs.length; i++) { - const credit = credits[i]; - - if (!credit) - continue; - - const coin = credit.coin; - - assert(coin.height !== -1); - - const path = await this.getPath(coin); - assert(path); - - details.setInput(i, path, coin); - accounts.add(path.account); - - this.pending.confirmed += coin.value; - - // Resave the credit and mark it - // as spent in the mempool instead. - credit.spent = true; - await this.saveCredit(credit, path); - } + /** + * Get TX hashes by height range. + * @param {Number} acct + * @param {Object} options + * @param {Number} options.start - Start height. + * @param {Number} options.end - End height. + * @param {Number?} options.limit - Max number of records. + * @param {Boolean?} options.reverse - Reverse order. + * @returns {Promise} - Returns {@link Hash}[]. + */ + + getHeightRangeHashes(acct, options) { + assert(typeof acct === 'number'); + + if (acct !== -1) + return this.getAccountHeightRangeHashes(acct, options); + + const start = options.start || 0; + const end = options.end || 0xffffffff; + + return this.bucket.keys({ + gte: layout.h.min(start), + lte: layout.h.max(end), + limit: options.limit, + reverse: options.reverse, + parse: (key) => { + const [, hash] = layout.h.decode(key); + return hash; + } + }); } - // We need to remove heights on - // the credits and undo coins. - for (let i = 0; i < tx.outputs.length; i++) { - const output = tx.outputs[i]; - const path = await this.getPath(output); - - if (!path) - continue; - - const credit = await this.getCredit(hash, i); - - // Potentially update undo coin height. - if (!credit) { - await this.updateSpentCoin(tx, i, height); - continue; - } - - if (credit.spent) - await this.updateSpentCoin(tx, i, height); - - details.setOutput(i, path); - accounts.add(path.account); + /** + * Get TX hashes by height. + * @param {Number} height + * @returns {Promise} - Returns {@link Hash}[]. + */ - // Update coin height and confirmed - // balance. Save once again. - const coin = credit.coin; - coin.height = -1; - - this.pending.confirmed -= output.value; - - await this.saveCredit(credit, path); + getHeightHashes(height) { + return this.getHeightRangeHashes({ start: height, end: height }); } - await this.removeBlockMap(hash, height); - await this.removeBlock(tx.hash(), height); - - // We need to update the now-removed - // block properties and reindex due - // to the height change. - this.put(layout.t(hash), wtx.toRaw()); - this.put(layout.p(hash), null); - this.del(layout.h(height, hash)); - - // Secondary indexing also needs to change. - for (const account of accounts) { - this.put(layout.P(account, hash), null); - this.del(layout.H(account, height, hash)); + /** + * Get TX hashes by timestamp range. + * @param {Number} acct + * @param {Object} options + * @param {Number} options.start - Start height. + * @param {Number} options.end - End height. + * @param {Number?} options.limit - Max number of records. + * @param {Boolean?} options.reverse - Reverse order. + * @returns {Promise} - Returns {@link Hash}[]. + */ + + getAccountRangeHashes(acct, options) { + assert(typeof acct === 'number'); + + const start = options.start || 0; + const end = options.end || 0xffffffff; + + return this.bucket.keys({ + gte: layout.M.min(acct, start), + lte: layout.M.max(acct, end), + limit: options.limit, + reverse: options.reverse, + parse: (key) => { + const [,, hash] = layout.M.decode(key); + return hash; + } + }); } - // Commit state due to unconfirmed - // vs. confirmed balance change. - this.put(layout.R, this.pending.commit()); - - this.emit('unconfirmed', tx, details); - this.emit('balance', this.pending.toBalance(), details); - - return details; -}; - -/** - * Remove spenders that have not been confirmed. We do this in the - * odd case of stuck transactions or when a coin is double-spent - * by a newer transaction. All previously-spending transactions - * of that coin that are _not_ confirmed will be removed from - * the database. - * @private - * @param {Hash} hash - * @param {TX} ref - Reference tx, the tx that double-spent. - * @returns {Promise} - Returns Boolean. - */ - -TXDB.prototype.removeConflict = async function removeConflict(wtx) { - const tx = wtx.tx; - - this.logger.warning('Handling conflicting tx: %s.', tx.txid()); - - this.drop(); - - const details = await this.removeRecursive(wtx); - - this.start(); - - this.logger.warning('Removed conflict: %s.', tx.txid()); - - // Emit the _removed_ transaction. - this.emit('conflict', tx, details); - - return details; -}; - -/** - * Retrieve coins for own inputs, remove - * double spenders, and verify inputs. - * @private - * @param {TX} tx - * @returns {Promise} - */ - -TXDB.prototype.removeConflicts = async function removeConflicts(tx, conf) { - const hash = tx.hash('hex'); - const spends = []; - - if (tx.isCoinbase()) - return true; - - // Gather all spent records first. - for (let i = 0; i < tx.inputs.length; i++) { - const input = tx.inputs[i]; - const prevout = input.prevout; - - // Is it already spent? - const spent = await this.getSpent(prevout.hash, prevout.index); - - if (!spent) - continue; - - // Did _we_ spend it? - if (spent.hash === hash) - continue; - - const spender = await this.getTX(spent.hash); - assert(spender); - - const block = spender.getBlock(); - - if (conf && block) - return false; - - spends[i] = spender; + /** + * Get TX hashes by timestamp range. + * @param {Number} acct + * @param {Object} options + * @param {Number} options.start - Start height. + * @param {Number} options.end - End height. + * @param {Number?} options.limit - Max number of records. + * @param {Boolean?} options.reverse - Reverse order. + * @returns {Promise} - Returns {@link Hash}[]. + */ + + getRangeHashes(acct, options) { + assert(typeof acct === 'number'); + + if (acct !== -1) + return this.getAccountRangeHashes(acct, options); + + const start = options.start || 0; + const end = options.end || 0xffffffff; + + return this.bucket.keys({ + gte: layout.m.min(start), + lte: layout.m.max(end), + limit: options.limit, + reverse: options.reverse, + parse: (key) => { + const [, hash] = layout.m.decode(key); + return hash; + } + }); } - // Once we know we're not going to - // screw things up, remove the double - // spenders. - for (const spender of spends) { - if (!spender) - continue; + /** + * Get transactions by timestamp range. + * @param {Number} acct + * @param {Object} options + * @param {Number} options.start - Start time. + * @param {Number} options.end - End time. + * @param {Number?} options.limit - Max number of records. + * @param {Boolean?} options.reverse - Reverse order. + * @returns {Promise} - Returns {@link TX}[]. + */ + + async getRange(acct, options) { + const hashes = await this.getRangeHashes(acct, options); + const txs = []; + + for (const hash of hashes) { + const tx = await this.getTX(hash); + assert(tx); + txs.push(tx); + } - // Remove the double spender. - await this.removeConflict(spender); + return txs; } - return true; -}; - -/** - * Attempt to verify an input. - * @private - * @param {TX} tx - * @param {Number} index - * @param {Coin} coin - * @returns {Promise} - */ - -TXDB.prototype.verifyInput = async function verifyInput(tx, index, coin) { - const flags = Script.flags.MANDATORY_VERIFY_FLAGS; - - if (!this.options.verify) - return true; - - return await tx.verifyInputAsync(index, coin, flags); -}; - -/** - * Lock all coins in a transaction. - * @param {TX} tx - */ - -TXDB.prototype.lockTX = function lockTX(tx) { - if (tx.isCoinbase()) - return; - - for (const input of tx.inputs) - this.lockCoin(input.prevout); -}; - -/** - * Unlock all coins in a transaction. - * @param {TX} tx - */ - -TXDB.prototype.unlockTX = function unlockTX(tx) { - if (tx.isCoinbase()) - return; - - for (const input of tx.inputs) - this.unlockCoin(input.prevout); -}; - -/** - * Lock a single coin. - * @param {Coin|Outpoint} coin - */ - -TXDB.prototype.lockCoin = function lockCoin(coin) { - const key = coin.toKey(); - this.locked.add(key); -}; - -/** - * Unlock a single coin. - * @param {Coin|Outpoint} coin - */ - -TXDB.prototype.unlockCoin = function unlockCoin(coin) { - const key = coin.toKey(); - return this.locked.delete(key); -}; - -/** - * Test locked status of a single coin. - * @param {Coin|Outpoint} coin - */ - -TXDB.prototype.isLocked = function isLocked(coin) { - const key = coin.toKey(); - return this.locked.has(key); -}; - -/** - * Filter array of coins or outpoints - * for only unlocked ones. - * @param {Coin[]|Outpoint[]} - * @returns {Array} - */ - -TXDB.prototype.filterLocked = function filterLocked(coins) { - const out = []; - - for (const coin of coins) { - if (!this.isLocked(coin)) - out.push(coin); + /** + * Get last N transactions. + * @param {Number} acct + * @param {Number} limit - Max number of transactions. + * @returns {Promise} - Returns {@link TX}[]. + */ + + getLast(acct, limit) { + return this.getRange(acct, { + start: 0, + end: 0xffffffff, + reverse: true, + limit: limit || 10 + }); } - return out; -}; - -/** - * Return an array of all locked outpoints. - * @returns {Outpoint[]} - */ - -TXDB.prototype.getLocked = function getLocked() { - const outpoints = []; - - for (const key of this.locked.keys()) - outpoints.push(Outpoint.fromKey(key)); - - return outpoints; -}; - -/** - * Get hashes of all transactions in the database. - * @param {Number?} account - * @returns {Promise} - Returns {@link Hash}[]. - */ - -TXDB.prototype.getAccountHistoryHashes = function getAccountHistoryHashes(account) { - return this.keys({ - gte: layout.T(account, encoding.NULL_HASH), - lte: layout.T(account, encoding.HIGH_HASH), - parse: (key) => { - const [, hash] = layout.Tt(key); - return hash; - } - }); -}; - -/** - * Get hashes of all transactions in the database. - * @param {Number?} account - * @returns {Promise} - Returns {@link Hash}[]. - */ - -TXDB.prototype.getHistoryHashes = function getHistoryHashes(account) { - if (account != null) - return this.getAccountHistoryHashes(account); - - return this.keys({ - gte: layout.t(encoding.NULL_HASH), - lte: layout.t(encoding.HIGH_HASH), - parse: key => layout.tt(key) - }); -}; - -/** - * Get hashes of all unconfirmed transactions in the database. - * @param {Number?} account - * @returns {Promise} - Returns {@link Hash}[]. - */ - -TXDB.prototype.getAccountPendingHashes = function getAccountPendingHashes(account) { - return this.keys({ - gte: layout.P(account, encoding.NULL_HASH), - lte: layout.P(account, encoding.HIGH_HASH), - parse: (key) => { - const [, hash] = layout.Pp(key); - return hash; - } - }); -}; - -/** - * Get hashes of all unconfirmed transactions in the database. - * @param {Number?} account - * @returns {Promise} - Returns {@link Hash}[]. - */ - -TXDB.prototype.getPendingHashes = function getPendingHashes(account) { - if (account != null) - return this.getAccountPendingHashes(account); - - return this.keys({ - gte: layout.p(encoding.NULL_HASH), - lte: layout.p(encoding.HIGH_HASH), - parse: key => layout.pp(key) - }); -}; - -/** - * Get all coin hashes in the database. - * @param {Number?} account - * @returns {Promise} - Returns {@link Hash}[]. - */ - -TXDB.prototype.getAccountOutpoints = function getAccountOutpoints(account) { - return this.keys({ - gte: layout.C(account, encoding.NULL_HASH, 0), - lte: layout.C(account, encoding.HIGH_HASH, 0xffffffff), - parse: (key) => { - const [, hash, index] = layout.Cc(key); - return new Outpoint(hash, index); - } - }); -}; - -/** - * Get all coin hashes in the database. - * @param {Number?} account - * @returns {Promise} - Returns {@link Hash}[]. - */ - -TXDB.prototype.getOutpoints = function getOutpoints(account) { - if (account != null) - return this.getAccountOutpoints(account); - - return this.keys({ - gte: layout.c(encoding.NULL_HASH, 0), - lte: layout.c(encoding.HIGH_HASH, 0xffffffff), - parse: (key) => { - const [hash, index] = layout.cc(key); - return new Outpoint(hash, index); - } - }); -}; - -/** - * Get TX hashes by height range. - * @param {Number?} account - * @param {Object} options - * @param {Number} options.start - Start height. - * @param {Number} options.end - End height. - * @param {Number?} options.limit - Max number of records. - * @param {Boolean?} options.reverse - Reverse order. - * @returns {Promise} - Returns {@link Hash}[]. - */ - -TXDB.prototype.getAccountHeightRangeHashes = function getAccountHeightRangeHashes(account, options) { - const start = options.start || 0; - const end = options.end || 0xffffffff; - - return this.keys({ - gte: layout.H(account, start, encoding.NULL_HASH), - lte: layout.H(account, end, encoding.HIGH_HASH), - limit: options.limit, - reverse: options.reverse, - parse: (key) => { - const [,, hash] = layout.Hh(key); - return hash; - } - }); -}; - -/** - * Get TX hashes by height range. - * @param {Number?} account - * @param {Object} options - * @param {Number} options.start - Start height. - * @param {Number} options.end - End height. - * @param {Number?} options.limit - Max number of records. - * @param {Boolean?} options.reverse - Reverse order. - * @returns {Promise} - Returns {@link Hash}[]. - */ - -TXDB.prototype.getHeightRangeHashes = function getHeightRangeHashes(account, options) { - if (account && typeof account === 'object') { - options = account; - account = null; + /** + * Get all transactions. + * @param {Number} acct + * @returns {Promise} - Returns {@link TX}[]. + */ + + getHistory(acct) { + assert(typeof acct === 'number'); + + // Slow case + if (acct !== -1) + return this.getAccountHistory(acct); + + // Fast case + return this.bucket.values({ + gte: layout.t.min(), + lte: layout.t.max(), + parse: data => TXRecord.fromRaw(data) + }); } - if (account != null) - return this.getAccountHeightRangeHashes(account, options); + /** + * Get all acct transactions. + * @param {Number} acct + * @returns {Promise} - Returns {@link TX}[]. + */ - const start = options.start || 0; - const end = options.end || 0xffffffff; + async getAccountHistory(acct) { + const hashes = await this.getHistoryHashes(acct); + const txs = []; - return this.keys({ - gte: layout.h(start, encoding.NULL_HASH), - lte: layout.h(end, encoding.HIGH_HASH), - limit: options.limit, - reverse: options.reverse, - parse: (key) => { - const [, hash] = layout.hh(key); - return hash; + for (const hash of hashes) { + const tx = await this.getTX(hash); + assert(tx); + txs.push(tx); } - }); -}; -/** - * Get TX hashes by height. - * @param {Number} height - * @returns {Promise} - Returns {@link Hash}[]. - */ + return txs; + } -TXDB.prototype.getHeightHashes = function getHeightHashes(height) { - return this.getHeightRangeHashes({ start: height, end: height }); -}; + /** + * Get unconfirmed transactions. + * @param {Number} acct + * @returns {Promise} - Returns {@link TX}[]. + */ -/** - * Get TX hashes by timestamp range. - * @param {Number?} account - * @param {Object} options - * @param {Number} options.start - Start height. - * @param {Number} options.end - End height. - * @param {Number?} options.limit - Max number of records. - * @param {Boolean?} options.reverse - Reverse order. - * @returns {Promise} - Returns {@link Hash}[]. - */ + async getPending(acct) { + const hashes = await this.getPendingHashes(acct); + const txs = []; -TXDB.prototype.getAccountRangeHashes = function getAccountRangeHashes(account, options) { - const start = options.start || 0; - const end = options.end || 0xffffffff; - - return this.keys({ - gte: layout.M(account, start, encoding.NULL_HASH), - lte: layout.M(account, end, encoding.HIGH_HASH), - limit: options.limit, - reverse: options.reverse, - parse: (key) => { - const [,, hash] = layout.Mm(key); - return hash; + for (const hash of hashes) { + const tx = await this.getTX(hash); + assert(tx); + txs.push(tx); } - }); -}; -/** - * Get TX hashes by timestamp range. - * @param {Number?} account - * @param {Object} options - * @param {Number} options.start - Start height. - * @param {Number} options.end - End height. - * @param {Number?} options.limit - Max number of records. - * @param {Boolean?} options.reverse - Reverse order. - * @returns {Promise} - Returns {@link Hash}[]. - */ + return txs; + } -TXDB.prototype.getRangeHashes = function getRangeHashes(account, options) { - if (account && typeof account === 'object') { - options = account; - account = null; + /** + * Get coins. + * @param {Number} acct + * @returns {Promise} - Returns {@link Coin}[]. + */ + + getCredits(acct) { + assert(typeof acct === 'number'); + + // Slow case + if (acct !== -1) + return this.getAccountCredits(acct); + + // Fast case + return this.bucket.range({ + gte: layout.c.min(), + lte: layout.c.max(), + parse: (key, value) => { + const [hash, index] = layout.c.decode(key); + const credit = Credit.fromRaw(value); + credit.coin.hash = hash; + credit.coin.index = index; + return credit; + } + }); } - if (account != null) - return this.getAccountRangeHashes(account, options); + /** + * Get coins by account. + * @param {Number} acct + * @returns {Promise} - Returns {@link Coin}[]. + */ - const start = options.start || 0; - const end = options.end || 0xffffffff; + async getAccountCredits(acct) { + const outpoints = await this.getOutpoints(acct); + const credits = []; - return this.keys({ - gte: layout.m(start, encoding.NULL_HASH), - lte: layout.m(end, encoding.HIGH_HASH), - limit: options.limit, - reverse: options.reverse, - parse: (key) => { - const [, hash] = layout.mm(key); - return hash; + for (const {hash, index} of outpoints) { + const credit = await this.getCredit(hash, index); + assert(credit); + credits.push(credit); } - }); -}; - -/** - * Get transactions by timestamp range. - * @param {Number?} account - * @param {Object} options - * @param {Number} options.start - Start time. - * @param {Number} options.end - End time. - * @param {Number?} options.limit - Max number of records. - * @param {Boolean?} options.reverse - Reverse order. - * @returns {Promise} - Returns {@link TX}[]. - */ -TXDB.prototype.getRange = async function getRange(account, options) { - const txs = []; - - if (account && typeof account === 'object') { - options = account; - account = null; + return credits; } - const hashes = await this.getRangeHashes(account, options); + /** + * Fill a transaction with coins (all historical coins). + * @param {TX} tx + * @returns {Promise} - Returns {@link TX}. + */ + + async getSpentCredits(tx) { + if (tx.isCoinbase()) + return []; + + const hash = tx.hash(); + const credits = []; + + for (let i = 0; i < tx.inputs.length; i++) + credits.push(null); + + await this.bucket.range({ + gte: layout.d.min(hash), + lte: layout.d.max(hash), + parse: (key, value) => { + const [, index] = layout.d.decode(key); + const coin = Coin.fromRaw(value); + const input = tx.inputs[index]; + assert(input); + coin.hash = input.prevout.hash; + coin.index = input.prevout.index; + credits[index] = new Credit(coin); + } + }); - for (const hash of hashes) { - const tx = await this.getTX(hash); - assert(tx); - txs.push(tx); + return credits; } - return txs; -}; - -/** - * Get last N transactions. - * @param {Number?} account - * @param {Number} limit - Max number of transactions. - * @returns {Promise} - Returns {@link TX}[]. - */ - -TXDB.prototype.getLast = function getLast(account, limit) { - return this.getRange(account, { - start: 0, - end: 0xffffffff, - reverse: true, - limit: limit || 10 - }); -}; - -/** - * Get all transactions. - * @param {Number?} account - * @returns {Promise} - Returns {@link TX}[]. - */ - -TXDB.prototype.getHistory = function getHistory(account) { - // Slow case - if (account != null) - return this.getAccountHistory(account); + /** + * Get coins. + * @param {Number} acct + * @returns {Promise} - Returns {@link Coin}[]. + */ - // Fast case - return this.values({ - gte: layout.t(encoding.NULL_HASH), - lte: layout.t(encoding.HIGH_HASH), - parse: TXRecord.fromRaw - }); -}; + async getCoins(acct) { + const credits = await this.getCredits(acct); + const coins = []; -/** - * Get all account transactions. - * @param {Number?} account - * @returns {Promise} - Returns {@link TX}[]. - */ + for (const credit of credits) { + if (credit.spent) + continue; -TXDB.prototype.getAccountHistory = async function getAccountHistory(account) { - const hashes = await this.getHistoryHashes(account); - const txs = []; + coins.push(credit.coin); + } - for (const hash of hashes) { - const tx = await this.getTX(hash); - assert(tx); - txs.push(tx); + return coins; } - return txs; -}; + /** + * Get coins by account. + * @param {Number} acct + * @returns {Promise} - Returns {@link Coin}[]. + */ -/** - * Get unconfirmed transactions. - * @param {Number?} account - * @returns {Promise} - Returns {@link TX}[]. - */ + async getAccountCoins(acct) { + const credits = await this.getAccountCredits(acct); + const coins = []; -TXDB.prototype.getPending = async function getPending(account) { - const hashes = await this.getPendingHashes(account); - const txs = []; - - for (const hash of hashes) { - const tx = await this.getTX(hash); - assert(tx); - txs.push(tx); - } - - return txs; -}; - -/** - * Get coins. - * @param {Number?} account - * @returns {Promise} - Returns {@link Coin}[]. - */ + for (const credit of credits) { + if (credit.spent) + continue; -TXDB.prototype.getCredits = function getCredits(account) { - // Slow case - if (account != null) - return this.getAccountCredits(account); - - // Fast case - return this.range({ - gte: layout.c(encoding.NULL_HASH, 0x00000000), - lte: layout.c(encoding.HIGH_HASH, 0xffffffff), - parse: (key, value) => { - const [hash, index] = layout.cc(key); - const credit = Credit.fromRaw(value); - const ckey = Outpoint.toKey(hash, index); - credit.coin.hash = hash; - credit.coin.index = index; - this.coinCache.set(ckey, value); - return credit; + coins.push(credit.coin); } - }); -}; -/** - * Get coins by account. - * @param {Number} account - * @returns {Promise} - Returns {@link Coin}[]. - */ - -TXDB.prototype.getAccountCredits = async function getAccountCredits(account) { - const outpoints = await this.getOutpoints(account); - const credits = []; - - for (const prevout of outpoints) { - const credit = await this.getCredit(prevout.hash, prevout.index); - assert(credit); - credits.push(credit); + return coins; } - return credits; -}; + /** + * Get historical coins for a transaction. + * @param {TX} tx + * @returns {Promise} - Returns {@link TX}. + */ -/** - * Fill a transaction with coins (all historical coins). - * @param {TX} tx - * @returns {Promise} - Returns {@link TX}. - */ - -TXDB.prototype.getSpentCredits = async function getSpentCredits(tx) { - if (tx.isCoinbase()) - return []; - - const hash = tx.hash('hex'); - const credits = []; - - for (let i = 0; i < tx.inputs.length; i++) - credits.push(null); - - await this.range({ - gte: layout.d(hash, 0x00000000), - lte: layout.d(hash, 0xffffffff), - parse: (key, value) => { - const [, index] = layout.dd(key); - const coin = Coin.fromRaw(value); - const input = tx.inputs[index]; - assert(input); - coin.hash = input.prevout.hash; - coin.index = input.prevout.index; - credits[index] = new Credit(coin); - } - }); + async getSpentCoins(tx) { + if (tx.isCoinbase()) + return []; - return credits; -}; - -/** - * Get coins. - * @param {Number?} account - * @returns {Promise} - Returns {@link Coin}[]. - */ + const credits = await this.getSpentCredits(tx); + const coins = []; -TXDB.prototype.getCoins = async function getCoins(account) { - const credits = await this.getCredits(account); - const coins = []; + for (const credit of credits) { + if (!credit) { + coins.push(null); + continue; + } - for (const credit of credits) { - if (credit.spent) - continue; + coins.push(credit.coin); + } - coins.push(credit.coin); + return coins; } - return coins; -}; - -/** - * Get coins by account. - * @param {Number} account - * @returns {Promise} - Returns {@link Coin}[]. - */ - -TXDB.prototype.getAccountCoins = async function getAccountCoins(account) { - const credits = await this.getAccountCredits(account); - const coins = []; - - for (const credit of credits) { - if (credit.spent) - continue; - - coins.push(credit.coin); - } + /** + * Get a coin viewpoint. + * @param {TX} tx + * @returns {Promise} - Returns {@link CoinView}. + */ - return coins; -}; + async getCoinView(tx) { + const view = new CoinView(); -/** - * Get historical coins for a transaction. - * @param {TX} tx - * @returns {Promise} - Returns {@link TX}. - */ + if (tx.isCoinbase()) + return view; -TXDB.prototype.getSpentCoins = async function getSpentCoins(tx) { - if (tx.isCoinbase()) - return []; + for (const {prevout} of tx.inputs) { + const {hash, index} = prevout; + const coin = await this.getCoin(hash, index); - const credits = await this.getSpentCredits(tx); - const coins = []; + if (!coin) + continue; - for (const credit of credits) { - if (!credit) { - coins.push(null); - continue; + view.addCoin(coin); } - coins.push(credit.coin); + return view; } - return coins; -}; + /** + * Get historical coin viewpoint. + * @param {TX} tx + * @returns {Promise} - Returns {@link CoinView}. + */ -/** - * Get a coin viewpoint. - * @param {TX} tx - * @returns {Promise} - Returns {@link CoinView}. - */ + async getSpentView(tx) { + const view = new CoinView(); -TXDB.prototype.getCoinView = async function getCoinView(tx) { - const view = new CoinView(); + if (tx.isCoinbase()) + return view; - if (tx.isCoinbase()) - return view; + const coins = await this.getSpentCoins(tx); - for (const input of tx.inputs) { - const prevout = input.prevout; - const coin = await this.getCoin(prevout.hash, prevout.index); + for (const coin of coins) { + if (!coin) + continue; - if (!coin) - continue; + view.addCoin(coin); + } - view.addCoin(coin); + return view; } - return view; -}; + /** + * Get transaction. + * @param {Hash} hash + * @returns {Promise} - Returns {@link TX}. + */ -/** - * Get historical coin viewpoint. - * @param {TX} tx - * @returns {Promise} - Returns {@link CoinView}. - */ - -TXDB.prototype.getSpentView = async function getSpentView(tx) { - const view = new CoinView(); + async getTX(hash) { + const raw = await this.bucket.get(layout.t.encode(hash)); - if (tx.isCoinbase()) - return view; - - const coins = await this.getSpentCoins(tx); - - for (const coin of coins) { - if (!coin) - continue; + if (!raw) + return null; - view.addCoin(coin); + return TXRecord.fromRaw(raw); } - return view; -}; + /** + * Get transaction details. + * @param {Hash} hash + * @returns {Promise} - Returns {@link TXDetails}. + */ -/** - * Get TXDB state. - * @returns {Promise} - */ + async getDetails(hash) { + const wtx = await this.getTX(hash); -TXDB.prototype.getState = async function getState() { - const data = await this.get(layout.R); + if (!wtx) + return null; - if (!data) - return null; + return this.toDetails(wtx); + } - return TXDBState.fromRaw(this.wallet.wid, this.wallet.id, data); -}; + /** + * Convert transaction to transaction details. + * @param {TXRecord[]} wtxs + * @returns {Promise} + */ -/** - * Get transaction. - * @param {Hash} hash - * @returns {Promise} - Returns {@link TX}. - */ + async toDetails(wtxs) { + const out = []; -TXDB.prototype.getTX = async function getTX(hash) { - const raw = await this.get(layout.t(hash)); + if (!Array.isArray(wtxs)) + return this._toDetails(wtxs); - if (!raw) - return null; + for (const wtx of wtxs) { + const details = await this._toDetails(wtx); - return TXRecord.fromRaw(raw); -}; + if (!details) + continue; -/** - * Get transaction details. - * @param {Hash} hash - * @returns {Promise} - Returns {@link TXDetails}. - */ + out.push(details); + } -TXDB.prototype.getDetails = async function getDetails(hash) { - const wtx = await this.getTX(hash); + return out; + } - if (!wtx) - return null; + /** + * Convert transaction to transaction details. + * @private + * @param {TXRecord} wtx + * @returns {Promise} + */ - return await this.toDetails(wtx); -}; + async _toDetails(wtx) { + const tx = wtx.tx; + const block = wtx.getBlock(); + const details = new Details(wtx, block); + const coins = await this.getSpentCoins(tx); -/** - * Convert transaction to transaction details. - * @param {TXRecord[]} wtxs - * @returns {Promise} - */ + for (let i = 0; i < tx.inputs.length; i++) { + const coin = coins[i]; -TXDB.prototype.toDetails = async function toDetails(wtxs) { - const out = []; + let path = null; - if (!Array.isArray(wtxs)) - return await this._toDetails(wtxs); + if (coin) + path = await this.getPath(coin); - for (const wtx of wtxs) { - const details = await this._toDetails(wtx); + details.setInput(i, path, coin); + } - if (!details) - continue; + for (let i = 0; i < tx.outputs.length; i++) { + const output = tx.outputs[i]; + const path = await this.getPath(output); + details.setOutput(i, path); + } - out.push(details); + return details; } - return out; -}; - -/** - * Convert transaction to transaction details. - * @private - * @param {TXRecord} wtx - * @returns {Promise} - */ + /** + * Test whether the database has a transaction. + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ -TXDB.prototype._toDetails = async function _toDetails(wtx) { - const tx = wtx.tx; - const block = wtx.getBlock(); - const details = new Details(this, wtx, block); - const coins = await this.getSpentCoins(tx); - - for (let i = 0; i < tx.inputs.length; i++) { - const coin = coins[i]; - let path = null; - - if (coin) - path = await this.getPath(coin); - - details.setInput(i, path, coin); + hasTX(hash) { + return this.bucket.has(layout.t.encode(hash)); } - for (let i = 0; i < tx.outputs.length; i++) { - const output = tx.outputs[i]; - const path = await this.getPath(output); - details.setOutput(i, path); - } + /** + * Get coin. + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} - Returns {@link Coin}. + */ - return details; -}; - -/** - * Test whether the database has a transaction. - * @param {Hash} hash - * @returns {Promise} - Returns Boolean. - */ + async getCoin(hash, index) { + const credit = await this.getCredit(hash, index); -TXDB.prototype.hasTX = function hasTX(hash) { - return this.has(layout.t(hash)); -}; - -/** - * Get coin. - * @param {Hash} hash - * @param {Number} index - * @returns {Promise} - Returns {@link Coin}. - */ - -TXDB.prototype.getCoin = async function getCoin(hash, index) { - const credit = await this.getCredit(hash, index); + if (!credit) + return null; - if (!credit) - return null; + return credit.coin; + } - return credit.coin; -}; + /** + * Get coin. + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} - Returns {@link Coin}. + */ -/** - * Get coin. - * @param {Hash} hash - * @param {Number} index - * @returns {Promise} - Returns {@link Coin}. - */ + async getCredit(hash, index) { + const data = await this.bucket.get(layout.c.encode(hash, index)); -TXDB.prototype.getCredit = async function getCredit(hash, index) { - const state = this.state; - const key = Outpoint.toKey(hash, index); - const cache = this.coinCache.get(key); + if (!data) + return null; - if (cache) { - const credit = Credit.fromRaw(cache); + const credit = Credit.fromRaw(data); credit.coin.hash = hash; credit.coin.index = index; + return credit; } - const data = await this.get(layout.c(hash, index)); - - if (!data) - return null; - - const credit = Credit.fromRaw(data); - credit.coin.hash = hash; - credit.coin.index = index; - - if (state === this.state) - this.coinCache.set(key, data); - - return credit; -}; - -/** - * Get spender coin. - * @param {Outpoint} spent - * @param {Outpoint} prevout - * @returns {Promise} - Returns {@link Coin}. - */ + /** + * Get spender coin. + * @param {Outpoint} spent + * @param {Outpoint} prevout + * @returns {Promise} - Returns {@link Coin}. + */ -TXDB.prototype.getSpentCoin = async function getSpentCoin(spent, prevout) { - const data = await this.get(layout.d(spent.hash, spent.index)); + async getSpentCoin(spent, prevout) { + const data = await this.bucket.get(layout.d.encode( + spent.hash, + spent.index + )); - if (!data) - return null; - - const coin = Coin.fromRaw(data); - coin.hash = prevout.hash; - coin.index = prevout.index; + if (!data) + return null; - return coin; -}; + const coin = Coin.fromRaw(data); + coin.hash = prevout.hash; + coin.index = prevout.index; -/** - * Test whether the database has a spent coin. - * @param {Outpoint} spent - * @returns {Promise} - Returns {@link Coin}. - */ + return coin; + } -TXDB.prototype.hasSpentCoin = function hasSpentCoin(spent) { - return this.has(layout.d(spent.hash, spent.index)); -}; + /** + * Test whether the database has a spent coin. + * @param {Outpoint} spent + * @returns {Promise} - Returns {@link Coin}. + */ -/** - * Update spent coin height in storage. - * @param {TX} tx - Sending transaction. - * @param {Number} index - * @param {Number} height - * @returns {Promise} - */ - -TXDB.prototype.updateSpentCoin = async function updateSpentCoin(tx, index, height) { - const prevout = Outpoint.fromTX(tx, index); - const spent = await this.getSpent(prevout.hash, prevout.index); + hasSpentCoin(spent) { + return this.bucket.has(layout.d.encode(spent.hash, spent.index)); + } - if (!spent) - return; + /** + * Update spent coin height in storage. + * @param {TX} tx - Sending transaction. + * @param {Number} index + * @param {Number} height + * @returns {Promise} + */ - const coin = await this.getSpentCoin(spent, prevout); + async updateSpentCoin(b, tx, index, height) { + const prevout = Outpoint.fromTX(tx, index); + const spent = await this.getSpent(prevout.hash, prevout.index); - if (!coin) - return; + if (!spent) + return; - coin.height = height; + const coin = await this.getSpentCoin(spent, prevout); - this.put(layout.d(spent.hash, spent.index), coin.toRaw()); -}; + if (!coin) + return; -/** - * Test whether the database has a transaction. - * @param {Hash} hash - * @returns {Promise} - Returns Boolean. - */ + coin.height = height; -TXDB.prototype.hasCoin = async function hasCoin(hash, index) { - const key = Outpoint.toKey(hash, index); + b.put(layout.d.encode(spent.hash, spent.index), coin.toRaw()); + } - if (this.coinCache.has(key)) - return true; + /** + * Test whether the database has a transaction. + * @param {Hash} hash + * @returns {Promise} - Returns Boolean. + */ - return await this.has(layout.c(hash, index)); -}; + async hasCoin(hash, index) { + return this.bucket.has(layout.c.encode(hash, index)); + } -/** - * Calculate balance. - * @param {Number?} account - * @returns {Promise} - Returns {@link Balance}. - */ + /** + * Calculate balance. + * @param {Number?} account + * @returns {Promise} - Returns {@link Balance}. + */ -TXDB.prototype.getBalance = async function getBalance(account) { - // Slow case - if (account != null) - return await this.getAccountBalance(account); + async getBalance(acct) { + assert(typeof acct === 'number'); - // Fast case - return this.state.toBalance(); -}; + if (acct !== -1) + return this.getAccountBalance(acct); -/** - * Calculate balance. - * @param {Number?} account - * @returns {Promise} - Returns {@link Balance}. - */ + return this.getWalletBalance(); + } -TXDB.prototype.getWalletBalance = async function getWalletBalance() { - const credits = await this.getCredits(); - const balance = new Balance(this.wallet.wid, this.wallet.id, -1); + /** + * Calculate balance. + * @returns {Promise} - Returns {@link Balance}. + */ - for (const credit of credits) { - const coin = credit.coin; + async getWalletBalance() { + const data = await this.bucket.get(layout.R.encode()); - if (coin.height !== -1) - balance.confirmed += coin.value; + if (!data) + return new Balance(); - if (!credit.spent) - balance.unconfirmed += coin.value; + return Balance.fromRaw(-1, data); } - return balance; -}; - -/** - * Calculate balance by account. - * @param {Number} account - * @returns {Promise} - Returns {@link Balance}. - */ - -TXDB.prototype.getAccountBalance = async function getAccountBalance(account) { - const credits = await this.getAccountCredits(account); - const balance = new Balance(this.wallet.wid, this.wallet.id, account); + /** + * Calculate balance by account. + * @param {Number} acct + * @returns {Promise} - Returns {@link Balance}. + */ - for (const credit of credits) { - const coin = credit.coin; + async getAccountBalance(acct) { + const data = await this.bucket.get(layout.r.encode(acct)); - if (coin.height !== -1) - balance.confirmed += coin.value; + if (!data) + return new Balance(acct); - if (!credit.spent) - balance.unconfirmed += coin.value; + return Balance.fromRaw(acct, data); } - return balance; -}; + /** + * Zap pending transactions older than `age`. + * @param {Number} acct + * @param {Number} age - Age delta. + * @returns {Promise} + */ -/** - * Zap pending transactions older than `age`. - * @param {Number?} account - * @param {Number} age - Age delta (delete transactions older than `now - age`). - * @returns {Promise} - */ + async zap(acct, age) { + assert((age >>> 0) === age); -TXDB.prototype.zap = async function zap(account, age) { - assert(util.isU32(age)); + const now = util.now(); - const now = util.now(); + const txs = await this.getRange(acct, { + start: 0, + end: now - age + }); - const txs = await this.getRange(account, { - start: 0, - end: now - age - }); + const hashes = []; - const hashes = []; + for (const wtx of txs) { + if (wtx.height !== -1) + continue; - for (const wtx of txs) { - if (wtx.height !== -1) - continue; + assert(now - wtx.mtime >= age); - assert(now - wtx.mtime >= age); + this.logger.debug('Zapping TX: %h (%d)', + wtx.tx.hash(), this.wid); - this.logger.debug('Zapping TX: %s (%s)', - wtx.tx.txid(), this.wallet.id); + await this.remove(wtx.hash); - await this.remove(wtx.hash); + hashes.push(wtx.hash); + } - hashes.push(wtx.hash); + return hashes; } - return hashes; -}; - -/** - * Abandon transaction. - * @param {Hash} hash - * @returns {Promise} - */ + /** + * Abandon transaction. + * @param {Hash} hash + * @returns {Promise} + */ -TXDB.prototype.abandon = async function abandon(hash) { - const result = await this.has(layout.p(hash)); + async abandon(hash) { + const result = await this.bucket.has(layout.p.encode(hash)); - if (!result) - throw new Error('TX not eligible.'); + if (!result) + throw new Error('TX not eligible.'); - return await this.remove(hash); -}; + return this.remove(hash); + } +} /** * Balance * @alias module:wallet.Balance - * @constructor - * @param {WalletID} wid - * @param {String} id - * @param {Number} account */ -function Balance(wid, id, account) { - if (!(this instanceof Balance)) - return new Balance(wid, id, account); +class Balance { + /** + * Create a balance. + * @constructor + * @param {Number} account + */ - this.wid = wid; - this.id = id; - this.account = account; - this.unconfirmed = 0; - this.confirmed = 0; -} - -/** - * Test whether a balance is equal. - * @param {Balance} balance - * @returns {Boolean} - */ - -Balance.prototype.equal = function equal(balance) { - return this.wid === balance.wid - && this.confirmed === balance.confirmed - && this.unconfirmed === balance.unconfirmed; -}; - -/** - * Convert balance to a more json-friendly object. - * @param {Boolean?} minimal - * @returns {Object} - */ + constructor(acct = -1) { + assert(typeof acct === 'number'); -Balance.prototype.toJSON = function toJSON(minimal) { - return { - wid: !minimal ? this.wid : undefined, - id: !minimal ? this.id : undefined, - account: !minimal ? this.account : undefined, - unconfirmed: this.unconfirmed, - confirmed: this.confirmed - }; -}; + this.account = acct; + this.tx = 0; + this.coin = 0; + this.unconfirmed = 0; + this.confirmed = 0; + } -/** - * Convert balance to human-readable string. - * @returns {String} - */ + /** + * Apply delta. + * @param {Balance} balance + */ + + applyTo(balance) { + balance.tx += this.tx; + balance.coin += this.coin; + balance.unconfirmed += this.unconfirmed; + balance.confirmed += this.confirmed; + + assert(balance.tx >= 0); + assert(balance.coin >= 0); + assert(balance.unconfirmed >= 0); + assert(balance.confirmed >= 0); + } -Balance.prototype.toString = function toString() { - return ''; -}; + /** + * Serialize balance. + * @returns {Buffer} + */ -/** - * Inspect balance. - * @param {String} - */ + toRaw() { + const bw = bio.write(32); -Balance.prototype.inspect = function inspect() { - return this.toString(); -}; + bw.writeU64(this.tx); + bw.writeU64(this.coin); + bw.writeU64(this.unconfirmed); + bw.writeU64(this.confirmed); -/** - * Chain State - * @alias module:wallet.ChainState - * @constructor - * @param {WalletID} wid - * @param {String} id - */ - -function TXDBState(wid, id) { - this.wid = wid; - this.id = id; - this.tx = 0; - this.coin = 0; - this.unconfirmed = 0; - this.confirmed = 0; - this.committed = false; -} - -/** - * Clone the state. - * @returns {TXDBState} - */ + return bw.render(); + } -TXDBState.prototype.clone = function clone() { - const state = new TXDBState(this.wid, this.id); - state.tx = this.tx; - state.coin = this.coin; - state.unconfirmed = this.unconfirmed; - state.confirmed = this.confirmed; - return state; -}; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + * @returns {TXDBState} + */ + + fromRaw(data) { + const br = bio.read(data); + this.tx = br.readU64(); + this.coin = br.readU64(); + this.unconfirmed = br.readU64(); + this.confirmed = br.readU64(); + return this; + } -/** - * Commit and serialize state. - * @returns {Buffer} - */ + /** + * Instantiate balance from serialized data. + * @param {Number} acct + * @param {Buffer} data + * @returns {TXDBState} + */ -TXDBState.prototype.commit = function commit() { - this.committed = true; - return this.toRaw(); -}; + static fromRaw(acct, data) { + return new this(acct).fromRaw(data); + } -/** - * Convert state to a balance object. - * @returns {Balance} - */ + /** + * Convert balance to a more json-friendly object. + * @param {Boolean?} minimal + * @returns {Object} + */ + + toJSON(minimal) { + return { + account: !minimal ? this.account : undefined, + tx: this.tx, + coin: this.coin, + unconfirmed: this.unconfirmed, + confirmed: this.confirmed + }; + } -TXDBState.prototype.toBalance = function toBalance() { - const balance = new Balance(this.wid, this.id, -1); - balance.unconfirmed = this.unconfirmed; - balance.confirmed = this.confirmed; - return balance; -}; + /** + * Inspect balance. + * @param {String} + */ + + [inspectSymbol]() { + return ''; + } +} /** - * Serialize state. - * @returns {Buffer} + * Balance Delta + * @ignore */ -TXDBState.prototype.toRaw = function toRaw() { - const bw = new StaticWriter(32); +class BalanceDelta { + /** + * Create a balance delta. + * @constructor + */ - bw.writeU64(this.tx); - bw.writeU64(this.coin); - bw.writeU64(this.unconfirmed); - bw.writeU64(this.confirmed); - - return bw.render(); -}; + constructor() { + this.wallet = new Balance(); + this.accounts = new Map(); + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @returns {TXDBState} - */ + updated() { + return this.wallet.tx !== 0; + } -TXDBState.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); - this.tx = br.readU64(); - this.coin = br.readU64(); - this.unconfirmed = br.readU64(); - this.confirmed = br.readU64(); - return this; -}; + applyTo(balance) { + this.wallet.applyTo(balance); + } -/** - * Instantiate txdb state from serialized data. - * @param {Buffer} data - * @returns {TXDBState} - */ + get(path) { + if (!this.accounts.has(path.account)) + this.accounts.set(path.account, new Balance()); -TXDBState.fromRaw = function fromRaw(wid, id, data) { - return new TXDBState(wid, id).fromRaw(data); -}; + return this.accounts.get(path.account); + } -/** - * Convert state to a more json-friendly object. - * @param {Boolean?} minimal - * @returns {Object} - */ + tx(path, value) { + const account = this.get(path); + account.tx = value; + this.wallet.tx = value; + } -TXDBState.prototype.toJSON = function toJSON(minimal) { - return { - wid: !minimal ? this.wid : undefined, - id: !minimal ? this.id : undefined, - tx: this.tx, - coin: this.coin, - unconfirmed: this.unconfirmed, - confirmed: this.confirmed - }; -}; + coin(path, value) { + const account = this.get(path); + account.coin += value; + this.wallet.coin += value; + } -/** - * Inspect the state. - * @returns {Object} - */ + unconfirmed(path, value) { + const account = this.get(path); + account.unconfirmed += value; + this.wallet.unconfirmed += value; + } -TXDBState.prototype.inspect = function inspect() { - return this.toJSON(); -}; + confirmed(path, value) { + const account = this.get(path); + account.confirmed += value; + this.wallet.confirmed += value; + } +} /** * Credit (wrapped coin) * @alias module:wallet.Credit - * @constructor - * @param {Coin} coin - * @param {Boolean?} spent * @property {Coin} coin * @property {Boolean} spent */ -function Credit(coin, spent) { - if (!(this instanceof Credit)) - return new Credit(coin, spent); - - this.coin = coin || new Coin(); - this.spent = spent || false; - this.own = false; -} +class Credit { + /** + * Create a credit. + * @constructor + * @param {Coin} coin + * @param {Boolean?} spent + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + constructor(coin, spent) { + this.coin = coin || new Coin(); + this.spent = spent || false; + this.own = false; + } -Credit.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); - this.coin.fromReader(br); - this.spent = br.readU8() === 1; - this.own = true; + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ - // Note: soft-fork - if (br.left() > 0) + fromRaw(data) { + const br = bio.read(data); + this.coin.fromReader(br); + this.spent = br.readU8() === 1; this.own = br.readU8() === 1; + return this; + } - return this; -}; - -/** - * Instantiate credit from serialized data. - * @param {Buffer} data - * @returns {Credit} - */ - -Credit.fromRaw = function fromRaw(data) { - return new Credit().fromRaw(data); -}; - -/** - * Get serialization size. - * @returns {Number} - */ + /** + * Instantiate credit from serialized data. + * @param {Buffer} data + * @returns {Credit} + */ -Credit.prototype.getSize = function getSize() { - return this.coin.getSize() + 2; -}; + static fromRaw(data) { + return new this().fromRaw(data); + } -/** - * Serialize credit. - * @returns {Buffer} - */ + /** + * Get serialization size. + * @returns {Number} + */ -Credit.prototype.toRaw = function toRaw() { - const size = this.getSize(); - const bw = new StaticWriter(size); - this.coin.toWriter(bw); - bw.writeU8(this.spent ? 1 : 0); - bw.writeU8(this.own ? 1 : 0); - return bw.render(); -}; + getSize() { + return this.coin.getSize() + 2; + } -/** - * Inject properties from tx object. - * @private - * @param {TX} tx - * @param {Number} index - * @returns {Credit} - */ + /** + * Serialize credit. + * @returns {Buffer} + */ + + toRaw() { + const size = this.getSize(); + const bw = bio.write(size); + this.coin.toWriter(bw); + bw.writeU8(this.spent ? 1 : 0); + bw.writeU8(this.own ? 1 : 0); + return bw.render(); + } -Credit.prototype.fromTX = function fromTX(tx, index, height) { - this.coin.fromTX(tx, index, height); - this.spent = false; - this.own = false; - return this; -}; + /** + * Inject properties from tx object. + * @private + * @param {TX} tx + * @param {Number} index + * @returns {Credit} + */ + + fromTX(tx, index, height) { + this.coin.fromTX(tx, index, height); + this.spent = false; + this.own = false; + return this; + } -/** - * Instantiate credit from transaction. - * @param {TX} tx - * @param {Number} index - * @returns {Credit} - */ + /** + * Instantiate credit from transaction. + * @param {TX} tx + * @param {Number} index + * @returns {Credit} + */ -Credit.fromTX = function fromTX(tx, index, height) { - return new Credit().fromTX(tx, index, height); -}; + static fromTX(tx, index, height) { + return new this().fromTX(tx, index, height); + } +} /** * Transaction Details * @alias module:wallet.Details - * @constructor - * @param {TXDB} txdb - * @param {TX} tx */ -function Details(txdb, wtx, block) { - if (!(this instanceof Details)) - return new Details(txdb, wtx, block); +class Details { + /** + * Create transaction details. + * @constructor + * @param {TXRecord} wtx + * @param {BlockMeta} block + */ + + constructor(wtx, block) { + this.hash = wtx.hash; + this.tx = wtx.tx; + this.mtime = wtx.mtime; + this.size = this.tx.getSize(); + this.vsize = this.tx.getVirtualSize(); + + this.block = null; + this.height = -1; + this.time = 0; + + if (block) { + this.block = block.hash; + this.height = block.height; + this.time = block.time; + } - this.wallet = txdb.wallet; - this.network = this.wallet.network; - this.wid = this.wallet.wid; - this.id = this.wallet.id; + this.inputs = []; + this.outputs = []; - this.chainHeight = txdb.walletdb.state.height; + this.init(); + } - this.hash = wtx.hash; - this.tx = wtx.tx; - this.mtime = wtx.mtime; - this.size = this.tx.getSize(); - this.vsize = this.tx.getVirtualSize(); + /** + * Initialize transaction details. + * @private + */ - this.block = null; - this.height = -1; - this.time = 0; + init() { + for (const input of this.tx.inputs) { + const member = new DetailsMember(); + member.address = input.getAddress(); + this.inputs.push(member); + } - if (block) { - this.block = block.hash; - this.height = block.height; - this.time = block.time; + for (const output of this.tx.outputs) { + const member = new DetailsMember(); + member.value = output.value; + member.address = output.getAddress(); + this.outputs.push(member); + } } - this.inputs = []; - this.outputs = []; + /** + * Add necessary info to input member. + * @param {Number} i + * @param {Path} path + * @param {Coin} coin + */ - this.init(); -} - -/** - * Initialize transaction details. - * @private - */ + setInput(i, path, coin) { + const member = this.inputs[i]; -Details.prototype.init = function init() { - for (const input of this.tx.inputs) { - const member = new DetailsMember(); - member.address = input.getAddress(); - this.inputs.push(member); - } + if (coin) { + member.value = coin.value; + member.address = coin.getAddress(); + } - for (const output of this.tx.outputs) { - const member = new DetailsMember(); - member.value = output.value; - member.address = output.getAddress(); - this.outputs.push(member); + if (path) + member.path = path; } -}; -/** - * Add necessary info to input member. - * @param {Number} i - * @param {Path} path - * @param {Coin} coin - */ + /** + * Add necessary info to output member. + * @param {Number} i + * @param {Path} path + */ -Details.prototype.setInput = function setInput(i, path, coin) { - const member = this.inputs[i]; + setOutput(i, path) { + const member = this.outputs[i]; - if (coin) { - member.value = coin.value; - member.address = coin.getAddress(); + if (path) + member.path = path; } - if (path) - member.path = path; -}; + /** + * Calculate confirmations. + * @returns {Number} + */ -/** - * Add necessary info to output member. - * @param {Number} i - * @param {Path} path - */ - -Details.prototype.setOutput = function setOutput(i, path) { - const member = this.outputs[i]; + getDepth(height) { + if (this.height === -1) + return 0; - if (path) - member.path = path; -}; + if (height == null) + return 0; -/** - * Calculate confirmations. - * @returns {Number} - */ + const depth = height - this.height; -Details.prototype.getDepth = function getDepth() { - if (this.height === -1) - return 0; + if (depth < 0) + return 0; - const depth = this.chainHeight - this.height; + return depth + 1; + } - if (depth < 0) - return 0; + /** + * Calculate fee. Only works if wallet + * owns all inputs. Returns 0 otherwise. + * @returns {Amount} + */ - return depth + 1; -}; + getFee() { + let inputValue = 0; + let outputValue = 0; -/** - * Calculate fee. Only works if wallet - * owns all inputs. Returns 0 otherwise. - * @returns {Amount} - */ + for (const input of this.inputs) { + if (!input.path) + return 0; -Details.prototype.getFee = function getFee() { - let inputValue = 0; - let outputValue = 0; + inputValue += input.value; + } - for (const input of this.inputs) { - if (!input.path) - return 0; + for (const output of this.outputs) + outputValue += output.value; - inputValue += input.value; + return inputValue - outputValue; } - for (const output of this.outputs) - outputValue += output.value; - - return inputValue - outputValue; -}; - -/** - * Calculate fee rate. Only works if wallet - * owns all inputs. Returns 0 otherwise. - * @param {Amount} fee - * @returns {Rate} - */ + /** + * Calculate fee rate. Only works if wallet + * owns all inputs. Returns 0 otherwise. + * @param {Amount} fee + * @returns {Rate} + */ -Details.prototype.getRate = function getRate(fee) { - return policy.getRate(this.vsize, fee); -}; - -/** - * Convert details to a more json-friendly object. - * @returns {Object} - */ + getRate(fee) { + return policy.getRate(this.vsize, fee); + } -Details.prototype.toJSON = function toJSON() { - const fee = this.getFee(); - let rate = this.getRate(fee); - - // Rate can exceed 53 bits in testing. - if (!Number.isSafeInteger(rate)) - rate = 0; - - return { - wid: this.wid, - id: this.id, - hash: util.revHex(this.hash), - height: this.height, - block: this.block ? util.revHex(this.block) : null, - time: this.time, - mtime: this.mtime, - date: util.date(this.time || this.mtime), - size: this.size, - virtualSize: this.vsize, - fee: fee, - rate: rate, - confirmations: this.getDepth(), - inputs: this.inputs.map((input) => { - return input.getJSON(this.network); - }), - outputs: this.outputs.map((output) => { - return output.getJSON(this.network); - }), - tx: this.tx.toRaw().toString('hex') - }; -}; + /** + * Convert details to a more json-friendly object. + * @returns {Object} + */ + + toJSON(network, height) { + const fee = this.getFee(); + const rate = this.getRate(fee); + + return { + hash: util.revHex(this.hash), + height: this.height, + block: this.block ? util.revHex(this.block) : null, + time: this.time, + mtime: this.mtime, + date: util.date(this.time), + mdate: util.date(this.mtime), + size: this.size, + virtualSize: this.vsize, + fee: fee, + rate: rate, + confirmations: this.getDepth(height), + inputs: this.inputs.map((input) => { + return input.getJSON(network); + }), + outputs: this.outputs.map((output) => { + return output.getJSON(network); + }), + tx: this.tx.toRaw().toString('hex') + }; + } +} /** * Transaction Details Member - * @alias module:wallet.DetailsMember - * @constructor * @property {Number} value * @property {Address} address * @property {Path} path */ -function DetailsMember() { - if (!(this instanceof DetailsMember)) - return new DetailsMember(); +class DetailsMember { + /** + * Create details member. + * @constructor + */ - this.value = 0; - this.address = null; - this.path = null; -} - -/** - * Convert the member to a more json-friendly object. - * @returns {Object} - */ + constructor() { + this.value = 0; + this.address = null; + this.path = null; + } -DetailsMember.prototype.toJSON = function toJSON() { - return this.getJSON(); -}; + /** + * Convert the member to a more json-friendly object. + * @returns {Object} + */ -/** - * Convert the member to a more json-friendly object. - * @param {Network} network - * @returns {Object} - */ + toJSON() { + return this.getJSON(); + } -DetailsMember.prototype.getJSON = function getJSON(network) { - return { - value: this.value, - address: this.address - ? this.address.toString(network) - : null, - path: this.path - ? this.path.toJSON() - : null - }; -}; + /** + * Convert the member to a more json-friendly object. + * @param {Network} network + * @returns {Object} + */ + + getJSON(network) { + return { + value: this.value, + address: this.address + ? this.address.toString(network) + : null, + path: this.path + ? this.path.toJSON() + : null + }; + } +} /** * Block Record * @alias module:wallet.BlockRecord - * @constructor - * @param {Hash} hash - * @param {Number} height - * @param {Number} time */ -function BlockRecord(hash, height, time) { - if (!(this instanceof BlockRecord)) - return new BlockRecord(hash, height, time); - - this.hash = hash || encoding.NULL_HASH; - this.height = height != null ? height : -1; - this.time = time || 0; - this.hashes = []; - this.index = new Set(); -} - -/** - * Add transaction to block record. - * @param {Hash} hash - * @returns {Boolean} - */ - -BlockRecord.prototype.add = function add(hash) { - if (this.index.has(hash)) - return false; - - this.index.add(hash); - this.hashes.push(hash); +class BlockRecord { + /** + * Create a block record. + * @constructor + * @param {Hash} hash + * @param {Number} height + * @param {Number} time + */ + + constructor(hash, height, time) { + this.hash = hash || consensus.ZERO_HASH; + this.height = height != null ? height : -1; + this.time = time || 0; + this.hashes = new BufferSet(); + } - return true; -}; + /** + * Add transaction to block record. + * @param {Hash} hash + * @returns {Boolean} + */ -/** - * Remove transaction from block record. - * @param {Hash} hash - * @returns {Boolean} - */ - -BlockRecord.prototype.remove = function remove(hash) { - if (!this.index.has(hash)) - return false; + add(hash) { + if (this.hashes.has(hash)) + return false; - this.index.delete(hash); + this.hashes.add(hash); - // Fast case - if (this.hashes[this.hashes.length - 1] === hash) { - this.hashes.pop(); return true; } - const index = this.hashes.indexOf(hash); - - assert(index !== -1); + /** + * Remove transaction from block record. + * @param {Hash} hash + * @returns {Boolean} + */ - this.hashes.splice(index, 1); + remove(hash) { + return this.hashes.delete(hash); + } - return true; -}; + /** + * Instantiate wallet block from serialized tip data. + * @private + * @param {Buffer} data + */ -/** - * Instantiate wallet block from serialized tip data. - * @private - * @param {Buffer} data - */ + fromRaw(data) { + const br = bio.read(data); -BlockRecord.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); + this.hash = br.readHash(); + this.height = br.readU32(); + this.time = br.readU32(); - this.hash = br.readHash('hex'); - this.height = br.readU32(); - this.time = br.readU32(); + const count = br.readU32(); - const count = br.readU32(); + for (let i = 0; i < count; i++) { + const hash = br.readHash(); + this.hashes.add(hash); + } - for (let i = 0; i < count; i++) { - const hash = br.readHash('hex'); - this.index.add(hash); - this.hashes.push(hash); + return this; } - return this; -}; + /** + * Instantiate wallet block from serialized data. + * @param {Buffer} data + * @returns {BlockRecord} + */ -/** - * Instantiate wallet block from serialized data. - * @param {Buffer} data - * @returns {BlockRecord} - */ + static fromRaw(data) { + return new this().fromRaw(data); + } -BlockRecord.fromRaw = function fromRaw(data) { - return new BlockRecord().fromRaw(data); -}; + /** + * Get serialization size. + * @returns {Number} + */ -/** - * Get serialization size. - * @returns {Number} - */ + getSize() { + return 44 + this.hashes.size * 32; + } -BlockRecord.prototype.getSize = function getSize() { - return 44 + this.hashes.length * 32; -}; + /** + * Serialize the wallet block as a tip (hash and height). + * @returns {Buffer} + */ -/** - * Serialize the wallet block as a tip (hash and height). - * @returns {Buffer} - */ + toRaw() { + const size = this.getSize(); + const bw = bio.write(size); -BlockRecord.prototype.toRaw = function toRaw() { - const size = this.getSize(); - const bw = new StaticWriter(size); + bw.writeHash(this.hash); + bw.writeU32(this.height); + bw.writeU32(this.time); - bw.writeHash(this.hash); - bw.writeU32(this.height); - bw.writeU32(this.time); + bw.writeU32(this.hashes.size); - bw.writeU32(this.hashes.length); + for (const hash of this.hashes) + bw.writeHash(hash); - for (const hash of this.hashes) - bw.writeHash(hash); + return bw.render(); + } - return bw.render(); -}; + /** + * Convert hashes set to an array. + * @returns {Hash[]} + */ -/** - * Convert the block to a more json-friendly object. - * @returns {Object} - */ + toArray() { + const hashes = []; + for (const hash of this.hashes) + hashes.push(hash); + return hashes; + } -BlockRecord.prototype.toJSON = function toJSON() { - return { - hash: util.revHex(this.hash), - height: this.height, - time: this.time, - hashes: this.hashes.map(util.revHex) - }; -}; + /** + * Convert the block to a more json-friendly object. + * @returns {Object} + */ + + toJSON() { + return { + hash: util.revHex(this.hash), + height: this.height, + time: this.time, + hashes: this.toArray().map(util.revHex) + }; + } -/** - * Instantiate wallet block from block meta. - * @private - * @param {BlockMeta} block - */ + /** + * Instantiate wallet block from block meta. + * @private + * @param {BlockMeta} block + */ -BlockRecord.prototype.fromMeta = function fromMeta(block) { - this.hash = block.hash; - this.height = block.height; - this.time = block.time; - return this; -}; + fromMeta(block) { + this.hash = block.hash; + this.height = block.height; + this.time = block.time; + return this; + } -/** - * Instantiate wallet block from block meta. - * @param {BlockMeta} block - * @returns {BlockRecord} - */ + /** + * Instantiate wallet block from block meta. + * @param {BlockMeta} block + * @returns {BlockRecord} + */ -BlockRecord.fromMeta = function fromMeta(block) { - return new BlockRecord().fromMeta(block); -}; + static fromMeta(block) { + return new this().fromMeta(block); + } +} /* * Expose diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 471eb0668..b42469535 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -7,18 +7,14 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const EventEmitter = require('events'); -const Network = require('../protocol/network'); -const util = require('../utils/util'); -const encoding = require('../utils/encoding'); -const Lock = require('../utils/lock'); -const MappedLock = require('../utils/mappedlock'); -const digest = require('../crypto/digest'); -const cleanse = require('../crypto/cleanse'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); -const base58 = require('../utils/base58'); +const {Lock} = require('bmutex'); +const {base58} = require('bstring'); +const bio = require('bufio'); +const hash160 = require('bcrypto/lib/hash160'); +const hash256 = require('bcrypto/lib/hash256'); +const cleanse = require('bcrypto/lib/cleanse'); const TXDB = require('./txdb'); const Path = require('./path'); const common = require('./common'); @@ -30,2557 +26,2326 @@ const HD = require('../hd/hd'); const Output = require('../primitives/output'); const Account = require('./account'); const MasterKey = require('./masterkey'); -const LRU = require('../utils/lru'); const policy = require('../protocol/policy'); const consensus = require('../protocol/consensus'); -const Mnemonic = HD.Mnemonic; +const {encoding} = bio; +const {Mnemonic} = HD; +const {inspectSymbol} = require('../utils'); /** - * BIP44 Wallet + * Wallet * @alias module:wallet.Wallet - * @constructor - * @param {Object} options - * @param {WalletDB} options.db - * present, no coins will be available. - * @param {(HDPrivateKey|HDPublicKey)?} options.master - Master HD key. If not - * present, it will be generated. - * @param {Boolean?} options.witness - Whether to use witness programs. - * @param {Number?} options.accountIndex - The BIP44 account index (default=0). - * @param {Number?} options.receiveDepth - The index of the _next_ receiving - * address. - * @param {Number?} options.changeDepth - The index of the _next_ change - * address. - * @param {String?} options.type - Type of wallet (pubkeyhash, multisig) - * (default=pubkeyhash). - * @param {Boolean?} options.compressed - Whether to use compressed - * public keys (default=true). - * @param {Number?} options.m - `m` value for multisig. - * @param {Number?} options.n - `n` value for multisig. - * @param {String?} options.id - Wallet ID (used for storage) - * @param {String?} options.mnemonic - mnemonic phrase to use to instantiate an - * hd private key for wallet - * (default=account key "address"). + * @extends EventEmitter */ -function Wallet(db, options) { - if (!(this instanceof Wallet)) - return new Wallet(db, options); - - EventEmitter.call(this); - - assert(db, 'DB required.'); - - this.db = db; - this.network = db.network; - this.logger = db.logger; - this.readLock = new MappedLock(); - this.writeLock = new Lock(); - this.fundLock = new Lock(); - this.indexCache = new LRU(10000); - this.accountCache = new LRU(10000); - this.pathCache = new LRU(100000); - this.current = null; - - this.wid = 0; - this.id = null; - this.initialized = false; - this.watchOnly = false; - this.accountDepth = 0; - this.token = encoding.ZERO_HASH; - this.tokenDepth = 0; - this.master = new MasterKey(); - - this.txdb = new TXDB(this); - this.account = null; - - if (options) - this.fromOptions(options); -} - -Object.setPrototypeOf(Wallet.prototype, EventEmitter.prototype); - -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -Wallet.prototype.fromOptions = function fromOptions(options) { - let key = options.master; - let id, token, mnemonic; +class Wallet extends EventEmitter { + /** + * Create a wallet. + * @constructor + * @param {Object} options + */ - if (key) { - if (typeof key === 'string') - key = HD.PrivateKey.fromBase58(key, this.network); + constructor(wdb, options) { + super(); - assert(HD.isPrivate(key), - 'Must create wallet with hd private key.'); - } else { - mnemonic = new Mnemonic(options.mnemonic); - key = HD.fromMnemonic(mnemonic, this.network); - } + assert(wdb, 'WDB required.'); - assert(key.network === this.network, - 'Network mismatch for master key.'); + this.wdb = wdb; + this.db = wdb.db; + this.network = wdb.network; + this.logger = wdb.logger; + this.writeLock = new Lock(); + this.fundLock = new Lock(); - this.master.fromKey(key, mnemonic); + this.wid = 0; + this.id = null; + this.watchOnly = false; + this.accountDepth = 0; + this.token = consensus.ZERO_HASH; + this.tokenDepth = 0; + this.master = new MasterKey(); - if (options.wid != null) { - assert(util.isU32(options.wid)); - this.wid = options.wid; - } + this.txdb = new TXDB(this.wdb); - if (options.id) { - assert(common.isName(options.id), 'Bad wallet ID.'); - id = options.id; + if (options) + this.fromOptions(options); } - if (options.initialized != null) { - assert(typeof options.initialized === 'boolean'); - this.initialized = options.initialized; - } + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ - if (options.watchOnly != null) { - assert(typeof options.watchOnly === 'boolean'); - this.watchOnly = options.watchOnly; - } + fromOptions(options) { + if (!options) + return this; - if (options.accountDepth != null) { - assert(util.isU32(options.accountDepth)); - this.accountDepth = options.accountDepth; - } + let key = options.master; + let id, token, mnemonic; - if (options.token) { - assert(Buffer.isBuffer(options.token)); - assert(options.token.length === 32); - token = options.token; - } + if (key) { + if (typeof key === 'string') + key = HD.PrivateKey.fromBase58(key, this.network); - if (options.tokenDepth != null) { - assert(util.isU32(options.tokenDepth)); - this.tokenDepth = options.tokenDepth; - } + assert(HD.isPrivate(key), + 'Must create wallet with hd private key.'); + } else { + mnemonic = new Mnemonic(options.mnemonic); + key = HD.fromMnemonic(mnemonic, options.password); + } - if (!id) - id = this.getID(); + this.master.fromKey(key, mnemonic); - if (!token) - token = this.getToken(this.tokenDepth); + if (options.wid != null) { + assert((options.wid >>> 0) === options.wid); + this.wid = options.wid; + } - this.id = id; - this.token = token; + if (options.id) { + assert(common.isName(options.id), 'Bad wallet ID.'); + id = options.id; + } - return this; -}; + if (options.watchOnly != null) { + assert(typeof options.watchOnly === 'boolean'); + this.watchOnly = options.watchOnly; + } -/** - * Instantiate wallet from options. - * @param {WalletDB} db - * @param {Object} options - * @returns {Wallet} - */ + if (options.accountDepth != null) { + assert((options.accountDepth >>> 0) === options.accountDepth); + this.accountDepth = options.accountDepth; + } -Wallet.fromOptions = function fromOptions(db, options) { - return new Wallet(db).fromOptions(options); -}; + if (options.token) { + assert(Buffer.isBuffer(options.token)); + assert(options.token.length === 32); + token = options.token; + } -/** - * Attempt to intialize the wallet (generating - * the first addresses along with the lookahead - * addresses). Called automatically from the - * walletdb. - * @returns {Promise} - */ + if (options.tokenDepth != null) { + assert((options.tokenDepth >>> 0) === options.tokenDepth); + this.tokenDepth = options.tokenDepth; + } -Wallet.prototype.init = async function init(options) { - const passphrase = options.passphrase; + if (!id) + id = this.getID(); - assert(!this.initialized); - this.initialized = true; + if (!token) + token = this.getToken(this.tokenDepth); - if (passphrase) - await this.master.encrypt(passphrase); + this.id = id; + this.token = token; - const account = await this._createAccount(options, passphrase); - assert(account); + return this; + } - this.account = account; + /** + * Instantiate wallet from options. + * @param {WalletDB} wdb + * @param {Object} options + * @returns {Wallet} + */ - this.logger.info('Wallet initialized (%s).', this.id); + static fromOptions(wdb, options) { + return new this(wdb).fromOptions(options); + } - await this.txdb.open(); -}; + /** + * Attempt to intialize the wallet (generating + * the first addresses along with the lookahead + * addresses). Called automatically from the + * walletdb. + * @returns {Promise} + */ -/** - * Open wallet (done after retrieval). - * @returns {Promise} - */ + async init(options, passphrase) { + if (passphrase) + await this.master.encrypt(passphrase); -Wallet.prototype.open = async function open() { - assert(this.initialized); + const account = await this._createAccount(options, passphrase); + assert(account); - const account = await this.getAccount(0); + this.logger.info('Wallet initialized (%s).', this.id); - if (!account) - throw new Error('Default account not found.'); + return this.txdb.open(this); + } - this.account = account; + /** + * Open wallet (done after retrieval). + * @returns {Promise} + */ - this.logger.info('Wallet opened (%s).', this.id); + async open() { + const account = await this.getAccount(0); - await this.txdb.open(); -}; + if (!account) + throw new Error('Default account not found.'); -/** - * Close the wallet, unregister with the database. - * @returns {Promise} - */ + this.logger.info('Wallet opened (%s).', this.id); -Wallet.prototype.destroy = async function destroy() { - const unlock1 = await this.writeLock.lock(); - const unlock2 = await this.fundLock.lock(); - try { - this.db.unregister(this); - await this.master.destroy(); - this.readLock.destroy(); - this.writeLock.destroy(); - this.fundLock.destroy(); - } finally { - unlock2(); - unlock1(); - } -}; + return this.txdb.open(this); + } -/** - * Add a public account key to the wallet (multisig). - * Saves the key in the wallet database. - * @param {(Number|String)} acct - * @param {HDPublicKey} key - * @returns {Promise} - */ + /** + * Close the wallet, unregister with the database. + * @returns {Promise} + */ -Wallet.prototype.addSharedKey = async function addSharedKey(acct, key) { - const unlock = await this.writeLock.lock(); - try { - return await this._addSharedKey(acct, key); - } finally { - unlock(); + async destroy() { + const unlock1 = await this.writeLock.lock(); + const unlock2 = await this.fundLock.lock(); + try { + await this.master.destroy(); + this.writeLock.destroy(); + this.fundLock.destroy(); + } finally { + unlock2(); + unlock1(); + } } -}; - -/** - * Add a public account key to the wallet without a lock. - * @private - * @param {(Number|String)} acct - * @param {HDPublicKey} key - * @returns {Promise} - */ -Wallet.prototype._addSharedKey = async function _addSharedKey(acct, key) { - if (!key) { - key = acct; - acct = null; + /** + * Add a public account key to the wallet (multisig). + * Saves the key in the wallet database. + * @param {(Number|String)} acct + * @param {HDPublicKey} key + * @returns {Promise} + */ + + async addSharedKey(acct, key) { + const unlock = await this.writeLock.lock(); + try { + return await this._addSharedKey(acct, key); + } finally { + unlock(); + } } - if (acct == null) - acct = 0; + /** + * Add a public account key to the wallet without a lock. + * @private + * @param {(Number|String)} acct + * @param {HDPublicKey} key + * @returns {Promise} + */ - const account = await this.getAccount(acct); + async _addSharedKey(acct, key) { + const account = await this.getAccount(acct); - if (!account) - throw new Error('Account not found.'); + if (!account) + throw new Error('Account not found.'); - this.start(); + const b = this.db.batch(); + const result = await account.addSharedKey(b, key); + await b.write(); - let result; - try { - result = await account.addSharedKey(key); - } catch (e) { - this.drop(); - throw e; + return result; } - await this.commit(); + /** + * Remove a public account key from the wallet (multisig). + * @param {(Number|String)} acct + * @param {HDPublicKey} key + * @returns {Promise} + */ - return result; -}; + async removeSharedKey(acct, key) { + const unlock = await this.writeLock.lock(); + try { + return await this._removeSharedKey(acct, key); + } finally { + unlock(); + } + } -/** - * Remove a public account key from the wallet (multisig). - * @param {(Number|String)} acct - * @param {HDPublicKey} key - * @returns {Promise} - */ + /** + * Remove a public account key from the wallet (multisig). + * @private + * @param {(Number|String)} acct + * @param {HDPublicKey} key + * @returns {Promise} + */ -Wallet.prototype.removeSharedKey = async function removeSharedKey(acct, key) { - const unlock = await this.writeLock.lock(); - try { - return await this._removeSharedKey(acct, key); - } finally { - unlock(); - } -}; + async _removeSharedKey(acct, key) { + const account = await this.getAccount(acct); -/** - * Remove a public account key from the wallet (multisig). - * @private - * @param {(Number|String)} acct - * @param {HDPublicKey} key - * @returns {Promise} - */ + if (!account) + throw new Error('Account not found.'); -Wallet.prototype._removeSharedKey = async function _removeSharedKey(acct, key) { - if (!key) { - key = acct; - acct = null; + const b = this.db.batch(); + const result = await account.removeSharedKey(b, key); + await b.write(); + + return result; } - if (acct == null) - acct = 0; + /** + * Change or set master key's passphrase. + * @param {String|Buffer} passphrase + * @param {String|Buffer} old + * @returns {Promise} + */ - const account = await this.getAccount(acct); + async setPassphrase(passphrase, old) { + if (old != null) + await this.decrypt(old); - if (!account) - throw new Error('Account not found.'); + await this.encrypt(passphrase); + } - this.start(); + /** + * Encrypt the wallet permanently. + * @param {String|Buffer} passphrase + * @returns {Promise} + */ - let result; - try { - result = await account.removeSharedKey(key); - } catch (e) { - this.drop(); - throw e; + async encrypt(passphrase) { + const unlock = await this.writeLock.lock(); + try { + return await this._encrypt(passphrase); + } finally { + unlock(); + } } - await this.commit(); + /** + * Encrypt the wallet permanently, without a lock. + * @private + * @param {String|Buffer} passphrase + * @returns {Promise} + */ - return result; -}; + async _encrypt(passphrase) { + const key = await this.master.encrypt(passphrase, true); + const b = this.db.batch(); -/** - * Change or set master key's passphrase. - * @param {(String|Buffer)?} old - * @param {String|Buffer} new_ - * @returns {Promise} - */ + try { + await this.wdb.encryptKeys(b, this.wid, key); + } finally { + cleanse(key); + } -Wallet.prototype.setPassphrase = async function setPassphrase(old, new_) { - if (old) - await this.decrypt(old); + this.save(b); - if (new_) - await this.encrypt(new_); -}; + await b.write(); + } -/** - * Encrypt the wallet permanently. - * @param {String|Buffer} passphrase - * @returns {Promise} - */ + /** + * Decrypt the wallet permanently. + * @param {String|Buffer} passphrase + * @returns {Promise} + */ -Wallet.prototype.encrypt = async function encrypt(passphrase) { - const unlock = await this.writeLock.lock(); - try { - return await this._encrypt(passphrase); - } finally { - unlock(); + async decrypt(passphrase) { + const unlock = await this.writeLock.lock(); + try { + return await this._decrypt(passphrase); + } finally { + unlock(); + } } -}; -/** - * Encrypt the wallet permanently, without a lock. - * @private - * @param {String|Buffer} passphrase - * @returns {Promise} - */ + /** + * Decrypt the wallet permanently, without a lock. + * @private + * @param {String|Buffer} passphrase + * @returns {Promise} + */ -Wallet.prototype._encrypt = async function _encrypt(passphrase) { - const key = await this.master.encrypt(passphrase, true); + async _decrypt(passphrase) { + const key = await this.master.decrypt(passphrase, true); + const b = this.db.batch(); - this.start(); + try { + await this.wdb.decryptKeys(b, this.wid, key); + } finally { + cleanse(key); + } - try { - await this.db.encryptKeys(this, key); - } catch (e) { - cleanse(key); - this.drop(); - throw e; - } + this.save(b); - cleanse(key); + await b.write(); + } - this.save(); + /** + * Generate a new token. + * @param {(String|Buffer)?} passphrase + * @returns {Promise} + */ - await this.commit(); -}; + async retoken(passphrase) { + const unlock = await this.writeLock.lock(); + try { + return await this._retoken(passphrase); + } finally { + unlock(); + } + } -/** - * Decrypt the wallet permanently. - * @param {String|Buffer} passphrase - * @returns {Promise} - */ + /** + * Generate a new token without a lock. + * @private + * @param {(String|Buffer)?} passphrase + * @returns {Promise} + */ -Wallet.prototype.decrypt = async function decrypt(passphrase) { - const unlock = await this.writeLock.lock(); - try { - return await this._decrypt(passphrase); - } finally { - unlock(); - } -}; + async _retoken(passphrase) { + if (passphrase) + await this.unlock(passphrase); -/** - * Decrypt the wallet permanently, without a lock. - * @private - * @param {String|Buffer} passphrase - * @returns {Promise} - */ + this.tokenDepth += 1; + this.token = this.getToken(this.tokenDepth); -Wallet.prototype._decrypt = async function _decrypt(passphrase) { - const key = await this.master.decrypt(passphrase, true); + const b = this.db.batch(); + this.save(b); - this.start(); + await b.write(); - try { - await this.db.decryptKeys(this, key); - } catch (e) { - cleanse(key); - this.drop(); - throw e; + return this.token; } - cleanse(key); + /** + * Rename the wallet. + * @param {String} id + * @returns {Promise} + */ - this.save(); - - await this.commit(); -}; + async rename(id) { + const unlock = await this.writeLock.lock(); + try { + return await this.wdb.rename(this, id); + } finally { + unlock(); + } + } -/** - * Generate a new token. - * @param {(String|Buffer)?} passphrase - * @returns {Promise} - */ + /** + * Rename account. + * @param {(String|Number)?} acct + * @param {String} name + * @returns {Promise} + */ -Wallet.prototype.retoken = async function retoken(passphrase) { - const unlock = await this.writeLock.lock(); - try { - return await this._retoken(passphrase); - } finally { - unlock(); + async renameAccount(acct, name) { + const unlock = await this.writeLock.lock(); + try { + return await this._renameAccount(acct, name); + } finally { + unlock(); + } } -}; -/** - * Generate a new token without a lock. - * @private - * @param {(String|Buffer)?} passphrase - * @returns {Promise} - */ + /** + * Rename account without a lock. + * @private + * @param {(String|Number)?} acct + * @param {String} name + * @returns {Promise} + */ -Wallet.prototype._retoken = async function _retoken(passphrase) { - await this.unlock(passphrase); + async _renameAccount(acct, name) { + if (!common.isName(name)) + throw new Error('Bad account name.'); - this.tokenDepth++; - this.token = this.getToken(this.tokenDepth); + const account = await this.getAccount(acct); - this.start(); - this.save(); + if (!account) + throw new Error('Account not found.'); - await this.commit(); + if (account.accountIndex === 0) + throw new Error('Cannot rename default account.'); - return this.token; -}; + if (await this.hasAccount(name)) + throw new Error('Account name not available.'); -/** - * Rename the wallet. - * @param {String} id - * @returns {Promise} - */ + const b = this.db.batch(); + + this.wdb.renameAccount(b, account, name); -Wallet.prototype.rename = async function rename(id) { - const unlock = await this.writeLock.lock(); - try { - return await this.db.rename(this, id); - } finally { - unlock(); + await b.write(); } -}; -/** - * Rename account. - * @param {(String|Number)?} acct - * @param {String} name - * @returns {Promise} - */ + /** + * Lock the wallet, destroy decrypted key. + */ -Wallet.prototype.renameAccount = async function renameAccount(acct, name) { - const unlock = await this.writeLock.lock(); - try { - return await this._renameAccount(acct, name); - } finally { - unlock(); + async lock() { + const unlock1 = await this.writeLock.lock(); + const unlock2 = await this.fundLock.lock(); + try { + await this.master.lock(); + } finally { + unlock2(); + unlock1(); + } } -}; -/** - * Rename account without a lock. - * @private - * @param {(String|Number)?} acct - * @param {String} name - * @returns {Promise} - */ + /** + * Unlock the key for `timeout` seconds. + * @param {Buffer|String} passphrase + * @param {Number?} [timeout=60] + */ -Wallet.prototype._renameAccount = async function _renameAccount(acct, name) { - if (!common.isName(name)) - throw new Error('Bad account name.'); + unlock(passphrase, timeout) { + return this.master.unlock(passphrase, timeout); + } - const account = await this.getAccount(acct); + /** + * Generate the wallet ID if none was passed in. + * It is represented as HASH160(m/44->public|magic) + * converted to an "address" with a prefix + * of `0x03be04` (`WLT` in base58). + * @private + * @returns {Base58String} + */ - if (!account) - throw new Error('Account not found.'); + getID() { + assert(this.master.key, 'Cannot derive id.'); - if (account.accountIndex === 0) - throw new Error('Cannot rename default account.'); + const key = this.master.key.derive(44); - if (await this.hasAccount(name)) - throw new Error('Account name not available.'); + const bw = bio.write(37); + bw.writeBytes(key.publicKey); + bw.writeU32(this.network.magic); - const old = account.name; + const hash = hash160.digest(bw.render()); - this.start(); + const b58 = bio.write(27); + b58.writeU8(0x03); + b58.writeU8(0xbe); + b58.writeU8(0x04); + b58.writeBytes(hash); + b58.writeChecksum(hash256.digest); - this.db.renameAccount(account, name); + return base58.encode(b58.render()); + } - await this.commit(); + /** + * Generate the wallet api key if none was passed in. + * It is represented as HASH256(m/44'->private|nonce). + * @private + * @param {HDPrivateKey} master + * @param {Number} nonce + * @returns {Buffer} + */ - this.indexCache.remove(old); + getToken(nonce) { + if (!this.master.key) + throw new Error('Cannot derive token.'); - const paths = this.pathCache.values(); + const key = this.master.key.derive(44, true); - for (const path of paths) { - if (path.account !== account.accountIndex) - continue; + const bw = bio.write(36); + bw.writeBytes(key.privateKey); + bw.writeU32(nonce); - path.name = name; + return hash256.digest(bw.render()); } -}; -/** - * Lock the wallet, destroy decrypted key. - */ + /** + * Create an account. Requires passphrase if master key is encrypted. + * @param {Object} options - See {@link Account} options. + * @returns {Promise} - Returns {@link Account}. + */ -Wallet.prototype.lock = async function lock() { - const unlock1 = await this.writeLock.lock(); - const unlock2 = await this.fundLock.lock(); - try { - await this.master.lock(); - } finally { - unlock2(); - unlock1(); + async createAccount(options, passphrase) { + const unlock = await this.writeLock.lock(); + try { + return await this._createAccount(options, passphrase); + } finally { + unlock(); + } } -}; -/** - * Unlock the key for `timeout` seconds. - * @param {Buffer|String} passphrase - * @param {Number?} [timeout=60] - */ + /** + * Create an account without a lock. + * @param {Object} options - See {@link Account} options. + * @returns {Promise} - Returns {@link Account}. + */ -Wallet.prototype.unlock = function unlock(passphrase, timeout) { - return this.master.unlock(passphrase, timeout); -}; + async _createAccount(options, passphrase) { + let name = options.name; -/** - * Generate the wallet ID if none was passed in. - * It is represented as HASH160(m/44->public|magic) - * converted to an "address" with a prefix - * of `0x03be04` (`WLT` in base58). - * @private - * @returns {Base58String} - */ + if (!name) + name = this.accountDepth.toString(10); -Wallet.prototype.getID = function getID() { - assert(this.master.key, 'Cannot derive id.'); + if (await this.hasAccount(name)) + throw new Error('Account already exists.'); - const key = this.master.key.derive(44); + await this.unlock(passphrase); - const bw = new StaticWriter(37); - bw.writeBytes(key.publicKey); - bw.writeU32(this.network.magic); + let key; + if (this.watchOnly) { + key = options.accountKey; - const hash = digest.hash160(bw.render()); + if (typeof key === 'string') + key = HD.PublicKey.fromBase58(key, this.network); - const b58 = new StaticWriter(27); - b58.writeU8(0x03); - b58.writeU8(0xbe); - b58.writeU8(0x04); - b58.writeBytes(hash); - b58.writeChecksum(); + if (!HD.isPublic(key)) + throw new Error('Must add HD public keys to watch only wallet.'); + } else { + assert(this.master.key); + const type = this.network.keyPrefix.coinType; + key = this.master.key.deriveAccount(44, type, this.accountDepth); + key = key.toPublic(); + } - return base58.encode(b58.render()); -}; + const opt = { + wid: this.wid, + id: this.id, + name: this.accountDepth === 0 ? 'default' : name, + witness: options.witness, + watchOnly: this.watchOnly, + accountKey: key, + accountIndex: this.accountDepth, + type: options.type, + m: options.m, + n: options.n, + keys: options.keys + }; -/** - * Generate the wallet api key if none was passed in. - * It is represented as HASH256(m/44'->private|nonce). - * @private - * @param {HDPrivateKey} master - * @param {Number} nonce - * @returns {Buffer} - */ + const b = this.db.batch(); -Wallet.prototype.getToken = function getToken(nonce) { - assert(this.master.key, 'Cannot derive token.'); + const account = Account.fromOptions(this.wdb, opt); - const key = this.master.key.derive(44, true); + await account.init(b); - const bw = new StaticWriter(36); - bw.writeBytes(key.privateKey); - bw.writeU32(nonce); + this.logger.info('Created account %s/%s/%d.', + account.id, + account.name, + account.accountIndex); - return digest.hash256(bw.render()); -}; + this.accountDepth += 1; + this.save(b); -/** - * Create an account. Requires passphrase if master key is encrypted. - * @param {Object} options - See {@link Account} options. - * @returns {Promise} - Returns {@link Account}. - */ + if (this.accountDepth === 1) + this.increment(b); -Wallet.prototype.createAccount = async function createAccount(options, passphrase) { - const unlock = await this.writeLock.lock(); - try { - return await this._createAccount(options, passphrase); - } finally { - unlock(); - } -}; + await b.write(); -/** - * Create an account without a lock. - * @param {Object} options - See {@link Account} options. - * @returns {Promise} - Returns {@link Account}. - */ + return account; + } -Wallet.prototype._createAccount = async function _createAccount(options, passphrase) { - let name = options.name; + /** + * Ensure an account. Requires passphrase if master key is encrypted. + * @param {Object} options - See {@link Account} options. + * @returns {Promise} - Returns {@link Account}. + */ - if (!name) - name = this.accountDepth.toString(10); + async ensureAccount(options, passphrase) { + const name = options.name; + const account = await this.getAccount(name); - if (await this.hasAccount(name)) - throw new Error('Account already exists.'); + if (account) + return account; - await this.unlock(passphrase); + return this.createAccount(options, passphrase); + } - let key; - if (this.watchOnly && options.accountKey) { - key = options.accountKey; + /** + * List account names and indexes from the db. + * @returns {Promise} - Returns Array. + */ - if (typeof key === 'string') - key = HD.PublicKey.fromBase58(key, this.network); + getAccounts() { + return this.wdb.getAccounts(this.wid); + } - if (!HD.isPublic(key)) - throw new Error('Must add HD public keys to watch only wallet.'); + /** + * Get all wallet address hashes. + * @param {(String|Number)?} acct + * @returns {Promise} - Returns Array. + */ - assert(key.network === this.network, - 'Network mismatch for watch only key.'); - } else { - assert(this.master.key); - key = this.master.key.deriveAccount(44, this.accountDepth); - key = key.toPublic(); + getAddressHashes(acct) { + if (acct != null) + return this.getAccountHashes(acct); + return this.wdb.getWalletHashes(this.wid); } - const opt = { - wid: this.wid, - id: this.id, - name: this.accountDepth === 0 ? 'default' : name, - witness: options.witness, - watchOnly: this.watchOnly, - accountKey: key, - accountIndex: this.accountDepth, - type: options.type, - m: options.m, - n: options.n, - keys: options.keys - }; + /** + * Get all account address hashes. + * @param {String|Number} acct + * @returns {Promise} - Returns Array. + */ - this.start(); + async getAccountHashes(acct) { + const index = await this.getAccountIndex(acct); - let account; - try { - account = Account.fromOptions(this.db, opt); - account.wallet = this; - await account.init(); - } catch (e) { - this.drop(); - throw e; + if (index === -1) + throw new Error('Account not found.'); + + return this.wdb.getAccountHashes(this.wid, index); } - this.logger.info('Created account %s/%s/%d.', - account.id, - account.name, - account.accountIndex); + /** + * Retrieve an account from the database. + * @param {Number|String} acct + * @returns {Promise} - Returns {@link Account}. + */ - this.accountDepth++; - this.save(); + async getAccount(acct) { + const index = await this.getAccountIndex(acct); - await this.commit(); + if (index === -1) + return null; - return account; -}; + const account = await this.wdb.getAccount(this.wid, index); -/** - * Ensure an account. Requires passphrase if master key is encrypted. - * @param {Object} options - See {@link Account} options. - * @returns {Promise} - Returns {@link Account}. - */ + if (!account) + return null; -Wallet.prototype.ensureAccount = async function ensureAccount(options, passphrase) { - const name = options.name; - const account = await this.getAccount(name); + account.wid = this.wid; + account.id = this.id; + account.watchOnly = this.watchOnly; - if (account) return account; + } - return await this.createAccount(options, passphrase); -}; + /** + * Lookup the corresponding account name's index. + * @param {String|Number} acct - Account name/index. + * @returns {Promise} - Returns Number. + */ -/** - * List account names and indexes from the db. - * @returns {Promise} - Returns Array. - */ + getAccountIndex(acct) { + if (acct == null) + return -1; -Wallet.prototype.getAccounts = function getAccounts() { - return this.db.getAccounts(this.wid); -}; + if (typeof acct === 'number') + return acct; -/** - * Get all wallet address hashes. - * @param {(String|Number)?} acct - * @returns {Promise} - Returns Array. - */ + return this.wdb.getAccountIndex(this.wid, acct); + } -Wallet.prototype.getAddressHashes = function getAddressHashes(acct) { - if (acct != null) - return this.getAccountHashes(acct); - return this.db.getWalletHashes(this.wid); -}; + /** + * Lookup the corresponding account name's index. + * @param {String|Number} acct - Account name/index. + * @returns {Promise} - Returns Number. + * @throws on non-existent account + */ -/** - * Get all account address hashes. - * @param {String|Number} acct - * @returns {Promise} - Returns Array. - */ + async ensureIndex(acct) { + if (acct == null || acct === -1) + return -1; -Wallet.prototype.getAccountHashes = async function getAccountHashes(acct) { - const index = await this.ensureIndex(acct, true); - return await this.db.getAccountHashes(this.wid, index); -}; + const index = await this.getAccountIndex(acct); -/** - * Retrieve an account from the database. - * @param {Number|String} acct - * @returns {Promise} - Returns {@link Account}. - */ + if (index === -1) + throw new Error('Account not found.'); -Wallet.prototype.getAccount = async function getAccount(acct) { - if (this.account) { - if (acct === 0 || acct === 'default') - return this.account; + return index; } - const index = await this.getAccountIndex(acct); - - if (index === -1) - return null; + /** + * Lookup the corresponding account index's name. + * @param {Number} index - Account index. + * @returns {Promise} - Returns String. + */ - const unlock = await this.readLock.lock(index); + async getAccountName(index) { + if (typeof index === 'string') + return index; - try { - return await this._getAccount(index); - } finally { - unlock(); + return this.wdb.getAccountName(this.wid, index); } -}; -/** - * Retrieve an account from the database without a lock. - * @param {Number} index - * @returns {Promise} - Returns {@link Account}. - */ + /** + * Test whether an account exists. + * @param {Number|String} acct + * @returns {Promise} - Returns {@link Boolean}. + */ -Wallet.prototype._getAccount = async function _getAccount(index) { - const cache = this.accountCache.get(index); + async hasAccount(acct) { + const index = await this.getAccountIndex(acct); - if (cache) - return cache; + if (index === -1) + return false; - const account = await this.db.getAccount(this.wid, index); + return this.wdb.hasAccount(this.wid, index); + } - if (!account) - return null; + /** + * Create a new receiving address (increments receiveDepth). + * @param {(Number|String)?} acct + * @returns {Promise} - Returns {@link WalletKey}. + */ - account.wallet = this; - account.wid = this.wid; - account.id = this.id; - account.watchOnly = this.watchOnly; + createReceive(acct = 0) { + return this.createKey(acct, 0); + } - await account.open(); + /** + * Create a new change address (increments receiveDepth). + * @param {(Number|String)?} acct + * @returns {Promise} - Returns {@link WalletKey}. + */ - this.accountCache.set(index, account); + createChange(acct = 0) { + return this.createKey(acct, 1); + } - return account; -}; + /** + * Create a new nested address (increments receiveDepth). + * @param {(Number|String)?} acct + * @returns {Promise} - Returns {@link WalletKey}. + */ -/** - * Lookup the corresponding account name's index. - * @param {WalletID} wid - * @param {String|Number} name - Account name/index. - * @returns {Promise} - Returns Number. - */ + createNested(acct = 0) { + return this.createKey(acct, 2); + } -Wallet.prototype.getAccountIndex = async function getAccountIndex(name) { - if (name == null) - return -1; + /** + * Create a new address (increments depth). + * @param {(Number|String)?} acct + * @param {Number} branch + * @returns {Promise} - Returns {@link WalletKey}. + */ - if (typeof name === 'number') - return name; + async createKey(acct, branch) { + const unlock = await this.writeLock.lock(); + try { + return await this._createKey(acct, branch); + } finally { + unlock(); + } + } - const cache = this.indexCache.get(name); + /** + * Create a new address (increments depth) without a lock. + * @private + * @param {(Number|String)?} acct + * @param {Number} branch + * @returns {Promise} - Returns {@link WalletKey}. + */ - if (cache != null) - return cache; + async _createKey(acct, branch) { + const account = await this.getAccount(acct); - const index = await this.db.getAccountIndex(this.wid, name); + if (!account) + throw new Error('Account not found.'); - if (index === -1) - return -1; + const b = this.db.batch(); + const key = await account.createKey(b, branch); + await b.write(); - this.indexCache.set(name, index); + return key; + } - return index; -}; + /** + * Save the wallet to the database. Necessary + * when address depth and keys change. + * @returns {Promise} + */ -/** - * Lookup the corresponding account index's name. - * @param {WalletID} wid - * @param {Number} index - Account index. - * @returns {Promise} - Returns String. - */ + save(b) { + return this.wdb.save(b, this); + } -Wallet.prototype.getAccountName = async function getAccountName(index) { - if (typeof index === 'string') - return index; + /** + * Increment the wid depth. + * @returns {Promise} + */ - const account = this.accountCache.get(index); + increment(b) { + return this.wdb.increment(b, this.wid); + } - if (account) - return account.name; + /** + * Test whether the wallet possesses an address. + * @param {Address|Hash} address + * @returns {Promise} - Returns Boolean. + */ - return await this.db.getAccountName(this.wid, index); -}; + async hasAddress(address) { + const hash = Address.getHash(address); + const path = await this.getPath(hash); + return path != null; + } -/** - * Test whether an account exists. - * @param {Number|String} acct - * @returns {Promise} - Returns {@link Boolean}. - */ + /** + * Get path by address hash. + * @param {Address|Hash} address + * @returns {Promise} - Returns {@link Path}. + */ -Wallet.prototype.hasAccount = async function hasAccount(acct) { - const index = await this.getAccountIndex(acct); + async getPath(address) { + const hash = Address.getHash(address); + return this.wdb.getPath(this.wid, hash); + } - if (index === -1) - return false; + /** + * Get path by address hash (without account name). + * @private + * @param {Address|Hash} address + * @returns {Promise} - Returns {@link Path}. + */ - if (this.accountCache.has(index)) - return true; + async readPath(address) { + const hash = Address.getHash(address); + return this.wdb.readPath(this.wid, hash); + } - return await this.db.hasAccount(this.wid, index); -}; + /** + * Test whether the wallet contains a path. + * @param {Address|Hash} address + * @returns {Promise} - Returns {Boolean}. + */ -/** - * Create a new receiving address (increments receiveDepth). - * @param {(Number|String)?} acct - * @returns {Promise} - Returns {@link WalletKey}. - */ + async hasPath(address) { + const hash = Address.getHash(address); + return this.wdb.hasPath(this.wid, hash); + } -Wallet.prototype.createReceive = function createReceive(acct) { - return this.createKey(acct, 0); -}; + /** + * Get all wallet paths. + * @param {(String|Number)?} acct + * @returns {Promise} - Returns {@link Path}. + */ -/** - * Create a new change address (increments receiveDepth). - * @param {(Number|String)?} acct - * @returns {Promise} - Returns {@link WalletKey}. - */ + async getPaths(acct) { + if (acct != null) + return this.getAccountPaths(acct); -Wallet.prototype.createChange = function createChange(acct) { - return this.createKey(acct, 1); -}; + return this.wdb.getWalletPaths(this.wid); + } -/** - * Create a new nested address (increments receiveDepth). - * @param {(Number|String)?} acct - * @returns {Promise} - Returns {@link WalletKey}. - */ + /** + * Get all account paths. + * @param {String|Number} acct + * @returns {Promise} - Returns {@link Path}. + */ -Wallet.prototype.createNested = function createNested(acct) { - return this.createKey(acct, 2); -}; + async getAccountPaths(acct) { + const index = await this.getAccountIndex(acct); -/** - * Create a new address (increments depth). - * @param {(Number|String)?} acct - * @param {Number} branch - * @returns {Promise} - Returns {@link WalletKey}. - */ + if (index === -1) + throw new Error('Account not found.'); -Wallet.prototype.createKey = async function createKey(acct, branch) { - const unlock = await this.writeLock.lock(); - try { - return await this._createKey(acct, branch); - } finally { - unlock(); - } -}; + const hashes = await this.getAccountHashes(index); + const name = await this.getAccountName(acct); -/** - * Create a new address (increments depth) without a lock. - * @private - * @param {(Number|String)?} acct - * @param {Number} branche - * @returns {Promise} - Returns {@link WalletKey}. - */ + assert(name); -Wallet.prototype._createKey = async function _createKey(acct, branch) { - if (branch == null) { - branch = acct; - acct = null; - } + const result = []; - if (acct == null) - acct = 0; + for (const hash of hashes) { + const path = await this.readPath(hash); - const account = await this.getAccount(acct); + assert(path); + assert(path.account === index); - if (!account) - throw new Error('Account not found.'); + path.name = name; - this.start(); + result.push(path); + } - let result; - try { - result = await account.createKey(branch); - } catch (e) { - this.drop(); - throw e; + return result; } - await this.commit(); - - return result; -}; + /** + * Import a keyring (will not exist on derivation chain). + * Rescanning must be invoked manually. + * @param {(String|Number)?} acct + * @param {WalletKey} ring + * @param {(String|Buffer)?} passphrase + * @returns {Promise} + */ -/** - * Save the wallet to the database. Necessary - * when address depth and keys change. - * @returns {Promise} - */ + async importKey(acct, ring, passphrase) { + const unlock = await this.writeLock.lock(); + try { + return await this._importKey(acct, ring, passphrase); + } finally { + unlock(); + } + } -Wallet.prototype.save = function save() { - return this.db.save(this); -}; + /** + * Import a keyring (will not exist on derivation chain) without a lock. + * @private + * @param {(String|Number)?} acct + * @param {WalletKey} ring + * @param {(String|Buffer)?} passphrase + * @returns {Promise} + */ + + async _importKey(acct, ring, passphrase) { + if (!this.watchOnly) { + if (!ring.privateKey) + throw new Error('Cannot import pubkey into non watch-only wallet.'); + } else { + if (ring.privateKey) + throw new Error('Cannot import privkey into watch-only wallet.'); + } -/** - * Start batch. - * @private - */ + const hash = ring.getHash(); -Wallet.prototype.start = function start() { - return this.db.start(this); -}; + if (await this.getPath(hash)) + throw new Error('Key already exists.'); -/** - * Drop batch. - * @private - */ + const account = await this.getAccount(acct); -Wallet.prototype.drop = function drop() { - return this.db.drop(this); -}; + if (!account) + throw new Error('Account not found.'); -/** - * Clear batch. - * @private - */ + if (account.type !== Account.types.PUBKEYHASH) + throw new Error('Cannot import into non-pkh account.'); -Wallet.prototype.clear = function clear() { - return this.db.clear(this); -}; + await this.unlock(passphrase); -/** - * Save batch. - * @returns {Promise} - */ + const key = WalletKey.fromRing(account, ring); + const path = key.toPath(); -Wallet.prototype.commit = function commit() { - return this.db.commit(this); -}; + if (this.master.encrypted) { + path.data = this.master.encipher(path.data, path.hash); + assert(path.data); + path.encrypted = true; + } -/** - * Test whether the wallet possesses an address. - * @param {Address|Hash} address - * @returns {Promise} - Returns Boolean. - */ + const b = this.db.batch(); + await account.savePath(b, path); + await b.write(); + } + + /** + * Import a keyring (will not exist on derivation chain). + * Rescanning must be invoked manually. + * @param {(String|Number)?} acct + * @param {WalletKey} ring + * @param {(String|Buffer)?} passphrase + * @returns {Promise} + */ + + async importAddress(acct, address) { + const unlock = await this.writeLock.lock(); + try { + return await this._importAddress(acct, address); + } finally { + unlock(); + } + } -Wallet.prototype.hasAddress = async function hasAddress(address) { - const hash = Address.getHash(address, 'hex'); - const path = await this.getPath(hash); - return path != null; -}; + /** + * Import a keyring (will not exist on derivation chain) without a lock. + * @private + * @param {(String|Number)?} acct + * @param {WalletKey} ring + * @param {(String|Buffer)?} passphrase + * @returns {Promise} + */ -/** - * Get path by address hash. - * @param {Address|Hash} address - * @returns {Promise} - Returns {@link Path}. - */ + async _importAddress(acct, address) { + if (!this.watchOnly) + throw new Error('Cannot import address into non watch-only wallet.'); -Wallet.prototype.getPath = async function getPath(address) { - const path = await this.readPath(address); + if (await this.getPath(address)) + throw new Error('Address already exists.'); - if (!path) - return null; + const account = await this.getAccount(acct); - path.name = await this.getAccountName(path.account); + if (!account) + throw new Error('Account not found.'); - assert(path.name); + if (account.type !== Account.types.PUBKEYHASH) + throw new Error('Cannot import into non-pkh account.'); + + const path = Path.fromAddress(account, address); + + const b = this.db.batch(); + await account.savePath(b, path); + await b.write(); + } + + /** + * Fill a transaction with inputs, estimate + * transaction size, calculate fee, and add a change output. + * @see MTX#selectCoins + * @see MTX#fill + * @param {MTX} mtx - _Must_ be a mutable transaction. + * @param {Object?} options + * @param {(String|Number)?} options.account - If no account is + * specified, coins from the entire wallet will be filled. + * @param {String?} options.selection - Coin selection priority. Can + * be `age`, `random`, or `all`. (default=age). + * @param {Boolean} options.round - Whether to round to the nearest + * kilobyte for fee calculation. + * See {@link TX#getMinFee} vs. {@link TX#getRoundFee}. + * @param {Rate} options.rate - Rate used for fee calculation. + * @param {Boolean} options.confirmed - Select only confirmed coins. + * @param {Boolean} options.free - Do not apply a fee if the + * transaction priority is high enough to be considered free. + * @param {Amount?} options.hardFee - Use a hard fee rather than + * calculating one. + * @param {Number|Boolean} options.subtractFee - Whether to subtract the + * fee from existing outputs rather than adding more inputs. + */ + + async fund(mtx, options, force) { + const unlock = await this.fundLock.lock(force); + try { + return await this._fund(mtx, options); + } finally { + unlock(); + } + } - this.pathCache.set(path.hash, path); + /** + * Fill a transaction with inputs without a lock. + * @private + * @see MTX#selectCoins + * @see MTX#fill + */ - return path; -}; + async _fund(mtx, options) { + if (!options) + options = {}; -/** - * Get path by address hash (without account name). - * @private - * @param {Address|Hash} address - * @returns {Promise} - Returns {@link Path}. - */ + const acct = options.account || 0; + const change = await this.changeAddress(acct); -Wallet.prototype.readPath = async function readPath(address) { - const hash = Address.getHash(address, 'hex'); - const cache = this.pathCache.get(hash); + if (!change) + throw new Error('Account not found.'); - if (cache) - return cache; + let rate = options.rate; + if (rate == null) + rate = await this.wdb.estimateFee(options.blocks); - const path = await this.db.getPath(this.wid, hash); + let coins; + if (options.smart) { + coins = await this.getSmartCoins(options.account); + } else { + coins = await this.getCoins(options.account); + coins = this.txdb.filterLocked(coins); + } - if (!path) - return null; + await mtx.fund(coins, { + selection: options.selection, + round: options.round, + depth: options.depth, + hardFee: options.hardFee, + subtractFee: options.subtractFee, + subtractIndex: options.subtractIndex, + changeAddress: change, + height: this.wdb.state.height, + rate: rate, + maxFee: options.maxFee, + estimate: prev => this.estimateSize(prev) + }); + + assert(mtx.getFee() <= MTX.Selector.MAX_FEE, 'TX exceeds MAX_FEE.'); + } + + /** + * Get account by address. + * @param {Address} address + * @returns {Account} + */ + + async getAccountByAddress(address) { + const hash = Address.getHash(address); + const path = await this.getPath(hash); - path.id = this.id; + if (!path) + return null; - return path; -}; + return this.getAccount(path.account); + } -/** - * Test whether the wallet contains a path. - * @param {Address|Hash} address - * @returns {Promise} - Returns {Boolean}. - */ + /** + * Input size estimator for max possible tx size. + * @param {Script} prev + * @returns {Number} + */ -Wallet.prototype.hasPath = async function hasPath(address) { - const hash = Address.getHash(address, 'hex'); + async estimateSize(prev) { + const scale = consensus.WITNESS_SCALE_FACTOR; + const address = prev.getAddress(); - if (this.pathCache.has(hash)) - return true; + if (!address) + return -1; - return await this.db.hasPath(this.wid, hash); -}; + const account = await this.getAccountByAddress(address); -/** - * Get all wallet paths. - * @param {(String|Number)?} acct - * @returns {Promise} - Returns {@link Path}. - */ + if (!account) + return -1; + + let size = 0; + + if (prev.isScripthash()) { + // Nested bullshit. + if (account.witness) { + switch (account.type) { + case Account.types.PUBKEYHASH: + size += 23; // redeem script + size *= 4; // vsize + break; + case Account.types.MULTISIG: + size += 35; // redeem script + size *= 4; // vsize + break; + } + } + } -Wallet.prototype.getPaths = async function getPaths(acct) { - if (acct != null) - return await this.getAccountPaths(acct); + switch (account.type) { + case Account.types.PUBKEYHASH: + // P2PKH + // OP_PUSHDATA0 [signature] + size += 1 + 73; + // OP_PUSHDATA0 [key] + size += 1 + 33; + break; + case Account.types.MULTISIG: + // P2SH Multisig + // OP_0 + size += 1; + // OP_PUSHDATA0 [signature] ... + size += (1 + 73) * account.m; + // OP_PUSHDATA2 [redeem] + size += 3; + // m value + size += 1; + // OP_PUSHDATA0 [key] ... + size += (1 + 33) * account.n; + // n value + size += 1; + // OP_CHECKMULTISIG + size += 1; + break; + } - const paths = await this.db.getWalletPaths(this.wid); - const result = []; + if (account.witness) { + // Varint witness items length. + size += 1; + // Calculate vsize if + // we're a witness program. + size = (size + scale - 1) / scale | 0; + } else { + // Byte for varint + // size of input script. + size += encoding.sizeVarint(size); + } - for (const path of paths) { - path.id = this.id; - path.name = await this.getAccountName(path.account); + return size; + } - assert(path.name); + /** + * Build a transaction, fill it with outputs and inputs, + * sort the members according to BIP69 (set options.sort=false + * to avoid sorting), set locktime, and template it. + * @param {Object} options - See {@link Wallet#fund options}. + * @param {Object[]} options.outputs - See {@link MTX#addOutput}. + * @param {Boolean} options.sort - Sort inputs and outputs (BIP69). + * @param {Boolean} options.template - Build scripts for inputs. + * @param {Number} options.locktime - TX locktime + * @returns {Promise} - Returns {@link MTX}. + */ - this.pathCache.set(path.hash, path); + async createTX(options, force) { + const outputs = options.outputs; + const mtx = new MTX(); - result.push(path); - } + assert(Array.isArray(outputs), 'Outputs must be an array.'); + assert(outputs.length > 0, 'At least one output required.'); - return result; -}; + // Add the outputs + for (const obj of outputs) { + const output = new Output(obj); + const addr = output.getAddress(); -/** - * Get all account paths. - * @param {String|Number} acct - * @returns {Promise} - Returns {@link Path}. - */ + if (output.isDust()) + throw new Error('Output is dust.'); -Wallet.prototype.getAccountPaths = async function getAccountPaths(acct) { - const index = await this.ensureIndex(acct, true); - const hashes = await this.getAccountHashes(index); - const name = await this.getAccountName(acct); + if (output.value > 0) { + if (!addr) + throw new Error('Cannot send to unknown address.'); - assert(name); + if (addr.isNull()) + throw new Error('Cannot send to null address.'); + } - const result = []; + mtx.outputs.push(output); + } - for (const hash of hashes) { - const path = await this.readPath(hash); + // Fill the inputs with unspents + await this.fund(mtx, options, force); - assert(path); - assert(path.account === index); + // Sort members a la BIP69 + if (options.sort !== false) + mtx.sortMembers(); - path.name = name; + // Set the locktime to target value. + if (options.locktime != null) + mtx.setLocktime(options.locktime); - this.pathCache.set(path.hash, path); + // Consensus sanity checks. + assert(mtx.isSane(), 'TX failed sanity check.'); + assert(mtx.verifyInputs(this.wdb.state.height + 1), + 'TX failed context check.'); - result.push(path); - } + if (options.template === false) + return mtx; - return result; -}; + const total = await this.template(mtx); -/** - * Import a keyring (will not exist on derivation chain). - * Rescanning must be invoked manually. - * @param {(String|Number)?} acct - * @param {WalletKey} ring - * @param {(String|Buffer)?} passphrase - * @returns {Promise} - */ + if (total === 0) + throw new Error('Templating failed.'); -Wallet.prototype.importKey = async function importKey(acct, ring, passphrase) { - const unlock = await this.writeLock.lock(); - try { - return await this._importKey(acct, ring, passphrase); - } finally { - unlock(); + return mtx; } -}; -/** - * Import a keyring (will not exist on derivation chain) without a lock. - * @private - * @param {(String|Number)?} acct - * @param {WalletKey} ring - * @param {(String|Buffer)?} passphrase - * @returns {Promise} - */ + /** + * Build a transaction, fill it with outputs and inputs, + * sort the members according to BIP69, set locktime, + * sign and broadcast. Doing this all in one go prevents + * coins from being double spent. + * @param {Object} options - See {@link Wallet#fund options}. + * @param {Object[]} options.outputs - See {@link MTX#addOutput}. + * @returns {Promise} - Returns {@link TX}. + */ -Wallet.prototype._importKey = async function _importKey(acct, ring, passphrase) { - if (acct && typeof acct === 'object') { - passphrase = ring; - ring = acct; - acct = null; + async send(options, passphrase) { + const unlock = await this.fundLock.lock(); + try { + return await this._send(options, passphrase); + } finally { + unlock(); + } } - if (acct == null) - acct = 0; + /** + * Build and send a transaction without a lock. + * @private + * @param {Object} options - See {@link Wallet#fund options}. + * @param {Object[]} options.outputs - See {@link MTX#addOutput}. + * @returns {Promise} - Returns {@link TX}. + */ - assert(ring.network === this.network, - 'Network mismatch for key.'); + async _send(options, passphrase) { + const mtx = await this.createTX(options, true); - if (!this.watchOnly) { - if (!ring.privateKey) - throw new Error('Cannot import pubkey into non watch-only wallet.'); - } else { - if (ring.privateKey) - throw new Error('Cannot import privkey into watch-only wallet.'); - } + await this.sign(mtx, passphrase); - const hash = ring.getHash('hex'); + if (!mtx.isSigned()) + throw new Error('TX could not be fully signed.'); - if (await this.getPath(hash)) - throw new Error('Key already exists.'); + const tx = mtx.toTX(); - const account = await this.getAccount(acct); + // Policy sanity checks. + if (tx.getSigopsCost(mtx.view) > policy.MAX_TX_SIGOPS_COST) + throw new Error('TX exceeds policy sigops.'); - if (!account) - throw new Error('Account not found.'); + if (tx.getWeight() > policy.MAX_TX_WEIGHT) + throw new Error('TX exceeds policy weight.'); - if (account.type !== Account.types.PUBKEYHASH) - throw new Error('Cannot import into non-pkh account.'); + await this.wdb.addTX(tx); - await this.unlock(passphrase); + this.logger.debug('Sending wallet tx (%s): %h', this.id, tx.hash()); - const key = WalletKey.fromRing(account, ring); - const path = key.toPath(); + await this.wdb.send(tx); - if (this.master.encrypted) { - path.data = this.master.encipher(path.data, path.hash); - assert(path.data); - path.encrypted = true; + return tx; } - this.start(); + /** + * Intentionally double-spend outputs by + * increasing fee for an existing transaction. + * @param {Hash} hash + * @param {Rate} rate + * @param {(String|Buffer)?} passphrase + * @returns {Promise} - Returns {@link TX}. + */ - try { - await account.savePath(path); - } catch (e) { - this.drop(); - throw e; - } + async increaseFee(hash, rate, passphrase) { + assert((rate >>> 0) === rate, 'Rate must be a number.'); - await this.commit(); -}; + const wtx = await this.getTX(hash); -/** - * Import a keyring (will not exist on derivation chain). - * Rescanning must be invoked manually. - * @param {(String|Number)?} acct - * @param {WalletKey} ring - * @param {(String|Buffer)?} passphrase - * @returns {Promise} - */ + if (!wtx) + throw new Error('Transaction not found.'); -Wallet.prototype.importAddress = async function importAddress(acct, address) { - const unlock = await this.writeLock.lock(); - try { - return await this._importAddress(acct, address); - } finally { - unlock(); - } -}; + if (wtx.height !== -1) + throw new Error('Transaction is confirmed.'); -/** - * Import a keyring (will not exist on derivation chain) without a lock. - * @private - * @param {(String|Number)?} acct - * @param {WalletKey} ring - * @param {(String|Buffer)?} passphrase - * @returns {Promise} - */ + const tx = wtx.tx; -Wallet.prototype._importAddress = async function _importAddress(acct, address) { - if (!address) { - address = acct; - acct = null; - } + if (tx.isCoinbase()) + throw new Error('Transaction is a coinbase.'); - if (acct == null) - acct = 0; + const view = await this.getSpentView(tx); - assert(address.network === this.network, - 'Network mismatch for address.'); + if (!tx.hasCoins(view)) + throw new Error('Not all coins available.'); - if (!this.watchOnly) - throw new Error('Cannot import address into non watch-only wallet.'); + const oldFee = tx.getFee(view); - if (await this.getPath(address)) - throw new Error('Address already exists.'); + let fee = tx.getMinFee(null, rate); - const account = await this.getAccount(acct); + if (fee > MTX.Selector.MAX_FEE) + fee = MTX.Selector.MAX_FEE; - if (!account) - throw new Error('Account not found.'); + if (oldFee >= fee) + throw new Error('Fee is not increasing.'); - if (account.type !== Account.types.PUBKEYHASH) - throw new Error('Cannot import into non-pkh account.'); + const mtx = MTX.fromTX(tx); + mtx.view = view; - const path = Path.fromAddress(account, address); + for (const input of mtx.inputs) { + input.script.clear(); + input.witness.clear(); + } - this.start(); + let change; + for (let i = 0; i < mtx.outputs.length; i++) { + const output = mtx.outputs[i]; + const addr = output.getAddress(); - try { - await account.savePath(path); - } catch (e) { - this.drop(); - throw e; - } + if (!addr) + continue; - await this.commit(); -}; + const path = await this.getPath(addr); -/** - * Fill a transaction with inputs, estimate - * transaction size, calculate fee, and add a change output. - * @see MTX#selectCoins - * @see MTX#fill - * @param {MTX} mtx - _Must_ be a mutable transaction. - * @param {Object?} options - * @param {(String|Number)?} options.account - If no account is - * specified, coins from the entire wallet will be filled. - * @param {String?} options.selection - Coin selection priority. Can - * be `age`, `random`, or `all`. (default=age). - * @param {Boolean} options.round - Whether to round to the nearest - * kilobyte for fee calculation. - * See {@link TX#getMinFee} vs. {@link TX#getRoundFee}. - * @param {Rate} options.rate - Rate used for fee calculation. - * @param {Boolean} options.confirmed - Select only confirmed coins. - * @param {Boolean} options.free - Do not apply a fee if the - * transaction priority is high enough to be considered free. - * @param {Amount?} options.hardFee - Use a hard fee rather than - * calculating one. - * @param {Number|Boolean} options.subtractFee - Whether to subtract the - * fee from existing outputs rather than adding more inputs. - */ + if (!path) + continue; -Wallet.prototype.fund = async function fund(mtx, options, force) { - const unlock = await this.fundLock.lock(force); - try { - return await this._fund(mtx, options); - } finally { - unlock(); - } -}; + if (path.branch === 1) { + change = output; + mtx.changeIndex = i; + break; + } + } -/** - * Fill a transaction with inputs without a lock. - * @private - * @see MTX#selectCoins - * @see MTX#fill - */ + if (!change) + throw new Error('No change output.'); -Wallet.prototype._fund = async function _fund(mtx, options) { - if (!options) - options = {}; + change.value += oldFee; - if (!this.initialized) - throw new Error('Wallet is not initialized.'); + if (mtx.getFee() !== 0) + throw new Error('Arithmetic error for change.'); - if (this.watchOnly) - throw new Error('Cannot fund from watch-only wallet.'); + change.value -= fee; - let account; - if (options.account != null) { - account = await this.getAccount(options.account); - if (!account) - throw new Error('Account not found.'); - } else { - account = this.account; - } - - if (!account.initialized) - throw new Error('Account is not initialized.'); - - let rate = options.rate; - if (rate == null) - rate = await this.db.estimateFee(options.blocks); - - let coins; - if (options.smart) { - coins = await this.getSmartCoins(options.account); - } else { - coins = await this.getCoins(options.account); - coins = this.txdb.filterLocked(coins); - } - - await mtx.fund(coins, { - selection: options.selection, - round: options.round, - depth: options.depth, - hardFee: options.hardFee, - subtractFee: options.subtractFee, - subtractIndex: options.subtractIndex, - changeAddress: account.change.getAddress(), - height: this.db.state.height, - rate: rate, - maxFee: options.maxFee, - estimate: prev => this.estimateSize(prev) - }); - - assert(mtx.getFee() <= MTX.Selector.MAX_FEE, 'TX exceeds MAX_FEE.'); -}; + if (change.value < 0) + throw new Error('Fee is too high.'); -/** - * Get account by address. - * @param {Address} address - * @returns {Account} - */ - -Wallet.prototype.getAccountByAddress = async function getAccountByAddress(address) { - const hash = Address.getHash(address, 'hex'); - const path = await this.getPath(hash); - - if (!path) - return null; - - return await this.getAccount(path.account); -}; - -/** - * Input size estimator for max possible tx size. - * @param {Script} prev - * @returns {Number} - */ - -Wallet.prototype.estimateSize = async function estimateSize(prev) { - const scale = consensus.WITNESS_SCALE_FACTOR; - const address = prev.getAddress(); - - if (!address) - return -1; - - const account = await this.getAccountByAddress(address); - - if (!account) - return -1; - - let size = 0; - - if (prev.isScripthash()) { - // Nested bullshit. - if (account.witness) { - switch (account.type) { - case Account.types.PUBKEYHASH: - size += 23; // redeem script - size *= 4; // vsize - break; - case Account.types.MULTISIG: - size += 35; // redeem script - size *= 4; // vsize - break; - } + if (change.isDust()) { + mtx.outputs.splice(mtx.changeIndex, 1); + mtx.changeIndex = -1; } - } - switch (account.type) { - case Account.types.PUBKEYHASH: - // P2PKH - // OP_PUSHDATA0 [signature] - size += 1 + 73; - // OP_PUSHDATA0 [key] - size += 1 + 33; - break; - case Account.types.MULTISIG: - // P2SH Multisig - // OP_0 - size += 1; - // OP_PUSHDATA0 [signature] ... - size += (1 + 73) * account.m; - // OP_PUSHDATA2 [redeem] - size += 3; - // m value - size += 1; - // OP_PUSHDATA0 [key] ... - size += (1 + 33) * account.n; - // n value - size += 1; - // OP_CHECKMULTISIG - size += 1; - break; - } - - if (account.witness) { - // Varint witness items length. - size += 1; - // Calculate vsize if - // we're a witness program. - size = (size + scale - 1) / scale | 0; - } else { - // Byte for varint - // size of input script. - size += encoding.sizeVarint(size); - } - - return size; -}; - -/** - * Build a transaction, fill it with outputs and inputs, - * sort the members according to BIP69 (set options.sort=false - * to avoid sorting), set locktime, and template it. - * @param {Object} options - See {@link Wallet#fund options}. - * @param {Object[]} options.outputs - See {@link MTX#addOutput}. - * @returns {Promise} - Returns {@link MTX}. - */ - -Wallet.prototype.createTX = async function createTX(options, force) { - const outputs = options.outputs; - const mtx = new MTX(); - - assert(Array.isArray(outputs), 'Outputs must be an array.'); - assert(outputs.length > 0, 'No outputs available.'); + await this.sign(mtx, passphrase); - // Add the outputs - for (const obj of outputs) { - const output = new Output(obj); - const addr = output.getAddress(); + if (!mtx.isSigned()) + throw new Error('TX could not be fully signed.'); - if (output.isDust()) - throw new Error('Output is dust.'); + const ntx = mtx.toTX(); - if (output.value > 0) { - if (!addr) - throw new Error('Cannot send to unknown address.'); + this.logger.debug( + 'Increasing fee for wallet tx (%s): %h', + this.id, ntx.hash()); - if (addr.isNull()) - throw new Error('Cannot send to null address.'); - } + await this.wdb.addTX(ntx); + await this.wdb.send(ntx); - mtx.outputs.push(output); + return ntx; } - // Fill the inputs with unspents - await this.fund(mtx, options, force); - - // Sort members a la BIP69 - if (options.sort !== false) - mtx.sortMembers(); + /** + * Resend pending wallet transactions. + * @returns {Promise} + */ - // Set the locktime to target value. - if (options.locktime != null) - mtx.setLocktime(options.locktime); + async resend() { + const wtxs = await this.getPending(); - // Consensus sanity checks. - assert(mtx.isSane(), 'TX failed sanity check.'); - assert(mtx.verifyInputs(this.db.state.height + 1), - 'TX failed context check.'); + if (wtxs.length > 0) + this.logger.info('Rebroadcasting %d transactions.', wtxs.length); - const total = await this.template(mtx); + const txs = []; - if (total === 0) - throw new Error('Templating failed.'); + for (const wtx of wtxs) + txs.push(wtx.tx); - return mtx; -}; + const sorted = common.sortDeps(txs); -/** - * Build a transaction, fill it with outputs and inputs, - * sort the members according to BIP69, set locktime, - * sign and broadcast. Doing this all in one go prevents - * coins from being double spent. - * @param {Object} options - See {@link Wallet#fund options}. - * @param {Object[]} options.outputs - See {@link MTX#addOutput}. - * @returns {Promise} - Returns {@link TX}. - */ + for (const tx of sorted) + await this.wdb.send(tx); -Wallet.prototype.send = async function send(options, passphrase) { - const unlock = await this.fundLock.lock(); - try { - return await this._send(options, passphrase); - } finally { - unlock(); + return txs; } -}; - -/** - * Build and send a transaction without a lock. - * @private - * @param {Object} options - See {@link Wallet#fund options}. - * @param {Object[]} options.outputs - See {@link MTX#addOutput}. - * @returns {Promise} - Returns {@link TX}. - */ - -Wallet.prototype._send = async function _send(options, passphrase) { - const mtx = await this.createTX(options, true); - await this.sign(mtx, passphrase); + /** + * Derive necessary addresses for signing a transaction. + * @param {MTX} mtx + * @param {Number?} index - Input index. + * @returns {Promise} - Returns {@link WalletKey}[]. + */ - if (!mtx.isSigned()) - throw new Error('TX could not be fully signed.'); + async deriveInputs(mtx) { + assert(mtx.mutable); - const tx = mtx.toTX(); + const paths = await this.getInputPaths(mtx); + const rings = []; - // Policy sanity checks. - if (tx.getSigopsCost(mtx.view) > policy.MAX_TX_SIGOPS_COST) - throw new Error('TX exceeds policy sigops.'); - - if (tx.getWeight() > policy.MAX_TX_WEIGHT) - throw new Error('TX exceeds policy weight.'); - - await this.db.addTX(tx); - - this.logger.debug('Sending wallet tx (%s): %s', this.id, tx.txid()); - - await this.db.send(tx); - - return tx; -}; - -/** - * Intentionally double-spend outputs by - * increasing fee for an existing transaction. - * @param {Hash} hash - * @param {Rate} rate - * @param {(String|Buffer)?} passphrase - * @returns {Promise} - Returns {@link TX}. - */ - -Wallet.prototype.increaseFee = async function increaseFee(hash, rate, passphrase) { - assert(util.isU32(rate), 'Rate must be a number.'); - - const wtx = await this.getTX(hash); - - if (!wtx) - throw new Error('Transaction not found.'); - - if (wtx.height !== -1) - throw new Error('Transaction is confirmed.'); + for (const path of paths) { + const account = await this.getAccount(path.account); - const tx = wtx.tx; + if (!account) + continue; - if (tx.isCoinbase()) - throw new Error('Transaction is a coinbase.'); + const ring = account.derivePath(path, this.master); - const view = await this.getSpentView(tx); + if (ring) + rings.push(ring); + } - if (!tx.hasCoins(view)) - throw new Error('Not all coins available.'); + return rings; + } - const oldFee = tx.getFee(view); + /** + * Retrieve a single keyring by address. + * @param {Address|Hash} hash + * @returns {Promise} + */ - let fee = tx.getMinFee(null, rate); + async getKey(address) { + const hash = Address.getHash(address); + const path = await this.getPath(hash); - if (fee > MTX.Selector.MAX_FEE) - fee = MTX.Selector.MAX_FEE; + if (!path) + return null; - if (oldFee >= fee) - throw new Error('Fee is not increasing.'); + const account = await this.getAccount(path.account); - const mtx = MTX.fromTX(tx); - mtx.view = view; + if (!account) + return null; - for (const input of mtx.inputs) { - input.script.clear(); - input.witness.clear(); + return account.derivePath(path, this.master); } - let change; - for (let i = 0; i < mtx.outputs.length; i++) { - const output = mtx.outputs[i]; - const addr = output.getAddress(); + /** + * Retrieve a single keyring by address + * (with the private key reference). + * @param {Address|Hash} hash + * @param {(Buffer|String)?} passphrase + * @returns {Promise} + */ - if (!addr) - continue; - - const path = await this.getPath(addr); + async getPrivateKey(address, passphrase) { + const hash = Address.getHash(address); + const path = await this.getPath(hash); if (!path) - continue; - - if (path.branch === 1) { - change = output; - mtx.changeIndex = i; - break; - } - } + return null; - if (!change) - throw new Error('No change output.'); + const account = await this.getAccount(path.account); - change.value += oldFee; + if (!account) + return null; - if (mtx.getFee() !== 0) - throw new Error('Arithmetic error for change.'); + await this.unlock(passphrase); - change.value -= fee; + const key = account.derivePath(path, this.master); - if (change.value < 0) - throw new Error('Fee is too high.'); + if (!key.privateKey) + return null; - if (change.isDust()) { - mtx.outputs.splice(mtx.changeIndex, 1); - mtx.changeIndex = -1; + return key; } - await this.sign(mtx, passphrase); - - if (!mtx.isSigned()) - throw new Error('TX could not be fully signed.'); - - const ntx = mtx.toTX(); + /** + * Map input addresses to paths. + * @param {MTX} mtx + * @returns {Promise} - Returns {@link Path}[]. + */ - this.logger.debug( - 'Increasing fee for wallet tx (%s): %s', - this.id, ntx.txid()); + async getInputPaths(mtx) { + assert(mtx.mutable); - await this.db.addTX(ntx); - await this.db.send(ntx); + if (!mtx.hasCoins()) + throw new Error('Not all coins available.'); - return ntx; -}; + const hashes = mtx.getInputHashes(); + const paths = []; -/** - * Resend pending wallet transactions. - * @returns {Promise} - */ - -Wallet.prototype.resend = async function resend() { - const wtxs = await this.getPending(); - - if (wtxs.length > 0) - this.logger.info('Rebroadcasting %d transactions.', wtxs.length); + for (const hash of hashes) { + const path = await this.getPath(hash); + if (path) + paths.push(path); + } - const txs = []; + return paths; + } - for (const wtx of wtxs) - txs.push(wtx.tx); + /** + * Map output addresses to paths. + * @param {TX} tx + * @returns {Promise} - Returns {@link Path}[]. + */ - const sorted = common.sortDeps(txs); + async getOutputPaths(tx) { + const paths = []; + const hashes = tx.getOutputHashes(); - for (const tx of sorted) - await this.db.send(tx); + for (const hash of hashes) { + const path = await this.getPath(hash); + if (path) + paths.push(path); + } - return txs; -}; + return paths; + } -/** - * Derive necessary addresses for signing a transaction. - * @param {MTX} mtx - * @param {Number?} index - Input index. - * @returns {Promise} - Returns {@link WalletKey}[]. - */ + /** + * Increase lookahead for account. + * @param {(Number|String)?} account + * @param {Number} lookahead + * @returns {Promise} + */ -Wallet.prototype.deriveInputs = async function deriveInputs(mtx) { - assert(mtx.mutable); + async setLookahead(acct, lookahead) { + const unlock = await this.writeLock.lock(); + try { + return this._setLookahead(acct, lookahead); + } finally { + unlock(); + } + } - const paths = await this.getInputPaths(mtx); - const rings = []; + /** + * Increase lookahead for account (without a lock). + * @private + * @param {(Number|String)?} account + * @param {Number} lookahead + * @returns {Promise} + */ - for (const path of paths) { - const account = await this.getAccount(path.account); + async _setLookahead(acct, lookahead) { + const account = await this.getAccount(acct); if (!account) - continue; - - const ring = account.derivePath(path, this.master); + throw new Error('Account not found.'); - if (ring) - rings.push(ring); + const b = this.db.batch(); + await account.setLookahead(b, lookahead); + await b.write(); } - return rings; -}; + /** + * Sync address depths based on a transaction's outputs. + * This is used for deriving new addresses when + * a confirmed transaction is seen. + * @param {TX} tx + * @returns {Promise} + */ -/** - * Retrieve a single keyring by address. - * @param {Address|Hash} hash - * @returns {Promise} - */ + async syncOutputDepth(tx) { + const map = new Map(); -Wallet.prototype.getKey = async function getKey(address) { - const hash = Address.getHash(address, 'hex'); - const path = await this.getPath(hash); + for (const hash of tx.getOutputHashes()) { + const path = await this.readPath(hash); - if (!path) - return null; + if (!path) + continue; - const account = await this.getAccount(path.account); + if (path.index === -1) + continue; - if (!account) - return null; + if (!map.has(path.account)) + map.set(path.account, []); - return account.derivePath(path, this.master); -}; + map.get(path.account).push(path); + } -/** - * Retrieve a single keyring by address - * (with the private key reference). - * @param {Address|Hash} hash - * @param {(Buffer|String)?} passphrase - * @returns {Promise} - */ + const derived = []; + const b = this.db.batch(); + + for (const [acct, paths] of map) { + let receive = -1; + let change = -1; + let nested = -1; + + for (const path of paths) { + switch (path.branch) { + case 0: + if (path.index > receive) + receive = path.index; + break; + case 1: + if (path.index > change) + change = path.index; + break; + case 2: + if (path.index > nested) + nested = path.index; + break; + } + } -Wallet.prototype.getPrivateKey = async function getPrivateKey(address, passphrase) { - const hash = Address.getHash(address, 'hex'); - const path = await this.getPath(hash); + receive += 2; + change += 2; + nested += 2; - if (!path) - return null; + const account = await this.getAccount(acct); + assert(account); - const account = await this.getAccount(path.account); + const ring = await account.syncDepth(b, receive, change, nested); - if (!account) - return null; + if (ring) + derived.push(ring); + } - await this.unlock(passphrase); + await b.write(); - const key = account.derivePath(path, this.master); + return derived; + } - if (!key.privateKey) - return null; + /** + * Build input scripts templates for a transaction (does not + * sign, only creates signature slots). Only builds scripts + * for inputs that are redeemable by this wallet. + * @param {MTX} mtx + * @returns {Promise} - Returns Number + * (total number of scripts built). + */ - return key; -}; + async template(mtx) { + const rings = await this.deriveInputs(mtx); + return mtx.template(rings); + } -/** - * Map input addresses to paths. - * @param {MTX} mtx - * @returns {Promise} - Returns {@link Path}[]. - */ + /** + * Build input scripts and sign inputs for a transaction. Only attempts + * to build/sign inputs that are redeemable by this wallet. + * @param {MTX} tx + * @param {Object|String|Buffer} options - Options or passphrase. + * @returns {Promise} - Returns Number (total number + * of inputs scripts built and signed). + */ -Wallet.prototype.getInputPaths = async function getInputPaths(mtx) { - assert(mtx.mutable); + async sign(mtx, passphrase) { + if (this.watchOnly) + throw new Error('Cannot sign from a watch-only wallet.'); - if (!mtx.hasCoins()) - throw new Error('Not all coins available.'); + await this.unlock(passphrase); - const hashes = mtx.getInputHashes('hex'); - const paths = []; + const rings = await this.deriveInputs(mtx); - for (const hash of hashes) { - const path = await this.getPath(hash); - if (path) - paths.push(path); + return mtx.signAsync(rings, Script.hashType.ALL, this.wdb.workers); } - return paths; -}; + /** + * Get a coin viewpoint. + * @param {TX} tx + * @returns {Promise} - Returns {@link CoinView}. + */ -/** - * Map output addresses to paths. - * @param {TX} tx - * @returns {Promise} - Returns {@link Path}[]. - */ - -Wallet.prototype.getOutputPaths = async function getOutputPaths(tx) { - const paths = []; - const hashes = tx.getOutputHashes('hex'); - - for (const hash of hashes) { - const path = await this.getPath(hash); - if (path) - paths.push(path); + getCoinView(tx) { + return this.txdb.getCoinView(tx); } - return paths; -}; + /** + * Get a historical coin viewpoint. + * @param {TX} tx + * @returns {Promise} - Returns {@link CoinView}. + */ -/** - * Increase lookahead for account. - * @param {(Number|String)?} account - * @param {Number} lookahead - * @returns {Promise} - */ - -Wallet.prototype.setLookahead = async function setLookahead(acct, lookahead) { - const unlock = await this.writeLock.lock(); - try { - return this._setLookahead(acct, lookahead); - } finally { - unlock(); + getSpentView(tx) { + return this.txdb.getSpentView(tx); } -}; -/** - * Increase lookahead for account (without a lock). - * @private - * @param {(Number|String)?} account - * @param {Number} lookahead - * @returns {Promise} - */ + /** + * Convert transaction to transaction details. + * @param {TXRecord} wtx + * @returns {Promise} - Returns {@link Details}. + */ -Wallet.prototype._setLookahead = async function _setLookahead(acct, lookahead) { - if (lookahead == null) { - lookahead = acct; - acct = null; + toDetails(wtx) { + return this.txdb.toDetails(wtx); } - if (acct == null) - acct = 0; - - const account = await this.getAccount(acct); + /** + * Get transaction details. + * @param {Hash} hash + * @returns {Promise} - Returns {@link Details}. + */ - if (!account) - throw new Error('Account not found.'); + getDetails(hash) { + return this.txdb.getDetails(hash); + } - this.start(); + /** + * Get a coin from the wallet. + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} - Returns {@link Coin}. + */ - try { - await account.setLookahead(lookahead); - } catch (e) { - this.drop(); - throw e; + getCoin(hash, index) { + return this.txdb.getCoin(hash, index); } - await this.commit(); -}; + /** + * Get a transaction from the wallet. + * @param {Hash} hash + * @returns {Promise} - Returns {@link TX}. + */ -/** - * Sync address depths based on a transaction's outputs. - * This is used for deriving new addresses when - * a confirmed transaction is seen. - * @param {Details} details - * @returns {Promise} - */ + getTX(hash) { + return this.txdb.getTX(hash); + } -Wallet.prototype.syncOutputDepth = async function syncOutputDepth(details) { - const map = new Map(); + /** + * List blocks for the wallet. + * @returns {Promise} - Returns {@link BlockRecord}. + */ - for (const output of details.outputs) { - const path = output.path; + getBlocks() { + return this.txdb.getBlocks(); + } - if (!path) - continue; + /** + * Get a block from the wallet. + * @param {Number} height + * @returns {Promise} - Returns {@link BlockRecord}. + */ - if (path.index === -1) - continue; + getBlock(height) { + return this.txdb.getBlock(height); + } - if (!map.has(path.account)) - map.set(path.account, []); + /** + * Add a transaction to the wallets TX history. + * @param {TX} tx + * @returns {Promise} + */ - map.get(path.account).push(path); + async add(tx, block) { + const unlock = await this.writeLock.lock(); + try { + return await this._add(tx, block); + } finally { + unlock(); + } } - const derived = []; + /** + * Add a transaction to the wallet without a lock. + * Potentially resolves orphans. + * @private + * @param {TX} tx + * @returns {Promise} + */ - for (const [acct, paths] of map) { - let receive = -1; - let change = -1; - let nested = -1; + async _add(tx, block) { + const details = await this.txdb.add(tx, block); - for (const path of paths) { - switch (path.branch) { - case 0: - if (path.index > receive) - receive = path.index; - break; - case 1: - if (path.index > change) - change = path.index; - break; - case 2: - if (path.index > nested) - nested = path.index; - break; + if (details) { + const derived = await this.syncOutputDepth(tx); + if (derived.length > 0) { + this.wdb.emit('address', this, derived); + this.emit('address', derived); } } - receive += 2; - change += 2; - nested += 2; - - const account = await this.getAccount(acct); - assert(account); - - const ring = await account.syncDepth(receive, change, nested); - - if (ring) - derived.push(ring); + return details; } - return derived; -}; - -/** - * Get a redeem script or witness script by hash. - * @param {Hash} hash - Can be a ripemd160 or a sha256. - * @returns {Script} - */ - -Wallet.prototype.getRedeem = async function getRedeem(hash) { - if (typeof hash === 'string') - hash = Buffer.from(hash, 'hex'); - - const ring = await this.getKey(hash.toString('hex')); - - if (!ring) - return null; - - return ring.getRedeem(hash); -}; - -/** - * Build input scripts templates for a transaction (does not - * sign, only creates signature slots). Only builds scripts - * for inputs that are redeemable by this wallet. - * @param {MTX} mtx - * @returns {Promise} - Returns Number - * (total number of scripts built). - */ - -Wallet.prototype.template = async function template(mtx) { - const rings = await this.deriveInputs(mtx); - return mtx.template(rings); -}; - -/** - * Build input scripts and sign inputs for a transaction. Only attempts - * to build/sign inputs that are redeemable by this wallet. - * @param {MTX} tx - * @param {Object|String|Buffer} options - Options or passphrase. - * @returns {Promise} - Returns Number (total number - * of inputs scripts built and signed). - */ - -Wallet.prototype.sign = async function sign(mtx, passphrase) { - if (this.watchOnly) - throw new Error('Cannot sign from a watch-only wallet.'); - - await this.unlock(passphrase); - - const rings = await this.deriveInputs(mtx); - - return await mtx.signAsync(rings, Script.hashType.ALL, this.db.workers); -}; - -/** - * Get a coin viewpoint. - * @param {TX} tx - * @returns {Promise} - Returns {@link CoinView}. - */ - -Wallet.prototype.getCoinView = function getCoinView(tx) { - return this.txdb.getCoinView(tx); -}; - -/** - * Get a historical coin viewpoint. - * @param {TX} tx - * @returns {Promise} - Returns {@link CoinView}. - */ - -Wallet.prototype.getSpentView = function getSpentView(tx) { - return this.txdb.getSpentView(tx); -}; - -/** - * Convert transaction to transaction details. - * @param {TXRecord} wtx - * @returns {Promise} - Returns {@link Details}. - */ - -Wallet.prototype.toDetails = function toDetails(wtx) { - return this.txdb.toDetails(wtx); -}; + /** + * Revert a block. + * @param {Number} height + * @returns {Promise} + */ -/** - * Get transaction details. - * @param {Hash} hash - * @returns {Promise} - Returns {@link Details}. - */ - -Wallet.prototype.getDetails = function getDetails(hash) { - return this.txdb.getDetails(hash); -}; - -/** - * Get a coin from the wallet. - * @param {Hash} hash - * @param {Number} index - * @returns {Promise} - Returns {@link Coin}. - */ + async revert(height) { + const unlock = await this.writeLock.lock(); + try { + return await this.txdb.revert(height); + } finally { + unlock(); + } + } -Wallet.prototype.getCoin = function getCoin(hash, index) { - return this.txdb.getCoin(hash, index); -}; + /** + * Remove a wallet transaction. + * @param {Hash} hash + * @returns {Promise} + */ -/** - * Get a transaction from the wallet. - * @param {Hash} hash - * @returns {Promise} - Returns {@link TX}. - */ - -Wallet.prototype.getTX = function getTX(hash) { - return this.txdb.getTX(hash); -}; + async remove(hash) { + const unlock = await this.writeLock.lock(); + try { + return await this.txdb.remove(hash); + } finally { + unlock(); + } + } -/** - * List blocks for the wallet. - * @returns {Promise} - Returns {@link BlockRecord}. - */ + /** + * Zap stale TXs from wallet. + * @param {(Number|String)?} acct + * @param {Number} age - Age threshold (unix time, default=72 hours). + * @returns {Promise} + */ -Wallet.prototype.getBlocks = function getBlocks() { - return this.txdb.getBlocks(); -}; + async zap(acct, age) { + const unlock = await this.writeLock.lock(); + try { + return await this._zap(acct, age); + } finally { + unlock(); + } + } -/** - * Get a block from the wallet. - * @param {Number} height - * @returns {Promise} - Returns {@link BlockRecord}. - */ + /** + * Zap stale TXs from wallet without a lock. + * @private + * @param {(Number|String)?} acct + * @param {Number} age + * @returns {Promise} + */ -Wallet.prototype.getBlock = function getBlock(height) { - return this.txdb.getBlock(height); -}; + async _zap(acct, age) { + const account = await this.ensureIndex(acct); + return this.txdb.zap(account, age); + } -/** - * Add a transaction to the wallets TX history. - * @param {TX} tx - * @returns {Promise} - */ + /** + * Abandon transaction. + * @param {Hash} hash + * @returns {Promise} + */ -Wallet.prototype.add = async function add(tx, block) { - const unlock = await this.writeLock.lock(); - try { - return await this._add(tx, block); - } finally { - unlock(); + async abandon(hash) { + const unlock = await this.writeLock.lock(); + try { + return await this._abandon(hash); + } finally { + unlock(); + } } -}; -/** - * Add a transaction to the wallet without a lock. - * Potentially resolves orphans. - * @private - * @param {TX} tx - * @returns {Promise} - */ - -Wallet.prototype._add = async function _add(tx, block) { - this.txdb.start(); + /** + * Abandon transaction without a lock. + * @private + * @param {Hash} hash + * @returns {Promise} + */ - let details, derived; - try { - details = await this.txdb._add(tx, block); - if (details) - derived = await this.syncOutputDepth(details); - } catch (e) { - this.txdb.drop(); - throw e; + _abandon(hash) { + return this.txdb.abandon(hash); } - await this.txdb.commit(); + /** + * Lock a single coin. + * @param {Coin|Outpoint} coin + */ - if (derived && derived.length > 0) { - this.db.emit('address', this.id, derived); - this.emit('address', derived); + lockCoin(coin) { + return this.txdb.lockCoin(coin); } - return details; -}; - -/** - * Unconfirm a wallet transcation. - * @param {Hash} hash - * @returns {Promise} - */ + /** + * Unlock a single coin. + * @param {Coin|Outpoint} coin + */ -Wallet.prototype.unconfirm = async function unconfirm(hash) { - const unlock = await this.writeLock.lock(); - try { - return await this.txdb.unconfirm(hash); - } finally { - unlock(); + unlockCoin(coin) { + return this.txdb.unlockCoin(coin); } -}; -/** - * Remove a wallet transaction. - * @param {Hash} hash - * @returns {Promise} - */ + /** + * Unlock all locked coins. + */ -Wallet.prototype.remove = async function remove(hash) { - const unlock = await this.writeLock.lock(); - try { - return await this.txdb.remove(hash); - } finally { - unlock(); + unlockCoins() { + return this.txdb.unlockCoins(); } -}; -/** - * Zap stale TXs from wallet. - * @param {(Number|String)?} acct - * @param {Number} age - Age threshold (unix time, default=72 hours). - * @returns {Promise} - */ + /** + * Test locked status of a single coin. + * @param {Coin|Outpoint} coin + */ -Wallet.prototype.zap = async function zap(acct, age) { - const unlock = await this.writeLock.lock(); - try { - return await this._zap(acct, age); - } finally { - unlock(); + isLocked(coin) { + return this.txdb.isLocked(coin); } -}; -/** - * Zap stale TXs from wallet without a lock. - * @private - * @param {(Number|String)?} acct - * @param {Number} age - * @returns {Promise} - */ + /** + * Return an array of all locked outpoints. + * @returns {Outpoint[]} + */ -Wallet.prototype._zap = async function _zap(acct, age) { - const account = await this.ensureIndex(acct); - return await this.txdb.zap(account, age); -}; + getLocked() { + return this.txdb.getLocked(); + } -/** - * Abandon transaction. - * @param {Hash} hash - * @returns {Promise} - */ + /** + * Get all transactions in transaction history. + * @param {(String|Number)?} acct + * @returns {Promise} - Returns {@link TX}[]. + */ -Wallet.prototype.abandon = async function abandon(hash) { - const unlock = await this.writeLock.lock(); - try { - return await this._abandon(hash); - } finally { - unlock(); + async getHistory(acct) { + const account = await this.ensureIndex(acct); + return this.txdb.getHistory(account); } -}; -/** - * Abandon transaction without a lock. - * @private - * @param {Hash} hash - * @returns {Promise} - */ + /** + * Get all available coins. + * @param {(String|Number)?} account + * @returns {Promise} - Returns {@link Coin}[]. + */ -Wallet.prototype._abandon = function _abandon(hash) { - return this.txdb.abandon(hash); -}; + async getCoins(acct) { + const account = await this.ensureIndex(acct); + return this.txdb.getCoins(account); + } -/** - * Lock a single coin. - * @param {Coin|Outpoint} coin - */ + /** + * Get all available credits. + * @param {(String|Number)?} account + * @returns {Promise} - Returns {@link Credit}[]. + */ -Wallet.prototype.lockCoin = function lockCoin(coin) { - return this.txdb.lockCoin(coin); -}; + async getCredits(acct) { + const account = await this.ensureIndex(acct); + return this.txdb.getCredits(account); + } -/** - * Unlock a single coin. - * @param {Coin|Outpoint} coin - */ + /** + * Get "smart" coins. + * @param {(String|Number)?} account + * @returns {Promise} - Returns {@link Coin}[]. + */ -Wallet.prototype.unlockCoin = function unlockCoin(coin) { - return this.txdb.unlockCoin(coin); -}; + async getSmartCoins(acct) { + const credits = await this.getCredits(acct); + const coins = []; -/** - * Test locked status of a single coin. - * @param {Coin|Outpoint} coin - */ + for (const credit of credits) { + const coin = credit.coin; -Wallet.prototype.isLocked = function isLocked(coin) { - return this.txdb.isLocked(coin); -}; + if (credit.spent) + continue; -/** - * Return an array of all locked outpoints. - * @returns {Outpoint[]} - */ + if (this.txdb.isLocked(coin)) + continue; -Wallet.prototype.getLocked = function getLocked() { - return this.txdb.getLocked(); -}; - -/** - * Get all transactions in transaction history. - * @param {(String|Number)?} acct - * @returns {Promise} - Returns {@link TX}[]. - */ + // Always use confirmed coins. + if (coin.height !== -1) { + coins.push(coin); + continue; + } -Wallet.prototype.getHistory = async function getHistory(acct) { - const account = await this.ensureIndex(acct); - return this.txdb.getHistory(account); -}; + // Use unconfirmed only if they were + // created as a result of one of our + // _own_ transactions. i.e. they're + // not low-fee and not in danger of + // being double-spent by a bad actor. + if (!credit.own) + continue; -/** - * Get all available coins. - * @param {(String|Number)?} account - * @returns {Promise} - Returns {@link Coin}[]. - */ + coins.push(coin); + } -Wallet.prototype.getCoins = async function getCoins(acct) { - const account = await this.ensureIndex(acct); - return await this.txdb.getCoins(account); -}; + return coins; + } -/** - * Get all available credits. - * @param {(String|Number)?} account - * @returns {Promise} - Returns {@link Credit}[]. - */ + /** + * Get all pending/unconfirmed transactions. + * @param {(String|Number)?} acct + * @returns {Promise} - Returns {@link TX}[]. + */ -Wallet.prototype.getCredits = async function getCredits(acct) { - const account = await this.ensureIndex(acct); - return await this.txdb.getCredits(account); -}; + async getPending(acct) { + const account = await this.ensureIndex(acct); + return this.txdb.getPending(account); + } -/** - * Get "smart" coins. - * @param {(String|Number)?} account - * @returns {Promise} - Returns {@link Coin}[]. - */ + /** + * Get wallet balance. + * @param {(String|Number)?} acct + * @returns {Promise} - Returns {@link Balance}. + */ -Wallet.prototype.getSmartCoins = async function getSmartCoins(acct) { - const credits = await this.getCredits(acct); - const coins = []; + async getBalance(acct) { + const account = await this.ensureIndex(acct); + return this.txdb.getBalance(account); + } - for (const credit of credits) { - const coin = credit.coin; + /** + * Get a range of transactions between two timestamps. + * @param {(String|Number)?} acct + * @param {Object} options + * @param {Number} options.start + * @param {Number} options.end + * @returns {Promise} - Returns {@link TX}[]. + */ - if (credit.spent) - continue; + async getRange(acct, options) { + const account = await this.ensureIndex(acct); + return this.txdb.getRange(account, options); + } - if (this.txdb.isLocked(coin)) - continue; + /** + * Get the last N transactions. + * @param {(String|Number)?} acct + * @param {Number} limit + * @returns {Promise} - Returns {@link TX}[]. + */ - // Always used confirmed coins. - if (coin.height !== -1) { - coins.push(coin); - continue; - } + async getLast(acct, limit) { + const account = await this.ensureIndex(acct); + return this.txdb.getLast(account, limit); + } - // Use unconfirmed only if they were - // created as a result of one of our - // _own_ transactions. i.e. they're - // not low-fee and not in danger of - // being double-spent by a bad actor. - if (!credit.own) - continue; + /** + * Get account key. + * @param {Number} [acct=0] + * @returns {HDPublicKey} + */ - coins.push(coin); + async accountKey(acct = 0) { + const account = await this.getAccount(acct); + if (!account) + throw new Error('Account not found.'); + return account.accountKey; } - return coins; -}; + /** + * Get current receive depth. + * @param {Number} [acct=0] + * @returns {Number} + */ -/** - * Get all pending/unconfirmed transactions. - * @param {(String|Number)?} acct - * @returns {Promise} - Returns {@link TX}[]. - */ - -Wallet.prototype.getPending = async function getPending(acct) { - const account = await this.ensureIndex(acct); - return await this.txdb.getPending(account); -}; + async receiveDepth(acct = 0) { + const account = await this.getAccount(acct); + if (!account) + throw new Error('Account not found.'); + return account.receiveDepth; + } -/** - * Get wallet balance. - * @param {(String|Number)?} acct - * @returns {Promise} - Returns {@link Balance}. - */ + /** + * Get current change depth. + * @param {Number} [acct=0] + * @returns {Number} + */ -Wallet.prototype.getBalance = async function getBalance(acct) { - const account = await this.ensureIndex(acct); - return await this.txdb.getBalance(account); -}; + async changeDepth(acct = 0) { + const account = await this.getAccount(acct); + if (!account) + throw new Error('Account not found.'); + return account.changeDepth; + } -/** - * Get a range of transactions between two timestamps. - * @param {(String|Number)?} acct - * @param {Object} options - * @param {Number} options.start - * @param {Number} options.end - * @returns {Promise} - Returns {@link TX}[]. - */ + /** + * Get current nested depth. + * @param {Number} [acct=0] + * @returns {Number} + */ -Wallet.prototype.getRange = async function getRange(acct, options) { - if (acct && typeof acct === 'object') { - options = acct; - acct = null; + async nestedDepth(acct = 0) { + const account = await this.getAccount(acct); + if (!account) + throw new Error('Account not found.'); + return account.nestedDepth; } - const account = await this.ensureIndex(acct); - return await this.txdb.getRange(account, options); -}; -/** - * Get the last N transactions. - * @param {(String|Number)?} acct - * @param {Number} limit - * @returns {Promise} - Returns {@link TX}[]. - */ + /** + * Get current receive address. + * @param {Number} [acct=0] + * @returns {Address} + */ -Wallet.prototype.getLast = async function getLast(acct, limit) { - const account = await this.ensureIndex(acct); - return await this.txdb.getLast(account, limit); -}; + async receiveAddress(acct = 0) { + const account = await this.getAccount(acct); + if (!account) + throw new Error('Account not found.'); + return account.receiveAddress(); + } -/** - * Resolve account index. - * @private - * @param {(Number|String)?} acct - * @param {Function} errback - Returns [Error]. - * @returns {Promise} - */ + /** + * Get current change address. + * @param {Number} [acct=0] + * @returns {Address} + */ -Wallet.prototype.ensureIndex = async function ensureIndex(acct, enforce) { - if (acct == null) { - if (enforce) - throw new Error('No account provided.'); - return null; + async changeAddress(acct = 0) { + const account = await this.getAccount(acct); + if (!account) + throw new Error('Account not found.'); + return account.changeAddress(); } - const index = await this.getAccountIndex(acct); - - if (index === -1) - throw new Error('Account not found.'); + /** + * Get current nested address. + * @param {Number} [acct=0] + * @returns {Address} + */ - return index; -}; + async nestedAddress(acct = 0) { + const account = await this.getAccount(acct); + if (!account) + throw new Error('Account not found.'); + return account.nestedAddress(); + } -/** - * Get current receive address. - * @param {String?} enc - `"base58"` or `null`. - * @returns {Address|Base58Address} - */ + /** + * Get current receive key. + * @param {Number} [acct=0] + * @returns {WalletKey} + */ -Wallet.prototype.getAddress = function getAddress(enc) { - return this.account.getAddress(enc); -}; + async receiveKey(acct = 0) { + const account = await this.getAccount(acct); + if (!account) + throw new Error('Account not found.'); + return account.receiveKey(); + } -/** - * Get current receive address. - * @param {String?} enc - `"base58"` or `null`. - * @returns {Address|Base58Address} - */ + /** + * Get current change key. + * @param {Number} [acct=0] + * @returns {WalletKey} + */ -Wallet.prototype.getReceive = function getReceive(enc) { - return this.account.getReceive(enc); -}; + async changeKey(acct = 0) { + const account = await this.getAccount(acct); + if (!account) + throw new Error('Account not found.'); + return account.changeKey(); + } -/** - * Get current change address. - * @param {String?} enc - `"base58"` or `null`. - * @returns {Address|Base58Address} - */ + /** + * Get current nested key. + * @param {Number} [acct=0] + * @returns {WalletKey} + */ -Wallet.prototype.getChange = function getChange(enc) { - return this.account.getChange(enc); -}; + async nestedKey(acct = 0) { + const account = await this.getAccount(acct); + if (!account) + throw new Error('Account not found.'); + return account.nestedKey(); + } -/** - * Get current nested address. - * @param {String?} enc - `"base58"` or `null`. - * @returns {Address|Base58Address} - */ + /** + * Convert the wallet to a more inspection-friendly object. + * @returns {Object} + */ -Wallet.prototype.getNested = function getNested(enc) { - return this.account.getNested(enc); -}; + [inspectSymbol]() { + return { + wid: this.wid, + id: this.id, + network: this.network.type, + accountDepth: this.accountDepth, + token: this.token.toString('hex'), + tokenDepth: this.tokenDepth, + master: this.master + }; + } -/** - * Convert the wallet to a more inspection-friendly object. - * @returns {Object} - */ + /** + * Convert the wallet to an object suitable for + * serialization. + * @param {Boolean?} unsafe - Whether to include + * the master key in the JSON. + * @returns {Object} + */ -Wallet.prototype.inspect = function inspect() { - return { - wid: this.wid, - id: this.id, - network: this.network.type, - initialized: this.initialized, - accountDepth: this.accountDepth, - token: this.token.toString('hex'), - tokenDepth: this.tokenDepth, - state: this.txdb.state ? this.txdb.state.toJSON(true) : null, - master: this.master, - account: this.account - }; -}; + toJSON(unsafe, balance) { + return { + network: this.network.type, + wid: this.wid, + id: this.id, + watchOnly: this.watchOnly, + accountDepth: this.accountDepth, + token: this.token.toString('hex'), + tokenDepth: this.tokenDepth, + master: this.master.toJSON(this.network, unsafe), + balance: balance ? balance.toJSON(true) : null + }; + } -/** - * Convert the wallet to an object suitable for - * serialization. - * @param {Boolean?} unsafe - Whether to include - * the master key in the JSON. - * @returns {Object} - */ + /** + * Calculate serialization size. + * @returns {Number} + */ -Wallet.prototype.toJSON = function toJSON(unsafe) { - return { - network: this.network.type, - wid: this.wid, - id: this.id, - initialized: this.initialized, - watchOnly: this.watchOnly, - accountDepth: this.accountDepth, - token: this.token.toString('hex'), - tokenDepth: this.tokenDepth, - state: this.txdb.state.toJSON(true), - master: this.master.toJSON(unsafe), - account: this.account.toJSON(true) - }; -}; + getSize() { + let size = 0; + size += 41; + size += this.master.getSize(); + return size; + } -/** - * Calculate serialization size. - * @returns {Number} - */ + /** + * Serialize the wallet. + * @returns {Buffer} + */ -Wallet.prototype.getSize = function getSize() { - let size = 0; - size += 50; - size += encoding.sizeVarString(this.id, 'ascii'); - size += encoding.sizeVarlen(this.master.getSize()); - return size; -}; + toRaw() { + const size = this.getSize(); + const bw = bio.write(size); -/** - * Serialize the wallet. - * @returns {Buffer} - */ + let flags = 0; -Wallet.prototype.toRaw = function toRaw() { - const size = this.getSize(); - const bw = new StaticWriter(size); + if (this.watchOnly) + flags |= 1; - bw.writeU32(this.network.magic); - bw.writeU32(this.wid); - bw.writeVarString(this.id, 'ascii'); - bw.writeU8(this.initialized ? 1 : 0); - bw.writeU8(this.watchOnly ? 1 : 0); - bw.writeU32(this.accountDepth); - bw.writeBytes(this.token); - bw.writeU32(this.tokenDepth); - bw.writeVarBytes(this.master.toRaw()); + bw.writeU8(flags); + bw.writeU32(this.accountDepth); + bw.writeBytes(this.token); + bw.writeU32(this.tokenDepth); + this.master.toWriter(bw); - return bw.render(); -}; + return bw.render(); + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ -Wallet.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); - const network = Network.fromMagic(br.readU32()); + fromRaw(data) { + const br = bio.read(data); - this.wid = br.readU32(); - this.id = br.readVarString('ascii'); - this.initialized = br.readU8() === 1; - this.watchOnly = br.readU8() === 1; - this.accountDepth = br.readU32(); - this.token = br.readBytes(32); - this.tokenDepth = br.readU32(); - this.master.fromRaw(br.readVarBytes()); + const flags = br.readU8(); - assert(network === this.db.network, 'Wallet network mismatch.'); + this.watchOnly = (flags & 1) !== 0; + this.accountDepth = br.readU32(); + this.token = br.readBytes(32); + this.tokenDepth = br.readU32(); + this.master.fromReader(br); - return this; -}; + return this; + } -/** - * Instantiate a wallet from serialized data. - * @param {Buffer} data - * @returns {Wallet} - */ + /** + * Instantiate a wallet from serialized data. + * @param {Buffer} data + * @returns {Wallet} + */ -Wallet.fromRaw = function fromRaw(db, data) { - return new Wallet(db).fromRaw(data); -}; + static fromRaw(wdb, data) { + return new this(wdb).fromRaw(data); + } -/** - * Test an object to see if it is a Wallet. - * @param {Object} obj - * @returns {Boolean} - */ + /** + * Test an object to see if it is a Wallet. + * @param {Object} obj + * @returns {Boolean} + */ -Wallet.isWallet = function isWallet(obj) { - return obj instanceof Wallet; -}; + static isWallet(obj) { + return obj instanceof Wallet; + } +} /* * Expose diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index 04fa81324..5745cb740 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -7,604 +7,577 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const path = require('path'); -const AsyncObject = require('../utils/asyncobject'); -const util = require('../utils/util'); -const Lock = require('../utils/lock'); -const MappedLock = require('../utils/mappedlock'); -const LRU = require('../utils/lru'); -const encoding = require('../utils/encoding'); -const ccmp = require('../crypto/ccmp'); -const aes = require('../crypto/aes'); +const EventEmitter = require('events'); +const bio = require('bufio'); +const {BloomFilter} = require('bfilter'); +const {Lock, MapLock} = require('bmutex'); +const bdb = require('bdb'); +const Logger = require('blgr'); +const {safeEqual} = require('bcrypto/lib/safe'); +const aes = require('bcrypto/lib/aes'); const Network = require('../protocol/network'); const Path = require('./path'); const common = require('./common'); const Wallet = require('./wallet'); const Account = require('./account'); -const LDB = require('../db/ldb'); -const Bloom = require('../utils/bloom'); -const Logger = require('../node/logger'); const Outpoint = require('../primitives/outpoint'); const layouts = require('./layout'); const records = require('./records'); -const HTTPServer = require('./http'); -const RPC = require('./rpc'); -const layout = layouts.walletdb; -const ChainState = records.ChainState; -const BlockMapRecord = records.BlockMapRecord; -const BlockMeta = records.BlockMeta; -const PathMapRecord = records.PathMapRecord; -const OutpointMapRecord = records.OutpointMapRecord; -const TXRecord = records.TXRecord; -const U32 = encoding.U32; +const NullClient = require('./nullclient'); +const layout = layouts.wdb; +const tlayout = layouts.txdb; + +const { + ChainState, + BlockMeta, + TXRecord, + MapRecord +} = records; /** * WalletDB * @alias module:wallet.WalletDB - * @constructor - * @param {Object} options - * @param {String?} options.name - Database name. - * @param {String?} options.location - Database file location. - * @param {String?} options.db - Database backend (`"leveldb"` by default). - * @param {Boolean?} options.verify - Verify transactions as they - * come in (note that this will not happen on the worker pool). - * @property {Boolean} loaded + * @extends EventEmitter */ -function WalletDB(options) { - if (!(this instanceof WalletDB)) - return new WalletDB(options); - - AsyncObject.call(this); - - this.options = new WalletOptions(options); - - this.network = this.options.network; - this.logger = this.options.logger.context('wallet'); - this.workers = this.options.workers; - - this.client = this.options.client; - this.feeRate = this.options.feeRate; - - this.db = LDB(this.options); - this.rpc = new RPC(this); - this.primary = null; - this.http = null; - - if (!HTTPServer.unsupported) { - this.http = new HTTPServer({ - walletdb: this, - network: this.network, - logger: this.logger, - prefix: this.options.prefix, - apiKey: this.options.apiKey, - walletAuth: this.options.walletAuth, - noAuth: this.options.noAuth, - host: this.options.host, - port: this.options.port, - ssl: this.options.ssl - }); - } +class WalletDB extends EventEmitter { + /** + * Create a wallet db. + * @constructor + * @param {Object} options + */ - this.state = new ChainState(); - this.wallets = new Map(); - this.depth = 0; - this.rescanning = false; - this.bound = false; + constructor(options) { + super(); - this.readLock = new MappedLock(); - this.writeLock = new Lock(); - this.txLock = new Lock(); + this.options = new WalletOptions(options); - this.widCache = new LRU(10000); - this.pathMapCache = new LRU(100000); + this.network = this.options.network; + this.logger = this.options.logger.context('wallet'); + this.workers = this.options.workers; + this.client = this.options.client || new NullClient(this); + this.feeRate = this.options.feeRate; + this.db = bdb.create(this.options); - this.filter = new Bloom(); + this.primary = null; + this.state = new ChainState(); + this.height = 0; + this.wallets = new Map(); + this.depth = 0; + this.rescanning = false; + this.filterSent = false; - this._init(); -} + // Wallet read lock. + this.readLock = new MapLock(); -Object.setPrototypeOf(WalletDB.prototype, AsyncObject.prototype); + // Wallet write lock (creation and rename). + this.writeLock = new Lock(); -/** - * Database layout. - * @type {Object} - */ + // Lock for handling anything tx related. + this.txLock = new Lock(); -WalletDB.layout = layout; + // Address and outpoint filter. + this.filter = new BloomFilter(); -/** - * Initialize walletdb. - * @private - */ + this.init(); + } + + /** + * Initialize walletdb. + * @private + */ + + init() { + let items = 3000000; + let flag = -1; -WalletDB.prototype._init = function _init() { - let items = 3000000; - let flag = -1; + // Highest number of items with an + // FPR of 0.001. We have to do this + // by hand because BloomFilter.fromRate's + // policy limit enforcing is fairly + // naive. + if (this.options.spv) { + items = 20000; + flag = BloomFilter.flags.ALL; + } - // Highest number of items with an - // FPR of 0.001. We have to do this - // by hand because Bloom.fromRate's - // policy limit enforcing is fairly - // naive. - if (this.options.spv) { - items = 20000; - flag = Bloom.flags.ALL; + this.filter = BloomFilter.fromRate(items, 0.001, flag); + this._bind(); } - this.filter = Bloom.fromRate(items, 0.001, flag); -}; + /** + * Bind to node events. + * @private + */ -/** - * Open the walletdb, wait for the database to load. - * @alias WalletDB#open - * @returns {Promise} - */ + _bind() { + this.client.on('error', (err) => { + this.emit('error', err); + }); -WalletDB.prototype._open = async function _open() { - if (this.options.listen) - await this.logger.open(); + this.client.on('connect', async () => { + try { + await this.syncNode(); + } catch (e) { + this.emit('error', e); + } + }); - await this.db.open(); - await this.db.checkVersion('V', 6); + this.client.on('disconnect', async () => { + this.filterSent = false; + }); - this.depth = await this.getDepth(); + this.client.bind('block connect', async (entry, txs) => { + try { + await this.addBlock(entry, txs); + } catch (e) { + this.emit('error', e); + } + }); - if (this.options.wipeNoReally) - await this.wipe(); + this.client.bind('block disconnect', async (entry) => { + try { + await this.removeBlock(entry); + } catch (e) { + this.emit('error', e); + } + }); - await this.load(); + this.client.hook('block rescan', async (entry, txs) => { + try { + await this.rescanBlock(entry, txs); + } catch (e) { + this.emit('error', e); + } + }); - this.logger.info( - 'WalletDB loaded (depth=%d, height=%d, start=%d).', - this.depth, - this.state.height, - this.state.startHeight); + this.client.bind('tx', async (tx) => { + try { + await this.addTX(tx); + } catch (e) { + this.emit('error', e); + } + }); - const wallet = await this.ensure({ - id: 'primary' - }); + this.client.bind('chain reset', async (tip) => { + try { + await this.resetChain(tip); + } catch (e) { + this.emit('error', e); + } + }); + } - this.logger.info( - 'Loaded primary wallet (id=%s, wid=%d, address=%s)', - wallet.id, wallet.wid, wallet.getAddress()); + /** + * Open the walletdb, wait for the database to load. + * @returns {Promise} + */ - this.primary = wallet; - this.rpc.wallet = wallet; + async open() { + this.logger.info('Opening WalletDB...'); + await this.db.open(); + await this.db.verify(layout.V.encode(), 'wallet', 7); - if (this.http && this.options.listen) - await this.http.open(); -}; + await this.verifyNetwork(); -/** - * Close the walletdb, wait for the database to close. - * @alias WalletDB#close - * @returns {Promise} - */ + this.depth = await this.getDepth(); -WalletDB.prototype._close = async function _close() { - await this.disconnect(); + if (this.options.wipeNoReally) + await this.wipe(); - if (this.http && this.options.listen) - await this.http.close(); + await this.watch(); + await this.connect(); - for (const wallet of this.wallets.values()) - await wallet.destroy(); + this.logger.info( + 'WalletDB loaded (depth=%d, height=%d, start=%d).', + this.depth, + this.state.height, + this.state.startHeight); - await this.db.close(); + const wallet = await this.ensure({ + id: 'primary' + }); - if (this.options.listen) - await this.logger.close(); -}; + const addr = await wallet.receiveAddress(); -/** - * Load the walletdb. - * @returns {Promise} - */ + this.logger.info( + 'Loaded primary wallet (id=%s, wid=%d, address=%s)', + wallet.id, wallet.wid, addr.toString(this.network)); -WalletDB.prototype.load = async function load() { - const unlock = await this.txLock.lock(); - try { - await this.connect(); - await this.init(); - await this.watch(); - await this.sync(); - await this.resend(); - } finally { - unlock(); + this.primary = wallet; } -}; -/** - * Bind to node events. - * @private - */ + /** + * Verify network. + * @returns {Promise} + */ -WalletDB.prototype.bind = function bind() { - if (!this.client) - return; + async verifyNetwork() { + const raw = await this.db.get(layout.O.encode()); - if (this.bound) - return; + if (!raw) { + const b = this.db.batch(); + b.put(layout.O.encode(), fromU32(this.network.magic)); + return b.write(); + } - this.bound = true; + const magic = raw.readUInt32LE(0, true); - this.client.on('error', (err) => { - this.emit('error', err); - }); + if (magic !== this.network.magic) + throw new Error('Network mismatch for WalletDB.'); - this.client.on('block connect', async (entry, txs) => { - try { - await this.addBlock(entry, txs); - } catch (e) { - this.emit('error', e); - } - }); + return undefined; + } - this.client.on('block disconnect', async (entry) => { - try { - await this.removeBlock(entry); - } catch (e) { - this.emit('error', e); - } - }); + /** + * Close the walletdb, wait for the database to close. + * @returns {Promise} + */ - this.client.hook('block rescan', async (entry, txs) => { - try { - await this.rescanBlock(entry, txs); - } catch (e) { - this.emit('error', e); - } - }); + async close() { + await this.disconnect(); - this.client.on('tx', async (tx) => { - try { - await this.addTX(tx); - } catch (e) { - this.emit('error', e); + for (const wallet of this.wallets.values()) { + await wallet.destroy(); + this.unregister(wallet); } - }); - this.client.on('chain reset', async (tip) => { - try { - await this.resetChain(tip); - } catch (e) { - this.emit('error', e); - } - }); -}; + return this.db.close(); + } -/** - * Connect to the node server (client required). - * @returns {Promise} - */ + /** + * Watch addresses and outpoints. + * @private + * @returns {Promise} + */ -WalletDB.prototype.connect = async function connect() { - if (!this.client) - return; + async watch() { + const piter = this.db.iterator({ + gte: layout.p.min(), + lte: layout.p.max() + }); - this.bind(); + let hashes = 0; - await this.client.open(); - await this.setFilter(); -}; + await piter.each((key) => { + const [data] = layout.p.decode(key); -/** - * Disconnect from node server (client required). - * @returns {Promise} - */ + this.filter.add(data); -WalletDB.prototype.disconnect = async function disconnect() { - if (!this.client) - return; + hashes += 1; + }); - await this.client.close(); -}; + this.logger.info('Added %d hashes to WalletDB filter.', hashes); -/** - * Initialize and write initial sync state. - * @returns {Promise} - */ + const oiter = this.db.iterator({ + gte: layout.o.min(), + lte: layout.o.max() + }); -WalletDB.prototype.init = async function init() { - const state = await this.getState(); - const startHeight = this.options.startHeight; + let outpoints = 0; - if (state) { - this.state = state; - return; + await oiter.each((key) => { + const [hash, index] = layout.o.decode(key); + const outpoint = new Outpoint(hash, index); + const data = outpoint.toRaw(); + + this.filter.add(data); + + outpoints += 1; + }); + + this.logger.info('Added %d outpoints to WalletDB filter.', outpoints); } - let tip; - if (this.client) { - if (startHeight != null) { - tip = await this.client.getEntry(startHeight); - if (!tip) - throw new Error('WDB: Could not find start block.'); - } else { - tip = await this.client.getTip(); - } - tip = BlockMeta.fromEntry(tip); - } else { - tip = BlockMeta.fromEntry(this.network.genesis); + /** + * Connect to the node server (client required). + * @returns {Promise} + */ + + async connect() { + return this.client.open(); } - this.logger.info( - 'Initializing WalletDB chain state at %s (%d).', - util.revHex(tip.hash), tip.height); + /** + * Disconnect from node server (client required). + * @returns {Promise} + */ + + async disconnect() { + return this.client.close(); + } - await this.resetState(tip, false); -}; + /** + * Sync state with server on every connect. + * @returns {Promise} + */ -/** - * Watch addresses and outpoints. - * @private - * @returns {Promise} - */ + async syncNode() { + const unlock = await this.txLock.lock(); + try { + this.logger.info('Resyncing from server...'); + await this.syncState(); + await this.syncFilter(); + await this.syncChain(); + await this.resend(); + } finally { + unlock(); + } + } -WalletDB.prototype.watch = async function watch() { - let iter = this.db.iterator({ - gte: layout.p(encoding.NULL_HASH), - lte: layout.p(encoding.HIGH_HASH) - }); + /** + * Initialize and write initial sync state. + * @returns {Promise} + */ - let hashes = 0; - let outpoints = 0; + async syncState() { + const cache = await this.getState(); - while (await iter.next()) { - const {key} = iter; + if (cache) { + if (!await this.getBlock(0)) + return this.migrateState(cache); - try { - const data = layout.pp(key); - this.filter.add(data, 'hex'); - } catch (e) { - await iter.end(); - throw e; + this.state = cache; + this.height = cache.height; + + return undefined; } - hashes++; - } + this.logger.info('Initializing database state from server.'); - iter = this.db.iterator({ - gte: layout.o(encoding.NULL_HASH, 0), - lte: layout.o(encoding.HIGH_HASH, 0xffffffff) - }); + const b = this.db.batch(); + const hashes = await this.client.getHashes(); - while (await iter.next()) { - const {key} = iter; + let tip = null; - try { - const [hash, index] = layout.oo(key); - const outpoint = new Outpoint(hash, index); - const data = outpoint.toRaw(); - this.filter.add(data); - } catch (e) { - await iter.end(); - throw e; + for (let height = 0; height < hashes.length; height++) { + const hash = hashes[height]; + const meta = new BlockMeta(hash, height); + b.put(layout.h.encode(height), meta.toHash()); + tip = meta; } - outpoints++; - } + assert(tip); - this.logger.info('Added %d hashes to WalletDB filter.', hashes); - this.logger.info('Added %d outpoints to WalletDB filter.', outpoints); + const state = this.state.clone(); + state.startHeight = tip.height; + state.startHash = tip.hash; + state.height = tip.height; + state.marked = false; - await this.setFilter(); -}; + b.put(layout.R.encode(), state.toRaw()); -/** - * Connect and sync with the chain server. - * @private - * @returns {Promise} - */ + await b.write(); -WalletDB.prototype.sync = async function sync() { - if (!this.client) - return; + this.state = state; + this.height = state.height; - let height = this.state.height; - let entry; + return undefined; + } - while (height >= 0) { - const tip = await this.getBlock(height); + /** + * Migrate sync state. + * @private + * @param {ChainState} state + * @returns {Promise} + */ - if (!tip) - break; + async migrateState(state) { + const b = this.db.batch(); - entry = await this.client.getEntry(tip.hash); + this.logger.info('Migrating to new sync state.'); - if (entry) - break; + const hashes = await this.client.getHashes(0, state.height); - height--; - } + for (let height = 0; height < hashes.length; height++) { + const hash = hashes[height]; + const meta = new BlockMeta(hash, height); + b.put(layout.h.encode(height), meta.toHash()); + } - if (!entry) { - height = this.state.startHeight; - entry = await this.client.getEntry(this.state.startHash); + await b.write(); - if (!entry) - height = 0; + this.state = state; + this.height = state.height; } - await this.scan(height); -}; + /** + * Connect and sync with the chain server. + * @private + * @returns {Promise} + */ -/** - * Rescan blockchain from a given height. - * @private - * @param {Number?} height - * @returns {Promise} - */ + async syncChain() { + let height = this.state.height; -WalletDB.prototype.scan = async function scan(height) { - if (!this.client) - return; + this.logger.info('Syncing state from height %d.', height); - if (height == null) - height = this.state.startHeight; + for (;;) { + const tip = await this.getBlock(height); + assert(tip); - assert(util.isU32(height), 'WDB: Must pass in a height.'); + if (await this.client.getEntry(tip.hash)) + break; - await this.rollback(height); + assert(height !== 0); + height -= 1; + } - this.logger.info( - 'WalletDB is scanning %d blocks.', - this.state.height - height + 1); + return this.scan(height); + } - const tip = await this.getTip(); + /** + * Rescan blockchain from a given height. + * @private + * @param {Number?} height + * @returns {Promise} + */ - try { - this.rescanning = true; - await this.client.rescan(tip.hash); - } finally { - this.rescanning = false; - } -}; + async scan(height) { + if (height == null) + height = this.state.startHeight; -/** - * Force a rescan. - * @param {Number} height - * @returns {Promise} - */ + assert((height >>> 0) === height, 'WDB: Must pass in a height.'); -WalletDB.prototype.rescan = async function rescan(height) { - const unlock = await this.txLock.lock(); - try { - return await this._rescan(height); - } finally { - unlock(); - } -}; + this.logger.info( + 'WalletDB is scanning %d blocks.', + this.state.height - height + 1); -/** - * Force a rescan (without a lock). - * @private - * @param {Number} height - * @returns {Promise} - */ + await this.rollback(height); -WalletDB.prototype._rescan = async function _rescan(height) { - return await this.scan(height); -}; + const tip = await this.getTip(); -/** - * Broadcast a transaction via chain server. - * @param {TX} tx - * @returns {Promise} - */ + try { + this.rescanning = true; + await this.client.rescan(tip.hash); + } finally { + this.rescanning = false; + } + } + + /** + * Force a rescan. + * @param {Number} height + * @returns {Promise} + */ -WalletDB.prototype.send = async function send(tx) { - if (!this.client) { - this.emit('send', tx); - return; + async rescan(height) { + const unlock = await this.txLock.lock(); + try { + return await this._rescan(height); + } finally { + unlock(); + } } - await this.client.send(tx); -}; + /** + * Force a rescan (without a lock). + * @private + * @param {Number} height + * @returns {Promise} + */ -/** - * Estimate smart fee from chain server. - * @param {Number} blocks - * @returns {Promise} - */ + async _rescan(height) { + return this.scan(height); + } -WalletDB.prototype.estimateFee = async function estimateFee(blocks) { - if (this.feeRate > 0) - return this.feeRate; + /** + * Broadcast a transaction via chain server. + * @param {TX} tx + * @returns {Promise} + */ - if (!this.client) - return this.network.feeRate; + async send(tx) { + return this.client.send(tx); + } - const rate = await this.client.estimateFee(blocks); + /** + * Estimate smart fee from chain server. + * @param {Number} blocks + * @returns {Promise} + */ - if (rate < this.network.feeRate) - return this.network.feeRate; + async estimateFee(blocks) { + if (this.feeRate > 0) + return this.feeRate; - if (rate > this.network.maxFeeRate) - return this.network.maxFeeRate; + const rate = await this.client.estimateFee(blocks); - return rate; -}; + if (rate < this.network.feeRate) + return this.network.feeRate; -/** - * Send filter to the remote node. - * @private - * @returns {Promise} - */ + if (rate > this.network.maxFeeRate) + return this.network.maxFeeRate; -WalletDB.prototype.setFilter = function setFilter() { - if (!this.client) { - this.emit('set filter', this.filter); - return Promise.resolve(); + return rate; } - return this.client.setFilter(this.filter); -}; + /** + * Send filter to the remote node. + * @private + * @returns {Promise} + */ -/** - * Add data to remote filter. - * @private - * @param {Buffer} data - * @returns {Promise} - */ + syncFilter() { + this.logger.info('Sending filter to server (%dmb).', + this.filter.size / 8 / (1 << 20)); -WalletDB.prototype.addFilter = function addFilter(data) { - if (!this.client) { - this.emit('add filter', data); - return Promise.resolve(); + this.filterSent = true; + return this.client.setFilter(this.filter); } - return this.client.addFilter(data); -}; - -/** - * Reset remote filter. - * @private - * @returns {Promise} - */ + /** + * Add data to remote filter. + * @private + * @param {Buffer} data + * @returns {Promise} + */ -WalletDB.prototype.resetFilter = function resetFilter() { - if (!this.client) { - this.emit('reset filter'); - return Promise.resolve(); + addFilter(data) { + if (!this.filterSent) + return undefined; + return this.client.addFilter(data); } - return this.client.resetFilter(); -}; + /** + * Reset remote filter. + * @private + * @returns {Promise} + */ -/** - * Backup the wallet db. - * @param {String} path - * @returns {Promise} - */ + resetFilter() { + if (!this.filterSent) + return undefined; + return this.client.resetFilter(); + } -WalletDB.prototype.backup = function backup(path) { - return this.db.backup(path); -}; + /** + * Backup the wallet db. + * @param {String} path + * @returns {Promise} + */ -/** - * Wipe the txdb - NEVER USE. - * @returns {Promise} - */ + backup(path) { + return this.db.backup(path); + } -WalletDB.prototype.wipe = async function wipe() { - this.logger.warning('Wiping WalletDB TXDB...'); - this.logger.warning('I hope you know what you\'re doing.'); + /** + * Wipe the txdb - NEVER USE. + * @returns {Promise} + */ - const iter = this.db.iterator({ - gte: Buffer.from([0x00]), - lte: Buffer.from([0xff]) - }); + async wipe() { + this.logger.warning('Wiping WalletDB TXDB...'); + this.logger.warning('I hope you know what you\'re doing.'); - const batch = this.db.batch(); - let total = 0; + const iter = this.db.iterator(); + const b = this.db.batch(); - while (await iter.next()) { - const {key} = iter; + let total = 0; - try { + await iter.each((key) => { switch (key[0]) { case 0x62: // b case 0x63: // c @@ -613,1702 +586,1752 @@ WalletDB.prototype.wipe = async function wipe() { case 0x6f: // o case 0x68: // h case 0x52: // R - batch.del(key); - total++; + b.del(key); + total += 1; break; } - } catch (e) { - await iter.end(); - throw e; - } - } + }); - this.logger.warning('Wiped %d txdb records.', total); + this.logger.warning('Wiped %d txdb records.', total); - await batch.write(); -}; + return b.write(); + } -/** - * Get current wallet wid depth. - * @private - * @returns {Promise} - */ + /** + * Get current wallet wid depth. + * @private + * @returns {Promise} + */ -WalletDB.prototype.getDepth = async function getDepth() { - // This may seem like a strange way to do - // this, but updating a global state when - // creating a new wallet is actually pretty - // damn tricky. There would be major atomicity - // issues if updating a global state inside - // a "scoped" state. So, we avoid all the - // nonsense of adding a global lock to - // walletdb.create by simply seeking to the - // highest wallet wid. - const iter = this.db.iterator({ - gte: layout.w(0x00000000), - lte: layout.w(0xffffffff), - reverse: true, - limit: 1 - }); + async getDepth() { + const raw = await this.db.get(layout.D.encode()); - if (!await iter.next()) - return 1; + if (!raw) + return 0; - const {key} = iter; + return raw.readUInt32LE(0, true); + } - await iter.end(); + /** + * Test the bloom filter against a tx or address hash. + * @private + * @param {Hash} hash + * @returns {Boolean} + */ - const depth = layout.ww(key); + testFilter(data) { + return this.filter.test(data); + } - return depth + 1; -}; + /** + * Add hash to local and remote filters. + * @private + * @param {Hash} hash + */ -/** - * Start batch. - * @private - * @param {WalletID} wid - */ + addHash(hash) { + this.filter.add(hash); + return this.addFilter(hash); + } -WalletDB.prototype.start = function start(wallet) { - assert(!wallet.current, 'WDB: Batch already started.'); - wallet.current = this.db.batch(); - wallet.accountCache.start(); - wallet.pathCache.start(); - return wallet.current; -}; + /** + * Add outpoint to local filter. + * @private + * @param {Hash} hash + * @param {Number} index + */ -/** - * Drop batch. - * @private - * @param {WalletID} wid - */ + addOutpoint(hash, index) { + const outpoint = new Outpoint(hash, index); + this.filter.add(outpoint.toRaw()); + } -WalletDB.prototype.drop = function drop(wallet) { - const batch = this.batch(wallet); - wallet.current = null; - wallet.accountCache.drop(); - wallet.pathCache.drop(); - batch.clear(); -}; + /** + * Dump database (for debugging). + * @returns {Promise} - Returns Object. + */ -/** - * Clear batch. - * @private - * @param {WalletID} wid - */ + dump() { + return this.db.dump(); + } -WalletDB.prototype.clear = function clear(wallet) { - const batch = this.batch(wallet); - wallet.accountCache.clear(); - wallet.pathCache.clear(); - batch.clear(); -}; + /** + * Register an object with the walletdb. + * @param {Object} object + */ -/** - * Get batch. - * @private - * @param {WalletID} wid - * @returns {Leveldown.Batch} - */ + register(wallet) { + assert(!this.wallets.has(wallet.wid)); + this.wallets.set(wallet.wid, wallet); + } -WalletDB.prototype.batch = function batch(wallet) { - assert(wallet.current, 'WDB: Batch does not exist.'); - return wallet.current; -}; + /** + * Unregister a object with the walletdb. + * @param {Object} object + * @returns {Boolean} + */ -/** - * Save batch. - * @private - * @param {WalletID} wid - * @returns {Promise} - */ + unregister(wallet) { + assert(this.wallets.has(wallet.wid)); + this.wallets.delete(wallet.wid); + } + + /** + * Map wallet id to wid. + * @param {String|Number} id + * @returns {Promise} - Returns {Number}. + */ -WalletDB.prototype.commit = async function commit(wallet) { - const batch = this.batch(wallet); + async ensureWID(id) { + if (typeof id === 'number') { + if (!await this.db.has(layout.W.encode(id))) + return -1; + return id; + } - try { - await batch.write(); - } catch (e) { - wallet.current = null; - wallet.accountCache.drop(); - wallet.pathCache.drop(); - throw e; + return this.getWID(id); } - wallet.current = null; - wallet.accountCache.commit(); - wallet.pathCache.commit(); -}; + /** + * Map wallet id to wid. + * @param {String} id + * @returns {Promise} - Returns {Number}. + */ -/** - * Test the bloom filter against a tx or address hash. - * @private - * @param {Hash} hash - * @returns {Boolean} - */ + async getWID(id) { + const data = await this.db.get(layout.l.encode(id)); -WalletDB.prototype.testFilter = function testFilter(data) { - return this.filter.test(data, 'hex'); -}; + if (!data) + return -1; -/** - * Add hash to local and remote filters. - * @private - * @param {Hash} hash - */ + assert(data.length === 4); -WalletDB.prototype.addHash = function addHash(hash) { - this.filter.add(hash, 'hex'); - return this.addFilter(hash); -}; + return data.readUInt32LE(0, true); + } -/** - * Add outpoint to local filter. - * @private - * @param {Hash} hash - * @param {Number} index - */ + /** + * Map wallet wid to id. + * @param {Number} wid + * @returns {Promise} - Returns {String}. + */ -WalletDB.prototype.addOutpoint = function addOutpoint(hash, index) { - const outpoint = new Outpoint(hash, index); - this.filter.add(outpoint.toRaw()); -}; + async getID(wid) { + const data = await this.db.get(layout.W.encode(wid)); -/** - * Dump database (for debugging). - * @returns {Promise} - Returns Object. - */ + if (!data) + return null; -WalletDB.prototype.dump = function dump() { - return this.db.dump(); -}; + return toString(data); + } -/** - * Register an object with the walletdb. - * @param {Object} object - */ + /** + * Get a wallet from the database, setup watcher. + * @param {Number|String} id + * @returns {Promise} - Returns {@link Wallet}. + */ -WalletDB.prototype.register = function register(wallet) { - assert(!this.wallets.has(wallet.wid)); - this.wallets.set(wallet.wid, wallet); -}; + async get(id) { + const wid = await this.ensureWID(id); -/** - * Unregister a object with the walletdb. - * @param {Object} object - * @returns {Boolean} - */ + if (wid === -1) + return null; -WalletDB.prototype.unregister = function unregister(wallet) { - assert(this.wallets.has(wallet.wid)); - this.wallets.delete(wallet.wid); -}; + const unlock = await this.readLock.lock(wid); -/** - * Map wallet id to wid. - * @param {String} id - * @returns {Promise} - Returns {WalletID}. - */ + try { + return await this._get(wid); + } finally { + unlock(); + } + } -WalletDB.prototype.getWalletID = async function getWalletID(id) { - if (!id) - return null; + /** + * Get a wallet from the database without a lock. + * @private + * @param {Number} wid + * @returns {Promise} - Returns {@link Wallet}. + */ - if (typeof id === 'number') - return id; + async _get(wid) { + const cache = this.wallets.get(wid); - const cache = this.widCache.get(id); + if (cache) + return cache; - if (cache) - return cache; + const id = await this.getID(wid); - const data = await this.db.get(layout.l(id)); + if (!id) + return null; - if (!data) - return null; + const data = await this.db.get(layout.w.encode(wid)); + assert(data); - const wid = data.readUInt32LE(0, true); + const wallet = Wallet.fromRaw(this, data); - this.widCache.set(id, wid); + wallet.wid = wid; + wallet.id = id; - return wid; -}; + await wallet.open(); -/** - * Get a wallet from the database, setup watcher. - * @param {WalletID} wid - * @returns {Promise} - Returns {@link Wallet}. - */ + this.register(wallet); -WalletDB.prototype.get = async function get(id) { - const wid = await this.getWalletID(id); + return wallet; + } - if (!wid) - return null; + /** + * Save a wallet to the database. + * @param {Wallet} wallet + */ - const unlock = await this.readLock.lock(wid); + save(b, wallet) { + const wid = wallet.wid; + const id = wallet.id; - try { - return await this._get(wid); - } finally { - unlock(); + b.put(layout.w.encode(wid), wallet.toRaw()); + b.put(layout.W.encode(wid), fromString(id)); + b.put(layout.l.encode(id), fromU32(wid)); } -}; -/** - * Get a wallet from the database without a lock. - * @private - * @param {WalletID} wid - * @returns {Promise} - Returns {@link Wallet}. - */ + /** + * Increment the wid depth. + * @param {Batch} b + * @param {Number} wid + */ -WalletDB.prototype._get = async function _get(wid) { - const cache = this.wallets.get(wid); + increment(b, wid) { + b.put(layout.D.encode(), fromU32(wid + 1)); + } - if (cache) - return cache; + /** + * Rename a wallet. + * @param {Wallet} wallet + * @param {String} id + * @returns {Promise} + */ - const data = await this.db.get(layout.w(wid)); + async rename(wallet, id) { + const unlock = await this.writeLock.lock(); + try { + return await this._rename(wallet, id); + } finally { + unlock(); + } + } - if (!data) - return null; + /** + * Rename a wallet without a lock. + * @private + * @param {Wallet} wallet + * @param {String} id + * @returns {Promise} + */ - const wallet = Wallet.fromRaw(this, data); + async _rename(wallet, id) { + if (!common.isName(id)) + throw new Error('WDB: Bad wallet ID.'); - await wallet.open(); + if (await this.has(id)) + throw new Error('WDB: ID not available.'); - this.register(wallet); + const b = this.db.batch(); - return wallet; -}; + // Update wid->id index. + b.put(layout.W.encode(wallet.wid), fromString(id)); -/** - * Save a wallet to the database. - * @param {Wallet} wallet - */ + // Delete old id->wid index. + b.del(layout.l.encode(wallet.id)); -WalletDB.prototype.save = function save(wallet) { - const wid = wallet.wid; - const id = wallet.id; - const batch = this.batch(wallet); + // Add new id->wid index. + b.put(layout.l.encode(id), fromU32(wallet.wid)); - this.widCache.set(id, wid); + await b.write(); - batch.put(layout.w(wid), wallet.toRaw()); - batch.put(layout.l(id), U32(wid)); -}; + wallet.id = id; + } -/** - * Rename a wallet. - * @param {Wallet} wallet - * @param {String} id - * @returns {Promise} - */ + /** + * Rename an account. + * @param {Account} account + * @param {String} name + */ -WalletDB.prototype.rename = async function rename(wallet, id) { - const unlock = await this.writeLock.lock(); - try { - return await this._rename(wallet, id); - } finally { - unlock(); - } -}; + renameAccount(b, account, name) { + const wid = account.wid; + const index = account.accountIndex; -/** - * Rename a wallet without a lock. - * @private - * @param {Wallet} wallet - * @param {String} id - * @returns {Promise} - */ + // Remove old wid/name->account index. + b.del(layout.i.encode(wid, account.name)); -WalletDB.prototype._rename = async function _rename(wallet, id) { - const old = wallet.id; + // Name->Index lookups + b.put(layout.i.encode(wid, name), fromU32(index)); - if (!common.isName(id)) - throw new Error('WDB: Bad wallet ID.'); + // Index->Name lookups + b.put(layout.n.encode(wid, index), fromString(name)); - if (await this.has(id)) - throw new Error('WDB: ID not available.'); + account.name = name; + } - const batch = this.start(wallet); - batch.del(layout.l(old)); + /** + * Remove a wallet. + * @param {Number|String} id + * @returns {Promise} + */ - wallet.id = id; + async remove(id) { + const wid = await this.ensureWID(id); - this.save(wallet); + if (wid === -1) + return false; - await this.commit(wallet); + // Grab all locks. + const unlock1 = await this.readLock.lock(wid); + const unlock2 = await this.writeLock.lock(); + const unlock3 = await this.txLock.lock(); - this.widCache.remove(old); + try { + return await this._remove(wid); + } finally { + unlock3(); + unlock2(); + unlock1(); + } + } - const paths = wallet.pathCache.values(); + /** + * Remove a wallet (without a lock). + * @private + * @param {Number} wid + * @returns {Promise} + */ - for (const path of paths) - path.id = id; -}; + async _remove(wid) { + const id = await this.getID(wid); -/** - * Rename an account. - * @param {Account} account - * @param {String} name - */ + if (!id) + return false; -WalletDB.prototype.renameAccount = function renameAccount(account, name) { - const wallet = account.wallet; - const batch = this.batch(wallet); + if (id === 'primary') + throw new Error('Cannot remove primary wallet.'); - // Remove old wid/name->account index. - batch.del(layout.i(account.wid, account.name)); + const b = this.db.batch(); - account.name = name; + b.del(layout.w.encode(wid)); + b.del(layout.W.encode(wid)); + b.del(layout.l.encode(id)); - this.saveAccount(account); -}; + const piter = this.db.iterator({ + gte: layout.P.min(wid), + lte: layout.P.max(wid) + }); -/** - * Get a wallet with token auth first. - * @param {WalletID} wid - * @param {String|Buffer} token - * @returns {Promise} - Returns {@link Wallet}. - */ + await piter.each((key, value) => { + const [, hash] = layout.P.decode(key); + b.del(key); + return this.removePathMap(b, hash, wid); + }); -WalletDB.prototype.auth = async function auth(wid, token) { - const wallet = await this.get(wid); + const removeRange = (opt) => { + return this.db.iterator(opt).each(key => b.del(key)); + }; - if (!wallet) - return null; + await removeRange({ + gte: layout.r.min(wid), + lte: layout.r.max(wid) + }); - if (typeof token === 'string') { - if (!util.isHex256(token)) - throw new Error('WDB: Authentication error.'); - token = Buffer.from(token, 'hex'); - } + await removeRange({ + gte: layout.a.min(wid), + lte: layout.a.max(wid) + }); - // Compare in constant time: - if (!ccmp(token, wallet.token)) - throw new Error('WDB: Authentication error.'); + await removeRange({ + gte: layout.i.min(wid), + lte: layout.i.max(wid) + }); - return wallet; -}; + await removeRange({ + gte: layout.n.min(wid), + lte: layout.n.max(wid) + }); -/** - * Create a new wallet, save to database, setup watcher. - * @param {Object} options - See {@link Wallet}. - * @returns {Promise} - Returns {@link Wallet}. - */ + await removeRange({ + gt: layout.t.encode(wid), + lt: layout.t.encode(wid + 1) + }); -WalletDB.prototype.create = async function create(options) { - const unlock = await this.writeLock.lock(); + const bucket = this.db.bucket(layout.t.encode(wid)); - if (!options) - options = {}; + const biter = bucket.iterator({ + gte: tlayout.b.min(), + lte: tlayout.b.max() + }); - try { - return await this._create(options); - } finally { - unlock(); - } -}; + await biter.each((key, value) => { + const [height] = tlayout.b.decode(key); + return this.removeBlockMap(b, height, wid); + }); -/** - * Create a new wallet, save to database without a lock. - * @private - * @param {Object} options - See {@link Wallet}. - * @returns {Promise} - Returns {@link Wallet}. - */ + const siter = bucket.iterator({ + gte: tlayout.s.min(), + lte: tlayout.s.max(), + keys: true + }); -WalletDB.prototype._create = async function _create(options) { - const exists = await this.has(options.id); + await siter.each((key, value) => { + const [hash, index] = tlayout.s.decode(key); + return this.removeOutpointMap(b, hash, index, wid); + }); - if (exists) - throw new Error('WDB: Wallet already exists.'); + const uiter = bucket.iterator({ + gte: tlayout.p.min(), + lte: tlayout.p.max(), + keys: true + }); - const wallet = Wallet.fromOptions(this, options); - wallet.wid = this.depth++; + await uiter.each((key, value) => { + const [hash] = tlayout.p.decode(key); + return this.removeTXMap(b, hash, wid); + }); - await wallet.init(options); + const wallet = this.wallets.get(wid); - this.register(wallet); + if (wallet) { + await wallet.destroy(); + this.unregister(wallet); + } - this.logger.info('Created wallet %s in WalletDB.', wallet.id); + await b.write(); - return wallet; -}; + return true; + } -/** - * Test for the existence of a wallet. - * @param {WalletID} id - * @returns {Promise} - */ + /** + * Get a wallet with token auth first. + * @param {Number|String} id + * @param {Buffer} token + * @returns {Promise} - Returns {@link Wallet}. + */ -WalletDB.prototype.has = async function has(id) { - const wid = await this.getWalletID(id); - return wid != null; -}; + async auth(id, token) { + const wallet = await this.get(id); -/** - * Attempt to create wallet, return wallet if already exists. - * @param {Object} options - See {@link Wallet}. - * @returns {Promise} - */ + if (!wallet) + return null; -WalletDB.prototype.ensure = async function ensure(options) { - const wallet = await this.get(options.id); + // Compare in constant time: + if (!safeEqual(token, wallet.token)) + throw new Error('WDB: Authentication error.'); - if (wallet) return wallet; + } - return await this.create(options); -}; + /** + * Create a new wallet, save to database, setup watcher. + * @param {Object} options - See {@link Wallet}. + * @returns {Promise} - Returns {@link Wallet}. + */ -/** - * Get an account from the database by wid. - * @private - * @param {WalletID} wid - * @param {Number} index - Account index. - * @returns {Promise} - Returns {@link Wallet}. - */ + async create(options) { + const unlock = await this.writeLock.lock(); -WalletDB.prototype.getAccount = async function getAccount(wid, index) { - const data = await this.db.get(layout.a(wid, index)); + if (!options) + options = {}; - if (!data) - return null; + try { + return await this._create(options); + } finally { + unlock(); + } + } - return Account.fromRaw(this, data); -}; + /** + * Create a new wallet, save to database without a lock. + * @private + * @param {Object} options - See {@link Wallet}. + * @returns {Promise} - Returns {@link Wallet}. + */ -/** - * List account names and indexes from the db. - * @param {WalletID} wid - * @returns {Promise} - Returns Array. - */ + async _create(options) { + if (options.id) { + if (await this.has(options.id)) + throw new Error('WDB: Wallet already exists.'); + } -WalletDB.prototype.getAccounts = function getAccounts(wid) { - return this.db.values({ - gte: layout.n(wid, 0x00000000), - lte: layout.n(wid, 0xffffffff), - parse: data => data.toString('ascii') - }); -}; + const wallet = Wallet.fromOptions(this, options); -/** - * Lookup the corresponding account name's index. - * @param {WalletID} wid - * @param {String} name - Account name/index. - * @returns {Promise} - Returns Number. - */ + wallet.wid = this.depth; -WalletDB.prototype.getAccountIndex = async function getAccountIndex(wid, name) { - const index = await this.db.get(layout.i(wid, name)); + await wallet.init(options, options.passphrase); - if (!index) - return -1; + this.depth += 1; - return index.readUInt32LE(0, true); -}; + this.register(wallet); -/** - * Lookup the corresponding account index's name. - * @param {WalletID} wid - * @param {Number} index - * @returns {Promise} - Returns Number. - */ + this.logger.info('Created wallet %s in WalletDB.', wallet.id); -WalletDB.prototype.getAccountName = async function getAccountName(wid, index) { - const name = await this.db.get(layout.n(wid, index)); + return wallet; + } - if (!name) - return null; + /** + * Test for the existence of a wallet. + * @param {Number|String} id + * @returns {Promise} + */ - return name.toString('ascii'); -}; + async has(id) { + const wid = await this.ensureWID(id); + return wid !== -1; + } -/** - * Save an account to the database. - * @param {Account} account - * @returns {Promise} - */ + /** + * Attempt to create wallet, return wallet if already exists. + * @param {Object} options - See {@link Wallet}. + * @returns {Promise} + */ -WalletDB.prototype.saveAccount = function saveAccount(account) { - const wid = account.wid; - const wallet = account.wallet; - const index = account.accountIndex; - const name = account.name; - const batch = this.batch(wallet); + async ensure(options) { + if (options.id) { + const wallet = await this.get(options.id); - // Account data - batch.put(layout.a(wid, index), account.toRaw()); + if (wallet) + return wallet; + } - // Name->Index lookups - batch.put(layout.i(wid, name), U32(index)); + return this.create(options); + } - // Index->Name lookups - batch.put(layout.n(wid, index), Buffer.from(name, 'ascii')); + /** + * Get an account from the database by wid. + * @private + * @param {Number} wid + * @param {Number} index - Account index. + * @returns {Promise} - Returns {@link Wallet}. + */ - wallet.accountCache.push(index, account); -}; + async getAccount(wid, index) { + const name = await this.getAccountName(wid, index); -/** - * Test for the existence of an account. - * @param {WalletID} wid - * @param {String|Number} acct - * @returns {Promise} - Returns Boolean. - */ + if (!name) + return null; -WalletDB.prototype.hasAccount = function hasAccount(wid, index) { - return this.db.has(layout.a(wid, index)); -}; + const data = await this.db.get(layout.a.encode(wid, index)); + assert(data); -/** - * Lookup the corresponding account name's index. - * @param {WalletID} wid - * @param {String|Number} name - Account name/index. - * @returns {Promise} - Returns Number. - */ + const account = Account.fromRaw(this, data); -WalletDB.prototype.getPathMap = async function getPathMap(hash) { - const cache = this.pathMapCache.get(hash); + account.accountIndex = index; + account.name = name; - if (cache) - return cache; + return account; + } - const data = await this.db.get(layout.p(hash)); + /** + * List account names and indexes from the db. + * @param {Number} wid + * @returns {Promise} - Returns Array. + */ - if (!data) - return null; + async getAccounts(wid) { + return this.db.values({ + gte: layout.n.min(wid), + lte: layout.n.max(wid), + parse: toString + }); + } - const map = PathMapRecord.fromRaw(hash, data); + /** + * Lookup the corresponding account name's index. + * @param {Number} wid + * @param {String} name - Account name/index. + * @returns {Promise} - Returns Number. + */ - this.pathMapCache.set(hash, map); + async getAccountIndex(wid, name) { + const index = await this.db.get(layout.i.encode(wid, name)); - return map; -}; + if (!index) + return -1; -/** - * Save an address to the path map. - * @param {Wallet} wallet - * @param {WalletKey} ring - * @returns {Promise} - */ + return index.readUInt32LE(0, true); + } -WalletDB.prototype.saveKey = function saveKey(wallet, ring) { - return this.savePath(wallet, ring.toPath()); -}; + /** + * Lookup the corresponding account index's name. + * @param {Number} wid + * @param {Number} index + * @returns {Promise} - Returns Number. + */ -/** - * Save a path to the path map. - * - * The path map exists in the form of: - * - `p[address-hash] -> wid map` - * - `P[wid][address-hash] -> path data` - * - `r[wid][account-index][address-hash] -> dummy` - * - * @param {Wallet} wallet - * @param {Path} path - * @returns {Promise} - */ + async getAccountName(wid, index) { + const name = await this.db.get(layout.n.encode(wid, index)); -WalletDB.prototype.savePath = async function savePath(wallet, path) { - const wid = wallet.wid; - const hash = path.hash; - const batch = this.batch(wallet); + if (!name) + return null; - await this.addHash(hash); + return toString(name); + } - let map = await this.getPathMap(hash); + /** + * Save an account to the database. + * @param {Account} account + * @returns {Promise} + */ - if (!map) - map = new PathMapRecord(hash); + saveAccount(b, account) { + const wid = account.wid; + const index = account.accountIndex; + const name = account.name; - if (!map.add(wid)) - return; + // Account data + b.put(layout.a.encode(wid, index), account.toRaw()); - this.pathMapCache.set(hash, map); - wallet.pathCache.push(hash, path); + // Name->Index lookups + b.put(layout.i.encode(wid, name), fromU32(index)); - // Address Hash -> Wallet Map - batch.put(layout.p(hash), map.toRaw()); + // Index->Name lookups + b.put(layout.n.encode(wid, index), fromString(name)); + } - // Wallet ID + Address Hash -> Path Data - batch.put(layout.P(wid, hash), path.toRaw()); + /** + * Test for the existence of an account. + * @param {Number} wid + * @param {String|Number} acct + * @returns {Promise} - Returns Boolean. + */ - // Wallet ID + Account Index + Address Hash -> Dummy - batch.put(layout.r(wid, path.account, hash), null); -}; + async hasAccount(wid, index) { + return this.db.has(layout.a.encode(wid, index)); + } -/** - * Retrieve path by hash. - * @param {WalletID} wid - * @param {Hash} hash - * @returns {Promise} - */ + /** + * Save an address to the path map. + * @param {Wallet} wallet + * @param {WalletKey} ring + * @returns {Promise} + */ -WalletDB.prototype.getPath = async function getPath(wid, hash) { - const data = await this.db.get(layout.P(wid, hash)); + async saveKey(b, wid, ring) { + return this.savePath(b, wid, ring.toPath()); + } - if (!data) - return null; + /** + * Save a path to the path map. + * + * The path map exists in the form of: + * - `p[address-hash] -> wid map` + * - `P[wid][address-hash] -> path data` + * - `r[wid][account-index][address-hash] -> dummy` + * + * @param {Wallet} wallet + * @param {Path} path + * @returns {Promise} + */ - const path = Path.fromRaw(data); - path.wid = wid; - path.hash = hash; + async savePath(b, wid, path) { + // Address Hash -> Wallet Map + await this.addPathMap(b, path.hash, wid); - return path; -}; + // Wallet ID + Address Hash -> Path Data + b.put(layout.P.encode(wid, path.hash), path.toRaw()); -/** - * Test whether a wallet contains a path. - * @param {WalletID} wid - * @param {Hash} hash - * @returns {Promise} - */ + // Wallet ID + Account Index + Address Hash -> Dummy + b.put(layout.r.encode(wid, path.account, path.hash), null); + } -WalletDB.prototype.hasPath = function hasPath(wid, hash) { - return this.db.has(layout.P(wid, hash)); -}; + /** + * Retrieve path by hash. + * @param {Number} wid + * @param {Hash} hash + * @returns {Promise} + */ -/** - * Get all address hashes. - * @returns {Promise} - */ + async getPath(wid, hash) { + const path = await this.readPath(wid, hash); -WalletDB.prototype.getHashes = function getHashes() { - return this.db.keys({ - gte: layout.p(encoding.NULL_HASH), - lte: layout.p(encoding.HIGH_HASH), - parse: layout.pp - }); -}; + if (!path) + return null; -/** - * Get all outpoints. - * @returns {Promise} - */ + path.name = await this.getAccountName(wid, path.account); + assert(path.name); -WalletDB.prototype.getOutpoints = function getOutpoints() { - return this.db.keys({ - gte: layout.o(encoding.NULL_HASH, 0), - lte: layout.o(encoding.HIGH_HASH, 0xffffffff), - parse: (key) => { - const [hash, index] = layout.oo(key); - return new Outpoint(hash, index); - } - }); -}; + return path; + } -/** - * Get all address hashes. - * @param {WalletID} wid - * @returns {Promise} - */ + /** + * Retrieve path by hash. + * @param {Number} wid + * @param {Hash} hash + * @returns {Promise} + */ -WalletDB.prototype.getWalletHashes = function getWalletHashes(wid) { - return this.db.keys({ - gte: layout.P(wid, encoding.NULL_HASH), - lte: layout.P(wid, encoding.HIGH_HASH), - parse: layout.Pp - }); -}; + async readPath(wid, hash) { + const data = await this.db.get(layout.P.encode(wid, hash)); -/** - * Get all account address hashes. - * @param {WalletID} wid - * @param {Number} account - * @returns {Promise} - */ + if (!data) + return null; -WalletDB.prototype.getAccountHashes = function getAccountHashes(wid, account) { - return this.db.keys({ - gte: layout.r(wid, account, encoding.NULL_HASH), - lte: layout.r(wid, account, encoding.HIGH_HASH), - parse: layout.rr - }); -}; + const path = Path.fromRaw(data); + path.hash = hash; -/** - * Get all paths for a wallet. - * @param {WalletID} wid - * @returns {Promise} - */ + return path; + } -WalletDB.prototype.getWalletPaths = async function getWalletPaths(wid) { - const items = await this.db.range({ - gte: layout.P(wid, encoding.NULL_HASH), - lte: layout.P(wid, encoding.HIGH_HASH) - }); + /** + * Test whether a wallet contains a path. + * @param {Number} wid + * @param {Hash} hash + * @returns {Promise} + */ - const paths = []; + async hasPath(wid, hash) { + return this.db.has(layout.P.encode(wid, hash)); + } - for (const item of items) { - const hash = layout.Pp(item.key); - const path = Path.fromRaw(item.value); + /** + * Get all address hashes. + * @returns {Promise} + */ - path.hash = hash; - path.wid = wid; + async getHashes() { + return this.db.keys({ + gte: layout.p.min(), + lte: layout.p.max(), + parse: key => layout.p.decode(key)[0] + }); + } - paths.push(path); + /** + * Get all outpoints. + * @returns {Promise} + */ + + async getOutpoints() { + return this.db.keys({ + gte: layout.o.min(), + lte: layout.o.max(), + parse: (key) => { + const [hash, index] = layout.o.decode(key); + return new Outpoint(hash, index); + } + }); } - return paths; -}; + /** + * Get all address hashes. + * @param {Number} wid + * @returns {Promise} + */ -/** - * Get all wallet ids. - * @returns {Promise} - */ + async getWalletHashes(wid) { + return this.db.keys({ + gte: layout.P.min(wid), + lte: layout.P.max(wid), + parse: key => layout.P.decode(key)[1] + }); + } -WalletDB.prototype.getWallets = function getWallets() { - return this.db.keys({ - gte: layout.l('\x00'), - lte: layout.l('\xff'), - parse: layout.ll - }); -}; + /** + * Get all account address hashes. + * @param {Number} wid + * @param {Number} account + * @returns {Promise} + */ -/** - * Encrypt all imported keys for a wallet. - * @param {WalletID} wid - * @param {Buffer} key - * @returns {Promise} - */ + async getAccountHashes(wid, account) { + return this.db.keys({ + gte: layout.r.min(wid, account), + lte: layout.r.max(wid, account), + parse: key => layout.r.decode(key)[2] + }); + } -WalletDB.prototype.encryptKeys = async function encryptKeys(wallet, key) { - const wid = wallet.wid; - const paths = await wallet.getPaths(); - const batch = this.batch(wallet); + /** + * Get all paths for a wallet. + * @param {Number} wid + * @returns {Promise} + */ - for (let path of paths) { - if (!path.data) - continue; + async getWalletPaths(wid) { + const items = await this.db.range({ + gte: layout.P.min(wid), + lte: layout.P.max(wid) + }); - assert(!path.encrypted); + const paths = []; - const hash = Buffer.from(path.hash, 'hex'); - const iv = hash.slice(0, 16); + for (const {key, value} of items) { + const [, hash] = layout.P.decode(key); + const path = Path.fromRaw(value); - path = path.clone(); - path.data = aes.encipher(path.data, key, iv); - path.encrypted = true; + path.hash = hash; + path.name = await this.getAccountName(wid, path.account); + assert(path.name); - wallet.pathCache.push(path.hash, path); + paths.push(path); + } - batch.put(layout.P(wid, path.hash), path.toRaw()); + return paths; } -}; -/** - * Decrypt all imported keys for a wallet. - * @param {WalletID} wid - * @param {Buffer} key - * @returns {Promise} - */ + /** + * Get all wallet ids. + * @returns {Promise} + */ -WalletDB.prototype.decryptKeys = async function decryptKeys(wallet, key) { - const wid = wallet.wid; - const paths = await wallet.getPaths(); - const batch = this.batch(wallet); + async getWallets() { + return this.db.values({ + gte: layout.W.min(), + lte: layout.W.max(), + parse: toString + }); + } - for (let path of paths) { - if (!path.data) - continue; + /** + * Encrypt all imported keys for a wallet. + * @param {Number} wid + * @param {Buffer} key + * @returns {Promise} + */ - assert(path.encrypted); + async encryptKeys(b, wid, key) { + const iter = this.db.iterator({ + gte: layout.P.min(wid), + lte: layout.P.max(wid), + values: true + }); - const hash = Buffer.from(path.hash, 'hex'); - const iv = hash.slice(0, 16); + await iter.each((k, value) => { + const [, hash] = layout.P.decode(k); + const path = Path.fromRaw(value); - path = path.clone(); - path.data = aes.decipher(path.data, key, iv); - path.encrypted = false; + if (!path.data) + return; - wallet.pathCache.push(path.hash, path); + assert(!path.encrypted); - batch.put(layout.P(wid, path.hash), path.toRaw()); - } -}; + const iv = hash.slice(0, 16); -/** - * Resend all pending transactions. - * @returns {Promise} - */ - -WalletDB.prototype.resend = async function resend() { - const keys = await this.db.keys({ - gte: layout.w(0x00000000), - lte: layout.w(0xffffffff) - }); + path.data = aes.encipher(path.data, key, iv); + path.encrypted = true; - for (const key of keys) { - const wid = layout.ww(key); - await this.resendPending(wid); + b.put(k, path.toRaw()); + }); } -}; -/** - * Resend all pending transactions for a specific wallet. - * @private - * @param {WalletID} wid - * @returns {Promise} - */ + /** + * Decrypt all imported keys for a wallet. + * @param {Number} wid + * @param {Buffer} key + * @returns {Promise} + */ -WalletDB.prototype.resendPending = async function resendPending(wid) { - const layout = layouts.txdb; + async decryptKeys(b, wid, key) { + const iter = this.db.iterator({ + gte: layout.P.min(wid), + lte: layout.P.max(wid), + values: true + }); - const keys = await this.db.keys({ - gte: layout.prefix(wid, layout.p(encoding.NULL_HASH)), - lte: layout.prefix(wid, layout.p(encoding.HIGH_HASH)) - }); + await iter.each((k, value) => { + const [, hash] = layout.P.decode(k); + const path = Path.fromRaw(value); - if (keys.length === 0) - return; + if (!path.data) + return; - this.logger.info( - 'Rebroadcasting %d transactions for %d.', - keys.length, - wid); + assert(path.encrypted); - const txs = []; + const iv = hash.slice(0, 16); - for (const key of keys) { - const hash = layout.pp(key); - const tkey = layout.prefix(wid, layout.t(hash)); - const data = await this.db.get(tkey); + path.data = aes.decipher(path.data, key, iv); + path.encrypted = false; - if (!data) - continue; + b.put(k, path.toRaw()); + }); + } - const wtx = TXRecord.fromRaw(data); + /** + * Resend all pending transactions. + * @returns {Promise} + */ - if (wtx.tx.isCoinbase()) - continue; + async resend() { + const wids = await this.db.keys({ + gte: layout.w.min(), + lte: layout.w.max(), + parse: key => layout.w.decode(key)[0] + }); - txs.push(wtx.tx); + this.logger.info('Resending from %d wallets.', wids.length); + + for (const wid of wids) + await this.resendPending(wid); } - const sorted = common.sortDeps(txs); + /** + * Resend all pending transactions for a specific wallet. + * @private + * @param {Number} wid + * @returns {Promise} + */ - for (const tx of sorted) - await this.send(tx); -}; + async resendPending(wid) { + const prefix = layout.t.encode(wid); + const b = this.db.bucket(prefix); -/** - * Get all wallet ids by output addresses and outpoints. - * @param {Hash[]} hashes - * @returns {Promise} - */ + const hashes = await b.keys({ + gte: tlayout.p.min(), + lte: tlayout.p.max(), + parse: key => tlayout.p.decode(key)[0] + }); + + if (hashes.length === 0) + return; -WalletDB.prototype.getWalletsByTX = async function getWalletsByTX(tx) { - const hashes = tx.getOutputHashes('hex'); - const result = new Set(); + this.logger.info( + 'Rebroadcasting %d transactions for %d.', + hashes.length, + wid); - if (!tx.isCoinbase()) { - for (const input of tx.inputs) { - const prevout = input.prevout; + const txs = []; - if (!this.testFilter(prevout.toRaw())) + for (const hash of hashes) { + const data = await b.get(tlayout.t.encode(hash)); + + if (!data) continue; - const map = await this.getOutpointMap(prevout.hash, prevout.index); + const wtx = TXRecord.fromRaw(data); - if (!map) + if (wtx.tx.isCoinbase()) continue; - for (const wid of map.wids) - result.add(wid); + txs.push(wtx.tx); } + + for (const tx of common.sortDeps(txs)) + await this.send(tx); } - for (const hash of hashes) { - if (!this.testFilter(hash)) - continue; + /** + * Get all wallet ids by output addresses and outpoints. + * @param {Hash[]} hashes + * @returns {Promise} + */ - const map = await this.getPathMap(hash); + async getWalletsByTX(tx) { + const wids = new Set(); - if (!map) - continue; + if (!tx.isCoinbase()) { + for (const {prevout} of tx.inputs) { + const {hash, index} = prevout; - for (const wid of map.wids) - result.add(wid); - } + if (!this.testFilter(prevout.toRaw())) + continue; - if (result.size === 0) - return null; + const map = await this.getOutpointMap(hash, index); - return result; -}; + if (!map) + continue; -/** - * Get the best block hash. - * @returns {Promise} - */ + for (const wid of map.wids) + wids.add(wid); + } + } -WalletDB.prototype.getState = async function getState() { - const data = await this.db.get(layout.R); + const hashes = tx.getOutputHashes(); - if (!data) - return null; + for (const hash of hashes) { + if (!this.testFilter(hash)) + continue; - return ChainState.fromRaw(data); -}; + const map = await this.getPathMap(hash); -/** - * Reset the chain state to a tip/start-block. - * @param {BlockMeta} tip - * @returns {Promise} - */ + if (!map) + continue; -WalletDB.prototype.resetState = async function resetState(tip, marked) { - const batch = this.db.batch(); - const state = this.state.clone(); + for (const wid of map.wids) + wids.add(wid); + } - const iter = this.db.iterator({ - gte: layout.h(0), - lte: layout.h(0xffffffff), - values: false - }); + if (wids.size === 0) + return null; - while (await iter.next()) { - try { - batch.del(iter.key); - } catch (e) { - await iter.end(); - throw e; - } + return wids; } - state.startHeight = tip.height; - state.startHash = tip.hash; - state.height = tip.height; - state.marked = marked; - - batch.put(layout.h(tip.height), tip.toHash()); - batch.put(layout.R, state.toRaw()); + /** + * Get the best block hash. + * @returns {Promise} + */ - await batch.write(); + async getState() { + const data = await this.db.get(layout.R.encode()); - this.state = state; -}; + if (!data) + return null; -/** - * Sync the current chain state to tip. - * @param {BlockMeta} tip - * @returns {Promise} - */ + return ChainState.fromRaw(data); + } -WalletDB.prototype.syncState = async function syncState(tip) { - const batch = this.db.batch(); - const state = this.state.clone(); + /** + * Sync the current chain state to tip. + * @param {BlockMeta} tip + * @returns {Promise} + */ - if (tip.height < state.height) { - // Hashes ahead of our new tip - // that we need to delete. - let height = state.height; - let blocks = height - tip.height; + async setTip(tip) { + const b = this.db.batch(); + const state = this.state.clone(); - if (blocks > this.options.keepBlocks) - blocks = this.options.keepBlocks; + if (tip.height < state.height) { + // Hashes ahead of our new tip + // that we need to delete. + while (state.height !== tip.height) { + b.del(layout.h.encode(state.height)); + state.height -= 1; + } + } else if (tip.height > state.height) { + assert(tip.height === state.height + 1, 'Bad chain sync.'); + state.height += 1; + } - for (let i = 0; i < blocks; i++) { - batch.del(layout.h(height)); - height--; + if (tip.height < state.startHeight) { + state.startHeight = tip.height; + state.startHash = tip.hash; + state.marked = false; } - } else if (tip.height > state.height) { - // Prune old hashes. - const height = tip.height - this.options.keepBlocks; - assert(tip.height === state.height + 1, 'Bad chain sync.'); + // Save tip and state. + b.put(layout.h.encode(tip.height), tip.toHash()); + b.put(layout.R.encode(), state.toRaw()); - if (height >= 0) - batch.del(layout.h(height)); - } + await b.write(); - state.height = tip.height; + this.state = state; + this.height = state.height; + } - // Save tip and state. - batch.put(layout.h(tip.height), tip.toHash()); - batch.put(layout.R, state.toRaw()); + /** + * Mark current state. + * @param {BlockMeta} block + * @returns {Promise} + */ - await batch.write(); + async markState(block) { + const state = this.state.clone(); + state.startHeight = block.height; + state.startHash = block.hash; + state.marked = true; - this.state = state; -}; + const b = this.db.batch(); + b.put(layout.R.encode(), state.toRaw()); + await b.write(); -/** - * Mark the start block once a confirmed tx is seen. - * @param {BlockMeta} tip - * @returns {Promise} - */ + this.state = state; + this.height = state.height; + } -WalletDB.prototype.maybeMark = async function maybeMark(tip) { - if (this.state.marked) - return; + /** + * Get a wallet map. + * @param {Buffer} key + * @returns {Promise} + */ - this.logger.info('Marking WalletDB start block at %s (%d).', - util.revHex(tip.hash), tip.height); + async getMap(key) { + const data = await this.db.get(key); - await this.resetState(tip, true); -}; + if (!data) + return null; -/** - * Get a block->wallet map. - * @param {Number} height - * @returns {Promise} - */ + return MapRecord.fromRaw(data); + } -WalletDB.prototype.getBlockMap = async function getBlockMap(height) { - const data = await this.db.get(layout.b(height)); + /** + * Add wid to a wallet map. + * @param {Wallet} wallet + * @param {Buffer} key + * @param {Number} wid + */ - if (!data) - return null; + async addMap(b, key, wid) { + const data = await this.db.get(key); - return BlockMapRecord.fromRaw(height, data); -}; + if (!data) { + const map = new MapRecord(); + map.add(wid); + b.put(key, map.toRaw()); + return; + } -/** - * Add block to the global block map. - * @param {Wallet} wallet - * @param {Number} height - * @param {BlockMapRecord} block - */ + assert(data.length >= 4); -WalletDB.prototype.writeBlockMap = function writeBlockMap(wallet, height, block) { - const batch = this.batch(wallet); - batch.put(layout.b(height), block.toRaw()); -}; + const len = data.readUInt32LE(0, true); + const bw = bio.write(data.length + 4); -/** - * Remove a block from the global block map. - * @param {Wallet} wallet - * @param {Number} height - */ + bw.writeU32(len + 1); + bw.copy(data, 4, data.length); + bw.writeU32(wid); -WalletDB.prototype.unwriteBlockMap = function unwriteBlockMap(wallet, height) { - const batch = this.batch(wallet); - batch.del(layout.b(height)); -}; + b.put(key, bw.render()); + } -/** - * Get a Unspent->Wallet map. - * @param {Hash} hash - * @param {Number} index - * @returns {Promise} - */ + /** + * Remove wid from a wallet map. + * @param {Wallet} wallet + * @param {Buffer} key + * @param {Number} wid + */ -WalletDB.prototype.getOutpointMap = async function getOutpointMap(hash, index) { - const data = await this.db.get(layout.o(hash, index)); + async removeMap(b, key, wid) { + const map = await this.getMap(key); - if (!data) - return null; + if (!map) + return; - return OutpointMapRecord.fromRaw(hash, index, data); -}; + if (!map.remove(wid)) + return; -/** - * Add an outpoint to global unspent map. - * @param {Wallet} wallet - * @param {Hash} hash - * @param {Number} index - * @param {OutpointMapRecord} map - */ + if (map.size === 0) { + b.del(key); + return; + } -WalletDB.prototype.writeOutpointMap = function writeOutpointMap(wallet, hash, index, map) { - const batch = this.batch(wallet); + b.put(key, map.toRaw()); + } - this.addOutpoint(hash, index); + /** + * Get a wallet map. + * @param {Buffer} key + * @returns {Promise} + */ - batch.put(layout.o(hash, index), map.toRaw()); -}; + async getPathMap(hash) { + return this.getMap(layout.p.encode(hash)); + } -/** - * Remove an outpoint from global unspent map. - * @param {Wallet} wallet - * @param {Hash} hash - * @param {Number} index - */ + /** + * Add wid to a wallet map. + * @param {Wallet} wallet + * @param {Buffer} key + * @param {Number} wid + */ -WalletDB.prototype.unwriteOutpointMap = function unwriteOutpointMap(wallet, hash, index) { - const batch = this.batch(wallet); - batch.del(layout.o(hash, index)); -}; + async addPathMap(b, hash, wid) { + await this.addHash(hash); + return this.addMap(b, layout.p.encode(hash), wid); + } -/** - * Get a wallet block meta. - * @param {Hash} hash - * @returns {Promise} - */ + /** + * Remove wid from a wallet map. + * @param {Wallet} wallet + * @param {Buffer} key + * @param {Number} wid + */ -WalletDB.prototype.getBlock = async function getBlock(height) { - const data = await this.db.get(layout.h(height)); + async removePathMap(b, hash, wid) { + return this.removeMap(b, layout.p.encode(hash), wid); + } - if (!data) - return null; + /** + * Get a wallet map. + * @param {Buffer} key + * @returns {Promise} + */ - const block = new BlockMeta(); - block.hash = data.toString('hex'); - block.height = height; + async getBlockMap(height) { + return this.getMap(layout.b.encode(height)); + } - return block; -}; + /** + * Add wid to a wallet map. + * @param {Wallet} wallet + * @param {Buffer} key + * @param {Number} wid + */ -/** - * Get wallet tip. - * @param {Hash} hash - * @returns {Promise} - */ + async addBlockMap(b, height, wid) { + return this.addMap(b, layout.b.encode(height), wid); + } -WalletDB.prototype.getTip = async function getTip() { - const tip = await this.getBlock(this.state.height); + /** + * Remove wid from a wallet map. + * @param {Wallet} wallet + * @param {Buffer} key + * @param {Number} wid + */ - if (!tip) - throw new Error('WDB: Tip not found!'); + async removeBlockMap(b, height, wid) { + return this.removeMap(b, layout.b.encode(height), wid); + } - return tip; -}; + /** + * Get a wallet map. + * @param {Buffer} key + * @returns {Promise} + */ -/** - * Sync with chain height. - * @param {Number} height - * @returns {Promise} - */ + async getTXMap(hash) { + return this.getMap(layout.T.encode(hash)); + } -WalletDB.prototype.rollback = async function rollback(height) { - if (height > this.state.height) - throw new Error('WDB: Cannot rollback to the future.'); + /** + * Add wid to a wallet map. + * @param {Wallet} wallet + * @param {Buffer} key + * @param {Number} wid + */ - if (height === this.state.height) { - this.logger.debug('Rolled back to same height (%d).', height); - return true; + async addTXMap(b, hash, wid) { + return this.addMap(b, layout.T.encode(hash), wid); } - this.logger.info( - 'Rolling back %d WalletDB blocks to height %d.', - this.state.height - height, height); + /** + * Remove wid from a wallet map. + * @param {Wallet} wallet + * @param {Buffer} key + * @param {Number} wid + */ - let tip = await this.getBlock(height); - let marked = false; + async removeTXMap(b, hash, wid) { + return this.removeMap(b, layout.T.encode(hash), wid); + } - if (tip) { - await this.revert(tip.height); - await this.syncState(tip); - return true; + /** + * Get a wallet map. + * @param {Buffer} key + * @returns {Promise} + */ + + async getOutpointMap(hash, index) { + return this.getMap(layout.o.encode(hash, index)); } - tip = new BlockMeta(); + /** + * Add wid to a wallet map. + * @param {Wallet} wallet + * @param {Buffer} key + * @param {Number} wid + */ - if (height >= this.state.startHeight) { - tip.height = this.state.startHeight; - tip.hash = this.state.startHash; - marked = this.state.marked; + async addOutpointMap(b, hash, index, wid) { + await this.addOutpoint(hash, index); + return this.addMap(b, layout.o.encode(hash, index), wid); + } - this.logger.warning( - 'Rolling back WalletDB to start block (%d).', - tip.height); - } else { - tip.height = 0; - tip.hash = this.network.genesis.hash; - marked = false; + /** + * Remove wid from a wallet map. + * @param {Wallet} wallet + * @param {Buffer} key + * @param {Number} wid + */ - this.logger.warning('Rolling back WalletDB to genesis block.'); + async removeOutpointMap(b, hash, index, wid) { + return this.removeMap(b, layout.o.encode(hash, index), wid); } - await this.revert(tip.height); - await this.resetState(tip, marked); + /** + * Get a wallet block meta. + * @param {Hash} hash + * @returns {Promise} + */ - return false; -}; + async getBlock(height) { + const data = await this.db.get(layout.h.encode(height)); -/** - * Revert TXDB to an older state. - * @param {Number} target - * @returns {Promise} - */ + if (!data) + return null; -WalletDB.prototype.revert = async function revert(target) { - const iter = this.db.iterator({ - gte: layout.b(target + 1), - lte: layout.b(0xffffffff), - reverse: true, - values: true - }); + const block = new BlockMeta(); + block.hash = data; + block.height = height; - let total = 0; + return block; + } - while (await iter.next()) { - const {key, value} = iter; + /** + * Get wallet tip. + * @param {Hash} hash + * @returns {Promise} + */ - try { - const height = layout.bb(key); - const block = BlockMapRecord.fromRaw(height, value); - const txs = block.toArray(); + async getTip() { + const tip = await this.getBlock(this.state.height); - total += txs.length; + if (!tip) + throw new Error('WDB: Tip not found!'); - for (let i = txs.length - 1; i >= 0; i--) { - const tx = txs[i]; - await this._unconfirm(tx); - } - } catch (e) { - await iter.end(); - throw e; - } + return tip; } - this.logger.info('Rolled back %d WalletDB transactions.', total); -}; + /** + * Sync with chain height. + * @param {Number} height + * @returns {Promise} + */ -/** - * Add a block's transactions and write the new best hash. - * @param {ChainEntry} entry - * @returns {Promise} - */ + async rollback(height) { + if (height > this.state.height) + throw new Error('WDB: Cannot rollback to the future.'); -WalletDB.prototype.addBlock = async function addBlock(entry, txs) { - const unlock = await this.txLock.lock(); - try { - return await this._addBlock(entry, txs); - } finally { - unlock(); - } -}; + if (height === this.state.height) { + this.logger.info('Rolled back to same height (%d).', height); + return; + } -/** - * Add a block's transactions without a lock. - * @private - * @param {ChainEntry} entry - * @param {TX[]} txs - * @returns {Promise} - */ + this.logger.info( + 'Rolling back %d WalletDB blocks to height %d.', + this.state.height - height, height); -WalletDB.prototype._addBlock = async function _addBlock(entry, txs) { - const tip = BlockMeta.fromEntry(entry); - let total = 0; + const tip = await this.getBlock(height); + assert(tip); - if (tip.height < this.state.height) { - this.logger.warning( - 'WalletDB is connecting low blocks (%d).', - tip.height); - return total; + await this.revert(tip.height); + await this.setTip(tip); } - if (tip.height === this.state.height) { - // We let blocks of the same height - // through specifically for rescans: - // we always want to rescan the last - // block since the state may have - // updated before the block was fully - // processed (in the case of a crash). - this.logger.warning('Already saw WalletDB block (%d).', tip.height); - } else if (tip.height !== this.state.height + 1) { - throw new Error('WDB: Bad connection (height mismatch).'); - } + /** + * Revert TXDB to an older state. + * @param {Number} target + * @returns {Promise} + */ - // Sync the state to the new tip. - await this.syncState(tip); + async revert(target) { + const iter = this.db.iterator({ + gte: layout.b.encode(target + 1), + lte: layout.b.max(), + reverse: true, + values: true + }); - if (this.options.checkpoints) { - if (tip.height <= this.network.lastCheckpoint) - return total; - } + let total = 0; - for (const tx of txs) { - if (await this._insert(tx, tip)) - total++; - } + await iter.each(async (key, value) => { + const [height] = layout.b.decode(key); + const block = MapRecord.fromRaw(value); - if (total > 0) { - this.logger.info('Connected WalletDB block %s (tx=%d).', - util.revHex(tip.hash), total); - } + for (const wid of block.wids) { + const wallet = await this.get(wid); + assert(wallet); + total += await wallet.revert(height); + } + }); - return total; -}; + this.logger.info('Rolled back %d WalletDB transactions.', total); + } -/** - * Unconfirm a block's transactions - * and write the new best hash (SPV version). - * @param {ChainEntry} entry - * @returns {Promise} - */ + /** + * Add a block's transactions and write the new best hash. + * @param {ChainEntry} entry + * @returns {Promise} + */ -WalletDB.prototype.removeBlock = async function removeBlock(entry) { - const unlock = await this.txLock.lock(); - try { - return await this._removeBlock(entry); - } finally { - unlock(); + async addBlock(entry, txs) { + const unlock = await this.txLock.lock(); + try { + return await this._addBlock(entry, txs); + } finally { + unlock(); + } } -}; -/** - * Unconfirm a block's transactions. - * @private - * @param {ChainEntry} entry - * @returns {Promise} - */ + /** + * Add a block's transactions without a lock. + * @private + * @param {ChainEntry} entry + * @param {TX[]} txs + * @returns {Promise} + */ -WalletDB.prototype._removeBlock = async function _removeBlock(entry) { - const tip = BlockMeta.fromEntry(entry); + async _addBlock(entry, txs) { + const tip = BlockMeta.fromEntry(entry); - if (tip.height > this.state.height) { - this.logger.warning( - 'WalletDB is disconnecting high blocks (%d).', - tip.height); - return 0; - } + if (tip.height < this.state.height) { + this.logger.warning( + 'WalletDB is connecting low blocks (%d).', + tip.height); + return 0; + } - if (tip.height !== this.state.height) - throw new Error('WDB: Bad disconnection (height mismatch).'); + if (tip.height >= this.network.block.slowHeight) + this.logger.debug('Adding block: %d.', tip.height); + + if (tip.height === this.state.height) { + // We let blocks of the same height + // through specifically for rescans: + // we always want to rescan the last + // block since the state may have + // updated before the block was fully + // processed (in the case of a crash). + this.logger.warning('Already saw WalletDB block (%d).', tip.height); + } else if (tip.height !== this.state.height + 1) { + await this.scan(this.state.height); + return 0; + } - const prev = await this.getBlock(tip.height - 1); + // Sync the state to the new tip. + await this.setTip(tip); - if (!prev) - throw new Error('WDB: Bad disconnection (no previous block).'); + if (this.options.checkpoints && !this.state.marked) { + if (tip.height <= this.network.lastCheckpoint) + return 0; + } - // Get the map of txids->wids. - const block = await this.getBlockMap(tip.height); + let total = 0; - if (!block) { - await this.syncState(prev); - return 0; - } + for (const tx of txs) { + if (await this._addTX(tx, tip)) + total += 1; + } - const txs = block.toArray(); + if (total > 0) { + this.logger.info('Connected WalletDB block %h (tx=%d).', + tip.hash, total); + } - for (let i = txs.length - 1; i >= 0; i--) { - const tx = txs[i]; - await this._unconfirm(tx); + return total; } - // Sync the state to the previous tip. - await this.syncState(prev); + /** + * Unconfirm a block's transactions + * and write the new best hash (SPV version). + * @param {ChainEntry} entry + * @returns {Promise} + */ - this.logger.warning('Disconnected wallet block %s (tx=%d).', - util.revHex(tip.hash), block.txs.size); + async removeBlock(entry) { + const unlock = await this.txLock.lock(); + try { + return await this._removeBlock(entry); + } finally { + unlock(); + } + } - return block.txs.size; -}; + /** + * Unconfirm a block's transactions. + * @private + * @param {ChainEntry} entry + * @returns {Promise} + */ -/** - * Rescan a block. - * @private - * @param {ChainEntry} entry - * @param {TX[]} txs - * @returns {Promise} - */ + async _removeBlock(entry) { + const tip = BlockMeta.fromEntry(entry); -WalletDB.prototype.rescanBlock = async function rescanBlock(entry, txs) { - if (!this.rescanning) { - this.logger.warning('Unsolicited rescan block: %s.', entry.height); - return; - } + if (tip.height === 0) + throw new Error('WDB: Bad disconnection (genesis block).'); - try { - await this._addBlock(entry, txs); - } catch (e) { - this.emit('error', e); - throw e; - } -}; + if (tip.height > this.state.height) { + this.logger.warning( + 'WalletDB is disconnecting high blocks (%d).', + tip.height); + return 0; + } -/** - * Add a transaction to the database, map addresses - * to wallet IDs, potentially store orphans, resolve - * orphans, or confirm a transaction. - * @param {TX} tx - * @returns {Promise} - */ + if (tip.height !== this.state.height) + throw new Error('WDB: Bad disconnection (height mismatch).'); -WalletDB.prototype.addTX = async function addTX(tx) { - const unlock = await this.txLock.lock(); + const prev = await this.getBlock(tip.height - 1); + assert(prev); - try { - return await this._insert(tx); - } finally { - unlock(); - } -}; + // Get the map of block->wids. + const map = await this.getBlockMap(tip.height); -/** - * Add a transaction to the database without a lock. - * @private - * @param {TX} tx - * @param {BlockMeta} block - * @returns {Promise} - */ + if (!map) { + await this.setTip(prev); + return 0; + } -WalletDB.prototype._insert = async function _insert(tx, block) { - const wids = await this.getWalletsByTX(tx); - let result = false; + let total = 0; - assert(!tx.mutable, 'WDB: Cannot add mutable TX.'); + for (const wid of map.wids) { + const wallet = await this.get(wid); + assert(wallet); + total += await wallet.revert(tip.height); + } - if (!wids) - return null; + // Sync the state to the previous tip. + await this.setTip(prev); - this.logger.info( - 'Incoming transaction for %d wallets in WalletDB (%s).', - wids.size, tx.txid()); + this.logger.warning('Disconnected wallet block %h (tx=%d).', + tip.hash, total); - // If this is our first transaction - // in a block, set the start block here. - if (block) - await this.maybeMark(block); + return total; + } - // Insert the transaction - // into every matching wallet. - for (const wid of wids) { - const wallet = await this.get(wid); + /** + * Rescan a block. + * @private + * @param {ChainEntry} entry + * @param {TX[]} txs + * @returns {Promise} + */ - assert(wallet); + async rescanBlock(entry, txs) { + if (!this.rescanning) { + this.logger.warning('Unsolicited rescan block: %d.', entry.height); + return; + } - if (await wallet.add(tx, block)) { - this.logger.info( - 'Added transaction to wallet in WalletDB: %s (%d).', - wallet.id, wid); - result = true; + if (entry.height > this.state.height + 1) { + this.logger.warning('Rescan block too high: %d.', entry.height); + return; + } + + try { + await this._addBlock(entry, txs); + } catch (e) { + this.emit('error', e); + throw e; } } - if (!result) - return null; + /** + * Add a transaction to the database, map addresses + * to wallet IDs, potentially store orphans, resolve + * orphans, or confirm a transaction. + * @param {TX} tx + * @returns {Promise} + */ - return wids; -}; + async addTX(tx) { + const unlock = await this.txLock.lock(); + try { + return await this._addTX(tx); + } finally { + unlock(); + } + } -/** - * Unconfirm a transaction from all - * relevant wallets without a lock. - * @private - * @param {TXMapRecord} tx - * @returns {Promise} - */ + /** + * Add a transaction to the database without a lock. + * @private + * @param {TX} tx + * @param {BlockMeta} block + * @returns {Promise} + */ -WalletDB.prototype._unconfirm = async function _unconfirm(tx) { - for (const wid of tx.wids) { - const wallet = await this.get(wid); - assert(wallet); - await wallet.unconfirm(tx.hash); - } -}; + async _addTX(tx, block) { + const wids = await this.getWalletsByTX(tx); -/** - * Handle a chain reset. - * @param {ChainEntry} entry - * @returns {Promise} - */ + assert(!tx.mutable, 'WDB: Cannot add mutable TX.'); -WalletDB.prototype.resetChain = async function resetChain(entry) { - const unlock = await this.txLock.lock(); - try { - return await this._resetChain(entry); - } finally { - unlock(); - } -}; + if (!wids) + return null; -/** - * Handle a chain reset without a lock. - * @private - * @param {ChainEntry} entry - * @returns {Promise} - */ + if (block && !this.state.marked) + await this.markState(block); -WalletDB.prototype._resetChain = async function _resetChain(entry) { - if (entry.height > this.state.height) - throw new Error('WDB: Bad reset height.'); + this.logger.info( + 'Incoming transaction for %d wallets in WalletDB (%h).', + wids.size, tx.hash()); - // Try to rollback. - if (await this.rollback(entry.height)) - return; + let result = false; - // If we rolled back to the - // start block, we need a rescan. - await this.scan(); -}; + // Insert the transaction + // into every matching wallet. + for (const wid of wids) { + const wallet = await this.get(wid); -/** - * WalletOptions - * @alias module:wallet.WalletOptions - * @constructor - * @param {Object} options - */ + assert(wallet); -function WalletOptions(options) { - if (!(this instanceof WalletOptions)) - return new WalletOptions(options); - - this.network = Network.primary; - this.logger = Logger.global; - this.workers = null; - this.client = null; - this.feeRate = 0; - - this.prefix = null; - this.location = null; - this.db = 'memory'; - this.maxFiles = 64; - this.cacheSize = 16 << 20; - this.compression = true; - this.bufferKeys = layout.binary; - - this.spv = false; - this.witness = false; - this.checkpoints = false; - this.startHeight = 0; - this.keepBlocks = this.network.block.keepBlocks; - this.wipeNoReally = false; - this.apiKey = null; - this.walletAuth = false; - this.noAuth = false; - this.ssl = false; - this.host = '127.0.0.1'; - this.port = this.network.rpcPort + 2; - this.listen = false; - - if (options) - this.fromOptions(options); -} + if (await wallet.add(tx, block)) { + this.logger.info( + 'Added transaction to wallet in WalletDB: %s (%d).', + wallet.id, wid); + result = true; + } + } -/** - * Inject properties from object. - * @private - * @param {Object} options - * @returns {WalletOptions} - */ + if (!result) + return null; -WalletOptions.prototype.fromOptions = function fromOptions(options) { - if (options.network != null) { - this.network = Network.get(options.network); - this.keepBlocks = this.network.block.keepBlocks; - this.port = this.network.rpcPort + 2; + return wids; } - if (options.logger != null) { - assert(typeof options.logger === 'object'); - this.logger = options.logger; - } + /** + * Handle a chain reset. + * @param {ChainEntry} entry + * @returns {Promise} + */ - if (options.workers != null) { - assert(typeof options.workers === 'object'); - this.workers = options.workers; + async resetChain(entry) { + const unlock = await this.txLock.lock(); + try { + return await this._resetChain(entry); + } finally { + unlock(); + } } - if (options.client != null) { - assert(typeof options.client === 'object'); - this.client = options.client; - } + /** + * Handle a chain reset without a lock. + * @private + * @param {ChainEntry} entry + * @returns {Promise} + */ - if (options.feeRate != null) { - assert(util.isU64(options.feeRate)); - this.feeRate = options.feeRate; - } + async _resetChain(entry) { + if (entry.height > this.state.height) + throw new Error('WDB: Bad reset height.'); - if (options.prefix != null) { - assert(typeof options.prefix === 'string'); - this.prefix = options.prefix; - this.location = path.join(this.prefix, 'walletdb'); + return this.rollback(entry.height); } +} - if (options.location != null) { - assert(typeof options.location === 'string'); - this.location = options.location; - } +/** + * Wallet Options + * @alias module:wallet.WalletOptions + */ - if (options.db != null) { - assert(typeof options.db === 'string'); - this.db = options.db; - } +class WalletOptions { + /** + * Create wallet options. + * @constructor + * @param {Object} options + */ + + constructor(options) { + this.network = Network.primary; + this.logger = Logger.global; + this.workers = null; + this.client = null; + this.feeRate = 0; + + this.prefix = null; + this.location = null; + this.memory = true; + this.maxFiles = 64; + this.cacheSize = 16 << 20; + this.compression = true; + + this.spv = false; + this.witness = false; + this.checkpoints = false; + this.wipeNoReally = false; + + if (options) + this.fromOptions(options); + } + + /** + * Inject properties from object. + * @private + * @param {Object} options + * @returns {WalletOptions} + */ + + fromOptions(options) { + if (options.network != null) + this.network = Network.get(options.network); + + if (options.logger != null) { + assert(typeof options.logger === 'object'); + this.logger = options.logger; + } - if (options.maxFiles != null) { - assert(util.isU32(options.maxFiles)); - this.maxFiles = options.maxFiles; - } + if (options.workers != null) { + assert(typeof options.workers === 'object'); + this.workers = options.workers; + } - if (options.cacheSize != null) { - assert(util.isU64(options.cacheSize)); - this.cacheSize = options.cacheSize; - } + if (options.client != null) { + assert(typeof options.client === 'object'); + this.client = options.client; + } - if (options.compression != null) { - assert(typeof options.compression === 'boolean'); - this.compression = options.compression; - } + if (options.feeRate != null) { + assert((options.feeRate >>> 0) === options.feeRate); + this.feeRate = options.feeRate; + } - if (options.spv != null) { - assert(typeof options.spv === 'boolean'); - this.spv = options.spv; - } + if (options.prefix != null) { + assert(typeof options.prefix === 'string'); + this.prefix = options.prefix; + this.location = path.join(this.prefix, 'wallet'); + } - if (options.witness != null) { - assert(typeof options.witness === 'boolean'); - this.witness = options.witness; - } + if (options.location != null) { + assert(typeof options.location === 'string'); + this.location = options.location; + } - if (options.checkpoints != null) { - assert(typeof options.checkpoints === 'boolean'); - this.checkpoints = options.checkpoints; - } + if (options.memory != null) { + assert(typeof options.memory === 'boolean'); + this.memory = options.memory; + } - if (options.startHeight != null) { - assert(typeof options.startHeight === 'number'); - assert(options.startHeight >= 0); - this.startHeight = options.startHeight; - } + if (options.maxFiles != null) { + assert((options.maxFiles >>> 0) === options.maxFiles); + this.maxFiles = options.maxFiles; + } - if (options.wipeNoReally != null) { - assert(typeof options.wipeNoReally === 'boolean'); - this.wipeNoReally = options.wipeNoReally; - } + if (options.cacheSize != null) { + assert(Number.isSafeInteger(options.cacheSize) && options.cacheSize >= 0); + this.cacheSize = options.cacheSize; + } - if (options.apiKey != null) { - assert(typeof options.apiKey === 'string'); - this.apiKey = options.apiKey; - } + if (options.compression != null) { + assert(typeof options.compression === 'boolean'); + this.compression = options.compression; + } - if (options.walletAuth != null) { - assert(typeof options.walletAuth === 'boolean'); - this.walletAuth = options.walletAuth; - } + if (options.spv != null) { + assert(typeof options.spv === 'boolean'); + this.spv = options.spv; + } - if (options.noAuth != null) { - assert(typeof options.noAuth === 'boolean'); - this.noAuth = options.noAuth; - } + if (options.witness != null) { + assert(typeof options.witness === 'boolean'); + this.witness = options.witness; + } - if (options.ssl != null) { - assert(typeof options.ssl === 'boolean'); - this.ssl = options.ssl; - } + if (options.checkpoints != null) { + assert(typeof options.checkpoints === 'boolean'); + this.checkpoints = options.checkpoints; + } - if (options.host != null) { - assert(typeof options.host === 'string'); - this.host = options.host; - } + if (options.wipeNoReally != null) { + assert(typeof options.wipeNoReally === 'boolean'); + this.wipeNoReally = options.wipeNoReally; + } - if (options.port != null) { - assert(typeof options.port === 'number'); - this.port = options.port; + return this; } - if (options.listen != null) { - assert(typeof options.listen === 'boolean'); - this.listen = options.listen; - } + /** + * Instantiate chain options from object. + * @param {Object} options + * @returns {WalletOptions} + */ - return this; -}; + static fromOptions(options) { + return new this().fromOptions(options); + } +} -/** - * Instantiate chain options from object. - * @param {Object} options - * @returns {WalletOptions} +/* + * Helpers */ -WalletOptions.fromOptions = function fromOptions(options) { - return new WalletOptions().fromOptions(options); -}; +function fromU32(num) { + const data = Buffer.allocUnsafe(4); + data.writeUInt32LE(num, 0, true); + return data; +} + +function fromString(str) { + const buf = Buffer.alloc(1 + str.length); + buf[0] = str.length; + buf.write(str, 1, str.length, 'ascii'); + return buf; +} + +function toString(buf) { + assert(buf.length > 0); + assert(buf[0] === buf.length - 1); + return buf.toString('ascii', 1, buf.length); +} /* * Expose diff --git a/lib/wallet/walletkey.js b/lib/wallet/walletkey.js index 73381e54e..3ebd29dd7 100644 --- a/lib/wallet/walletkey.js +++ b/lib/wallet/walletkey.js @@ -12,290 +12,183 @@ const KeyRing = require('../primitives/keyring'); const Path = require('./path'); /** + * Wallet Key * Represents a key ring which amounts to an address. * @alias module:wallet.WalletKey - * @constructor - * @param {Object} options + * @extends KeyRing */ -function WalletKey(options, network) { - if (!(this instanceof WalletKey)) - return new WalletKey(options, network); +class WalletKey extends KeyRing { + /** + * Create a wallet key. + * @constructor + * @param {Object?} options + */ - KeyRing.call(this, options, network); + constructor(options) { + super(options); - this.keyType = Path.types.HD; + this.keyType = Path.types.HD; - this.id = null; - this.wid = -1; - this.name = null; - this.account = -1; - this.branch = -1; - this.index = -1; -} - -Object.setPrototypeOf(WalletKey.prototype, KeyRing.prototype); - -/** - * Instantiate key ring from options. - * @param {Object} options - * @returns {WalletKey} - */ - -WalletKey.fromOptions = function fromOptions(options) { - return new WalletKey().fromOptions(options); -}; - -/** - * Instantiate wallet key from a private key. - * @param {Buffer} key - * @param {Boolean?} compressed - * @param {(NetworkType|Network)} network - * @returns {WalletKey} - */ - -WalletKey.fromPrivate = function fromPrivate(key, compressed, network) { - return new WalletKey().fromPrivate(key, compressed, network); -}; - -/** - * Generate a wallet key. - * @param {(Network|NetworkType)?} network - * @returns {WalletKey} - */ - -WalletKey.generate = function generate(compressed, network) { - return new WalletKey().generate(compressed, network); -}; - -/** - * Instantiate wallet key from a public key. - * @param {Buffer} publicKey - * @param {(NetworkType|Network)} network - * @returns {WalletKey} - */ - -WalletKey.fromPublic = function fromPublic(key, network) { - return new WalletKey().fromPublic(key, network); -}; - -/** - * Instantiate wallet key from a public key. - * @param {Buffer} publicKey - * @param {(NetworkType|Network)} network - * @returns {WalletKey} - */ - -WalletKey.fromKey = function fromKey(key, compressed, network) { - return new WalletKey().fromKey(key, compressed, network); -}; - -/** - * Instantiate wallet key from script. - * @param {Buffer} key - * @param {Script} script - * @param {(NetworkType|Network)} network - * @returns {WalletKey} - */ - -WalletKey.fromScript = function fromScript(key, script, compressed, network) { - return new WalletKey().fromScript(key, script, compressed, network); -}; - -/** - * Instantiate a wallet key from a serialized CBitcoinSecret. - * @param {Base58String} secret - * @param {Network?} network - * @returns {WalletKey} - */ - -WalletKey.fromSecret = function fromSecret(data, network) { - return new WalletKey().fromSecret(data, network); -}; - -/** - * Convert an WalletKey to a more json-friendly object. - * @returns {Object} - */ - -WalletKey.prototype.toJSON = function toJSON() { - return { - network: this.network.type, - wid: this.wid, - id: this.id, - name: this.name, - account: this.account, - branch: this.branch, - index: this.index, - witness: this.witness, - nested: this.nested, - publicKey: this.publicKey.toString('hex'), - script: this.script ? this.script.toRaw().toString('hex') : null, - program: this.witness ? this.getProgram().toRaw().toString('hex') : null, - type: Address.typesByVal[this.getType()].toLowerCase(), - address: this.getAddress('string') - }; -}; - -/** - * Instantiate an WalletKey from a jsonified transaction object. - * @param {Object} json - The jsonified transaction object. - * @returns {WalletKey} - */ - -WalletKey.fromJSON = function fromJSON(json) { - return new WalletKey().fromJSON(json); -}; - -/** - * Instantiate a wallet key from serialized data. - * @param {Buffer} data - * @returns {WalletKey} - */ - -WalletKey.fromRaw = function fromRaw(data) { - return new WalletKey().fromRaw(data); -}; + this.name = null; + this.account = -1; + this.branch = -1; + this.index = -1; + } -/** - * Inject properties from hd key. - * @private - * @param {Account} account - * @param {HDPrivateKey|HDPublicKey} key - * @param {Number} branch - * @param {Number} index - * @returns {WalletKey} - */ + /** + * Convert an WalletKey to a more json-friendly object. + * @returns {Object} + */ + + toJSON(network) { + return { + name: this.name, + account: this.account, + branch: this.branch, + index: this.index, + witness: this.witness, + nested: this.nested, + publicKey: this.publicKey.toString('hex'), + script: this.script ? this.script.toRaw().toString('hex') : null, + program: this.witness ? this.getProgram().toRaw().toString('hex') : null, + type: Address.typesByVal[this.getType()].toLowerCase(), + address: this.getAddress('string', network) + }; + } -WalletKey.prototype.fromHD = function fromHD(account, key, branch, index) { - this.keyType = Path.types.HD; - this.id = account.id; - this.wid = account.wid; - this.name = account.name; - this.account = account.accountIndex; - this.branch = branch; - this.index = index; - this.witness = account.witness; - this.nested = branch === 2; + /** + * Inject properties from hd key. + * @private + * @param {Account} account + * @param {HDPrivateKey|HDPublicKey} key + * @param {Number} branch + * @param {Number} index + * @returns {WalletKey} + */ + + fromHD(account, key, branch, index) { + this.keyType = Path.types.HD; + this.name = account.name; + this.account = account.accountIndex; + this.branch = branch; + this.index = index; + this.witness = account.witness; + this.nested = branch === 2; + + if (key.privateKey) + return this.fromPrivate(key.privateKey); + + return this.fromPublic(key.publicKey); + } - if (key.privateKey) - return this.fromPrivate(key.privateKey, account.network); + /** + * Instantiate a wallet key from hd key. + * @param {Account} account + * @param {HDPrivateKey|HDPublicKey} key + * @param {Number} branch + * @param {Number} index + * @returns {WalletKey} + */ + + static fromHD(account, key, branch, index) { + return new this().fromHD(account, key, branch, index); + } - return this.fromPublic(key.publicKey, account.network); -}; + /** + * Inject properties from imported data. + * @private + * @param {Account} account + * @param {Buffer} data + * @returns {WalletKey} + */ + + fromImport(account, data) { + this.keyType = Path.types.KEY; + this.name = account.name; + this.account = account.accountIndex; + this.witness = account.witness; + return this.fromRaw(data); + } -/** - * Instantiate a wallet key from hd key. - * @param {Account} account - * @param {HDPrivateKey|HDPublicKey} key - * @param {Number} branch - * @param {Number} index - * @returns {WalletKey} - */ + /** + * Instantiate a wallet key from imported data. + * @param {Account} account + * @param {Buffer} data + * @returns {WalletKey} + */ -WalletKey.fromHD = function fromHD(account, key, branch, index) { - return new WalletKey().fromHD(account, key, branch, index); -}; + static fromImport(account, data) { + return new this().fromImport(account, data); + } -/** - * Inject properties from imported data. - * @private - * @param {Account} account - * @param {Buffer} data - * @returns {WalletKey} - */ + /** + * Inject properties from key. + * @private + * @param {Account} account + * @param {KeyRing} ring + * @returns {WalletKey} + */ + + fromRing(account, ring) { + this.keyType = Path.types.KEY; + this.name = account.name; + this.account = account.accountIndex; + this.witness = account.witness; + return this.fromOptions(ring); + } -WalletKey.prototype.fromImport = function fromImport(account, data) { - this.keyType = Path.types.KEY; - this.id = account.id; - this.wid = account.wid; - this.name = account.name; - this.account = account.accountIndex; - this.witness = account.witness; - return this.fromRaw(data, account.network); -}; + /** + * Instantiate a wallet key from regular key. + * @param {Account} account + * @param {KeyRing} ring + * @returns {WalletKey} + */ -/** - * Instantiate a wallet key from imported data. - * @param {Account} account - * @param {Buffer} data - * @returns {WalletKey} - */ + static fromRing(account, ring) { + return new this().fromRing(account, ring); + } -WalletKey.fromImport = function fromImport(account, data) { - return new WalletKey().fromImport(account, data); -}; + /** + * Convert wallet key to a path. + * @returns {Path} + */ -/** - * Inject properties from key. - * @private - * @param {Account} account - * @param {KeyRing} ring - * @returns {WalletKey} - */ + toPath() { + const path = new Path(); -WalletKey.prototype.fromRing = function fromRing(account, ring) { - this.keyType = Path.types.KEY; - this.id = account.id; - this.wid = account.wid; - this.name = account.name; - this.account = account.accountIndex; - this.witness = account.witness; - return this.fromOptions(ring, ring.network); -}; + path.name = this.name; + path.account = this.account; -/** - * Instantiate a wallet key from regular key. - * @param {Account} account - * @param {KeyRing} ring - * @returns {WalletKey} - */ + switch (this.keyType) { + case Path.types.HD: + path.branch = this.branch; + path.index = this.index; + break; + case Path.types.KEY: + path.data = this.toRaw(); + break; + } -WalletKey.fromRing = function fromRing(account, ring) { - return new WalletKey().fromRing(account, ring); -}; + path.keyType = this.keyType; -/** - * Convert wallet key to a path. - * @returns {Path} - */ + path.version = this.getVersion(); + path.type = this.getType(); + path.hash = this.getHash(); -WalletKey.prototype.toPath = function toPath() { - const path = new Path(); - - path.id = this.id; - path.wid = this.wid; - path.name = this.name; - path.account = this.account; - - switch (this.keyType) { - case Path.types.HD: - path.branch = this.branch; - path.index = this.index; - break; - case Path.types.KEY: - path.data = this.toRaw(); - break; + return path; } - path.keyType = this.keyType; - - path.version = this.getVersion(); - path.type = this.getType(); - path.hash = this.getHash('hex'); - - return path; -}; - -/** - * Test whether an object is a WalletKey. - * @param {Object} obj - * @returns {Boolean} - */ + /** + * Test whether an object is a WalletKey. + * @param {Object} obj + * @returns {Boolean} + */ -WalletKey.isWalletKey = function isWalletKey(obj) { - return obj instanceof WalletKey; -}; + static isWalletKey(obj) { + return obj instanceof WalletKey; + } +} /* * Expose diff --git a/lib/workers/child-browser.js b/lib/workers/child-browser.js index 8e10f85d2..86bded97f 100644 --- a/lib/workers/child-browser.js +++ b/lib/workers/child-browser.js @@ -4,92 +4,100 @@ * https://github.com/bcoin-org/bcoin */ +/* global register */ + 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const EventEmitter = require('events'); /** + * Child * Represents a child process. * @alias module:workers.Child - * @constructor + * @extends EventEmitter * @ignore - * @param {String} file */ -function Child(file) { - if (!(this instanceof Child)) - return new Child(file); - - EventEmitter.call(this); +class Child extends EventEmitter { + /** + * Represents a child process. + * @constructor + * @param {String} file + */ - this.init(file); -} + constructor(file) { + super(); -Object.setPrototypeOf(Child.prototype, EventEmitter.prototype); - -/** - * Test whether child process support is available. - * @returns {Boolean} - */ + this.init(file); + } -Child.hasSupport = function hasSupport() { - return typeof global.postMessage === 'function'; -}; + /** + * Test whether child process support is available. + * @returns {Boolean} + */ -/** - * Initialize child process. Bind to events. - * @private - * @param {String} file - */ + static hasSupport() { + return typeof global.postMessage === 'function'; + } -Child.prototype.init = function init(file) { - this.child = new global.Worker(file); + /** + * Initialize child process. Bind to events. + * @private + * @param {String} file + */ + + init(file) { + if (process.env.BMOCHA) + register(file, [__dirname, file]); + + this.child = new global.Worker(file); + + this.child.onerror = (event) => { + this.emit('error', new Error('Child error.')); + this.emit('exit', 1, null); + }; + + this.child.onmessage = (event) => { + let data; + if (typeof event.data === 'string') { + data = Buffer.from(event.data, 'hex'); + assert(data.length === event.data.length / 2); + } else { + assert(event.data && typeof event.data === 'object'); + assert(event.data.data && typeof event.data.data.length === 'number'); + data = event.data.data; + data.__proto__ = Buffer.prototype; + } + this.emit('data', data); + }; + } - this.child.onerror = (event) => { - this.emit('error', new Error('Child error.')); - this.emit('exit', 1, null); - }; + /** + * Send data to child process. + * @param {Buffer} data + * @returns {Boolean} + */ - this.child.onmessage = (event) => { - let data; - if (typeof event.data === 'string') { - data = Buffer.from(event.data, 'hex'); - assert(data.length === event.data.length / 2); + write(data) { + if (this.child.postMessage.length === 2) { + data.__proto__ = Uint8Array.prototype; + this.child.postMessage({ data }, [data]); } else { - assert(event.data && typeof event.data === 'object'); - assert(event.data.data && typeof event.data.data.length === 'number'); - data = event.data.data; - data.__proto__ = Buffer.prototype; + this.child.postMessage(data.toString('hex')); } - this.emit('data', data); - }; -}; - -/** - * Send data to child process. - * @param {Buffer} data - * @returns {Boolean} - */ - -Child.prototype.write = function write(data) { - if (this.child.postMessage.length === 2) { - data.__proto__ = Uint8Array.prototype; - this.child.postMessage({ data }, [data]); - } else { - this.child.postMessage(data.toString('hex')); + return true; } - return true; -}; -/** - * Destroy the child process. - */ + /** + * Destroy the child process. + */ -Child.prototype.destroy = function destroy() { - this.child.terminate(); - this.emit('exit', 15 | 0x80, 'SIGTERM'); -}; + destroy() { + this.child.terminate(); + this.emit('exit', 15 | 0x80, 'SIGTERM'); + } +} /* * Expose diff --git a/lib/workers/child.js b/lib/workers/child.js index f6df990d2..142782c14 100644 --- a/lib/workers/child.js +++ b/lib/workers/child.js @@ -11,100 +11,105 @@ const path = require('path'); const cp = require('child_process'); const children = new Set(); + let exitBound = false; /** + * Child * Represents a child process. * @alias module:workers.Child - * @constructor - * @param {String} file + * @extends EventEmitter + * @ignore */ -function Child(file) { - if (!(this instanceof Child)) - return new Child(file); - - EventEmitter.call(this); - - bindExit(); - children.add(this); - - this.init(file); +class Child extends EventEmitter { + /** + * Represents a child process. + * @constructor + * @param {String} file + */ + + constructor(file) { + super(); + + bindExit(); + children.add(this); + + this.init(file); + } + + /** + * Test whether child process support is available. + * @returns {Boolean} + */ + + static hasSupport() { + return true; + } + + /** + * Initialize child process (node.js). + * @private + * @param {String} file + */ + + init(file) { + const bin = process.argv[0]; + const filename = path.resolve(__dirname, file); + const options = { stdio: 'pipe', env: process.env }; + + this.child = cp.spawn(bin, [filename], options); + + this.child.unref(); + this.child.stdin.unref(); + this.child.stdout.unref(); + this.child.stderr.unref(); + + this.child.on('error', (err) => { + this.emit('error', err); + }); + + this.child.once('exit', (code, signal) => { + children.delete(this); + this.emit('exit', code == null ? -1 : code, signal); + }); + + this.child.stdin.on('error', (err) => { + this.emit('error', err); + }); + + this.child.stdout.on('error', (err) => { + this.emit('error', err); + }); + + this.child.stderr.on('error', (err) => { + this.emit('error', err); + }); + + this.child.stdout.on('data', (data) => { + this.emit('data', data); + }); + } + + /** + * Send data to child process. + * @param {Buffer} data + * @returns {Boolean} + */ + + write(data) { + return this.child.stdin.write(data); + } + + /** + * Destroy the child process. + */ + + destroy() { + this.child.kill('SIGTERM'); + } } -Object.setPrototypeOf(Child.prototype, EventEmitter.prototype); - -/** - * Test whether child process support is available. - * @returns {Boolean} - */ - -Child.hasSupport = function hasSupport() { - return true; -}; - -/** - * Initialize child process (node.js). - * @private - * @param {String} file - */ - -Child.prototype.init = function init(file) { - const bin = process.argv[0]; - const filename = path.resolve(__dirname, file); - const options = { stdio: 'pipe', env: process.env }; - - this.child = cp.spawn(bin, [filename], options); - - this.child.unref(); - this.child.stdin.unref(); - this.child.stdout.unref(); - this.child.stderr.unref(); - - this.child.on('error', (err) => { - this.emit('error', err); - }); - - this.child.once('exit', (code, signal) => { - children.delete(this); - this.emit('exit', code == null ? -1 : code, signal); - }); - - this.child.stdin.on('error', (err) => { - this.emit('error', err); - }); - - this.child.stdout.on('error', (err) => { - this.emit('error', err); - }); - - this.child.stderr.on('error', (err) => { - this.emit('error', err); - }); - - this.child.stdout.on('data', (data) => { - this.emit('data', data); - }); -}; - -/** - * Send data to child process. - * @param {Buffer} data - * @returns {Boolean} - */ - -Child.prototype.write = function write(data) { - return this.child.stdin.write(data); -}; - -/** - * Destroy the child process. - */ - -Child.prototype.destroy = function destroy() { - this.child.kill('SIGTERM'); -}; - /** * Cleanup all child processes. * @private diff --git a/lib/workers/framer.js b/lib/workers/framer.js index 720375b22..757252849 100644 --- a/lib/workers/framer.js +++ b/lib/workers/framer.js @@ -7,36 +7,39 @@ 'use strict'; -const StaticWriter = require('../utils/staticwriter'); +const bio = require('bufio'); /** * Framer * @alias module:workers.Framer - * @constructor */ -function Framer() { - if (!(this instanceof Framer)) - return new Framer(); -} +class Framer { + /** + * Create a framer. + * @constructor + */ + + constructor() {} -Framer.prototype.packet = function packet(payload) { - const size = 10 + payload.getSize(); - const bw = new StaticWriter(size); + packet(payload) { + const size = 10 + payload.getSize(); + const bw = bio.write(size); - bw.writeU32(payload.id); - bw.writeU8(payload.cmd); - bw.seek(4); + bw.writeU32(payload.id); + bw.writeU8(payload.cmd); + bw.seek(4); - payload.toWriter(bw); + payload.toWriter(bw); - bw.writeU8(0x0a); + bw.writeU8(0x0a); - const msg = bw.render(); - msg.writeUInt32LE(msg.length - 10, 5, true); + const msg = bw.render(); + msg.writeUInt32LE(msg.length - 10, 5, true); - return msg; -}; + return msg; + } +} /* * Expose diff --git a/lib/workers/jobs.js b/lib/workers/jobs.js index 5da2aec48..84c3c0dcc 100644 --- a/lib/workers/jobs.js +++ b/lib/workers/jobs.js @@ -6,8 +6,8 @@ 'use strict'; -const secp256k1 = require('../crypto/secp256k1'); -const {derive} = require('../crypto/scrypt'); +const secp256k1 = require('bcrypto/lib/secp256k1'); +const {derive} = require('bcrypto/lib/scrypt'); const hashcash = require('../mining/mine'); const packets = require('./packets'); @@ -142,7 +142,7 @@ jobs.signInput = function signInput(tx, index, coin, ring, type) { */ jobs.ecVerify = function ecVerify(msg, sig, key) { - const result = secp256k1.verify(msg, sig, key); + const result = secp256k1.verifyDER(msg, sig, key); return new packets.ECVerifyResultPacket(result); }; @@ -156,7 +156,7 @@ jobs.ecVerify = function ecVerify(msg, sig, key) { */ jobs.ecSign = function ecSign(msg, key) { - const sig = secp256k1.sign(msg, key); + const sig = secp256k1.signDER(msg, key); return new packets.ECSignResultPacket(sig); }; diff --git a/lib/workers/master.js b/lib/workers/master.js index 9cc109f60..e57b05b31 100644 --- a/lib/workers/master.js +++ b/lib/workers/master.js @@ -7,9 +7,9 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const EventEmitter = require('events'); -const util = require('../utils/util'); +const {format} = require('util'); const Network = require('../protocol/network'); const jobs = require('./jobs'); const Parser = require('./parser'); @@ -18,174 +18,177 @@ const packets = require('./packets'); const Parent = require('./parent'); /** + * Master * Represents the master process. * @alias module:workers.Master - * @constructor + * @extends EventEmitter */ -function Master() { - if (!(this instanceof Master)) - return new Master(); +class Master extends EventEmitter { + /** + * Create the master process. + * @constructor + */ - EventEmitter.call(this); + constructor() { + super(); - this.parent = new Parent(); - this.framer = new Framer(); - this.parser = new Parser(); - this.listening = false; - this.color = false; + this.parent = new Parent(); + this.framer = new Framer(); + this.parser = new Parser(); + this.listening = false; + this.color = false; - this.init(); -} + this.init(); + } -Object.setPrototypeOf(Master.prototype, EventEmitter.prototype); + /** + * Initialize master. Bind events. + * @private + */ -/** - * Initialize master. Bind events. - * @private - */ + init() { + this.parent.on('data', (data) => { + this.parser.feed(data); + }); -Master.prototype.init = function init() { - this.parent.on('data', (data) => { - this.parser.feed(data); - }); + this.parent.on('error', (err) => { + this.emit('error', err); + }); - this.parent.on('error', (err) => { - this.emit('error', err); - }); + this.parent.on('exception', (err) => { + this.send(new packets.ErrorPacket(err)); + setTimeout(() => this.destroy(), 1000); + }); - this.parent.on('exception', (err) => { - this.send(new packets.ErrorPacket(err)); - setTimeout(() => this.destroy(), 1000); - }); + this.parser.on('error', (err) => { + this.emit('error', err); + }); - this.parser.on('error', (err) => { - this.emit('error', err); - }); + this.parser.on('packet', (packet) => { + this.emit('packet', packet); + }); + } - this.parser.on('packet', (packet) => { - this.emit('packet', packet); - }); -}; + /** + * Set environment. + * @param {Object} env + */ -/** - * Set environment. - * @param {Object} env - */ + setEnv(env) { + this.color = env.BCOIN_WORKER_ISTTY === '1'; + this.set(env.BCOIN_WORKER_NETWORK); + } -Master.prototype.setEnv = function setEnv(env) { - this.color = env.BCOIN_WORKER_ISTTY === '1'; - this.set(env.BCOIN_WORKER_NETWORK); -}; + /** + * Set primary network. + * @param {NetworkType|Network} network + */ -/** - * Set primary network. - * @param {NetworkType|Network} network - */ + set(network) { + return Network.set(network); + } -Master.prototype.set = function set(network) { - return Network.set(network); -}; + /** + * Send data to worker. + * @param {Buffer} data + * @returns {Boolean} + */ -/** - * Send data to worker. - * @param {Buffer} data - * @returns {Boolean} - */ + write(data) { + return this.parent.write(data); + } -Master.prototype.write = function write(data) { - return this.parent.write(data); -}; + /** + * Frame and send a packet. + * @param {Packet} packet + * @returns {Boolean} + */ -/** - * Frame and send a packet. - * @param {Packet} packet - * @returns {Boolean} - */ + send(packet) { + return this.write(this.framer.packet(packet)); + } -Master.prototype.send = function send(packet) { - return this.write(this.framer.packet(packet)); -}; + /** + * Emit an event on the worker side. + * @param {String} event + * @param {...Object} arg + * @returns {Boolean} + */ -/** - * Emit an event on the worker side. - * @param {String} event - * @param {...Object} arg - * @returns {Boolean} - */ + sendEvent(...items) { + return this.send(new packets.EventPacket(items)); + } -Master.prototype.sendEvent = function sendEvent(...items) { - return this.send(new packets.EventPacket(items)); -}; + /** + * Destroy the worker. + */ -/** - * Destroy the worker. - */ + destroy() { + return this.parent.destroy(); + } -Master.prototype.destroy = function destroy() { - return this.parent.destroy(); -}; + /** + * Write a message to stdout in the master process. + * @param {Object|String} obj + * @param {...String} args + */ -/** - * Write a message to stdout in the master process. - * @param {Object|String} obj - * @param {...String} args - */ + log() { + const text = format.apply(null, arguments); + this.send(new packets.LogPacket(text)); + } -Master.prototype.log = function log(...items) { - const text = util.format(items, this.color); - this.send(new packets.LogPacket(text)); -}; + /** + * Listen for messages from master process (only if worker). + */ -/** - * Listen for messages from master process (only if worker). - */ + listen() { + assert(!this.listening, 'Already listening.'); -Master.prototype.listen = function listen() { - assert(!this.listening, 'Already listening.'); + this.listening = true; - this.listening = true; + this.on('error', (err) => { + this.send(new packets.ErrorPacket(err)); + }); - this.on('error', (err) => { - this.send(new packets.ErrorPacket(err)); - }); + this.on('packet', (packet) => { + try { + this.handlePacket(packet); + } catch (e) { + this.emit('error', e); + } + }); + } - this.on('packet', (packet) => { - try { - this.handlePacket(packet); - } catch (e) { - this.emit('error', e); + /** + * Handle packet. + * @private + * @param {Packet} + */ + + handlePacket(packet) { + let result; + + switch (packet.cmd) { + case packets.types.ENV: + this.setEnv(packet.env); + break; + case packets.types.EVENT: + this.emit('event', packet.items); + this.emit(...packet.items); + break; + case packets.types.ERROR: + this.emit('error', packet.error); + break; + default: + result = jobs.execute(packet); + result.id = packet.id; + this.send(result); + break; } - }); -}; - -/** - * Handle packet. - * @private - * @param {Packet} - */ - -Master.prototype.handlePacket = function handlePacket(packet) { - let result; - - switch (packet.cmd) { - case packets.types.ENV: - this.setEnv(packet.env); - break; - case packets.types.EVENT: - this.emit('event', packet.items); - this.emit(...packet.items); - break; - case packets.types.ERROR: - this.emit('error', packet.error); - break; - default: - result = jobs.execute(packet); - result.id = packet.id; - this.send(result); - break; } -}; +} /* * Expose diff --git a/lib/workers/packets.js b/lib/workers/packets.js index 16bff2e3a..a9cbbcdb2 100644 --- a/lib/workers/packets.js +++ b/lib/workers/packets.js @@ -10,9 +10,8 @@ * @module workers/packets */ -const assert = require('assert'); -const BufferReader = require('../utils/reader'); -const encoding = require('../utils/encoding'); +const assert = require('bsert'); +const bio = require('bufio'); const Script = require('../script/script'); const Witness = require('../script/witness'); const Output = require('../primitives/output'); @@ -21,6 +20,7 @@ const TX = require('../primitives/tx'); const KeyRing = require('../primitives/keyring'); const CoinView = require('../coins/coinview'); const ScriptError = require('../script/scripterror'); +const {encoding} = bio; /* * Constants @@ -52,1035 +52,991 @@ const packetTypes = { /** * Packet - * @constructor */ -function Packet() { - this.id = ++Packet.id >>> 0; -} - -Packet.id = 0; +class Packet { + constructor() { + this.id = ++Packet.id >>> 0; + this.cmd = -1; + } -Packet.prototype.cmd = -1; + getSize() { + throw new Error('Abstract method.'); + } -Packet.prototype.getSize = function getSize() { - throw new Error('Abstract method.'); -}; + toWriter() { + throw new Error('Abstract method.'); + } -Packet.prototype.toWriter = function toWriter() { - throw new Error('Abstract method.'); -}; + fromRaw() { + throw new Error('Abstract method.'); + } -Packet.prototype.fromRaw = function fromRaw() { - throw new Error('Abstract method.'); -}; + static fromRaw() { + throw new Error('Abstract method.'); + } +} -Packet.fromRaw = function fromRaw() { - throw new Error('Abstract method.'); -}; +Packet.id = 0; /** * EnvPacket - * @constructor */ -function EnvPacket(env) { - Packet.call(this); - this.env = env || {}; - this.json = JSON.stringify(this.env); -} - -Object.setPrototypeOf(EnvPacket.prototype, Packet.prototype); - -EnvPacket.prototype.cmd = packetTypes.ENV; +class EnvPacket extends Packet { + constructor(env) { + super(); + this.cmd = packetTypes.ENV; + this.env = env || {}; + this.json = JSON.stringify(this.env); + } -EnvPacket.prototype.getSize = function getSize() { - return encoding.sizeVarString(this.json, 'utf8'); -}; + getSize() { + return encoding.sizeVarString(this.json, 'utf8'); + } -EnvPacket.prototype.toWriter = function toWriter(bw) { - bw.writeVarString(this.json, 'utf8'); - return bw; -}; + toWriter(bw) { + bw.writeVarString(this.json, 'utf8'); + return bw; + } -EnvPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); - this.json = br.readVarString('utf8'); - this.env = JSON.parse(this.json); - return this; -}; + fromRaw(data) { + const br = bio.read(data, true); + this.json = br.readVarString('utf8'); + this.env = JSON.parse(this.json); + return this; + } -EnvPacket.fromRaw = function fromRaw(data) { - return new EnvPacket().fromRaw(data); -}; + static fromRaw(data) { + return new EnvPacket().fromRaw(data); + } +} /** * EventPacket - * @constructor */ -function EventPacket(items) { - Packet.call(this); - this.items = items || []; - this.json = JSON.stringify(this.items); -} - -Object.setPrototypeOf(EventPacket.prototype, Packet.prototype); - -EventPacket.prototype.cmd = packetTypes.EVENT; +class EventPacket extends Packet { + constructor(items) { + super(); + this.cmd = packetTypes.EVENT; + this.items = items || []; + this.json = JSON.stringify(this.items); + } -EventPacket.prototype.getSize = function getSize() { - return encoding.sizeVarString(this.json, 'utf8'); -}; + getSize() { + return encoding.sizeVarString(this.json, 'utf8'); + } -EventPacket.prototype.toWriter = function toWriter(bw) { - bw.writeVarString(this.json, 'utf8'); - return bw; -}; + toWriter(bw) { + bw.writeVarString(this.json, 'utf8'); + return bw; + } -EventPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); - this.json = br.readVarString('utf8'); - this.items = JSON.parse(this.json); - return this; -}; + fromRaw(data) { + const br = bio.read(data, true); + this.json = br.readVarString('utf8'); + this.items = JSON.parse(this.json); + return this; + } -EventPacket.fromRaw = function fromRaw(data) { - return new EventPacket().fromRaw(data); -}; + static fromRaw(data) { + return new EventPacket().fromRaw(data); + } +} /** * LogPacket - * @constructor */ -function LogPacket(text) { - Packet.call(this); - this.text = text || ''; -} - -Object.setPrototypeOf(LogPacket.prototype, Packet.prototype); - -LogPacket.prototype.cmd = packetTypes.LOG; +class LogPacket extends Packet { + constructor(text) { + super(); + this.cmd = packetTypes.LOG; + this.text = text || ''; + } -LogPacket.prototype.getSize = function getSize() { - return encoding.sizeVarString(this.text, 'utf8'); -}; + getSize() { + return encoding.sizeVarString(this.text, 'utf8'); + } -LogPacket.prototype.toWriter = function toWriter(bw) { - bw.writeVarString(this.text, 'utf8'); - return bw; -}; + toWriter(bw) { + bw.writeVarString(this.text, 'utf8'); + return bw; + } -LogPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); - this.text = br.readVarString('utf8'); - return this; -}; + fromRaw(data) { + const br = bio.read(data, true); + this.text = br.readVarString('utf8'); + return this; + } -LogPacket.fromRaw = function fromRaw(data) { - return new LogPacket().fromRaw(data); -}; + static fromRaw(data) { + return new LogPacket().fromRaw(data); + } +} /** * ErrorPacket - * @constructor */ -function ErrorPacket(error) { - Packet.call(this); - this.error = error || new Error(); -} - -Object.setPrototypeOf(ErrorPacket.prototype, Packet.prototype); - -ErrorPacket.prototype.cmd = packetTypes.ERROR; - -ErrorPacket.prototype.getSize = function getSize() { - const err = this.error; - - let size = 0; - - size += encoding.sizeVarString(stringify(err.message), 'utf8'); - size += encoding.sizeVarString(stringify(err.stack), 'utf8'); - size += encoding.sizeVarString(stringify(err.type), 'utf8'); - - switch (typeof err.code) { - case 'number': - size += 1; - size += 4; - break; - case 'string': - size += 1; - size += encoding.sizeVarString(err.code, 'utf8'); - break; - default: - size += 1; - break; +class ErrorPacket extends Packet { + constructor(error) { + super(); + this.cmd = packetTypes.ERROR; + this.error = error || new Error(); } - return size; -}; + getSize() { + const err = this.error; + + let size = 0; + + size += encoding.sizeVarString(stringify(err.message), 'utf8'); + size += encoding.sizeVarString(stringify(err.stack), 'utf8'); + size += encoding.sizeVarString(stringify(err.type), 'utf8'); + + switch (typeof err.code) { + case 'number': + size += 1; + size += 4; + break; + case 'string': + size += 1; + size += encoding.sizeVarString(err.code, 'utf8'); + break; + default: + size += 1; + break; + } -ErrorPacket.prototype.toWriter = function toWriter(bw) { - const err = this.error; - - bw.writeVarString(stringify(err.message), 'utf8'); - bw.writeVarString(stringify(err.stack), 'utf8'); - bw.writeVarString(stringify(err.type), 'utf8'); - - switch (typeof err.code) { - case 'number': - bw.writeU8(2); - bw.writeI32(err.code); - break; - case 'string': - bw.writeU8(1); - bw.writeVarString(err.code, 'utf8'); - break; - default: - bw.writeU8(0); - break; + return size; } - return bw; -}; + toWriter(bw) { + const err = this.error; + + bw.writeVarString(stringify(err.message), 'utf8'); + bw.writeVarString(stringify(err.stack), 'utf8'); + bw.writeVarString(stringify(err.type), 'utf8'); + + switch (typeof err.code) { + case 'number': + bw.writeU8(2); + bw.writeI32(err.code); + break; + case 'string': + bw.writeU8(1); + bw.writeVarString(err.code, 'utf8'); + break; + default: + bw.writeU8(0); + break; + } -ErrorPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); - const err = this.error; + return bw; + } - err.message = br.readVarString('utf8'); - err.stack = br.readVarString('utf8'); - err.type = br.readVarString('utf8'); + fromRaw(data) { + const br = bio.read(data, true); + const err = this.error; + + err.message = br.readVarString('utf8'); + err.stack = br.readVarString('utf8'); + err.type = br.readVarString('utf8'); + + switch (br.readU8()) { + case 2: + err.code = br.readI32(); + break; + case 1: + err.code = br.readVarString('utf8'); + break; + default: + err.code = null; + break; + } - switch (br.readU8()) { - case 2: - err.code = br.readI32(); - break; - case 1: - err.code = br.readVarString('utf8'); - break; - default: - err.code = null; - break; + return this; } - return this; -}; - -ErrorPacket.fromRaw = function fromRaw(data) { - return new ErrorPacket().fromRaw(data); -}; + static fromRaw(data) { + return new ErrorPacket().fromRaw(data); + } +} /** * ErrorResultPacket - * @constructor */ -function ErrorResultPacket(error) { - ErrorPacket.call(this, error); -} - -Object.setPrototypeOf(ErrorResultPacket.prototype, ErrorPacket.prototype); - -ErrorResultPacket.prototype.cmd = packetTypes.ERRORRESULT; +class ErrorResultPacket extends ErrorPacket { + constructor(error) { + super(error); + this.cmd = packetTypes.ERRORRESULT; + } -ErrorResultPacket.fromRaw = function fromRaw(data) { - return new ErrorResultPacket().fromRaw(data); -}; + static fromRaw(data) { + return new ErrorResultPacket().fromRaw(data); + } +} /** * CheckPacket - * @constructor */ -function CheckPacket(tx, view, flags) { - Packet.call(this); - this.tx = tx || null; - this.view = view || null; - this.flags = flags != null ? flags : null; -} - -Object.setPrototypeOf(CheckPacket.prototype, Packet.prototype); - -CheckPacket.prototype.cmd = packetTypes.CHECK; +class CheckPacket extends Packet { + constructor(tx, view, flags) { + super(); + this.cmd = packetTypes.CHECK; + this.tx = tx || null; + this.view = view || null; + this.flags = flags != null ? flags : null; + } -CheckPacket.prototype.getSize = function getSize() { - return this.tx.getSize() + this.view.getSize(this.tx) + 4; -}; + getSize() { + return this.tx.getSize() + this.view.getSize(this.tx) + 4; + } -CheckPacket.prototype.toWriter = function toWriter(bw) { - this.tx.toWriter(bw); - this.view.toWriter(bw, this.tx); - bw.writeI32(this.flags != null ? this.flags : -1); - return bw; -}; + toWriter(bw) { + this.tx.toWriter(bw); + this.view.toWriter(bw, this.tx); + bw.writeI32(this.flags != null ? this.flags : -1); + return bw; + } -CheckPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); + fromRaw(data) { + const br = bio.read(data, true); - this.tx = TX.fromReader(br); - this.view = CoinView.fromReader(br, this.tx); - this.flags = br.readI32(); + this.tx = TX.fromReader(br); + this.view = CoinView.fromReader(br, this.tx); + this.flags = br.readI32(); - if (this.flags === -1) - this.flags = null; + if (this.flags === -1) + this.flags = null; - return this; -}; + return this; + } -CheckPacket.fromRaw = function fromRaw(data) { - return new CheckPacket().fromRaw(data); -}; + static fromRaw(data) { + return new CheckPacket().fromRaw(data); + } +} /** * CheckResultPacket - * @constructor */ -function CheckResultPacket(error) { - Packet.call(this); - this.error = error || null; -} - -Object.setPrototypeOf(CheckResultPacket.prototype, Packet.prototype); +class CheckResultPacket extends Packet { + constructor(error) { + super(); + this.cmd = packetTypes.CHECKRESULT; + this.error = error || null; + } -CheckResultPacket.prototype.cmd = packetTypes.CHECKRESULT; + getSize() { + const err = this.error; -CheckResultPacket.prototype.getSize = function getSize() { - const err = this.error; + let size = 0; - let size = 0; + if (!err) { + size += 1; + return size; + } - if (!err) { size += 1; + size += encoding.sizeVarString(stringify(err.message), 'utf8'); + size += encoding.sizeVarString(stringify(err.stack), 'utf8'); + size += encoding.sizeVarString(stringify(err.code), 'utf8'); + size += 1; + size += 4; + return size; } - size += 1; - size += encoding.sizeVarString(stringify(err.message), 'utf8'); - size += encoding.sizeVarString(stringify(err.stack), 'utf8'); - size += encoding.sizeVarString(stringify(err.code), 'utf8'); - size += 1; - size += 4; + toWriter(bw) { + const err = this.error; - return size; -}; + if (!err) { + bw.writeU8(0); + return bw; + } -CheckResultPacket.prototype.toWriter = function toWriter(bw) { - const err = this.error; + bw.writeU8(1); + bw.writeVarString(stringify(err.message), 'utf8'); + bw.writeVarString(stringify(err.stack), 'utf8'); + bw.writeVarString(stringify(err.code), 'utf8'); + bw.writeU8(err.op === -1 ? 0xff : err.op); + bw.writeU32(err.ip === -1 ? 0xffffffff : err.ip); - if (!err) { - bw.writeU8(0); return bw; } - bw.writeU8(1); - bw.writeVarString(stringify(err.message), 'utf8'); - bw.writeVarString(stringify(err.stack), 'utf8'); - bw.writeVarString(stringify(err.code), 'utf8'); - bw.writeU8(err.op === -1 ? 0xff : err.op); - bw.writeU32(err.ip === -1 ? 0xffffffff : err.ip); - - return bw; -}; - -CheckResultPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); + fromRaw(data) { + const br = bio.read(data, true); - if (br.readU8() === 0) - return this; + if (br.readU8() === 0) + return this; - const err = new ScriptError(''); + const err = new ScriptError(''); - err.message = br.readVarString('utf8'); - err.stack = br.readVarString('utf8'); - err.code = br.readVarString('utf8'); - err.op = br.readU8(); - err.ip = br.readU32(); + err.message = br.readVarString('utf8'); + err.stack = br.readVarString('utf8'); + err.code = br.readVarString('utf8'); + err.op = br.readU8(); + err.ip = br.readU32(); - if (err.op === 0xff) - err.op = -1; + if (err.op === 0xff) + err.op = -1; - if (err.ip === 0xffffffff) - err.ip = -1; + if (err.ip === 0xffffffff) + err.ip = -1; - this.error = err; + this.error = err; - return this; -}; + return this; + } -CheckResultPacket.fromRaw = function fromRaw(data) { - return new CheckResultPacket().fromRaw(data); -}; + static fromRaw(data) { + return new CheckResultPacket().fromRaw(data); + } +} /** * SignPacket - * @constructor */ -function SignPacket(tx, rings, type) { - Packet.call(this); - this.tx = tx || null; - this.rings = rings || []; - this.type = type != null ? type : 1; -} +class SignPacket extends Packet { + constructor(tx, rings, type) { + super(); + this.cmd = packetTypes.SIGN; + this.tx = tx || null; + this.rings = rings || []; + this.type = type != null ? type : 1; + } -Object.setPrototypeOf(SignPacket.prototype, Packet.prototype); + getSize() { + let size = 0; -SignPacket.prototype.cmd = packetTypes.SIGN; + size += this.tx.getSize(); + size += this.tx.view.getSize(this.tx); + size += encoding.sizeVarint(this.rings.length); -SignPacket.prototype.getSize = function getSize() { - let size = 0; + for (const ring of this.rings) + size += ring.getSize(); - size += this.tx.getSize(); - size += this.tx.view.getSize(this.tx); - size += encoding.sizeVarint(this.rings.length); + size += 1; - for (const ring of this.rings) - size += ring.getSize(); + return size; + } - size += 1; + toWriter(bw) { + this.tx.toWriter(bw); + this.tx.view.toWriter(bw, this.tx); - return size; -}; + bw.writeVarint(this.rings.length); -SignPacket.prototype.toWriter = function toWriter(bw) { - this.tx.toWriter(bw); - this.tx.view.toWriter(bw, this.tx); + for (const ring of this.rings) + ring.toWriter(bw); - bw.writeVarint(this.rings.length); + bw.writeU8(this.type); - for (const ring of this.rings) - ring.toWriter(bw); + return bw; + } - bw.writeU8(this.type); + fromRaw(data) { + const br = bio.read(data, true); - return bw; -}; + this.tx = MTX.fromReader(br); + this.tx.view.fromReader(br, this.tx); -SignPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); + const count = br.readVarint(); - this.tx = MTX.fromReader(br); - this.tx.view.fromReader(br, this.tx); + for (let i = 0; i < count; i++) { + const ring = KeyRing.fromReader(br); + this.rings.push(ring); + } - const count = br.readVarint(); + this.type = br.readU8(); - for (let i = 0; i < count; i++) { - const ring = KeyRing.fromReader(br); - this.rings.push(ring); + return this; } - this.type = br.readU8(); - - return this; -}; - -SignPacket.fromRaw = function fromRaw(data) { - return new SignPacket().fromRaw(data); -}; + static fromRaw(data) { + return new SignPacket().fromRaw(data); + } +} /** * SignResultPacket - * @constructor */ -function SignResultPacket(total, witness, script) { - Packet.call(this); - this.total = total || 0; - this.script = script || []; - this.witness = witness || []; -} - -Object.setPrototypeOf(SignResultPacket.prototype, Packet.prototype); +class SignResultPacket extends Packet { + constructor(total, witness, script) { + super(); + this.cmd = packetTypes.SIGNRESULT; + this.total = total || 0; + this.script = script || []; + this.witness = witness || []; + } -SignResultPacket.prototype.cmd = packetTypes.SIGNRESULT; + fromTX(tx, total) { + this.total = total; -SignResultPacket.prototype.fromTX = function fromTX(tx, total) { - this.total = total; + for (const input of tx.inputs) { + this.script.push(input.script); + this.witness.push(input.witness); + } - for (const input of tx.inputs) { - this.script.push(input.script); - this.witness.push(input.witness); + return this; } - return this; -}; + static fromTX(tx, total) { + return new SignResultPacket().fromTX(tx, total); + } -SignResultPacket.fromTX = function fromTX(tx, total) { - return new SignResultPacket().fromTX(tx, total); -}; + getSize() { + let size = 0; -SignResultPacket.prototype.getSize = function getSize() { - let size = 0; + size += encoding.sizeVarint(this.total); + size += encoding.sizeVarint(this.script.length); - size += encoding.sizeVarint(this.total); - size += encoding.sizeVarint(this.script.length); + for (let i = 0; i < this.script.length; i++) { + const script = this.script[i]; + const witness = this.witness[i]; + size += script.getVarSize(); + size += witness.getVarSize(); + } - for (let i = 0; i < this.script.length; i++) { - const script = this.script[i]; - const witness = this.witness[i]; - size += script.getVarSize(); - size += witness.getVarSize(); + return size; } - return size; -}; + toWriter(bw) { + assert(this.script.length === this.witness.length); -SignResultPacket.prototype.toWriter = function toWriter(bw) { - assert(this.script.length === this.witness.length); + bw.writeVarint(this.total); + bw.writeVarint(this.script.length); - bw.writeVarint(this.total); - bw.writeVarint(this.script.length); + for (let i = 0; i < this.script.length; i++) { + this.script[i].toWriter(bw); + this.witness[i].toWriter(bw); + } - for (let i = 0; i < this.script.length; i++) { - this.script[i].toWriter(bw); - this.witness[i].toWriter(bw); + return bw; } - return bw; -}; - -SignResultPacket.prototype.inject = function inject(tx) { - assert(this.script.length === tx.inputs.length); - assert(this.witness.length === tx.inputs.length); + inject(tx) { + assert(this.script.length === tx.inputs.length); + assert(this.witness.length === tx.inputs.length); - for (let i = 0; i < tx.inputs.length; i++) { - const input = tx.inputs[i]; - input.script = this.script[i]; - input.witness = this.witness[i]; + for (let i = 0; i < tx.inputs.length; i++) { + const input = tx.inputs[i]; + input.script = this.script[i]; + input.witness = this.witness[i]; + } } -}; -SignResultPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); + fromRaw(data) { + const br = bio.read(data, true); - this.total = br.readVarint(); + this.total = br.readVarint(); - const count = br.readVarint(); + const count = br.readVarint(); - for (let i = 0; i < count; i++) { - this.script.push(Script.fromReader(br)); - this.witness.push(Witness.fromReader(br)); - } + for (let i = 0; i < count; i++) { + this.script.push(Script.fromReader(br)); + this.witness.push(Witness.fromReader(br)); + } - return this; -}; + return this; + } -SignResultPacket.fromRaw = function fromRaw(data) { - return new SignResultPacket().fromRaw(data); -}; + static fromRaw(data) { + return new SignResultPacket().fromRaw(data); + } +} /** * CheckInputPacket - * @constructor */ -function CheckInputPacket(tx, index, coin, flags) { - Packet.call(this); - this.tx = tx || null; - this.index = index; - this.coin = coin || null; - this.flags = flags != null ? flags : null; -} - -Object.setPrototypeOf(CheckInputPacket.prototype, Packet.prototype); - -CheckInputPacket.prototype.cmd = packetTypes.CHECKINPUT; +class CheckInputPacket extends Packet { + constructor(tx, index, coin, flags) { + super(); + this.cmd = packetTypes.CHECKINPUT; + this.tx = tx || null; + this.index = index; + this.coin = coin || null; + this.flags = flags != null ? flags : null; + } -CheckInputPacket.prototype.getSize = function getSize() { - let size = 0; - size += this.tx.getSize(); - size += encoding.sizeVarint(this.index); - size += encoding.sizeVarint(this.coin.value); - size += this.coin.script.getVarSize(); - size += 4; - return size; -}; + getSize() { + let size = 0; + size += this.tx.getSize(); + size += encoding.sizeVarint(this.index); + size += encoding.sizeVarint(this.coin.value); + size += this.coin.script.getVarSize(); + size += 4; + return size; + } -CheckInputPacket.prototype.toWriter = function toWriter(bw) { - this.tx.toWriter(bw); - bw.writeVarint(this.index); - bw.writeVarint(this.coin.value); - this.coin.script.toWriter(bw); - bw.writeI32(this.flags != null ? this.flags : -1); - return bw; -}; + toWriter(bw) { + this.tx.toWriter(bw); + bw.writeVarint(this.index); + bw.writeVarint(this.coin.value); + this.coin.script.toWriter(bw); + bw.writeI32(this.flags != null ? this.flags : -1); + return bw; + } -CheckInputPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); + fromRaw(data) { + const br = bio.read(data, true); - this.tx = TX.fromReader(br); - this.index = br.readVarint(); + this.tx = TX.fromReader(br); + this.index = br.readVarint(); - this.coin = new Output(); - this.coin.value = br.readVarint(); - this.coin.script.fromReader(br); + this.coin = new Output(); + this.coin.value = br.readVarint(); + this.coin.script.fromReader(br); - this.flags = br.readI32(); + this.flags = br.readI32(); - if (this.flags === -1) - this.flags = null; + if (this.flags === -1) + this.flags = null; - return this; -}; + return this; + } -CheckInputPacket.fromRaw = function fromRaw(data) { - return new CheckInputPacket().fromRaw(data); -}; + static fromRaw(data) { + return new CheckInputPacket().fromRaw(data); + } +} /** * CheckInputResultPacket - * @constructor */ -function CheckInputResultPacket(error) { - CheckResultPacket.call(this, error); -} - -Object.setPrototypeOf( - CheckInputResultPacket.prototype, - CheckResultPacket.prototype); - -CheckInputResultPacket.prototype.cmd = packetTypes.CHECKINPUTRESULT; +class CheckInputResultPacket extends CheckResultPacket { + constructor(error) { + super(error); + this.cmd = packetTypes.CHECKINPUTRESULT; + } -CheckInputResultPacket.fromRaw = function fromRaw(data) { - return new CheckInputResultPacket().fromRaw(data); -}; + static fromRaw(data) { + return new CheckInputResultPacket().fromRaw(data); + } +} /** * SignInputPacket - * @constructor */ -function SignInputPacket(tx, index, coin, ring, type) { - Packet.call(this); - this.tx = tx || null; - this.index = index; - this.coin = coin || null; - this.ring = ring || null; - this.type = type != null ? type : 1; -} - -Object.setPrototypeOf(SignInputPacket.prototype, Packet.prototype); - -SignInputPacket.prototype.cmd = packetTypes.SIGNINPUT; +class SignInputPacket extends Packet { + constructor(tx, index, coin, ring, type) { + super(); + this.cmd = packetTypes.SIGNINPUT; + this.tx = tx || null; + this.index = index; + this.coin = coin || null; + this.ring = ring || null; + this.type = type != null ? type : 1; + } -SignInputPacket.prototype.getSize = function getSize() { - let size = 0; - size += this.tx.getSize(); - size += encoding.sizeVarint(this.index); - size += encoding.sizeVarint(this.coin.value); - size += this.coin.script.getVarSize(); - size += this.ring.getSize(); - size += 1; - return size; -}; + getSize() { + let size = 0; + size += this.tx.getSize(); + size += encoding.sizeVarint(this.index); + size += encoding.sizeVarint(this.coin.value); + size += this.coin.script.getVarSize(); + size += this.ring.getSize(); + size += 1; + return size; + } -SignInputPacket.prototype.toWriter = function toWriter(bw) { - this.tx.toWriter(bw); - bw.writeVarint(this.index); - bw.writeVarint(this.coin.value); - this.coin.script.toWriter(bw); - this.ring.toWriter(bw); - bw.writeU8(this.type); - return bw; -}; + toWriter(bw) { + this.tx.toWriter(bw); + bw.writeVarint(this.index); + bw.writeVarint(this.coin.value); + this.coin.script.toWriter(bw); + this.ring.toWriter(bw); + bw.writeU8(this.type); + return bw; + } -SignInputPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); + fromRaw(data) { + const br = bio.read(data, true); - this.tx = MTX.fromReader(br); - this.index = br.readVarint(); + this.tx = MTX.fromReader(br); + this.index = br.readVarint(); - this.coin = new Output(); - this.coin.value = br.readVarint(); - this.coin.script.fromReader(br); + this.coin = new Output(); + this.coin.value = br.readVarint(); + this.coin.script.fromReader(br); - this.ring = KeyRing.fromReader(br); - this.type = br.readU8(); + this.ring = KeyRing.fromReader(br); + this.type = br.readU8(); - return this; -}; + return this; + } -SignInputPacket.fromRaw = function fromRaw(data) { - return new SignInputPacket().fromRaw(data); -}; + static fromRaw(data) { + return new SignInputPacket().fromRaw(data); + } +} /** * SignInputResultPacket - * @constructor */ -function SignInputResultPacket(value, witness, script) { - Packet.call(this); - this.value = value || false; - this.script = script || null; - this.witness = witness || null; -} - -Object.setPrototypeOf(SignInputResultPacket.prototype, Packet.prototype); - -SignInputResultPacket.prototype.cmd = packetTypes.SIGNINPUTRESULT; +class SignInputResultPacket extends Packet { + constructor(value, witness, script) { + super(); + this.cmd = packetTypes.SIGNINPUTRESULT; + this.value = value || false; + this.script = script || null; + this.witness = witness || null; + } -SignInputResultPacket.prototype.fromTX = function fromTX(tx, i, value) { - const input = tx.inputs[i]; + fromTX(tx, i, value) { + const input = tx.inputs[i]; - assert(input); + assert(input); - this.value = value; - this.script = input.script; - this.witness = input.witness; + this.value = value; + this.script = input.script; + this.witness = input.witness; - return this; -}; + return this; + } -SignInputResultPacket.fromTX = function fromTX(tx, i, value) { - return new SignInputResultPacket().fromTX(tx, i, value); -}; + static fromTX(tx, i, value) { + return new SignInputResultPacket().fromTX(tx, i, value); + } -SignInputResultPacket.prototype.getSize = function getSize() { - return 1 + this.script.getVarSize() + this.witness.getVarSize(); -}; + getSize() { + return 1 + this.script.getVarSize() + this.witness.getVarSize(); + } -SignInputResultPacket.prototype.toWriter = function toWriter(bw) { - bw.writeU8(this.value ? 1 : 0); - this.script.toWriter(bw); - this.witness.toWriter(bw); - return bw; -}; + toWriter(bw) { + bw.writeU8(this.value ? 1 : 0); + this.script.toWriter(bw); + this.witness.toWriter(bw); + return bw; + } -SignInputResultPacket.prototype.inject = function inject(tx, i) { - const input = tx.inputs[i]; - assert(input); - input.script = this.script; - input.witness = this.witness; -}; + inject(tx, i) { + const input = tx.inputs[i]; + assert(input); + input.script = this.script; + input.witness = this.witness; + } -SignInputResultPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); - this.value = br.readU8() === 1; - this.script = Script.fromReader(br); - this.witness = Witness.fromReader(br); - return this; -}; + fromRaw(data) { + const br = bio.read(data, true); + this.value = br.readU8() === 1; + this.script = Script.fromReader(br); + this.witness = Witness.fromReader(br); + return this; + } -SignInputResultPacket.fromRaw = function fromRaw(data) { - return new SignInputResultPacket().fromRaw(data); -}; + static fromRaw(data) { + return new SignInputResultPacket().fromRaw(data); + } +} /** * ECVerifyPacket - * @constructor */ -function ECVerifyPacket(msg, sig, key) { - Packet.call(this); - this.msg = msg || null; - this.sig = sig || null; - this.key = key || null; -} - -Object.setPrototypeOf(ECVerifyPacket.prototype, Packet.prototype); - -ECVerifyPacket.prototype.cmd = packetTypes.ECVERIFY; +class ECVerifyPacket extends Packet { + constructor(msg, sig, key) { + super(); + this.cmd = packetTypes.ECVERIFY; + this.msg = msg || null; + this.sig = sig || null; + this.key = key || null; + } -ECVerifyPacket.prototype.getSize = function getSize() { - let size = 0; - size += encoding.sizeVarBytes(this.msg); - size += encoding.sizeVarBytes(this.sig); - size += encoding.sizeVarBytes(this.key); - return size; -}; + getSize() { + let size = 0; + size += encoding.sizeVarBytes(this.msg); + size += encoding.sizeVarBytes(this.sig); + size += encoding.sizeVarBytes(this.key); + return size; + } -ECVerifyPacket.prototype.toWriter = function toWriter(bw) { - bw.writeVarBytes(this.msg); - bw.writeVarBytes(this.sig); - bw.writeVarBytes(this.key); - return bw; -}; + toWriter(bw) { + bw.writeVarBytes(this.msg); + bw.writeVarBytes(this.sig); + bw.writeVarBytes(this.key); + return bw; + } -ECVerifyPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); - this.msg = br.readVarBytes(); - this.sig = br.readVarBytes(); - this.key = br.readVarBytes(); - return this; -}; + fromRaw(data) { + const br = bio.read(data, true); + this.msg = br.readVarBytes(); + this.sig = br.readVarBytes(); + this.key = br.readVarBytes(); + return this; + } -ECVerifyPacket.fromRaw = function fromRaw(data) { - return new ECVerifyPacket().fromRaw(data); -}; + static fromRaw(data) { + return new ECVerifyPacket().fromRaw(data); + } +} /** * ECVerifyResultPacket - * @constructor */ -function ECVerifyResultPacket(value) { - Packet.call(this); - this.value = value; -} - -Object.setPrototypeOf(ECVerifyResultPacket.prototype, Packet.prototype); - -ECVerifyResultPacket.prototype.cmd = packetTypes.ECVERIFYRESULT; +class ECVerifyResultPacket extends Packet { + constructor(value) { + super(); + this.cmd = packetTypes.ECVERIFYRESULT; + this.value = value; + } -ECVerifyResultPacket.prototype.getSize = function getSize() { - return 1; -}; + getSize() { + return 1; + } -ECVerifyResultPacket.prototype.toWriter = function toWriter(bw) { - bw.writeU8(this.value ? 1 : 0); - return bw; -}; + toWriter(bw) { + bw.writeU8(this.value ? 1 : 0); + return bw; + } -ECVerifyResultPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); - this.value = br.readU8() === 1; - return this; -}; + fromRaw(data) { + const br = bio.read(data, true); + this.value = br.readU8() === 1; + return this; + } -ECVerifyResultPacket.fromRaw = function fromRaw(data) { - return new ECVerifyResultPacket().fromRaw(data); -}; + static fromRaw(data) { + return new ECVerifyResultPacket().fromRaw(data); + } +} /** * ECSignPacket - * @constructor */ -function ECSignPacket(msg, key) { - Packet.call(this); - this.msg = msg || null; - this.key = key || null; -} - -Object.setPrototypeOf(ECSignPacket.prototype, Packet.prototype); - -ECSignPacket.prototype.cmd = packetTypes.ECSIGN; +class ECSignPacket extends Packet { + constructor(msg, key) { + super(); + this.cmd = packetTypes.ECSIGN; + this.msg = msg || null; + this.key = key || null; + } -ECSignPacket.prototype.getSize = function getSize() { - let size = 0; - size += encoding.sizeVarBytes(this.msg); - size += encoding.sizeVarBytes(this.key); - return size; -}; + getSize() { + let size = 0; + size += encoding.sizeVarBytes(this.msg); + size += encoding.sizeVarBytes(this.key); + return size; + } -ECSignPacket.prototype.toWriter = function toWriter(bw) { - bw.writeVarBytes(this.msg); - bw.writeVarBytes(this.key); - return bw; -}; + toWriter(bw) { + bw.writeVarBytes(this.msg); + bw.writeVarBytes(this.key); + return bw; + } -ECSignPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); - this.msg = br.readVarBytes(); - this.key = br.readVarBytes(); - return this; -}; + fromRaw(data) { + const br = bio.read(data, true); + this.msg = br.readVarBytes(); + this.key = br.readVarBytes(); + return this; + } -ECSignPacket.fromRaw = function fromRaw(data) { - return new ECSignPacket().fromRaw(data); -}; + static fromRaw(data) { + return new ECSignPacket().fromRaw(data); + } +} /** * ECSignResultPacket - * @constructor */ -function ECSignResultPacket(sig) { - Packet.call(this); - this.sig = sig; -} - -Object.setPrototypeOf(ECSignResultPacket.prototype, Packet.prototype); - -ECSignResultPacket.prototype.cmd = packetTypes.ECSIGNRESULT; +class ECSignResultPacket extends Packet { + constructor(sig) { + super(); + this.cmd = packetTypes.ECSIGNRESULT; + this.sig = sig; + } -ECSignResultPacket.prototype.getSize = function getSize() { - return encoding.sizeVarBytes(this.sig); -}; + getSize() { + return encoding.sizeVarBytes(this.sig); + } -ECSignResultPacket.prototype.toWriter = function toWriter(bw) { - bw.writeVarBytes(this.sig); - return bw; -}; + toWriter(bw) { + bw.writeVarBytes(this.sig); + return bw; + } -ECSignResultPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); - this.sig = br.readVarBytes(); - return this; -}; + fromRaw(data) { + const br = bio.read(data, true); + this.sig = br.readVarBytes(); + return this; + } -ECSignResultPacket.fromRaw = function fromRaw(data) { - return new ECSignResultPacket().fromRaw(data); -}; + static fromRaw(data) { + return new ECSignResultPacket().fromRaw(data); + } +} /** * MinePacket - * @constructor */ -function MinePacket(data, target, min, max) { - Packet.call(this); - this.data = data || null; - this.target = target || null; - this.min = min != null ? min : -1; - this.max = max != null ? max : -1; -} - -Object.setPrototypeOf(MinePacket.prototype, Packet.prototype); - -MinePacket.prototype.cmd = packetTypes.MINE; +class MinePacket extends Packet { + constructor(data, target, min, max) { + super(); + this.cmd = packetTypes.MINE; + this.data = data || null; + this.target = target || null; + this.min = min != null ? min : -1; + this.max = max != null ? max : -1; + } -MinePacket.prototype.getSize = function getSize() { - return 120; -}; + getSize() { + return 120; + } -MinePacket.prototype.toWriter = function toWriter(bw) { - bw.writeBytes(this.data); - bw.writeBytes(this.target); - bw.writeU32(this.min); - bw.writeU32(this.max); - return bw; -}; + toWriter(bw) { + bw.writeBytes(this.data); + bw.writeBytes(this.target); + bw.writeU32(this.min); + bw.writeU32(this.max); + return bw; + } -MinePacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); - this.data = br.readBytes(80); - this.target = br.readBytes(32); - this.min = br.readU32(); - this.max = br.readU32(); - return this; -}; + fromRaw(data) { + const br = bio.read(data, true); + this.data = br.readBytes(80); + this.target = br.readBytes(32); + this.min = br.readU32(); + this.max = br.readU32(); + return this; + } -MinePacket.fromRaw = function fromRaw(data) { - return new MinePacket().fromRaw(data); -}; + static fromRaw(data) { + return new MinePacket().fromRaw(data); + } +} /** * MineResultPacket - * @constructor */ -function MineResultPacket(nonce) { - Packet.call(this); - this.nonce = nonce != null ? nonce : -1; -} - -Object.setPrototypeOf(MineResultPacket.prototype, Packet.prototype); - -MineResultPacket.prototype.cmd = packetTypes.MINERESULT; +class MineResultPacket extends Packet { + constructor(nonce) { + super(); + this.cmd = packetTypes.MINERESULT; + this.nonce = nonce != null ? nonce : -1; + } -MineResultPacket.prototype.getSize = function getSize() { - return 5; -}; + getSize() { + return 5; + } -MineResultPacket.prototype.toWriter = function toWriter(bw) { - bw.writeU8(this.nonce !== -1 ? 1 : 0); - bw.writeU32(this.nonce); - return bw; -}; + toWriter(bw) { + bw.writeU8(this.nonce !== -1 ? 1 : 0); + bw.writeU32(this.nonce); + return bw; + } -MineResultPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); - this.nonce = -1; - if (br.readU8() === 1) - this.nonce = br.readU32(); - return this; -}; + fromRaw(data) { + const br = bio.read(data, true); + this.nonce = -1; + if (br.readU8() === 1) + this.nonce = br.readU32(); + return this; + } -MineResultPacket.fromRaw = function fromRaw(data) { - return new MineResultPacket().fromRaw(data); -}; + static fromRaw(data) { + return new MineResultPacket().fromRaw(data); + } +} /** * ScryptPacket - * @constructor */ -function ScryptPacket(passwd, salt, N, r, p, len) { - Packet.call(this); - this.passwd = passwd || null; - this.salt = salt || null; - this.N = N != null ? N : -1; - this.r = r != null ? r : -1; - this.p = p != null ? p : -1; - this.len = len != null ? len : -1; -} - -Object.setPrototypeOf(ScryptPacket.prototype, Packet.prototype); - -ScryptPacket.prototype.cmd = packetTypes.SCRYPT; +class ScryptPacket extends Packet { + constructor(passwd, salt, N, r, p, len) { + super(); + this.cmd = packetTypes.SCRYPT; + this.passwd = passwd || null; + this.salt = salt || null; + this.N = N != null ? N : -1; + this.r = r != null ? r : -1; + this.p = p != null ? p : -1; + this.len = len != null ? len : -1; + } -ScryptPacket.prototype.getSize = function getSize() { - let size = 0; - size += encoding.sizeVarBytes(this.passwd); - size += encoding.sizeVarBytes(this.salt); - size += 16; - return size; -}; + getSize() { + let size = 0; + size += encoding.sizeVarBytes(this.passwd); + size += encoding.sizeVarBytes(this.salt); + size += 16; + return size; + } -ScryptPacket.prototype.toWriter = function toWriter(bw) { - bw.writeVarBytes(this.passwd); - bw.writeVarBytes(this.salt); - bw.writeU32(this.N); - bw.writeU32(this.r); - bw.writeU32(this.p); - bw.writeU32(this.len); - return bw; -}; + toWriter(bw) { + bw.writeVarBytes(this.passwd); + bw.writeVarBytes(this.salt); + bw.writeU32(this.N); + bw.writeU32(this.r); + bw.writeU32(this.p); + bw.writeU32(this.len); + return bw; + } -ScryptPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); - this.passwd = br.readVarBytes(); - this.salt = br.readVarBytes(); - this.N = br.readU32(); - this.r = br.readU32(); - this.p = br.readU32(); - this.len = br.readU32(); - return this; -}; + fromRaw(data) { + const br = bio.read(data, true); + this.passwd = br.readVarBytes(); + this.salt = br.readVarBytes(); + this.N = br.readU32(); + this.r = br.readU32(); + this.p = br.readU32(); + this.len = br.readU32(); + return this; + } -ScryptPacket.fromRaw = function fromRaw(data) { - return new ScryptPacket().fromRaw(data); -}; + static fromRaw(data) { + return new ScryptPacket().fromRaw(data); + } +} /** * ScryptResultPacket - * @constructor */ -function ScryptResultPacket(key) { - Packet.call(this); - this.key = key || null; -} - -Object.setPrototypeOf(ScryptResultPacket.prototype, Packet.prototype); - -ScryptResultPacket.prototype.cmd = packetTypes.SCRYPTRESULT; +class ScryptResultPacket extends Packet { + constructor(key) { + super(); + this.cmd = packetTypes.SCRYPTRESULT; + this.key = key || null; + } -ScryptResultPacket.prototype.getSize = function getSize() { - return encoding.sizeVarBytes(this.key); -}; + getSize() { + return encoding.sizeVarBytes(this.key); + } -ScryptResultPacket.prototype.toWriter = function toWriter(bw) { - bw.writeVarBytes(this.key); - return bw; -}; + toWriter(bw) { + bw.writeVarBytes(this.key); + return bw; + } -ScryptResultPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data, true); - this.key = br.readVarBytes(); - return this; -}; + fromRaw(data) { + const br = bio.read(data, true); + this.key = br.readVarBytes(); + return this; + } -ScryptResultPacket.fromRaw = function fromRaw(data) { - return new ScryptResultPacket().fromRaw(data); -}; + static fromRaw(data) { + return new ScryptResultPacket().fromRaw(data); + } +} /* * Helpers diff --git a/lib/workers/parent-browser.js b/lib/workers/parent-browser.js index 2eb865e72..d4bd5460e 100644 --- a/lib/workers/parent-browser.js +++ b/lib/workers/parent-browser.js @@ -6,75 +6,78 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const EventEmitter = require('events'); /** + * Parent * Represents the parent process. * @alias module:workers.Parent - * @constructor + * @extends EventEmitter * @ignore */ -function Parent() { - if (!(this instanceof Parent)) - return new Parent(); +class Parent extends EventEmitter { + /** + * Create the parent process. + * @constructor + */ - EventEmitter.call(this); + constructor() { + super(); - this.init(); -} - -Object.setPrototypeOf(Parent.prototype, EventEmitter.prototype); + this.init(); + } -/** - * Initialize master (web workers). - * @private - */ + /** + * Initialize master (web workers). + * @private + */ + + init() { + global.onerror = (event) => { + this.emit('error', new Error('Worker error.')); + }; + + global.onmessage = (event) => { + let data; + if (typeof event.data === 'string') { + data = Buffer.from(event.data, 'hex'); + assert(data.length === event.data.length / 2); + } else { + assert(event.data && typeof event.data === 'object'); + assert(event.data.data && typeof event.data.data.length === 'number'); + data = event.data.data; + data.__proto__ = Buffer.prototype; + } + this.emit('data', data); + }; + } -Parent.prototype.init = function init() { - global.onerror = (event) => { - this.emit('error', new Error('Worker error.')); - }; + /** + * Send data to parent process. + * @param {Buffer} data + * @returns {Boolean} + */ - global.onmessage = (event) => { - let data; - if (typeof event.data === 'string') { - data = Buffer.from(event.data, 'hex'); - assert(data.length === event.data.length / 2); + write(data) { + if (global.postMessage.length === 2) { + data.__proto__ = Uint8Array.prototype; + global.postMessage({ data }, [data]); } else { - assert(event.data && typeof event.data === 'object'); - assert(event.data.data && typeof event.data.data.length === 'number'); - data = event.data.data; - data.__proto__ = Buffer.prototype; + global.postMessage(data.toString('hex')); } - this.emit('data', data); - }; -}; - -/** - * Send data to parent process. - * @param {Buffer} data - * @returns {Boolean} - */ - -Parent.prototype.write = function write(data) { - if (global.postMessage.length === 2) { - data.__proto__ = Uint8Array.prototype; - global.postMessage({ data }, [data]); - } else { - global.postMessage(data.toString('hex')); + return true; } - return true; -}; -/** - * Destroy the parent process. - */ + /** + * Destroy the parent process. + */ -Parent.prototype.destroy = function destroy() { - global.close(); -}; + destroy() { + global.close(); + } +} /* * Expose diff --git a/lib/workers/parent.js b/lib/workers/parent.js index c08686809..da57f0556 100644 --- a/lib/workers/parent.js +++ b/lib/workers/parent.js @@ -9,60 +9,63 @@ const EventEmitter = require('events'); /** + * Parent * Represents the parent process. * @alias module:workers.Parent - * @constructor + * @extends EventEmitter */ -function Parent() { - if (!(this instanceof Parent)) - return new Parent(); - - EventEmitter.call(this); - - this.init(); +class Parent extends EventEmitter { + /** + * Create the parent process. + * @constructor + */ + + constructor() { + super(); + + this.init(); + } + + /** + * Initialize master (node.js). + * @private + */ + + init() { + process.stdin.on('data', (data) => { + this.emit('data', data); + }); + + // Nowhere to send these errors: + process.stdin.on('error', () => {}); + process.stdout.on('error', () => {}); + process.stderr.on('error', () => {}); + + process.on('uncaughtException', (err) => { + this.emit('exception', err); + }); + } + + /** + * Send data to parent process. + * @param {Buffer} data + * @returns {Boolean} + */ + + write(data) { + return process.stdout.write(data); + } + + /** + * Destroy the parent process. + */ + + destroy() { + process.exit(0); + } } -Object.setPrototypeOf(Parent.prototype, EventEmitter.prototype); - -/** - * Initialize master (node.js). - * @private - */ - -Parent.prototype.init = function init() { - process.stdin.on('data', (data) => { - this.emit('data', data); - }); - - // Nowhere to send these errors: - process.stdin.on('error', () => {}); - process.stdout.on('error', () => {}); - process.stderr.on('error', () => {}); - - process.on('uncaughtException', (err) => { - this.emit('exception', err); - }); -}; - -/** - * Send data to parent process. - * @param {Buffer} data - * @returns {Boolean} - */ - -Parent.prototype.write = function write(data) { - return process.stdout.write(data); -}; - -/** - * Destroy the parent process. - */ - -Parent.prototype.destroy = function destroy() { - return process.exit(0); -}; - /* * Expose */ diff --git a/lib/workers/parser.js b/lib/workers/parser.js index 26c21788c..3e8098f9e 100644 --- a/lib/workers/parser.js +++ b/lib/workers/parser.js @@ -1,5 +1,5 @@ /*! - * workers.js - worker processes for bcoin + * parser.js - worker parser for bcoin * Copyright (c) 2014-2015, Fedor Indutny (MIT License) * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin @@ -7,185 +7,193 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const EventEmitter = require('events'); const packets = require('./packets'); /** * Parser * @alias module:workers.Parser - * @constructor + * @extends EventEmitter */ -function Parser() { - if (!(this instanceof Parser)) - return new Parser(); +class Parser extends EventEmitter { + /** + * Create a parser. + * @constructor + */ - EventEmitter.call(this); + constructor() { + super(); - this.waiting = 9; - this.header = null; - this.pending = []; - this.total = 0; -} - -Object.setPrototypeOf(Parser.prototype, EventEmitter.prototype); + this.waiting = 9; + this.header = null; + this.pending = []; + this.total = 0; + } -Parser.prototype.feed = function feed(data) { - this.total += data.length; - this.pending.push(data); + feed(data) { + this.total += data.length; + this.pending.push(data); - while (this.total >= this.waiting) { - const chunk = this.read(this.waiting); - this.parse(chunk); + while (this.total >= this.waiting) { + const chunk = this.read(this.waiting); + this.parse(chunk); + } } -}; -Parser.prototype.read = function read(size) { - assert(this.total >= size, 'Reading too much.'); + read(size) { + assert(this.total >= size, 'Reading too much.'); - if (size === 0) - return Buffer.alloc(0); + if (size === 0) + return Buffer.alloc(0); - const pending = this.pending[0]; + const pending = this.pending[0]; - if (pending.length > size) { - const chunk = pending.slice(0, size); - this.pending[0] = pending.slice(size); - this.total -= chunk.length; - return chunk; - } + if (pending.length > size) { + const chunk = pending.slice(0, size); + this.pending[0] = pending.slice(size); + this.total -= chunk.length; + return chunk; + } + + if (pending.length === size) { + const chunk = this.pending.shift(); + this.total -= chunk.length; + return chunk; + } + + const chunk = Buffer.allocUnsafe(size); + let off = 0; + + while (off < chunk.length) { + const pending = this.pending[0]; + const len = pending.copy(chunk, off); + if (len === pending.length) + this.pending.shift(); + else + this.pending[0] = pending.slice(len); + off += len; + } + + assert.strictEqual(off, chunk.length); - if (pending.length === size) { - const chunk = this.pending.shift(); this.total -= chunk.length; + return chunk; } - const chunk = Buffer.allocUnsafe(size); - let off = 0; - - while (off < chunk.length) { - const pending = this.pending[0]; - const len = pending.copy(chunk, off); - if (len === pending.length) - this.pending.shift(); - else - this.pending[0] = pending.slice(len); - off += len; - } + parse(data) { + let header = this.header; - assert.strictEqual(off, chunk.length); + if (!header) { + try { + header = this.parseHeader(data); + } catch (e) { + this.emit('error', e); + return; + } - this.total -= chunk.length; + this.header = header; + this.waiting = header.size + 1; - return chunk; -}; + return; + } -Parser.prototype.parse = function parse(data) { - let header = this.header; + this.waiting = 9; + this.header = null; - if (!header) { + let packet; try { - header = this.parseHeader(data); + packet = this.parsePacket(header, data); } catch (e) { this.emit('error', e); return; } - this.header = header; - this.waiting = header.size + 1; - - return; - } + if (data[data.length - 1] !== 0x0a) { + this.emit('error', new Error('No trailing newline.')); + return; + } - this.waiting = 9; - this.header = null; + packet.id = header.id; - let packet; - try { - packet = this.parsePacket(header, data); - } catch (e) { - this.emit('error', e); - return; + this.emit('packet', packet); } - if (data[data.length - 1] !== 0x0a) { - this.emit('error', new Error('No trailing newline.')); - return; + parseHeader(data) { + const id = data.readUInt32LE(0, true); + const cmd = data.readUInt8(4, true); + const size = data.readUInt32LE(5, true); + return new Header(id, cmd, size); } - packet.id = header.id; - - this.emit('packet', packet); -}; - -Parser.prototype.parseHeader = function parseHeader(data) { - const id = data.readUInt32LE(0, true); - const cmd = data.readUInt8(4, true); - const size = data.readUInt32LE(5, true); - return new Header(id, cmd, size); -}; - -Parser.prototype.parsePacket = function parsePacket(header, data) { - switch (header.cmd) { - case packets.types.ENV: - return packets.EnvPacket.fromRaw(data); - case packets.types.EVENT: - return packets.EventPacket.fromRaw(data); - case packets.types.LOG: - return packets.LogPacket.fromRaw(data); - case packets.types.ERROR: - return packets.ErrorPacket.fromRaw(data); - case packets.types.ERRORRESULT: - return packets.ErrorResultPacket.fromRaw(data); - case packets.types.CHECK: - return packets.CheckPacket.fromRaw(data); - case packets.types.CHECKRESULT: - return packets.CheckResultPacket.fromRaw(data); - case packets.types.SIGN: - return packets.SignPacket.fromRaw(data); - case packets.types.SIGNRESULT: - return packets.SignResultPacket.fromRaw(data); - case packets.types.CHECKINPUT: - return packets.CheckInputPacket.fromRaw(data); - case packets.types.CHECKINPUTRESULT: - return packets.CheckInputResultPacket.fromRaw(data); - case packets.types.SIGNINPUT: - return packets.SignInputPacket.fromRaw(data); - case packets.types.SIGNINPUTRESULT: - return packets.SignInputResultPacket.fromRaw(data); - case packets.types.ECVERIFY: - return packets.ECVerifyPacket.fromRaw(data); - case packets.types.ECVERIFYRESULT: - return packets.ECVerifyResultPacket.fromRaw(data); - case packets.types.ECSIGN: - return packets.ECSignPacket.fromRaw(data); - case packets.types.ECSIGNRESULT: - return packets.ECSignResultPacket.fromRaw(data); - case packets.types.MINE: - return packets.MinePacket.fromRaw(data); - case packets.types.MINERESULT: - return packets.MineResultPacket.fromRaw(data); - case packets.types.SCRYPT: - return packets.ScryptPacket.fromRaw(data); - case packets.types.SCRYPTRESULT: - return packets.ScryptResultPacket.fromRaw(data); - default: - throw new Error('Unknown packet.'); + parsePacket(header, data) { + switch (header.cmd) { + case packets.types.ENV: + return packets.EnvPacket.fromRaw(data); + case packets.types.EVENT: + return packets.EventPacket.fromRaw(data); + case packets.types.LOG: + return packets.LogPacket.fromRaw(data); + case packets.types.ERROR: + return packets.ErrorPacket.fromRaw(data); + case packets.types.ERRORRESULT: + return packets.ErrorResultPacket.fromRaw(data); + case packets.types.CHECK: + return packets.CheckPacket.fromRaw(data); + case packets.types.CHECKRESULT: + return packets.CheckResultPacket.fromRaw(data); + case packets.types.SIGN: + return packets.SignPacket.fromRaw(data); + case packets.types.SIGNRESULT: + return packets.SignResultPacket.fromRaw(data); + case packets.types.CHECKINPUT: + return packets.CheckInputPacket.fromRaw(data); + case packets.types.CHECKINPUTRESULT: + return packets.CheckInputResultPacket.fromRaw(data); + case packets.types.SIGNINPUT: + return packets.SignInputPacket.fromRaw(data); + case packets.types.SIGNINPUTRESULT: + return packets.SignInputResultPacket.fromRaw(data); + case packets.types.ECVERIFY: + return packets.ECVerifyPacket.fromRaw(data); + case packets.types.ECVERIFYRESULT: + return packets.ECVerifyResultPacket.fromRaw(data); + case packets.types.ECSIGN: + return packets.ECSignPacket.fromRaw(data); + case packets.types.ECSIGNRESULT: + return packets.ECSignResultPacket.fromRaw(data); + case packets.types.MINE: + return packets.MinePacket.fromRaw(data); + case packets.types.MINERESULT: + return packets.MineResultPacket.fromRaw(data); + case packets.types.SCRYPT: + return packets.ScryptPacket.fromRaw(data); + case packets.types.SCRYPTRESULT: + return packets.ScryptResultPacket.fromRaw(data); + default: + throw new Error('Unknown packet.'); + } } -}; +} /** * Header - * @constructor * @ignore */ -function Header(id, cmd, size) { - this.id = id; - this.cmd = cmd; - this.size = size; +class Header { + /** + * Create a header. + * @constructor + */ + + constructor(id, cmd, size) { + this.id = id; + this.cmd = cmd; + this.size = size; + } } /* diff --git a/lib/workers/worker.js b/lib/workers/worker.js index 197a7b1c5..92fdc33fa 100644 --- a/lib/workers/worker.js +++ b/lib/workers/worker.js @@ -8,10 +8,6 @@ 'use strict'; const Master = require('./master'); -const util = require('../utils/util'); const server = new Master(); -util.log = server.log.bind(server); -util.error = util.log; - server.listen(); diff --git a/lib/workers/workerpool.js b/lib/workers/workerpool.js index 05ce36d8c..51326599a 100644 --- a/lib/workers/workerpool.js +++ b/lib/workers/workerpool.js @@ -9,11 +9,9 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const EventEmitter = require('events'); const os = require('os'); -const util = require('../utils/util'); -const co = require('../utils/co'); const Network = require('../protocol/network'); const Child = require('./child'); const jobs = require('./jobs'); @@ -22,654 +20,664 @@ const Framer = require('./framer'); const packets = require('./packets'); /** - * A worker pool. + * Worker Pool * @alias module:workers.WorkerPool - * @constructor - * @param {Object} options - * @param {Number} [options.size=num-cores] - Max pool size. - * @param {Number} [options.timeout=120000] - Execution timeout. + * @extends EventEmitter * @property {Number} size * @property {Number} timeout * @property {Map} children * @property {Number} uid */ -function WorkerPool(options) { - if (!(this instanceof WorkerPool)) - return new WorkerPool(options); +class WorkerPool extends EventEmitter { + /** + * Create a worker pool. + * @constructor + * @param {Object} options + * @param {Number} [options.size=num-cores] - Max pool size. + * @param {Number} [options.timeout=120000] - Execution timeout. + */ - EventEmitter.call(this); + constructor(options) { + super(); - this.enabled = false; - this.size = getCores(); - this.timeout = 120000; - this.file = process.env.BCOIN_WORKER_FILE || 'worker.js'; + this.enabled = false; + this.size = getCores(); + this.timeout = 120000; + this.file = process.env.BCOIN_WORKER_FILE || 'worker.js'; - this.children = new Map(); - this.uid = 0; + this.children = new Map(); + this.uid = 0; - this.set(options); -} - -Object.setPrototypeOf(WorkerPool.prototype, EventEmitter.prototype); - -/** - * Set worker pool options. - * @param {Object} options - */ - -WorkerPool.prototype.set = function set(options) { - if (!options) - return; - - if (options.enabled != null) { - assert(typeof options.enabled === 'boolean'); - this.enabled = options.enabled; + this.set(options); } - if (options.size != null) { - assert(util.isU32(options.size)); - assert(options.size > 0); - this.size = options.size; - } + /** + * Set worker pool options. + * @param {Object} options + */ - if (options.timeout != null) { - assert(util.isInt(options.timeout)); - assert(options.timeout >= -1); - this.timeout = options.timeout; - } + set(options) { + if (!options) + return; - if (options.file != null) { - assert(typeof options.file === 'string'); - this.file = options.file; - } -}; + if (options.enabled != null) { + assert(typeof options.enabled === 'boolean'); + this.enabled = options.enabled; + } -/** - * Open worker pool. - * @returns {Promise} - */ + if (options.size != null) { + assert((options.size >>> 0) === options.size); + assert(options.size > 0); + this.size = options.size; + } -WorkerPool.prototype.open = async function open() { - ; -}; + if (options.timeout != null) { + assert(Number.isSafeInteger(options.timeout)); + assert(options.timeout >= -1); + this.timeout = options.timeout; + } -/** - * Close worker pool. - * @returns {Promise} - */ + if (options.file != null) { + assert(typeof options.file === 'string'); + this.file = options.file; + } + } -WorkerPool.prototype.close = async function close() { - this.destroy(); -}; + /** + * Open worker pool. + * @returns {Promise} + */ -/** - * Spawn a new worker. - * @param {Number} id - Worker ID. - * @returns {Worker} - */ + async open() { + ; + } -WorkerPool.prototype.spawn = function spawn(id) { - const child = new Worker(this.file); + /** + * Close worker pool. + * @returns {Promise} + */ - child.id = id; + async close() { + this.destroy(); + } - child.on('error', (err) => { - this.emit('error', err, child); - }); + /** + * Spawn a new worker. + * @param {Number} id - Worker ID. + * @returns {Worker} + */ - child.on('exit', (code) => { - this.emit('exit', code, child); + spawn(id) { + const child = new Worker(this.file); - if (this.children.get(id) === child) - this.children.delete(id); - }); + child.id = id; - child.on('event', (items) => { - this.emit('event', items, child); - this.emit(...items); - }); + child.on('error', (err) => { + this.emit('error', err, child); + }); - child.on('log', (text) => { - this.emit('log', text, child); - }); + child.on('exit', (code) => { + this.emit('exit', code, child); - this.emit('spawn', child); + if (this.children.get(id) === child) + this.children.delete(id); + }); - return child; -}; + child.on('event', (items) => { + this.emit('event', items, child); + this.emit(...items); + }); -/** - * Allocate a new worker, will not go above `size` option - * and will automatically load balance the workers. - * @returns {Worker} - */ + child.on('log', (text) => { + this.emit('log', text, child); + }); -WorkerPool.prototype.alloc = function alloc() { - const id = this.uid++ % this.size; + this.emit('spawn', child); - if (!this.children.has(id)) - this.children.set(id, this.spawn(id)); + return child; + } - return this.children.get(id); -}; + /** + * Allocate a new worker, will not go above `size` option + * and will automatically load balance the workers. + * @returns {Worker} + */ -/** - * Emit an event on the worker side (all workers). - * @param {String} event - * @param {...Object} arg - * @returns {Boolean} - */ + alloc() { + const id = this.uid++ % this.size; -WorkerPool.prototype.sendEvent = function sendEvent() { - let result = true; + if (!this.children.has(id)) + this.children.set(id, this.spawn(id)); - for (const child of this.children.values()) { - if (!child.sendEvent.apply(child, arguments)) - result = false; + return this.children.get(id); } - return result; -}; + /** + * Emit an event on the worker side (all workers). + * @param {String} event + * @param {...Object} arg + * @returns {Boolean} + */ -/** - * Destroy all workers. - */ + sendEvent() { + let result = true; -WorkerPool.prototype.destroy = function destroy() { - for (const child of this.children.values()) - child.destroy(); -}; - -/** - * Call a method for a worker to execute. - * @param {Packet} packet - * @param {Number} timeout - * @returns {Promise} - */ + for (const child of this.children.values()) { + if (!child.sendEvent.apply(child, arguments)) + result = false; + } -WorkerPool.prototype.execute = function execute(packet, timeout) { - if (!this.enabled || !Child.hasSupport()) { - return new Promise((resolve, reject) => { - setImmediate(() => { - let result; - try { - result = jobs.handle(packet); - } catch (e) { - reject(e); - return; - } - resolve(result); - }); - }); + return result; } - if (!timeout) - timeout = this.timeout; - - const child = this.alloc(); - - return child.execute(packet, timeout); -}; + /** + * Destroy all workers. + */ -/** - * Execute the tx check job (default timeout). - * @method - * @param {TX} tx - * @param {CoinView} view - * @param {VerifyFlags} flags - * @returns {Promise} - */ + destroy() { + for (const child of this.children.values()) + child.destroy(); + } -WorkerPool.prototype.check = async function check(tx, view, flags) { - const packet = new packets.CheckPacket(tx, view, flags); - const result = await this.execute(packet, -1); + /** + * Call a method for a worker to execute. + * @param {Packet} packet + * @param {Number} timeout + * @returns {Promise} + */ + + execute(packet, timeout) { + if (!this.enabled || !Child.hasSupport()) { + return new Promise((resolve, reject) => { + setImmediate(() => { + let result; + try { + result = jobs.handle(packet); + } catch (e) { + reject(e); + return; + } + resolve(result); + }); + }); + } - if (result.error) - throw result.error; + if (!timeout) + timeout = this.timeout; - return null; -}; + const child = this.alloc(); -/** - * Execute the tx signing job (default timeout). - * @method - * @param {MTX} tx - * @param {KeyRing[]} ring - * @param {SighashType} type - * @returns {Promise} - */ + return child.execute(packet, timeout); + } -WorkerPool.prototype.sign = async function sign(tx, ring, type) { - let rings = ring; + /** + * Execute the tx check job (default timeout). + * @method + * @param {TX} tx + * @param {CoinView} view + * @param {VerifyFlags} flags + * @returns {Promise} + */ - if (!Array.isArray(rings)) - rings = [rings]; + async check(tx, view, flags) { + const packet = new packets.CheckPacket(tx, view, flags); + const result = await this.execute(packet, -1); - const packet = new packets.SignPacket(tx, rings, type); - const result = await this.execute(packet, -1); + if (result.error) + throw result.error; - result.inject(tx); + return null; + } - return result.total; -}; + /** + * Execute the tx signing job (default timeout). + * @method + * @param {MTX} tx + * @param {KeyRing[]} ring + * @param {SighashType} type + * @returns {Promise} + */ -/** - * Execute the tx input check job (default timeout). - * @method - * @param {TX} tx - * @param {Number} index - * @param {Coin|Output} coin - * @param {VerifyFlags} flags - * @returns {Promise} - */ + async sign(tx, ring, type) { + let rings = ring; -WorkerPool.prototype.checkInput = async function checkInput(tx, index, coin, flags) { - const packet = new packets.CheckInputPacket(tx, index, coin, flags); - const result = await this.execute(packet, -1); + if (!Array.isArray(rings)) + rings = [rings]; - if (result.error) - throw result.error; + const packet = new packets.SignPacket(tx, rings, type); + const result = await this.execute(packet, -1); - return null; -}; + result.inject(tx); -/** - * Execute the tx input signing job (default timeout). - * @method - * @param {MTX} tx - * @param {Number} index - * @param {Coin|Output} coin - * @param {KeyRing} ring - * @param {SighashType} type - * @returns {Promise} - */ + return result.total; + } -WorkerPool.prototype.signInput = async function signInput(tx, index, coin, ring, type) { - const packet = new packets.SignInputPacket(tx, index, coin, ring, type); - const result = await this.execute(packet, -1); - result.inject(tx); - return result.value; -}; + /** + * Execute the tx input check job (default timeout). + * @method + * @param {TX} tx + * @param {Number} index + * @param {Coin|Output} coin + * @param {VerifyFlags} flags + * @returns {Promise} + */ -/** - * Execute the secp256k1 verify job (no timeout). - * @method - * @param {Buffer} msg - * @param {Buffer} sig - DER formatted. - * @param {Buffer} key - * @returns {Promise} - */ + async checkInput(tx, index, coin, flags) { + const packet = new packets.CheckInputPacket(tx, index, coin, flags); + const result = await this.execute(packet, -1); -WorkerPool.prototype.ecVerify = async function ecVerify(msg, sig, key) { - const packet = new packets.ECVerifyPacket(msg, sig, key); - const result = await this.execute(packet, -1); - return result.value; -}; + if (result.error) + throw result.error; -/** - * Execute the secp256k1 signing job (no timeout). - * @method - * @param {Buffer} msg - * @param {Buffer} key - * @returns {Promise} - */ + return null; + } -WorkerPool.prototype.ecSign = async function ecSign(msg, key) { - const packet = new packets.ECSignPacket(msg, key); - const result = await this.execute(packet, -1); - return result.sig; -}; + /** + * Execute the tx input signing job (default timeout). + * @method + * @param {MTX} tx + * @param {Number} index + * @param {Coin|Output} coin + * @param {KeyRing} ring + * @param {SighashType} type + * @returns {Promise} + */ + + async signInput(tx, index, coin, ring, type) { + const packet = new packets.SignInputPacket(tx, index, coin, ring, type); + const result = await this.execute(packet, -1); + result.inject(tx); + return result.value; + } -/** - * Execute the mining job (no timeout). - * @method - * @param {Buffer} data - * @param {Buffer} target - * @param {Number} min - * @param {Number} max - * @returns {Promise} - Returns {Number}. - */ + /** + * Execute the secp256k1 verify job (no timeout). + * @method + * @param {Buffer} msg + * @param {Buffer} sig - DER formatted. + * @param {Buffer} key + * @returns {Promise} + */ + + async ecVerify(msg, sig, key) { + const packet = new packets.ECVerifyPacket(msg, sig, key); + const result = await this.execute(packet, -1); + return result.value; + } -WorkerPool.prototype.mine = async function mine(data, target, min, max) { - const packet = new packets.MinePacket(data, target, min, max); - const result = await this.execute(packet, -1); - return result.nonce; -}; + /** + * Execute the secp256k1 signing job (no timeout). + * @method + * @param {Buffer} msg + * @param {Buffer} key + * @returns {Promise} + */ + + async ecSign(msg, key) { + const packet = new packets.ECSignPacket(msg, key); + const result = await this.execute(packet, -1); + return result.sig; + } -/** - * Execute scrypt job (no timeout). - * @method - * @param {Buffer} passwd - * @param {Buffer} salt - * @param {Number} N - * @param {Number} r - * @param {Number} p - * @param {Number} len - * @returns {Promise} - */ + /** + * Execute the mining job (no timeout). + * @method + * @param {Buffer} data + * @param {Buffer} target + * @param {Number} min + * @param {Number} max + * @returns {Promise} - Returns {Number}. + */ + + async mine(data, target, min, max) { + const packet = new packets.MinePacket(data, target, min, max); + const result = await this.execute(packet, -1); + return result.nonce; + } -WorkerPool.prototype.scrypt = async function scrypt(passwd, salt, N, r, p, len) { - const packet = new packets.ScryptPacket(passwd, salt, N, r, p, len); - const result = await this.execute(packet, -1); - return result.key; -}; + /** + * Execute scrypt job (no timeout). + * @method + * @param {Buffer} passwd + * @param {Buffer} salt + * @param {Number} N + * @param {Number} r + * @param {Number} p + * @param {Number} len + * @returns {Promise} + */ + + async scrypt(passwd, salt, N, r, p, len) { + const packet = new packets.ScryptPacket(passwd, salt, N, r, p, len); + const result = await this.execute(packet, -1); + return result.key; + } +} /** - * Represents a worker. + * Worker * @alias module:workers.Worker - * @constructor - * @param {String} file + * @extends EventEmitter */ -function Worker(file) { - if (!(this instanceof Worker)) - return new Worker(file); +class Worker extends EventEmitter { + /** + * Create a worker. + * @constructor + * @param {String} file + */ - EventEmitter.call(this); + constructor(file) { + super(); - this.id = -1; - this.framer = new Framer(); - this.parser = new Parser(); - this.pending = new Map(); + this.id = -1; + this.framer = new Framer(); + this.parser = new Parser(); + this.pending = new Map(); - this.child = new Child(file); + this.child = new Child(file); - this.init(); -} - -Object.setPrototypeOf(Worker.prototype, EventEmitter.prototype); - -/** - * Initialize worker. Bind to events. - * @private - */ + this.init(); + } -Worker.prototype.init = function init() { - this.child.on('data', (data) => { - this.parser.feed(data); - }); + /** + * Initialize worker. Bind to events. + * @private + */ - this.child.on('exit', (code, signal) => { - this.emit('exit', code, signal); - }); + init() { + this.child.on('data', (data) => { + this.parser.feed(data); + }); - this.child.on('error', (err) => { - this.emit('error', err); - }); + this.child.on('exit', (code, signal) => { + this.emit('exit', code, signal); + }); - this.parser.on('error', (err) => { - this.emit('error', err); - }); + this.child.on('error', (err) => { + this.emit('error', err); + }); - this.parser.on('packet', (packet) => { - this.emit('packet', packet); - }); + this.parser.on('error', (err) => { + this.emit('error', err); + }); - this.listen(); -}; + this.parser.on('packet', (packet) => { + this.emit('packet', packet); + }); -/** - * Listen for packets. - * @private - */ + this.listen(); + } -Worker.prototype.listen = function listen() { - this.on('exit', (code, signal) => { - this.killJobs(); - }); + /** + * Listen for packets. + * @private + */ - this.on('error', (err) => { - this.killJobs(); - }); + listen() { + this.on('exit', (code, signal) => { + this.killJobs(); + }); - this.on('packet', (packet) => { - try { - this.handlePacket(packet); - } catch (e) { - this.emit('error', e); - } - }); + this.on('error', (err) => { + this.killJobs(); + }); - this.sendEnv({ - BCOIN_WORKER_NETWORK: Network.type, - BCOIN_WORKER_ISTTY: process.stdout - ? (process.stdout.isTTY ? '1' : '0') - : '0' - }); -}; + this.on('packet', (packet) => { + try { + this.handlePacket(packet); + } catch (e) { + this.emit('error', e); + } + }); -/** - * Handle packet. - * @private - * @param {Packet} packet - */ + this.sendEnv({ + BCOIN_WORKER_NETWORK: Network.type, + BCOIN_WORKER_ISTTY: process.stdout + ? (process.stdout.isTTY ? '1' : '0') + : '0' + }); + } -Worker.prototype.handlePacket = function handlePacket(packet) { - switch (packet.cmd) { - case packets.types.EVENT: - this.emit('event', packet.items); - this.emit(...packet.items); - break; - case packets.types.LOG: - this.emit('log', packet.text); - break; - case packets.types.ERROR: - this.emit('error', packet.error); - break; - case packets.types.ERRORRESULT: - this.rejectJob(packet.id, packet.error); - break; - default: - this.resolveJob(packet.id, packet); - break; - } -}; + /** + * Handle packet. + * @private + * @param {Packet} packet + */ + + handlePacket(packet) { + switch (packet.cmd) { + case packets.types.EVENT: + this.emit('event', packet.items); + this.emit(...packet.items); + break; + case packets.types.LOG: + this.emit('log', packet.text); + break; + case packets.types.ERROR: + this.emit('error', packet.error); + break; + case packets.types.ERRORRESULT: + this.rejectJob(packet.id, packet.error); + break; + default: + this.resolveJob(packet.id, packet); + break; + } + } -/** - * Send data to worker. - * @param {Buffer} data - * @returns {Boolean} - */ + /** + * Send data to worker. + * @param {Buffer} data + * @returns {Boolean} + */ -Worker.prototype.write = function write(data) { - return this.child.write(data); -}; + write(data) { + return this.child.write(data); + } -/** - * Frame and send a packet. - * @param {Packet} packet - * @returns {Boolean} - */ + /** + * Frame and send a packet. + * @param {Packet} packet + * @returns {Boolean} + */ -Worker.prototype.send = function send(packet) { - return this.write(this.framer.packet(packet)); -}; + send(packet) { + return this.write(this.framer.packet(packet)); + } -/** - * Send environment. - * @param {Object} env - * @returns {Boolean} - */ + /** + * Send environment. + * @param {Object} env + * @returns {Boolean} + */ -Worker.prototype.sendEnv = function sendEnv(env) { - return this.send(new packets.EnvPacket(env)); -}; + sendEnv(env) { + return this.send(new packets.EnvPacket(env)); + } -/** - * Emit an event on the worker side. - * @param {String} event - * @param {...Object} arg - * @returns {Boolean} - */ + /** + * Emit an event on the worker side. + * @param {String} event + * @param {...Object} arg + * @returns {Boolean} + */ -Worker.prototype.sendEvent = function sendEvent(...items) { - return this.send(new packets.EventPacket(items)); -}; + sendEvent(...items) { + return this.send(new packets.EventPacket(items)); + } -/** - * Destroy the worker. - */ + /** + * Destroy the worker. + */ -Worker.prototype.destroy = function destroy() { - return this.child.destroy(); -}; + destroy() { + return this.child.destroy(); + } -/** - * Call a method for a worker to execute. - * @param {Packet} packet - * @param {Number} timeout - * @returns {Promise} - */ + /** + * Call a method for a worker to execute. + * @param {Packet} packet + * @param {Number} timeout + * @returns {Promise} + */ -Worker.prototype.execute = function execute(packet, timeout) { - return new Promise((resolve, reject) => { - this._execute(packet, timeout, resolve, reject); - }); -}; + execute(packet, timeout) { + return new Promise((resolve, reject) => { + this._execute(packet, timeout, resolve, reject); + }); + } -/** - * Call a method for a worker to execute. - * @private - * @param {Packet} packet - * @param {Number} timeout - * @param {Function} resolve - * @param {Function} reject - * the worker method specifies. - */ + /** + * Call a method for a worker to execute. + * @private + * @param {Packet} packet + * @param {Number} timeout + * @param {Function} resolve + * @param {Function} reject + * the worker method specifies. + */ -Worker.prototype._execute = function _execute(packet, timeout, resolve, reject) { - const job = new PendingJob(this, packet.id, resolve, reject); + _execute(packet, timeout, resolve, reject) { + const job = new PendingJob(this, packet.id, resolve, reject); - assert(!this.pending.has(packet.id), 'ID overflow.'); + assert(!this.pending.has(packet.id), 'ID overflow.'); - this.pending.set(packet.id, job); + this.pending.set(packet.id, job); - job.start(timeout); + job.start(timeout); - this.send(packet); -}; + this.send(packet); + } -/** - * Resolve a job. - * @param {Number} id - * @param {Packet} result - */ + /** + * Resolve a job. + * @param {Number} id + * @param {Packet} result + */ -Worker.prototype.resolveJob = function resolveJob(id, result) { - const job = this.pending.get(id); + resolveJob(id, result) { + const job = this.pending.get(id); - if (!job) - throw new Error(`Job ${id} is not in progress.`); + if (!job) + throw new Error(`Job ${id} is not in progress.`); - job.resolve(result); -}; + job.resolve(result); + } -/** - * Reject a job. - * @param {Number} id - * @param {Error} err - */ + /** + * Reject a job. + * @param {Number} id + * @param {Error} err + */ -Worker.prototype.rejectJob = function rejectJob(id, err) { - const job = this.pending.get(id); + rejectJob(id, err) { + const job = this.pending.get(id); - if (!job) - throw new Error(`Job ${id} is not in progress.`); + if (!job) + throw new Error(`Job ${id} is not in progress.`); - job.reject(err); -}; + job.reject(err); + } -/** - * Kill all jobs associated with worker. - */ + /** + * Kill all jobs associated with worker. + */ -Worker.prototype.killJobs = function killJobs() { - for (const job of this.pending.values()) - job.destroy(); -}; + killJobs() { + for (const job of this.pending.values()) + job.destroy(); + } +} /** * Pending Job - * @constructor * @ignore - * @param {Worker} worker - * @param {Number} id - * @param {Function} resolve - * @param {Function} reject */ -function PendingJob(worker, id, resolve, reject) { - this.worker = worker; - this.id = id; - this.job = co.job(resolve, reject); - this.timer = null; -} +class PendingJob { + /** + * Create a pending job. + * @constructor + * @param {Worker} worker + * @param {Number} id + * @param {Function} resolve + * @param {Function} reject + */ + + constructor(worker, id, resolve, reject) { + this.worker = worker; + this.id = id; + this.job = { resolve, reject }; + this.timer = null; + } -/** - * Start the timer. - * @param {Number} timeout - */ + /** + * Start the timer. + * @param {Number} timeout + */ -PendingJob.prototype.start = function start(timeout) { - if (!timeout || timeout <= 0) - return; + start(timeout) { + if (!timeout || timeout <= 0) + return; - this.timer = setTimeout(() => { - this.reject(new Error('Worker timed out.')); - }, timeout); -}; + this.timer = setTimeout(() => { + this.reject(new Error('Worker timed out.')); + }, timeout); + } -/** - * Destroy the job with an error. - */ + /** + * Destroy the job with an error. + */ -PendingJob.prototype.destroy = function destroy() { - this.reject(new Error('Job was destroyed.')); -}; + destroy() { + this.reject(new Error('Job was destroyed.')); + } -/** - * Cleanup job state. - * @returns {Job} - */ + /** + * Cleanup job state. + * @returns {Job} + */ -PendingJob.prototype.cleanup = function cleanup() { - const job = this.job; + cleanup() { + const job = this.job; - assert(job, 'Already finished.'); + assert(job, 'Already finished.'); - this.job = null; + this.job = null; - if (this.timer != null) { - clearTimeout(this.timer); - this.timer = null; - } + if (this.timer != null) { + clearTimeout(this.timer); + this.timer = null; + } - assert(this.worker.pending.has(this.id)); - this.worker.pending.delete(this.id); + assert(this.worker.pending.has(this.id)); + this.worker.pending.delete(this.id); - return job; -}; + return job; + } -/** - * Complete job with result. - * @param {Object} result - */ + /** + * Complete job with result. + * @param {Object} result + */ -PendingJob.prototype.resolve = function resolve(result) { - const job = this.cleanup(); - job.resolve(result); -}; + resolve(result) { + const job = this.cleanup(); + job.resolve(result); + } -/** - * Complete job with error. - * @param {Error} err - */ + /** + * Complete job with error. + * @param {Error} err + */ -PendingJob.prototype.reject = function reject(err) { - const job = this.cleanup(); - job.reject(err); -}; + reject(err) { + const job = this.cleanup(); + job.reject(err); + } +} /* * Helpers diff --git a/migrate/chaindb0to1.js b/migrate/chaindb0to1.js deleted file mode 100644 index c8bd9561a..000000000 --- a/migrate/chaindb0to1.js +++ /dev/null @@ -1,109 +0,0 @@ -'use strict'; - -const bcoin = require('../'); -const assert = require('assert'); -const BufferWriter = require('../lib/utils/writer'); -let file = process.argv[2]; - -assert(typeof file === 'string', 'Please pass in a database path.'); - -file = file.replace(/\.ldb\/?$/, ''); - -const db = bcoin.ldb({ - location: file, - db: 'leveldb', - compression: true, - cacheSize: 32 << 20, - createIfMissing: false, - bufferKeys: true -}); - -function makeKey(data) { - const height = data.readUInt32LE(1, true); - const key = Buffer.allocUnsafe(5); - key[0] = 0x48; - key.writeUInt32BE(height, 1, true); - return key; -} - -async function checkVersion() { - console.log('Checking version.'); - - const data = await db.get('V'); - - if (!data) - return; - - const ver = data.readUInt32LE(0, true); - - if (ver !== 0) - throw Error(`DB is version ${ver}.`); -} - -async function updateState() { - console.log('Updating chain state.'); - - const data = await db.get('R'); - - if (!data || data.length < 32) - throw new Error('No chain state.'); - - const hash = data.slice(0, 32); - - let p = new BufferWriter(); - p.writeHash(hash); - p.writeU64(0); - p.writeU64(0); - p.writeU64(0); - p = p.render(); - - const batch = db.batch(); - - batch.put('R', p); - - const ver = Buffer.allocUnsafe(4); - ver.writeUInt32LE(1, 0, true); - batch.put('V', ver); - - await batch.write(); - - console.log('Updated chain state.'); -} - -async function updateEndian() { - const batch = db.batch(); - let total = 0; - - console.log('Updating endianness.'); - console.log('Iterating...'); - - const iter = db.iterator({ - gte: Buffer.from('4800000000', 'hex'), - lte: Buffer.from('48ffffffff', 'hex'), - values: true - }); - - while (await iter.next()) { - const {key, value} = iter; - batch.del(key); - batch.put(makeKey(key), value); - total++; - } - - console.log('Migrating %d items.', total); - - await batch.write(); - - console.log('Migrated endianness.'); -} - -(async () => { - await db.open(); - console.log('Opened %s.', file); - await checkVersion(); - await updateState(); - await updateEndian(); -})().then(() => { - console.log('Migration complete.'); - process.exit(0); -}); diff --git a/migrate/chaindb1to2.js b/migrate/chaindb1to2.js deleted file mode 100644 index 3189267cf..000000000 --- a/migrate/chaindb1to2.js +++ /dev/null @@ -1,274 +0,0 @@ -'use strict'; - -const assert = require('assert'); -const encoding = require('../lib/utils/encoding'); -const networks = require('../lib/protocol/networks'); -const co = require('../lib/utils/co'); -const BufferWriter = require('../lib/utils/writer'); -const BufferReader = require('../lib/utils/reader'); -const OldCoins = require('./coins-old'); -const Coins = require('../lib/coins/coins'); -const UndoCoins = require('../lib/coins/undocoins'); -const Coin = require('../lib/primitives/coin'); -const Output = require('../lib/primitives/output'); -const LDB = require('../lib/db/ldb'); -let file = process.argv[2]; -let batch; - -assert(typeof file === 'string', 'Please pass in a database path.'); - -file = file.replace(/\.ldb\/?$/, ''); - -const db = LDB({ - location: file, - db: 'leveldb', - compression: true, - cacheSize: 32 << 20, - createIfMissing: false, - bufferKeys: true -}); - -const options = {}; -options.spv = process.argv.indexOf('--spv') !== -1; -options.prune = process.argv.indexOf('--prune') !== -1; -options.indexTX = process.argv.indexOf('--index-tx') !== -1; -options.indexAddress = process.argv.indexOf('--index-address') !== -1; -options.network = networks.main; - -const index = process.argv.indexOf('--network'); - -if (index !== -1) { - options.network = networks[process.argv[index + 1]]; - assert(options.network, 'Invalid network.'); -} - -async function updateVersion() { - console.log('Checking version.'); - - const data = await db.get('V'); - - if (!data) - throw new Error('No DB version found!'); - - let ver = data.readUInt32LE(0, true); - - if (ver !== 1) - throw Error(`DB is version ${ver}.`); - - ver = Buffer.allocUnsafe(4); - ver.writeUInt32LE(2, 0, true); - batch.put('V', ver); -} - -async function checkTipIndex() { - const keys = await db.keys({ - gte: pair('p', encoding.ZERO_HASH), - lte: pair('p', encoding.MAX_HASH) - }); - - if (keys.length === 0) { - console.log('No tip index found.'); - console.log('Please run migrate/ensure-tip-index.js first!'); - process.exit(1); - return; - } - - if (keys.length < 3) { - console.log('Note: please run ensure-tip-index.js if you haven\'t yet.'); - await co.timeout(2000); - return; - } -} - -async function updateOptions() { - if (await db.has('O')) - return; - - if (process.argv.indexOf('--network') === -1) { - console.log('Warning: no options found in chaindb.'); - console.log('Make sure you selected the correct options'); - console.log('which may include any of:'); - console.log('`--network [name]`, `--spv`, `--witness`,'); - console.log('`--prune`, `--index-tx`, and `--index-address`.'); - console.log('Continuing migration in 5 seconds...'); - await co.timeout(5000); - } - - batch.put('O', defaultOptions()); -} - -async function updateDeployments() { - if (await db.has('v')) - return; - - if (process.argv.indexOf('--network') === -1) { - console.log('Warning: no deployment table found.'); - console.log('Make sure `--network` is set properly.'); - console.log('Continuing migration in 5 seconds...'); - await co.timeout(5000); - } - - batch.put('v', defaultDeployments()); -} - -async function reserializeCoins() { - let total = 0; - - const iter = db.iterator({ - gte: pair('c', encoding.ZERO_HASH), - lte: pair('c', encoding.MAX_HASH), - values: true - }); - - while (await iter.next()) { - const {key, value} = iter; - const hash = key.toString('hex', 1, 33); - const old = OldCoins.fromRaw(value, hash); - - const coins = new Coins(); - coins.version = old.version; - coins.hash = old.hash; - coins.height = old.height; - coins.coinbase = old.coinbase; - - for (let i = 0; i < old.outputs.length; i++) { - const coin = old.get(i); - - if (!coin) { - coins.outputs.push(null); - continue; - } - - const output = new Output(); - output.script = coin.script; - output.value = coin.value; - - if (!output.script.isUnspendable()) - coins.addOutput(coin.index, output); - } - - coins.cleanup(); - - batch.put(key, coins.toRaw()); - - if (++total % 100000 === 0) - console.log('Reserialized %d coins.', total); - } - - console.log('Reserialized %d coins.', total); -} - -async function reserializeUndo() { - let total = 0; - - const iter = db.iterator({ - gte: pair('u', encoding.ZERO_HASH), - lte: pair('u', encoding.MAX_HASH), - values: true - }); - - for (;;) { - const item = await iter.next(); - - if (!item) - break; - - const br = new BufferReader(item.value); - const undo = new UndoCoins(); - - while (br.left()) { - undo.push(null); - injectCoin(undo.top(), Coin.fromReader(br)); - } - - batch.put(item.key, undo.toRaw()); - - if (++total % 10000 === 0) - console.log('Reserialized %d undo coins.', total); - } - - console.log('Reserialized %d undo coins.', total); -} - -function write(data, str, off) { - if (Buffer.isBuffer(str)) - return str.copy(data, off); - return data.write(str, off, 'hex'); -} - -function pair(prefix, hash) { - const key = Buffer.allocUnsafe(33); - if (typeof prefix === 'string') - prefix = prefix.charCodeAt(0); - key[0] = prefix; - write(key, hash, 1); - return key; -} - -function injectCoin(undo, coin) { - const output = new Output(); - - output.value = coin.value; - output.script = coin.script; - - undo.output = output; - undo.version = coin.version; - undo.height = coin.height; - undo.coinbase = coin.coinbase; -} - -function defaultOptions() { - const bw = new BufferWriter(); - let flags = 0; - - if (options.spv) - flags |= 1 << 0; - - flags |= 1 << 1; - - if (options.prune) - flags |= 1 << 2; - - if (options.indexTX) - flags |= 1 << 3; - - if (options.indexAddress) - flags |= 1 << 4; - - bw.writeU32(options.network.magic); - bw.writeU32(flags); - bw.writeU32(0); - - return bw.render(); -} - -function defaultDeployments() { - const bw = new BufferWriter(); - - bw.writeU8(options.network.deploys.length); - - for (let i = 0; i < options.network.deploys.length; i++) { - const deployment = options.network.deploys[i]; - bw.writeU8(deployment.bit); - bw.writeU32(deployment.startTime); - bw.writeU32(deployment.timeout); - } - - return bw.render(); -} - -(async () => { - await db.open(); - console.log('Opened %s.', file); - batch = db.batch(); - await updateVersion(); - await checkTipIndex(); - await updateOptions(); - await updateDeployments(); - await reserializeCoins(); - await reserializeUndo(); - await batch.write(); -})().then(() => { - console.log('Migration complete.'); - process.exit(0); -}); diff --git a/migrate/chaindb2to3.js b/migrate/chaindb2to3.js index 4248684b8..a07a7e4c6 100644 --- a/migrate/chaindb2to3.js +++ b/migrate/chaindb2to3.js @@ -16,34 +16,31 @@ if (process.argv.indexOf('-h') !== -1 } const assert = require('assert'); -const encoding = require('../lib/utils/encoding'); -const co = require('../lib/utils/co'); +const bdb = require('bdb'); +const hash256 = require('bcrypto/lib/hash256'); +const BN = require('bcrypto/lib/bn.js'); +const bio = require('bufio'); +const LRU = require('blru'); +const {BufferMap} = require('buffer-map'); const util = require('../lib/utils/util'); -const digest = require('../lib/crypto/digest'); -const BN = require('../lib/crypto/bn'); -const StaticWriter = require('../lib/utils/staticwriter'); -const BufferReader = require('../lib/utils/reader'); const OldCoins = require('./coins/coins'); const OldUndoCoins = require('./coins/undocoins'); const CoinEntry = require('../lib/coins/coinentry'); const UndoCoins = require('../lib/coins/undocoins'); const Block = require('../lib/primitives/block'); -const LDB = require('../lib/db/ldb'); -const LRU = require('../lib/utils/lru'); +const consensus = require('../lib/protocol/consensus'); -const file = process.argv[2].replace(/\.ldb\/?$/, ''); const shouldPrune = process.argv.indexOf('--prune') !== -1; + let hasIndex = false; let hasPruned = false; let hasSPV = false; -const db = LDB({ - location: file, - db: 'leveldb', +const db = bdb.create({ + location: process.argv[2], compression: true, cacheSize: 32 << 20, - createIfMissing: false, - bufferKeys: true + createIfMissing: false }); // \0\0migrate @@ -57,18 +54,18 @@ const STATE_ENTRY = 3; const STATE_FINAL = 4; const STATE_DONE = 5; -const metaCache = new Map(); -const lruCache = new LRU(200000); +const metaCache = new BufferMap(); +const lruCache = new LRU(200000, null, BufferMap); function writeJournal(batch, state, hash) { const data = Buffer.allocUnsafe(34); if (!hash) - hash = encoding.NULL_HASH; + hash = consensus.ZERO_HASH; data[0] = MIGRATION_ID; data[1] = state; - data.write(hash, 2, 'hex'); + hash.copy(data, 2); batch.put(JOURNAL_KEY, data); } @@ -77,16 +74,16 @@ async function readJournal() { const data = await db.get(JOURNAL_KEY); if (!data) - return [STATE_VERSION, encoding.NULL_HASH]; - - if (data[0] !== MIGRATION_ID) - throw new Error('Bad migration id.'); + return [STATE_VERSION, consensus.ZERO_HASH]; if (data.length !== 34) throw new Error('Bad migration length.'); + if (data[0] !== MIGRATION_ID) + throw new Error('Bad migration id.'); + const state = data.readUInt8(1, true); - const hash = data.toString('hex', 2, 34); + const hash = data.slice(2, 34); console.log('Reading journal.'); console.log('Recovering from state %d.', state); @@ -99,12 +96,12 @@ async function updateVersion() { console.log('Checking version.'); - const verRaw = await db.get('V'); + const raw = await db.get('V'); - if (!verRaw) + if (!raw) throw new Error('No DB version found!'); - const version = verRaw.readUInt32LE(0, true); + const version = raw.readUInt32LE(0, true); if (version !== 2) throw Error(`DB is version ${version}.`); @@ -122,17 +119,19 @@ async function updateVersion() { await batch.write(); - return [STATE_UNDO, encoding.NULL_HASH]; + return [STATE_UNDO, consensus.ZERO_HASH]; } async function reserializeUndo(hash) { let tip = await getTip(); + const height = tip.height; - if (hash !== encoding.NULL_HASH) + if (!hash.equals(consensus.ZERO_HASH)) tip = await getEntry(hash); - console.log('Reserializing undo coins from tip %s.', util.revHex(tip.hash)); + console.log('Reserializing undo coins from tip %s.', + util.revHex(tip.hash)); let batch = db.batch(); let pruning = false; @@ -256,16 +255,16 @@ async function reserializeUndo(hash) { 'Reserialized %d undo records (%d coins).', total, totalCoins); - return [STATE_CLEANUP, encoding.NULL_HASH]; + return [STATE_CLEANUP, consensus.ZERO_HASH]; } async function cleanupIndex() { if (hasSPV) - return [STATE_COINS, encoding.NULL_HASH]; + return [STATE_COINS, consensus.ZERO_HASH]; const iter = db.iterator({ - gte: pair(0x01, encoding.ZERO_HASH), - lte: pair(0x01, encoding.MAX_HASH), + gte: pair(0x01, consensus.ZERO_HASH), + lte: pair(0x01, Buffer.alloc(32, 0xff)), keys: true }); @@ -292,23 +291,23 @@ async function cleanupIndex() { console.log('Cleaned up %d undo records.', total); - return [STATE_COINS, encoding.NULL_HASH]; + return [STATE_COINS, consensus.ZERO_HASH]; } async function reserializeCoins(hash) { if (hasSPV) - return [STATE_ENTRY, encoding.NULL_HASH]; + return [STATE_ENTRY, consensus.ZERO_HASH]; const iter = db.iterator({ gte: pair('c', hash), - lte: pair('c', encoding.MAX_HASH), + lte: pair('c', Buffer.alloc(32, 0xff)), keys: true, values: true }); let start = true; - if (hash !== encoding.NULL_HASH) { + if (!hash.equals(consensus.ZERO_HASH)) { const item = await iter.next(); if (!item) start = false; @@ -328,7 +327,7 @@ async function reserializeCoins(hash) { if (item.key.length !== 33) continue; - const hash = item.key.toString('hex', 1, 33); + const hash = item.key.slice(1, 33); const old = OldCoins.fromRaw(item.value, hash); let update = false; @@ -369,19 +368,19 @@ async function reserializeCoins(hash) { console.log('Reserialized %d coins.', total); - return [STATE_ENTRY, encoding.NULL_HASH]; + return [STATE_ENTRY, consensus.ZERO_HASH]; } async function reserializeEntries(hash) { const iter = db.iterator({ gte: pair('e', hash), - lte: pair('e', encoding.MAX_HASH), + lte: pair('e', Buffer.alloc(32, 0xff)), values: true }); let start = true; - if (hash !== encoding.NULL_HASH) { + if (!hash.equals(consensus.ZERO_HASH)) { const item = await iter.next(); if (!item) start = false; @@ -420,7 +419,7 @@ async function reserializeEntries(hash) { console.log('Reserialized %d entries.', total); - return [STATE_FINAL, encoding.NULL_HASH]; + return [STATE_FINAL, consensus.ZERO_HASH]; } async function finalize() { @@ -433,7 +432,7 @@ async function finalize() { batch.put('V', data); // This has bugged me for a while. - batch.del(pair('n', encoding.ZERO_HASH)); + batch.del(pair('n', consensus.ZERO_HASH)); if (shouldPrune) { const data = await db.get('O'); @@ -456,7 +455,7 @@ async function finalize() { await db.compactRange(); - return [STATE_DONE, encoding.NULL_HASH]; + return [STATE_DONE, consensus.ZERO_HASH]; } async function getMeta(coin, prevout) { @@ -525,7 +524,7 @@ async function getMeta(coin, prevout) { return [1, 1, false]; } - const br = new BufferReader(coinsRaw); + const br = bio.read(coinsRaw); const version = br.readVarint(); const height = br.readU32(); @@ -540,7 +539,7 @@ async function getTip() { async function getTipHash() { const state = await db.get('R'); assert(state); - return state.toString('hex', 0, 32); + return state.slice(0, 32); } async function getEntry(hash) { @@ -568,7 +567,7 @@ async function isIndexed() { } async function isMainChain(entry, tip) { - if (entry.hash === tip) + if (entry.hash.equals(tip)) return true; if (await db.get(pair('n', entry.hash))) @@ -578,17 +577,17 @@ async function isMainChain(entry, tip) { } function entryFromRaw(data) { - const br = new BufferReader(data, true); - const hash = digest.hash256(br.readBytes(80)); + const br = bio.read(data, true); + const hash = hash256.digest(br.readBytes(80)); br.seek(-80); const entry = {}; - entry.hash = hash.toString('hex'); + entry.hash = hash.toString(); entry.version = br.readU32(); - entry.prevBlock = br.readHash('hex'); - entry.merkleRoot = br.readHash('hex'); - entry.ts = br.readU32(); + entry.prevBlock = br.readHash(); + entry.merkleRoot = br.readHash(); + entry.time = br.readU32(); entry.bits = br.readU32(); entry.nonce = br.readU32(); entry.height = br.readU32(); @@ -598,12 +597,12 @@ function entryFromRaw(data) { } function entryToRaw(entry, main) { - const bw = new StaticWriter(116 + 1); + const bw = bio.write(116 + 1); bw.writeU32(entry.version); bw.writeHash(entry.prevBlock); bw.writeHash(entry.merkleRoot); - bw.writeU32(entry.ts); + bw.writeU32(entry.time); bw.writeU32(entry.bits); bw.writeU32(entry.nonce); bw.writeU32(entry.height); @@ -613,10 +612,9 @@ function entryToRaw(entry, main) { return bw.render(); } -function write(data, str, off) { - if (Buffer.isBuffer(str)) - return str.copy(data, off); - return data.write(str, off, 'hex'); +function write(data, hash, off) { + assert(Buffer.isBuffer(hash)); + return hash.copy(data, off); } function pair(prefix, hash) { @@ -644,7 +642,7 @@ reserializeEntries; (async () => { await db.open(); - console.log('Opened %s.', file); + console.log('Opened %s.', process.argv[2]); if (await isSPV()) hasSPV = true; @@ -664,7 +662,7 @@ reserializeEntries; console.log('Starting migration in 3 seconds...'); console.log('If you crash you can start over.'); - await co.timeout(3000); + await new Promise(r => setTimeout(r, 3000)); let [state, hash] = await readJournal(); @@ -684,14 +682,14 @@ reserializeEntries; // [state, hash] = await reserializeEntries(hash); if (state === STATE_ENTRY) - [state, hash] = [STATE_FINAL, encoding.NULL_HASH]; + [state, hash] = [STATE_FINAL, consensus.ZERO_HASH]; if (state === STATE_FINAL) [state, hash] = await finalize(); assert(state === STATE_DONE); - console.log('Closing %s.', file); + console.log('Closing %s.', process.argv[2]); await db.close(); diff --git a/migrate/chaindb3to4.js b/migrate/chaindb3to4.js new file mode 100644 index 000000000..497940a63 --- /dev/null +++ b/migrate/chaindb3to4.js @@ -0,0 +1,159 @@ +'use strict'; + +const assert = require('assert'); +const bdb = require('bdb'); +const layout = require('../lib/blockchain/layout'); + +// changes: +// db version record +// deployment table v->D +// C/T key format + +assert(process.argv.length > 2, 'Please pass in a database path.'); + +let parent = null; + +const db = bdb.create({ + location: process.argv[2], + memory: false, + compression: true, + cacheSize: 32 << 20, + createIfMissing: false +}); + +async function updateVersion() { + console.log('Checking version.'); + + const data = await db.get(layout.V.encode()); + assert(data, 'No version.'); + + const ver = data.readUInt32LE(0, true); + + if (ver !== 3) + throw Error(`DB is version ${ver}.`); + + console.log('Updating version to %d.', ver + 1); + + const buf = Buffer.allocUnsafe(5 + 4); + buf.write('chain', 0, 'ascii'); + buf.writeUInt32LE(4, 5, true); + + parent.put(layout.V.encode(), buf); +} + +async function migrateKeys(id, from, to) { + console.log('Migrating keys for %s.', String.fromCharCode(id)); + + const iter = db.iterator({ + gt: Buffer.from([id]), + lt: Buffer.from([id + 1]), + keys: true + }); + + let batch = db.batch(); + let total = 0; + let items = 0; + + await iter.each(async (key) => { + batch.put(to.encode(...from(key)), null); + batch.del(key); + + total += (key.length + 80) * 2; + items += 1; + + if (total >= (128 << 20)) { + await batch.write(); + batch = db.batch(); + total = 0; + } + }); + + console.log('Migrated %d keys for %s.', items, String.fromCharCode(id)); + + return batch.write(); +} + +async function updateKeys() { + console.log('Updating keys...'); + + const v = Buffer.from('v', 'ascii'); + + const table = await db.get(v); + assert(table); + + parent.put(layout.D.encode(), table); + parent.del(v); + + const raw = await db.get(layout.O.encode()); + assert(raw); + + const flags = raw.readUInt32LE(8, true); + + if (!(flags & 16)) { + console.log('Updated keys.'); + return; + } + + console.log('Updating address index keys...'); + + await migrateKeys(0x54, parseT, layout.T); // T + await migrateKeys(0xab, parseT, layout.T); // W + T + await migrateKeys(0x43, parseC, layout.C); // C + await migrateKeys(0x9a, parseC, layout.C); // W + C + + console.log('Updated keys.'); +} + +function parseT(key) { + assert(Buffer.isBuffer(key)); + + if (key.length === 65) + return [key.slice(1, 33), key.slice(33, 65)]; + + assert(key.length === 53); + return [key.slice(1, 21), key.slice(21, 53)]; +} + +function parseC(key) { + assert(Buffer.isBuffer(key)); + + let addr, hash, index; + + if (key.length === 69) { + addr = key.slice(1, 33); + hash = key.slice(33, 65); + index = key.readUInt32BE(65, 0); + } else if (key.length === 57) { + addr = key.slice(1, 21); + hash = key.slice(21, 53); + index = key.readUInt32BE(53, 0); + } else { + assert(false); + } + + return [addr, hash, index]; +} + +/* + * Execute + */ + +(async () => { + await db.open(); + + console.log('Opened %s.', process.argv[2]); + + parent = db.batch(); + + await updateVersion(); + await updateKeys(); + + await parent.write(); + await db.close(); +})().then(() => { + console.log('Migration complete.'); + process.exit(0); +}).catch((err) => { + console.error(err.stack); + process.exit(1); +}); diff --git a/migrate/chaindb4to6.js b/migrate/chaindb4to6.js new file mode 100644 index 000000000..0e9e8b36a --- /dev/null +++ b/migrate/chaindb4to6.js @@ -0,0 +1,206 @@ +'use strict'; + +const assert = require('assert'); +const bdb = require('bdb'); +const layout = require('../lib/blockchain/layout'); +const FileBlockStore = require('../lib/blockstore/file'); +const {resolve} = require('path'); + +assert(process.argv.length > 2, 'Please pass in a database path.'); + +// Changes: +// 1. Moves blocks and undo blocks from leveldb to flat files. +// 2. Removes tx and addr indexes from chaindb. + +const db = bdb.create({ + location: process.argv[2], + memory: false, + compression: true, + cacheSize: 32 << 20, + createIfMissing: false +}); + +const location = resolve(process.argv[2], '../blocks'); + +const blockStore = new FileBlockStore({ + location: location +}); + +async function getVersion() { + const data = await db.get(layout.V.encode()); + assert(data, 'No version.'); + + return data.readUInt32LE(5, true); +} + +async function updateVersion(version) { + await checkVersion(version - 1); + + console.log('Updating version to %d.', version); + + const buf = Buffer.allocUnsafe(5 + 4); + buf.write('chain', 0, 'ascii'); + buf.writeUInt32LE(version, 5, true); + + const parent = db.batch(); + parent.put(layout.V.encode(), buf); + await parent.write(); +} + +async function checkVersion(version) { + console.log('Checking version.'); + + const ver = await getVersion(); + + if (ver !== version) + throw Error(`DB is version ${ver}.`); + + return ver; +} + +async function migrateUndoBlocks() { + console.log('Migrating undo blocks'); + + let parent = db.batch(); + + const iter = db.iterator({ + gte: layout.u.min(), + lte: layout.u.max(), + keys: true, + values: true + }); + + let total = 0; + + await iter.each(async (key, value) => { + const hash = key.slice(1); + await blockStore.writeUndo(hash, value); + parent.del(key); + + if (++total % 10000 === 0) { + console.log('Migrated up %d undo blocks.', total); + await parent.write(); + parent = db.batch(); + } + }); + + console.log('Migrated all %d undo blocks.', total); + await parent.write(); +} + +async function migrateBlocks() { + console.log('Migrating blocks'); + + let parent = db.batch(); + + const iter = db.iterator({ + gte: layout.b.min(), + lte: layout.b.max(), + keys: true, + values: true + }); + + let total = 0; + + await iter.each(async (key, value) => { + const hash = key.slice(1); + await blockStore.write(hash, value); + parent.del(key); + + if (++total % 10000 === 0) { + console.log('Migrated up %d blocks.', total); + await parent.write(); + parent = db.batch(); + } + }); + + console.log('Migrated all %d blocks.', total); + await parent.write(); +} + +async function removeKey(name, key) { + const iter = db.iterator({ + gte: key.min(), + lte: key.max(), + reverse: true, + keys: true + }); + + let batch = db.batch(); + let total = 0; + + while (await iter.next()) { + const {key} = iter; + batch.del(key); + + if (++total % 10000 === 0) { + console.log('Cleaned up %d %s index records.', total, name); + await batch.write(); + batch = db.batch(); + } + } + await batch.write(); + + console.log('Cleaned up %d %s index records.', total, name); +} + +async function migrateIndexes() { + const t = bdb.key('t', ['hash256']); + const T = bdb.key('T', ['hash', 'hash256']); + const C = bdb.key('C', ['hash', 'hash256', 'uint32']); + + await removeKey('hash -> tx', t); + await removeKey('addr -> tx', T); + await removeKey('addr -> coin', C); +} + +/* + * Execute + */ + +(async () => { + await db.open(); + + console.log('Opened %s.', process.argv[2]); + + const version = await getVersion(); + let compact = false; + + switch (version) { + case 4: + // Upgrade from version 4 to 5. + await checkVersion(4); + await blockStore.ensure(); + await blockStore.open(); + await migrateBlocks(); + await migrateUndoBlocks(); + await updateVersion(5); + await blockStore.close(); + compact = true; + case 5: + // Upgrade from version 5 to 6. + await checkVersion(5); + await migrateIndexes(); + await updateVersion(6); + compact = true; + break; + case 6: + console.log('Already upgraded.'); + break; + default: + console.log(`DB version is ${version}.`); + } + + if (compact) { + console.log('Compacting database'); + await db.compactRange(); + } + + await db.close(); +})().then(() => { + console.log('Migration complete.'); + process.exit(0); +}).catch((err) => { + console.error(err.stack); + process.exit(1); +}); diff --git a/migrate/coins-old.js b/migrate/coins-old.js deleted file mode 100644 index 4b8148627..000000000 --- a/migrate/coins-old.js +++ /dev/null @@ -1,612 +0,0 @@ -/*! - * coins.js - coins object for bcoin - * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -/* eslint-disable */ - -'use strict'; - -const assert = require('assert'); -const util = require('../lib/utils/util'); -const encoding = require('../lib/utils/encoding'); -const Coin = require('../lib/primitives/coin'); -const Output = require('../lib/primitives/output'); -const BufferReader = require('../lib/utils/reader'); -const BufferWriter = require('../lib/utils/writer'); -const {compress, decompress} = require('./compress-old'); - -/** - * Represents the outputs for a single transaction. - * @exports Coins - * @constructor - * @param {TX|Object} tx/options - TX or options object. - * @property {Hash} hash - Transaction hash. - * @property {Number} version - Transaction version. - * @property {Number} height - Transaction height (-1 if unconfirmed). - * @property {Boolean} coinbase - Whether the containing - * transaction is a coinbase. - * @property {Coin[]} outputs - Coins. - */ - -function Coins(options) { - if (!(this instanceof Coins)) - return new Coins(options); - - this.version = 1; - this.hash = encoding.NULL_HASH; - this.height = -1; - this.coinbase = true; - this.outputs = []; - - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -Coins.prototype.fromOptions = function fromOptions(options) { - if (options.version != null) { - assert(util.isU32(options.version)); - this.version = options.version; - } - - if (options.hash) { - assert(typeof options.hash === 'string'); - this.hash = options.hash; - } - - if (options.height != null) { - assert(util.isInt(options.height)); - this.height = options.height; - } - - if (options.coinbase != null) { - assert(typeof options.coinbase === 'boolean'); - this.coinbase = options.coinbase; - } - - if (options.outputs) { - assert(Array.isArray(options.outputs)); - this.outputs = options.outputs; - } - - return this; -}; - -/** - * Instantiate coins from options object. - * @param {Object} options - * @returns {Coins} - */ - -Coins.fromOptions = function fromOptions(options) { - return new Coins().fromOptions(options); -}; - -/** - * Add a single coin to the collection. - * @param {Coin} coin - */ - -Coins.prototype.add = function add(coin) { - if (this.outputs.length === 0) { - this.version = coin.version; - this.hash = coin.hash; - this.height = coin.height; - this.coinbase = coin.coinbase; - } - - while (this.outputs.length <= coin.index) - this.outputs.push(null); - - if (coin.script.isUnspendable()) { - this.outputs[coin.index] = null; - return; - } - - this.outputs[coin.index] = CoinEntry.fromCoin(coin); -}; - -/** - * Test whether the collection has a coin. - * @param {Number} index - * @returns {Boolean} - */ - -Coins.prototype.has = function has(index) { - if (index >= this.outputs.length) - return false; - - return this.outputs[index] != null; -}; - -/** - * Get a coin. - * @param {Number} index - * @returns {Coin} - */ - -Coins.prototype.get = function get(index) { - if (index >= this.outputs.length) - return; - - const coin = this.outputs[index]; - - if (!coin) - return; - - return coin.toCoin(this, index); -}; - -/** - * Remove a coin and return it. - * @param {Number} index - * @returns {Coin} - */ - -Coins.prototype.spend = function spend(index) { - const coin = this.get(index); - - if (!coin) - return; - - this.outputs[index] = null; - - return coin; -}; - -/** - * Count up to the last available index. - * @returns {Number} - */ - -Coins.prototype.size = function size() { - let index = -1; - - for (let i = this.outputs.length - 1; i >= 0; i--) { - const output = this.outputs[i]; - if (output) { - index = i; - break; - } - } - - return index + 1; -}; - -/** - * Test whether the coins are fully spent. - * @returns {Boolean} - */ - -Coins.prototype.isEmpty = function isEmpty() { - return this.size() === 0; -}; - -/* - * Coins serialization: - * version: varint - * bits: uint32 (31-bit height | 1-bit coinbase-flag) - * spent-field: varint size | bitfield (0=unspent, 1=spent) - * outputs (repeated): - * compressed-script: - * prefix: 0x00 = varint size | raw script - * 0x01 = 20 byte pubkey hash - * 0x02 = 20 byte script hash - * 0x03 = 33 byte compressed key - * data: script data, dictated by the prefix - * value: varint - * - * The compression below sacrifices some cpu in exchange - * for reduced size, but in some cases the use of varints - * actually increases speed (varint versions and values - * for example). We do as much compression as possible - * without sacrificing too much cpu. Value compression - * is intentionally excluded for now as it seems to be - * too much of a perf hit. Maybe when v8 optimizes - * non-smi arithmetic better we can enable it. - */ - -/** - * Serialize the coins object. - * @param {TX|Coins} tx - * @returns {Buffer} - */ - -Coins.prototype.toRaw = function toRaw() { - const bw = new BufferWriter(); - const length = this.size(); - const len = Math.ceil(length / 8); - - // Return nothing if we're fully spent. - if (length === 0) - return; - - // Varint version: hopefully we - // never run into `-1` versions. - bw.writeVarint(this.version); - - // Create the `bits` value: - // (height | coinbase-flag). - let bits = this.height << 1; - - // Append the coinbase bit. - if (this.coinbase) - bits |= 1; - - if (bits < 0) - bits += 0x100000000; - - // Making this a varint would actually - // make 99% of coins bigger. Varints - // are really only useful up until - // 0x10000, but since we're also - // storing the coinbase flag on the - // lo bit, varints are useless (and - // actually harmful) after height - // 32767 (0x7fff). - bw.writeU32(bits); - - // Fill the spent field with zeroes to avoid - // allocating a buffer. We mark the spents - // after rendering the final buffer. - bw.writeVarint(len); - const start = bw.offset; - bw.fill(0, len); - - // Write the compressed outputs. - for (let i = 0; i < length; i++) { - const output = this.outputs[i]; - - if (!output) - continue; - - output.toWriter(bw); - } - - // Render the buffer with all - // zeroes in the spent field. - const data = bw.render(); - - // Mark the spents in the spent field. - // This is essentially a NOP for new coins. - for (let i = 0; i < length; i++) { - const output = this.outputs[i]; - - if (output) - continue; - - const bit = i % 8; - let oct = (i - bit) / 8; - oct += start; - - data[oct] |= 1 << (7 - bit); - } - - return data; -}; - -/** - * Parse serialized coins. - * @param {Buffer} data - * @param {Hash} hash - * @returns {Object} A "naked" coins object. - */ - -Coins.prototype.fromRaw = function fromRaw(data, hash, index) { - const br = new BufferReader(data); - let pos = 0; - - this.version = br.readVarint(); - - const bits = br.readU32(); - - this.height = bits >>> 1; - this.hash = hash; - this.coinbase = (bits & 1) !== 0; - - // Mark the start of the spent field and - // seek past it to avoid reading a buffer. - const len = br.readVarint(); - const start = br.offset; - br.seek(len); - - while (br.left()) { - const bit = pos % 8; - let oct = (pos - bit) / 8; - oct += start; - - // Read a single bit out of the spent field. - let spent = data[oct] >>> (7 - bit); - spent &= 1; - - // Already spent. - if (spent) { - this.outputs.push(null); - pos++; - continue; - } - - // Store the offset and size - // in the compressed coin object. - const coin = CoinEntry.fromReader(br); - - this.outputs.push(coin); - pos++; - } - - return this; -}; - -/** - * Parse a single serialized coin. - * @param {Buffer} data - * @param {Hash} hash - * @param {Number} index - * @returns {Coin} - */ - -Coins.parseCoin = function parseCoin(data, hash, index) { - const br = new BufferReader(data); - const coin = new Coin(); - let pos = 0; - - coin.version = br.readVarint(); - - const bits = br.readU32(); - - coin.hash = hash; - coin.index = index; - coin.height = bits >>> 1; - coin.hash = hash; - coin.coinbase = (bits & 1) !== 0; - - // Mark the start of the spent field and - // seek past it to avoid reading a buffer. - const len = br.readVarint(); - const start = br.offset; - br.seek(len); - - while (br.left()) { - const bit = pos % 8; - let oct = (pos - bit) / 8; - oct += start; - - // Read a single bit out of the spent field. - let spent = data[oct] >>> (7 - bit); - spent &= 1; - - // We found our coin. - if (pos === index) { - if (spent) - return; - decompress.script(coin.script, br); - coin.value = br.readVarint(); - return coin; - } - - // Already spent. - if (spent) { - pos++; - continue; - } - - // Skip past the compressed coin. - skipCoin(br); - pos++; - } -}; - -/** - * Instantiate coins from a serialized Buffer. - * @param {Buffer} data - * @param {Hash} hash - Transaction hash. - * @returns {Coins} - */ - -Coins.fromRaw = function fromRaw(data, hash) { - return new Coins().fromRaw(data, hash); -}; - -/** - * Inject properties from tx. - * @private - * @param {TX} tx - */ - -Coins.prototype.fromTX = function fromTX(tx) { - this.version = tx.version; - this.hash = tx.hash('hex'); - this.height = tx.height; - this.coinbase = tx.isCoinbase(); - - for (let i = 0; i < tx.outputs.length; i++) { - const output = tx.outputs[i]; - - if (output.script.isUnspendable()) { - this.outputs.push(null); - continue; - } - - this.outputs.push(CoinEntry.fromTX(tx, i)); - } - - return this; -}; - -/** - * Instantiate a coins object from a transaction. - * @param {TX} tx - * @returns {Coins} - */ - -Coins.fromTX = function fromTX(tx) { - return new Coins().fromTX(tx); -}; - -/** - * A compressed coin is an object which defers - * parsing of a coin. Say there is a transaction - * with 100 outputs. When a block comes in, - * there may only be _one_ input in that entire - * block which redeems an output from that - * transaction. When parsing the Coins, there - * is no sense to get _all_ of them into their - * abstract form. A compressed coin is just a - * pointer to that coin in the Coins buffer, as - * well as a size. Parsing is done only if that - * coin is being redeemed. - * @constructor - * @private - * @param {Number} offset - * @param {Number} size - * @param {Buffer} raw - */ - -function CoinEntry() { - this.offset = 0; - this.size = 0; - this.raw = null; - this.output = null; -} - -/** - * Parse the deferred data and return a Coin. - * @param {Coins} coins - * @param {Number} index - * @returns {Coin} - */ - -CoinEntry.prototype.toCoin = function toCoin(coins, index) { - const coin = new Coin(); - - // Load in all necessary properties - // from the parent Coins object. - coin.version = coins.version; - coin.coinbase = coins.coinbase; - coin.height = coins.height; - coin.hash = coins.hash; - coin.index = index; - - if (this.output) { - coin.script = this.output.script; - coin.value = this.output.value; - return coin; - } - - const br = new BufferReader(this.raw); - - // Seek to the coin's offset. - br.seek(this.offset); - - decompress.script(coin.script, br); - - coin.value = br.readVarint(); - - return coin; -}; - -/** - * Slice off the part of the buffer - * relevant to this particular coin. - */ - -CoinEntry.prototype.toWriter = function toWriter(bw) { - if (this.output) { - compress.script(this.output.script, bw); - bw.writeVarint(this.output.value); - return; - } - - assert(this.raw); - - // If we read this coin from the db and - // didn't use it, it's still in its - // compressed form. Just write it back - // as a buffer for speed. - const raw = this.raw.slice(this.offset, this.offset + this.size); - - bw.writeBytes(raw); -}; - -/** - * Instantiate compressed coin from reader. - * @param {BufferReader} br - * @returns {CoinEntry} - */ - -CoinEntry.fromReader = function fromReader(br) { - const entry = new CoinEntry(); - entry.offset = br.offset; - entry.size = skipCoin(br); - entry.raw = br.data; - return entry; -}; - -/** - * Instantiate compressed coin from tx. - * @param {TX} tx - * @param {Number} index - * @returns {CoinEntry} - */ - -CoinEntry.fromTX = function fromTX(tx, index) { - const entry = new CoinEntry(); - entry.output = tx.outputs[index]; - return entry; -}; - -/** - * Instantiate compressed coin from coin. - * @param {Coin} coin - * @returns {CoinEntry} - */ - -CoinEntry.fromCoin = function fromCoin(coin) { - const entry = new CoinEntry(); - entry.output = new Output(); - entry.output.script = coin.script; - entry.output.value = coin.value; - return entry; -}; - -/* - * Helpers - */ - -function skipCoin(br) { - const start = br.offset; - - // Skip past the compressed scripts. - switch (br.readU8()) { - case 0: - br.seek(br.readVarint()); - break; - case 1: - case 2: - br.seek(20); - break; - case 3: - br.seek(33); - break; - default: - throw new Error('Bad prefix.'); - } - - // Skip past the value. - br.readVarint(); - - return br.offset - start; -} - -/* - * Expose - */ - -module.exports = Coins; diff --git a/migrate/coins/coins.js b/migrate/coins/coins.js index 9aaf1c9b8..401992697 100644 --- a/migrate/coins/coins.js +++ b/migrate/coins/coins.js @@ -37,7 +37,7 @@ function Coins(options) { return new Coins(options); this.version = 1; - this.hash = encoding.NULL_HASH; + this.hash = encoding.ZERO_HASH; this.height = -1; this.coinbase = true; this.outputs = []; @@ -54,17 +54,17 @@ function Coins(options) { Coins.prototype.fromOptions = function fromOptions(options) { if (options.version != null) { - assert(util.isU32(options.version)); + assert((options.version >>> 0) === options.version); this.version = options.version; } if (options.hash) { - assert(typeof options.hash === 'string'); + assert(Buffer.isBuffer(options.hash)); this.hash = options.hash; } if (options.height != null) { - assert(util.isInt(options.height)); + assert(Number.isSafeInteger(options.height)); this.height = options.height; } @@ -562,7 +562,7 @@ Coins.prototype.fromTX = function fromTX(tx, height) { assert(typeof height === 'number'); this.version = tx.version; - this.hash = tx.hash('hex'); + this.hash = tx.hash(); this.height = height; this.coinbase = tx.isCoinbase(); diff --git a/migrate/coins/coinview.js b/migrate/coins/coinview.js index 91b9e4257..8d27a08aa 100644 --- a/migrate/coins/coinview.js +++ b/migrate/coins/coinview.js @@ -9,6 +9,7 @@ 'use strict'; const assert = require('assert'); +const {BufferMap} = require('buffer-map'); const Coins = require('./coins'); const UndoCoins = require('./undocoins'); const CoinEntry = Coins.CoinEntry; @@ -26,7 +27,7 @@ function CoinView() { if (!(this instanceof CoinView)) return new CoinView(); - this.map = new Map(); + this.map = new BufferMap(); this.undo = new UndoCoins(); } diff --git a/migrate/coinview-old.js b/migrate/coinview-old.js deleted file mode 100644 index 4d3639217..000000000 --- a/migrate/coinview-old.js +++ /dev/null @@ -1,149 +0,0 @@ -/*! - * coinview.js - coinview object for bcoin - * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -/* eslint-disable */ - -'use strict'; - -const assert = require('assert'); -const Coins = require('./coins-old'); - -/** - * A collections of {@link Coins} objects. - * @exports CoinView - * @constructor - * @param {Object} coins - A hash-to-coins map. - * @property {Object} coins - */ - -function CoinView(coins) { - if (!(this instanceof CoinView)) - return new CoinView(coins); - - this.coins = coins || {}; -} - -/** - * Add coins to the collection. - * @param {Coins} coins - */ - -CoinView.prototype.add = function add(coins) { - this.coins[coins.hash] = coins; -}; - -/** - * Add a coin to the collection. - * @param {Coin} coin - */ - -CoinView.prototype.addCoin = function addCoin(coin) { - assert(typeof coin.hash === 'string'); - if (!this.coins[coin.hash]) - this.coins[coin.hash] = new Coins(); - this.coins[coin.hash].add(coin); -}; - -/** - * Add a tx to the collection. - * @param {TX} tx - */ - -CoinView.prototype.addTX = function addTX(tx) { - this.add(Coins.fromTX(tx)); -}; - -/** - * Get a coin. - * @param {Hash} hash - * @param {Number} index - * @returns {Coin} - */ - -CoinView.prototype.get = function get(hash, index) { - const coins = this.coins[hash]; - - if (!coins) - return; - - return coins.get(index); -}; - -/** - * Test whether the collection has a coin. - * @param {Hash} hash - * @param {Number} index - * @returns {Boolean} - */ - -CoinView.prototype.has = function has(hash, index) { - const coins = this.coins[hash]; - - if (!coins) - return false; - - return coins.has(index); -}; - -/** - * Remove a coin and return it. - * @param {Hash} hash - * @param {Number} index - * @returns {Coin} - */ - -CoinView.prototype.spend = function spend(hash, index) { - const coins = this.coins[hash]; - - if (!coins) - return; - - return coins.spend(index); -}; - -/** - * Fill transaction(s) with coins. - * @param {TX} tx - * @returns {Boolean} True if all inputs were filled. - */ - -CoinView.prototype.fillCoins = function fillCoins(tx) { - let i, input, prevout; - - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - prevout = input.prevout; - input.coin = this.spend(prevout.hash, prevout.index); - if (!input.coin) - return false; - } - - return true; -}; - -/** - * Convert collection to an array. - * @returns {Coins[]} - */ - -CoinView.prototype.toArray = function toArray() { - const keys = Object.keys(this.coins); - const out = []; - let i, hash; - - for (i = 0; i < keys.length; i++) { - hash = keys[i]; - out.push(this.coins[hash]); - } - - return out; -}; - -/* - * Expose - */ - -module.exports = CoinView; diff --git a/migrate/compress-old.js b/migrate/compress-old.js deleted file mode 100644 index bceac2f1d..000000000 --- a/migrate/compress-old.js +++ /dev/null @@ -1,247 +0,0 @@ -/*! - * compress.js - coin compressor for bcoin - * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -const assert = require('assert'); -const secp256k1 = require('../lib/crypto/secp256k1'); - -/* - * Compression - */ - -/** - * Compress a script, write directly to the buffer. - * @param {Script} script - * @param {BufferWriter} bw - */ - -function compressScript(script, bw) { - // Attempt to compress the output scripts. - // We can _only_ ever compress them if - // they are serialized as minimaldata, as - // we need to recreate them when we read - // them. - - // P2PKH -> 1 | key-hash - // Saves 5 bytes. - if (script.isPubkeyhash(true)) { - const data = script.code[2].data; - bw.writeU8(1); - bw.writeBytes(data); - return bw; - } - - // P2SH -> 2 | script-hash - // Saves 3 bytes. - if (script.isScripthash()) { - const data = script.code[1].data; - bw.writeU8(2); - bw.writeBytes(data); - return bw; - } - - // P2PK -> 3 | compressed-key - // Only works if the key is valid. - // Saves up to 34 bytes. - if (script.isPubkey(true)) { - let data = script.code[0].data; - if (secp256k1.publicKeyVerify(data)) { - data = compressKey(data); - bw.writeU8(3); - bw.writeBytes(data); - return bw; - } - } - - // Raw -> 0 | varlen | script - bw.writeU8(0); - bw.writeVarBytes(script.toRaw()); - - return bw; -} - -/** - * Decompress a script from buffer reader. - * @param {Script} script - * @param {BufferReader} br - */ - -function decompressScript(script, br) { - let data; - - // Decompress the script. - switch (br.readU8()) { - case 0: - data = br.readVarBytes(); - script.fromRaw(data); - break; - case 1: - data = br.readBytes(20, true); - script.fromPubkeyhash(data); - break; - case 2: - data = br.readBytes(20, true); - script.fromScripthash(data); - break; - case 3: - data = br.readBytes(33, true); - // Decompress the key. If this fails, - // we have database corruption! - data = decompressKey(data); - script.fromPubkey(data); - break; - default: - throw new Error('Bad prefix.'); - } - - return script; -} - -/** - * Compress value using an exponent. Takes advantage of - * the fact that many bitcoin values are divisible by 10. - * @see https://github.com/btcsuite/btcd/blob/master/blockchain/compress.go - * @param {Amount} value - * @returns {Number} - */ - -function compressValue(value) { - if (value === 0) - return 0; - - let exp = 0; - while (value % 10 === 0 && exp < 9) { - value /= 10; - exp++; - } - - if (exp < 9) { - const last = value % 10; - value = (value - last) / 10; - return 1 + 10 * (9 * value + last - 1) + exp; - } - - return 10 + 10 * (value - 1); -} - -/** - * Decompress value. - * @param {Number} value - Compressed value. - * @returns {Amount} value - */ - -function decompressValue(value) { - if (value === 0) - return 0; - - value--; - - let exp = value % 10; - value = (value - exp) / 10; - - let n; - if (exp < 9) { - const last = value % 9; - value = (value - last) / 9; - n = value * 10 + last + 1; - } else { - n = value + 1; - } - - while (exp > 0) { - n *= 10; - exp--; - } - - return n; -} - -/** - * Compress a public key to coins compression format. - * @param {Buffer} key - * @returns {Buffer} - */ - -function compressKey(key) { - let out; - - switch (key[0]) { - case 0x02: - case 0x03: - // Key is already compressed. - out = key; - break; - case 0x04: - case 0x06: - case 0x07: - // Compress the key normally. - out = secp256k1.publicKeyConvert(key, true); - // Store the original format (which - // may be a hybrid byte) in the hi - // 3 bits so we can restore it later. - // The hi bits being set also lets us - // know that this key was originally - // decompressed. - out[0] |= key[0] << 2; - break; - default: - throw new Error('Bad point format.'); - } - - assert(out.length === 33); - - return out; -} - -/** - * Decompress a public key from the coins compression format. - * @param {Buffer} key - * @returns {Buffer} - */ - -function decompressKey(key) { - const format = key[0] >>> 2; - - assert(key.length === 33); - - // Hi bits are not set. This key - // is not meant to be decompressed. - if (format === 0) - return key; - - // Decompress the key, and off the - // low bits so publicKeyConvert - // actually understands it. - key[0] &= 0x03; - const out = secp256k1.publicKeyConvert(key, false); - - // Reset the hi bits so as not to - // mutate the original buffer. - key[0] |= format << 2; - - // Set the original format, which - // may have been a hybrid prefix byte. - out[0] = format; - - return out; -} - -/* - * Expose - */ - -exports.compress = { - script: compressScript, - value: compressValue, - key: compressKey -}; - -exports.decompress = { - script: decompressScript, - value: decompressValue, - key: decompressKey -}; diff --git a/migrate/ensure-tip-index.js b/migrate/ensure-tip-index.js deleted file mode 100644 index 200322dc1..000000000 --- a/migrate/ensure-tip-index.js +++ /dev/null @@ -1,146 +0,0 @@ -'use strict'; - -const assert = require('assert'); -const encoding = require('../lib/utils/encoding'); -const BufferReader = require('../lib/utils/reader'); -const digest = require('../lib/crypto/digest'); -const util = require('../lib/utils/util'); -const LDB = require('../lib/db/ldb'); -const BN = require('../lib/crypto/bn'); -const DUMMY = Buffer.from([0]); -let file = process.argv[2]; -let batch; - -assert(typeof file === 'string', 'Please pass in a database path.'); - -file = file.replace(/\.ldb\/?$/, ''); - -const db = LDB({ - location: file, - db: 'leveldb', - compression: true, - cacheSize: 32 << 20, - createIfMissing: false, - bufferKeys: true -}); - -async function checkVersion() { - console.log('Checking version.'); - - const data = await db.get('V'); - - if (!data) - return; - - const ver = data.readUInt32LE(0, true); - - if (ver !== 1) - throw Error(`DB is version ${ver}.`); -} - -function entryFromRaw(data) { - const p = new BufferReader(data, true); - const hash = digest.hash256(p.readBytes(80)); - const entry = {}; - - p.seek(-80); - - entry.hash = hash.toString('hex'); - entry.version = p.readU32(); // Technically signed - entry.prevBlock = p.readHash('hex'); - entry.merkleRoot = p.readHash('hex'); - entry.time = p.readU32(); - entry.bits = p.readU32(); - entry.nonce = p.readU32(); - entry.height = p.readU32(); - entry.chainwork = new BN(p.readBytes(32), 'le'); - - return entry; -} - -function getEntries() { - return db.values({ - gte: pair('e', encoding.ZERO_HASH), - lte: pair('e', encoding.MAX_HASH), - parse: entryFromRaw - }); -} - -async function getTip(entry) { - const state = await db.get('R'); - assert(state); - const tip = state.toString('hex', 0, 32); - const data = await db.get(pair('e', tip)); - assert(data); - return entryFromRaw(data); -} - -async function isMainChain(entry, tip) { - if (entry.hash === tip) - return true; - - if (await db.get(pair('n', entry.hash))) - return true; - - return false; -} - -// And this insane function is why we should -// be indexing tips in the first place! -async function indexTips() { - const entries = await getEntries(); - const tip = await getTip(); - const tips = []; - const orphans = []; - const prevs = {}; - - for (let i = 0; i < entries.length; i++) { - const entry = entries[i]; - const main = await isMainChain(entry, tip.hash); - if (!main) { - orphans.push(entry); - prevs[entry.prevBlock] = true; - } - } - - for (let i = 0; i < orphans.length; i++) { - const orphan = orphans[i]; - if (!prevs[orphan.hash]) - tips.push(orphan.hash); - } - - tips.push(tip.hash); - - for (let i = 0; i < tips.length; i++) { - const tip = tips[i]; - console.log('Indexing chain tip: %s.', util.revHex(tip)); - batch.put(pair('p', tip), DUMMY); - } -} - -function write(data, str, off) { - if (Buffer.isBuffer(str)) - return str.copy(data, off); - return data.write(str, off, 'hex'); -} - -function pair(prefix, hash) { - const key = Buffer.allocUnsafe(33); - if (typeof prefix === 'string') - prefix = prefix.charCodeAt(0); - key[0] = prefix; - write(key, hash, 1); - return key; -} - -(async () => { - await db.open(); - console.log('Opened %s.', file); - batch = db.batch(); - await checkVersion(); - await indexTips(); - await batch.write(); -})().then(() => { - console.log('Migration complete.'); - process.exit(0); -}); diff --git a/migrate/latest b/migrate/latest new file mode 100755 index 000000000..b4ebd9810 --- /dev/null +++ b/migrate/latest @@ -0,0 +1,47 @@ +#!/usr/bin/env node + +'use strict'; + +const cp = require('child_process'); +const fs = require('bfile'); +const {resolve} = require('path'); +const {argv} = process; + +if (argv.length < 3) { + console.error('Usage: $ ./migrate/latest [bcoin-prefix]'); + console.error('Example: $ ./migrate/latest ~/.bcoin'); + process.exit(1); + return; +} + +function exec(file, ...args) { + try { + const result = cp.spawnSync(file, args, { + stdio: 'inherit', + env: process.env, + maxBuffer: -1 >>> 0, + windowsHide: true + }); + if (result.error) + console.error(result.error.message); + } catch (e) { + console.error(e.message); + } +} + +const node = argv[0]; +const prefix = argv[2]; + +const chain = resolve(prefix, 'chain'); +const spvchain = resolve(prefix, 'spvchain'); + +(async () => { + if (await fs.exists(chain)) + exec(node, resolve(__dirname, 'chaindb4to6.js'), chain); + + if (await fs.exists(spvchain)) + exec(node, resolve(__dirname, 'chaindb4to6.js'), spvchain); +})().catch((err) => { + console.error(err.stack); + process.exit(1); +}); diff --git a/migrate/walletdb2to3.js b/migrate/walletdb2to3.js deleted file mode 100644 index e2832cdf6..000000000 --- a/migrate/walletdb2to3.js +++ /dev/null @@ -1,365 +0,0 @@ -'use strict'; - -const assert = require('assert'); -const bcoin = require('../'); -const walletdb = require('../lib/wallet/walletdb'); -const encoding = require('../lib/utils/encoding'); -const Path = require('../lib/wallet/path'); -const MasterKey = require('../lib/wallet/masterkey'); -const Account = require('../lib/wallet/account'); -const Wallet = require('../lib/wallet/wallet'); -const KeyRing = require('../lib/primitives/keyring'); -const BufferReader = require('../lib/utils/reader'); -const BufferWriter = require('../lib/utils/writer'); -const layout = walletdb.layout; -let file = process.argv[2]; -let batch; - -assert(typeof file === 'string', 'Please pass in a database path.'); - -file = file.replace(/\.ldb\/?$/, ''); - -const db = bcoin.ldb({ - location: file, - db: 'leveldb', - compression: true, - cacheSize: 32 << 20, - createIfMissing: false, - bufferKeys: true -}); - -async function updateVersion() { - const bak = `${process.env.HOME}/walletdb-bak-${Date.now()}.ldb`; - - console.log('Checking version.'); - - const data = await db.get('V'); - assert(data, 'No version.'); - - let ver = data.readUInt32LE(0, true); - - if (ver !== 2) - throw Error(`DB is version ${ver}.`); - - console.log('Backing up DB to: %s.', bak); - - await db.backup(bak); - - ver = Buffer.allocUnsafe(4); - ver.writeUInt32LE(3, 0, true); - batch.put('V', ver); -} - -async function updatePathMap() { - let total = 0; - - const iter = db.iterator({ - gte: layout.p(encoding.NULL_HASH), - lte: layout.p(encoding.HIGH_HASH), - values: true - }); - - console.log('Migrating path map.'); - - while (await iter.next()) { - const {key, value} = iter; - - total++; - - const hash = layout.pp(key); - const oldPaths = parsePaths(value, hash); - const keys = Object.keys(oldPaths); - - for (let i = 0; i < keys.length; i++) { - keys[i] = Number(keys[i]); - const key = keys[i]; - const oldPath = oldPaths[key]; - const path = new Path(oldPath); - if (path.data) { - if (path.encrypted) { - console.log( - 'Cannot migrate encrypted import: %s (%s)', - path.data.toString('hex'), - path.toAddress().toBase58()); - continue; - } - const ring = keyFromRaw(path.data); - path.data = new KeyRing(ring).toRaw(); - } - batch.put(layout.P(key, hash), path.toRaw()); - } - - batch.put(key, serializeWallets(keys.sort())); - } - - console.log('Migrated %d paths.', total); -} - -async function updateAccounts() { - let total = 0; - - const iter = db.iterator({ - gte: layout.a(0, 0), - lte: layout.a(0xffffffff, 0xffffffff), - values: true - }); - - console.log('Migrating accounts.'); - - for (;;) { - const item = await iter.next(); - - if (!item) - break; - - total++; - let account = accountFromRaw(item.value, item.key); - account = new Account({ network: account.network, options: {} }, account); - batch.put(item.key, account.toRaw()); - - if (account._old) { - batch.del(layout.i(account.wid, account._old)); - const buf = Buffer.allocUnsafe(4); - buf.writeUInt32LE(account.accountIndex, 0, true); - batch.put(layout.i(account.wid, account.name), buf); - } - } - - console.log('Migrated %d accounts.', total); -} - -async function updateWallets() { - let total = 0; - - const iter = db.iterator({ - gte: layout.w(0), - lte: layout.w(0xffffffff), - values: true - }); - - console.log('Migrating wallets.'); - - for (;;) { - const item = await iter.next(); - - if (!item) - break; - - total++; - let wallet = walletFromRaw(item.value); - wallet = new Wallet({ network: wallet.network }, wallet); - batch.put(item.key, wallet.toRaw()); - - if (wallet._old) { - batch.del(layout.l(wallet._old)); - const buf = Buffer.allocUnsafe(4); - buf.writeUInt32LE(wallet.wid, 0, true); - batch.put(layout.l(wallet.id), buf); - } - } - - console.log('Migrated %d wallets.', total); -} - -async function updateTXMap() { - let total = 0; - - const iter = db.iterator({ - gte: layout.e(encoding.NULL_HASH), - lte: layout.e(encoding.HIGH_HASH), - values: true - }); - - console.log('Migrating tx map.'); - - for (;;) { - const item = await iter.next(); - - if (!item) - break; - - total++; - const wallets = parseWallets(item.value); - batch.put(item.key, serializeWallets(wallets.sort())); - } - - console.log('Migrated %d tx maps.', total); -} - -function pathFromRaw(data) { - const path = {}; - const p = new BufferReader(data); - - path.wid = p.readU32(); - path.name = p.readVarString('utf8'); - path.account = p.readU32(); - - switch (p.readU8()) { - case 0: - path.keyType = 0; - path.branch = p.readU32(); - path.index = p.readU32(); - if (p.readU8() === 1) - assert(false, 'Cannot migrate custom redeem script.'); - break; - case 1: - path.keyType = 1; - path.encrypted = p.readU8() === 1; - path.data = p.readVarBytes(); - path.branch = -1; - path.index = -1; - break; - default: - assert(false); - break; - } - - path.version = p.readI8(); - path.type = p.readU8(); - - return path; -} - -function parsePaths(data, hash) { - const p = new BufferReader(data); - const out = {}; - - while (p.left()) { - const path = pathFromRaw(p); - out[path.wid] = path; - if (hash) - path.hash = hash; - } - - return out; -} - -function parseWallets(data) { - const p = new BufferReader(data); - const wallets = []; - while (p.left()) - wallets.push(p.readU32()); - return wallets; -} - -function serializeWallets(wallets) { - const p = new BufferWriter(); - - for (let i = 0; i < wallets.length; i++) { - const wid = wallets[i]; - p.writeU32(wid); - } - - return p.render(); -} - -function readAccountKey(key) { - return { - wid: key.readUInt32BE(1, true), - index: key.readUInt32BE(5, true) - }; -} - -function accountFromRaw(data, dbkey) { - const account = {}; - const p = new BufferReader(data); - - dbkey = readAccountKey(dbkey); - account.wid = dbkey.wid; - account.id = 'doesntmatter'; - account.network = bcoin.network.fromMagic(p.readU32()); - account.name = p.readVarString('utf8'); - account.initialized = p.readU8() === 1; - account.type = p.readU8(); - account.m = p.readU8(); - account.n = p.readU8(); - account.witness = p.readU8() === 1; - account.accountIndex = p.readU32(); - account.receiveDepth = p.readU32(); - account.changeDepth = p.readU32(); - account.accountKey = bcoin.hd.fromRaw(p.readBytes(82)); - account.keys = []; - account.watchOnly = false; - account.nestedDepth = 0; - - const name = account.name.replace(/[^\-\._0-9A-Za-z]+/g, ''); - - if (name !== account.name) { - console.log('Account name changed: %s -> %s.', account.name, name); - account._old = account.name; - account.name = name; - } - - const count = p.readU8(); - - for (let i = 0; i < count; i++) { - const key = bcoin.hd.fromRaw(p.readBytes(82)); - account.keys.push(key); - } - - return account; -} - -function walletFromRaw(data) { - const wallet = {}; - const p = new BufferReader(data); - - wallet.network = bcoin.network.fromMagic(p.readU32()); - wallet.wid = p.readU32(); - wallet.id = p.readVarString('utf8'); - wallet.initialized = p.readU8() === 1; - wallet.accountDepth = p.readU32(); - wallet.token = p.readBytes(32); - wallet.tokenDepth = p.readU32(); - wallet.master = MasterKey.fromRaw(p.readVarBytes()); - wallet.watchOnly = false; - - const id = wallet.id.replace(/[^\-\._0-9A-Za-z]+/g, ''); - - if (id !== wallet.id) { - console.log('Wallet ID changed: %s -> %s.', wallet.id, id); - wallet._old = wallet.id; - wallet.id = id; - } - - return wallet; -} - -function keyFromRaw(data, network) { - const ring = {}; - const p = new BufferReader(data); - - ring.network = bcoin.network.get(network); - ring.witness = p.readU8() === 1; - - const key = p.readVarBytes(); - - if (key.length === 32) { - ring.privateKey = key; - ring.publicKey = bcoin.secp256k1.publicKeyCreate(key, true); - } else { - ring.publicKey = key; - } - - const script = p.readVarBytes(); - - if (script.length > 0) - ring.script = bcoin.script.fromRaw(script); - - return ring; -} - -(async () => { - await db.open(); - batch = db.batch(); - console.log('Opened %s.', file); - await updateVersion(); - await updatePathMap(); - await updateAccounts(); - await updateWallets(); - await updateTXMap(); - await batch.write(); -})().then(() => { - console.log('Migration complete.'); - process.exit(0); -}); diff --git a/migrate/walletdb3to4.js b/migrate/walletdb3to4.js deleted file mode 100644 index b00d84ada..000000000 --- a/migrate/walletdb3to4.js +++ /dev/null @@ -1,146 +0,0 @@ -'use strict'; - -const assert = require('assert'); -const bcoin = require('../'); -const encoding = require('../lib/utils/encoding'); -const WalletDB = require('../lib/wallet/walletdb'); -const BufferReader = require('../lib/utils/reader'); -const TX = require('../lib/primitives/tx'); -const Coin = require('../lib/primitives/coin'); -let file = process.argv[2]; -let batch; - -assert(typeof file === 'string', 'Please pass in a database path.'); - -file = file.replace(/\.ldb\/?$/, ''); - -const db = bcoin.ldb({ - location: file, - db: 'leveldb', - compression: true, - cacheSize: 32 << 20, - createIfMissing: false, - bufferKeys: true -}); - -async function updateVersion() { - const bak = `${process.env.HOME}/walletdb-bak-${Date.now()}.ldb`; - - console.log('Checking version.'); - - const data = await db.get('V'); - assert(data, 'No version.'); - - let ver = data.readUInt32LE(0, true); - - if (ver !== 3) - throw Error(`DB is version ${ver}.`); - - console.log('Backing up DB to: %s.', bak); - - await db.backup(bak); - - ver = Buffer.allocUnsafe(4); - ver.writeUInt32LE(4, 0, true); - batch.put('V', ver); -} - -async function updateTXDB() { - let txs = {}; - - const keys = await db.keys({ - gte: Buffer.from([0x00]), - lte: Buffer.from([0xff]) - }); - - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - if (key[0] === 0x74 && key[5] === 0x74) { - let tx = await db.get(key); - tx = fromExtended(tx); - const hash = tx.hash('hex'); - txs[hash] = tx; - } - if (key[0] === 0x74) - batch.del(key); - } - - txs = getValues(txs); - - await batch.write(); - await db.close(); - - const walletdb = new WalletDB({ - location: file, - db: 'leveldb', - resolution: true, - verify: false, - network: process.argv[3] - }); - - await walletdb.open(); - - for (let i = 0; i < txs.length; i++) { - const tx = txs[i]; - await walletdb.addTX(tx); - } - - await walletdb.close(); -} - -function fromExtended(data, saveCoins) { - const tx = new TX(); - const p = BufferReader(data); - - tx.fromRaw(p); - - tx.height = p.readU32(); - tx.block = p.readHash('hex'); - tx.index = p.readU32(); - tx.time = p.readU32(); - tx.mtime = p.readU32(); - - if (tx.block === encoding.NULL_HASH) - tx.block = null; - - if (tx.height === 0x7fffffff) - tx.height = -1; - - if (tx.index === 0x7fffffff) - tx.index = -1; - - if (saveCoins) { - const coinCount = p.readVarint(); - for (let i = 0; i < coinCount; i++) { - let coin = p.readVarBytes(); - if (coin.length === 0) - continue; - coin = Coin.fromRaw(coin); - coin.hash = tx.inputs[i].prevout.hash; - coin.index = tx.inputs[i].prevout.index; - tx.inputs[i].coin = coin; - } - } - - return tx; -} - -function getValues(map) { - const items = []; - - for (const key of Object.keys(map)) - items.push(map[key]); - - return items; -} - -(async () => { - await db.open(); - batch = db.batch(); - console.log('Opened %s.', file); - await updateVersion(); - await updateTXDB(); -})().then(() => { - console.log('Migration complete.'); - process.exit(0); -}); diff --git a/migrate/walletdb4to5.js b/migrate/walletdb4to5.js deleted file mode 100644 index 9a767607b..000000000 --- a/migrate/walletdb4to5.js +++ /dev/null @@ -1,74 +0,0 @@ -'use strict'; - -const assert = require('assert'); -const bcoin = require('../'); -let file = process.argv[2]; -let batch; - -assert(typeof file === 'string', 'Please pass in a database path.'); - -file = file.replace(/\.ldb\/?$/, ''); - -const db = bcoin.ldb({ - location: file, - db: 'leveldb', - compression: true, - cacheSize: 32 << 20, - createIfMissing: false, - bufferKeys: true -}); - -async function updateVersion() { - const bak = `${process.env.HOME}/walletdb-bak-${Date.now()}.ldb`; - - console.log('Checking version.'); - - const data = await db.get('V'); - assert(data, 'No version.'); - - let ver = data.readUInt32LE(0, true); - - if (ver !== 4) - throw Error(`DB is version ${ver}.`); - - console.log('Backing up DB to: %s.', bak); - - await db.backup(bak); - - ver = Buffer.allocUnsafe(4); - ver.writeUInt32LE(5, 0, true); - batch.put('V', ver); -} - -async function updateTXDB() { - const keys = await db.keys({ - gte: Buffer.from([0x00]), - lte: Buffer.from([0xff]) - }); - - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - switch (key[0]) { - case 0x62: // b - case 0x63: // c - case 0x65: // e - case 0x74: // t - batch.del(key); - break; - } - } - - await batch.write(); -} - -(async () => { - await db.open(); - batch = db.batch(); - console.log('Opened %s.', file); - await updateVersion(); - await updateTXDB(); - await db.close(); -})().then(() => { - console.log('Migration complete.'); - process.exit(0); -}); diff --git a/migrate/walletdb5to6.js b/migrate/walletdb5to6.js index 353cf4a11..ac584fb52 100644 --- a/migrate/walletdb5to6.js +++ b/migrate/walletdb5to6.js @@ -1,55 +1,46 @@ 'use strict'; const assert = require('assert'); -const bcoin = require('../'); -const encoding = require('../lib/utils/encoding'); -const BufferWriter = require('../lib/utils/writer'); -const BufferReader = require('../lib/utils/reader'); -let file = process.argv[2]; -let batch; +const bdb = require('bdb'); +const bio = require('bufio'); -assert(typeof file === 'string', 'Please pass in a database path.'); +assert(process.argv.length > 2, 'Please pass in a database path.'); -file = file.replace(/\.ldb\/?$/, ''); +let batch; -const db = bcoin.ldb({ - location: file, - db: 'leveldb', +const db = bdb.create({ + location: process.argv[2], compression: true, cacheSize: 32 << 20, - createIfMissing: false, - bufferKeys: true + createIfMissing: false }); async function updateVersion() { - const bak = `${process.env.HOME}/walletdb-bak-${Date.now()}.ldb`; + const bak = `${process.env.HOME}/wallet-bak-${Date.now()}`; console.log('Checking version.'); - const data = await db.get('V'); - assert(data, 'No version.'); + const raw = await db.get('V'); + assert(raw, 'No version.'); - let ver = data.readUInt32LE(0, true); + const version = raw.readUInt32LE(0, true); - if (ver !== 5) - throw Error(`DB is version ${ver}.`); + if (version !== 5) + throw Error(`DB is version ${version}.`); console.log('Backing up DB to: %s.', bak); await db.backup(bak); - ver = Buffer.allocUnsafe(4); - ver.writeUInt32LE(6, 0, true); - batch.put('V', ver); + const data = Buffer.allocUnsafe(4); + data.writeUInt32LE(6, 0, true); + batch.put('V', data); } async function wipeTXDB() { let total = 0; - const keys = await db.keys({ - gte: Buffer.from([0x00]), - lte: Buffer.from([0xff]) - }); + const keys = await db.keys(); for (let i = 0; i < keys.length; i++) { const key = keys[i]; @@ -61,7 +52,7 @@ async function wipeTXDB() { case 0x6f: // o case 0x68: // h batch.del(key); - total++; + total += 1; break; } } @@ -73,8 +64,8 @@ async function wipeTXDB() { async function patchAccounts() { const items = await db.range({ - gte: Buffer.from('610000000000000000', 'hex'), // a - lte: Buffer.from('61ffffffffffffffff', 'hex') // a + gt: Buffer.from([0x61]), // a + lt: Buffer.from([0x62]) }); for (let i = 0; i < items.length; i++) { @@ -91,14 +82,14 @@ async function patchAccounts() { async function indexPaths() { const items = await db.range({ - gte: Buffer.from('5000000000' + encoding.NULL_HASH, 'hex'), // P - lte: Buffer.from('50ffffffff' + encoding.HIGH_HASH, 'hex') // P + gt: Buffer.from([0x50]), // P + lt: Buffer.from([0x51]) }); for (let i = 0; i < items.length; i++) { const item = items[i]; const wid = item.key.readUInt32BE(1, true); - const hash = item.key.toString('hex', 5); + const hash = item.key.slice(5); const index = item.value.readUInt32LE(0, true); console.log('r[%d][%d][%s] -> NUL', wid, index, hash); batch.put(r(wid, index, hash), Buffer.from([0])); @@ -107,13 +98,13 @@ async function indexPaths() { async function patchPathMaps() { const items = await db.range({ - gte: Buffer.from('70' + encoding.NULL_HASH, 'hex'), // p - lte: Buffer.from('70' + encoding.HIGH_HASH, 'hex') // p + gt: Buffer.from([0x70]), // p + lt: Buffer.from([0x71]) }); for (let i = 0; i < items.length; i++) { const item = items[i]; - const hash = item.key.toString('hex', 1); + const hash = item.key.slice(1); const wids = parseWallets(item.value); console.log('p[%s] -> u32(%d)', hash, wids.length); batch.put(item.key, serializeWallets(wids)); @@ -121,7 +112,7 @@ async function patchPathMaps() { } function parseWallets(data) { - const p = new BufferReader(data); + const p = bio.read(data); const wids = []; while (p.left()) @@ -131,7 +122,7 @@ function parseWallets(data) { } function serializeWallets(wids) { - const p = new BufferWriter(); + const p = bio.write(); p.writeU32(wids.length); @@ -144,7 +135,7 @@ function serializeWallets(wids) { } function accountToRaw(account) { - const p = new BufferWriter(); + const p = bio.write(); p.writeVarString(account.name, 'ascii'); p.writeU8(account.initialized ? 1 : 0); @@ -170,7 +161,7 @@ function accountToRaw(account) { function accountFromRaw(data) { const account = {}; - const p = new BufferReader(data); + const p = bio.read(data); account.name = p.readVarString('ascii'); account.initialized = p.readU8() === 1; @@ -209,7 +200,7 @@ function r(wid, index, hash) { key[0] = 0x72; key.writeUInt32BE(wid, 1, true); key.writeUInt32BE(index, 5, true); - key.write(hash, 9, 'hex'); + hash.copy(key, 9); return key; } @@ -219,7 +210,7 @@ async function updateLookahead() { const db = new WalletDB({ network: process.argv[3], db: 'leveldb', - location: file, + location: process.argv[2], witness: false, useCheckpoints: false, maxFiles: 64, @@ -240,6 +231,8 @@ async function updateLookahead() { await db.close(); } +updateLookahead; + async function unstate() { await db.open(); batch = db.batch(); @@ -251,7 +244,7 @@ async function unstate() { (async () => { await db.open(); batch = db.batch(); - console.log('Opened %s.', file); + console.log('Opened %s.', process.argv[2]); await updateVersion(); await wipeTXDB(); await patchAccounts(); @@ -261,7 +254,7 @@ async function unstate() { await db.close(); // Do not use: - await updateLookahead(); + // await updateLookahead(); await unstate(); })().then(() => { console.log('Migration complete.'); diff --git a/migrate/walletdb6to7.js b/migrate/walletdb6to7.js new file mode 100644 index 000000000..1a7e45773 --- /dev/null +++ b/migrate/walletdb6to7.js @@ -0,0 +1,930 @@ +'use strict'; + +const assert = require('assert'); +const bdb = require('bdb'); +const bio = require('bufio'); +const layouts = require('../lib/wallet/layout'); +const TX = require('../lib/primitives/tx'); +const Coin = require('../lib/primitives/coin'); +const layout = layouts.wdb; +const tlayout = layouts.txdb; + +// changes: +// db version record +// headers - all headers +// block map - just a map +// input map - only on unconfirmed +// marked byte - no longer a soft fork +// coin `own` flag - no longer a soft fork +// tx map - for unconfirmed +// balances - index account balances +// wallet - serialization +// account - serialization +// path - serialization +// depth - counter record +// hash/ascii - variable length key prefixes + +let parent = null; + +assert(process.argv.length > 2, 'Please pass in a database path.'); + +const db = bdb.create({ + location: process.argv[2], + memory: false, + compression: true, + cacheSize: 32 << 20, + createIfMissing: false +}); + +async function updateVersion() { + const bak = `${process.env.HOME}/wallet-bak-${Date.now()}`; + + console.log('Checking version.'); + + const data = await db.get(layout.V.encode()); + assert(data, 'No version.'); + + const ver = data.readUInt32LE(0, true); + + if (ver !== 6) + throw Error(`DB is version ${ver}.`); + + console.log('Backing up DB to: %s.', bak); + console.log('Updating version to %d.', ver + 1); + + await db.backup(bak); + + const buf = Buffer.allocUnsafe(6 + 4); + buf.write('wallet', 0, 'ascii'); + buf.writeUInt32LE(7, 6, true); + + parent.put(layout.V.encode(), buf); +} + +async function migrateKeys(id, from, to) { + console.log('Migrating keys for %s.', String.fromCharCode(id)); + + const iter = db.iterator({ + gt: Buffer.from([id]), + lt: Buffer.from([id + 1]), + keys: true, + values: true + }); + + let batch = db.batch(); + let total = 0; + let items = 0; + + await iter.each(async (key, value) => { + batch.put(to.encode(...from(key)), value); + batch.del(key); + + total += (key.length + 80) * 2; + total += value.length + 80; + items += 1; + + if (total >= (128 << 20)) { + await batch.write(); + batch = db.batch(); + total = 0; + } + }); + + console.log('Migrated %d keys for %s.', items, String.fromCharCode(id)); + + return batch.write(); +} + +async function updateKeys() { + console.log('Updating keys...'); + + await migrateKeys(0x70, parsep, layout.p); // p + await migrateKeys(0x50, parseP, layout.P); // P + await migrateKeys(0x72, parser, layout.r); // r + await migrateKeys(0x6c, parsel, layout.l); // l + await migrateKeys(0x69, parsei, layout.i); // i + + console.log('Updated keys.'); +} + +async function updateState() { + const raw = await db.get(layout.R.encode()); + + if (!raw) + return; + + console.log('Updating state...'); + + if (raw.length === 40) { + const bw = bio.write(41); + bw.writeBytes(raw); + bw.writeU8(1); + parent.put(layout.R.encode(), bw.render()); + console.log('State updated.'); + } + + const depth = await getDepth(); + + const buf = Buffer.allocUnsafe(4); + buf.writeUInt32LE(depth, 0, true); + + parent.put(layout.D.encode(), buf); +} + +async function updateBlockMap() { + const iter = db.iterator({ + gte: layout.b.min(), + lte: layout.b.max(), + keys: true, + values: true + }); + + console.log('Updating block map...'); + + let total = 0; + + await iter.each((key, value) => { + const [height] = layout.b.decode(key); + const block = BlockMapRecord.fromRaw(height, value); + const map = new Set(); + + for (const tx of block.txs.values()) { + for (const wid of tx.wids) + map.add(wid); + } + + const bw = bio.write(sizeMap(map)); + serializeMap(bw, map); + + parent.put(key, bw.render()); + + total += 1; + }); + + console.log('Updated %d block maps.', total); +} + +async function updateTXDB() { + const wids = await db.keys({ + gte: layout.w.min(), + lte: layout.w.max(), + keys: true, + parse: key => layout.w.decode(key)[0] + }); + + console.log('Updating wallets...'); + + let total = 0; + + for (const wid of wids) { + const bucket = db.bucket(layout.t.encode(wid)); + const batch = bucket.wrap(parent); + + await updateInputs(wid, bucket, batch); + await updateCoins(wid, bucket, batch); + await updateTX(wid, bucket, batch); + await updateWalletBalance(wid, bucket, batch); + await updateAccountBalances(wid, bucket, batch); + await updateWallet(wid); + + total += 1; + } + + console.log('Updated %d wallets.', total); +} + +async function updateInputs(wid, bucket, batch) { + const iter = bucket.iterator({ + gte: tlayout.h.min(), + lte: tlayout.h.max(), + keys: true + }); + + console.log('Updating inputs for %d...', wid); + + let total = 0; + + await iter.each(async (key, value) => { + const [, hash] = tlayout.h.decode(key); + const data = await bucket.get(tlayout.t.encode(hash)); + assert(data); + const tx = TX.fromRaw(data); + + for (const {prevout} of tx.inputs) { + const {hash, index} = prevout; + batch.del(tlayout.s.encode(hash, index)); + total += 1; + } + }); + + console.log('Updated %d inputs for %d.', total, wid); +} + +async function updateCoins(wid, bucket, batch) { + const iter = bucket.iterator({ + gte: tlayout.c.min(), + lte: tlayout.c.max(), + keys: true, + values: true + }); + + console.log('Updating coins for %d...', wid); + + let total = 0; + + await iter.each((key, value) => { + const br = bio.read(value, true); + + Coin.fromReader(br); + br.readU8(); + + if (br.left() === 0) { + const bw = bio.write(value.length + 1); + bw.writeBytes(value); + bw.writeU8(0); + batch.put(key, bw.render()); + total += 1; + } + }); + + console.log('Updated %d coins for %d.', total, wid); +} + +async function updateTX(wid, bucket, batch) { + const iter = bucket.iterator({ + gte: tlayout.p.min(), + lte: tlayout.p.max(), + keys: true + }); + + console.log('Adding TX maps for %d...', wid); + + let total = 0; + + await iter.each(async (key, value) => { + const [hash] = tlayout.p.decode(key); + const raw = await db.get(layout.T.encode(hash)); + + let map = null; + + if (!raw) { + map = new Set(); + } else { + const br = bio.read(raw, true); + map = parseMap(br); + } + + map.add(wid); + + const bw = bio.write(sizeMap(map)); + serializeMap(bw, map); + batch.put(layout.T.encode(hash), bw.render()); + + total += 1; + }); + + console.log('Added %d TX maps for %d.', total, wid); +} + +async function updateWalletBalance(wid, bucket, batch) { + const bal = newBalance(); + + const keys = await bucket.keys({ + gte: tlayout.t.min(), + lte: tlayout.t.max(), + keys: true + }); + + bal.tx = keys.length; + + const iter = bucket.iterator({ + gte: tlayout.c.min(), + lte: tlayout.c.max(), + keys: true, + values: true + }); + + console.log('Updating wallet balance for %d...', wid); + + await iter.each((key, value) => { + const br = bio.read(value, true); + const coin = Coin.fromReader(br); + const spent = br.readU8() === 1; + + bal.coin += 1; + + if (coin.height !== -1) + bal.confirmed += coin.value; + + if (!spent) + bal.unconfirmed += coin.value; + }); + + batch.put(tlayout.R.encode(), serializeBalance(bal)); + + console.log('Updated wallet balance for %d.', wid); +} + +async function updateAccountBalances(wid, bucket, batch) { + const raw = await db.get(layout.w.encode(wid)); + assert(raw); + + const br = bio.read(raw, true); + + br.readU32(); + br.readU32(); + br.readVarString('ascii'); + br.readU8(); + br.readU8(); + + const depth = br.readU32(); + + console.log('Updating account balances for %d...', wid); + + for (let acct = 0; acct < depth; acct++) + await updateAccountBalance(wid, acct, bucket, batch); + + console.log('Updated %d account balances for %d.', depth, wid); +} + +async function updateAccountBalance(wid, acct, bucket, batch) { + const bal = newBalance(); + + const keys = await bucket.keys({ + gte: tlayout.T.min(acct), + lte: tlayout.T.max(acct), + keys: true + }); + + bal.tx = keys.length; + + const iter = bucket.iterator({ + gte: tlayout.C.min(acct), + lte: tlayout.C.max(acct), + keys: true + }); + + console.log('Updating account balance for %d/%d...', wid, acct); + + await iter.each(async (key, value) => { + const [, hash, index] = tlayout.C.decode(key); + const raw = await bucket.get(tlayout.c.encode(hash, index)); + assert(raw); + const br = bio.read(raw, true); + const coin = Coin.fromReader(br); + const spent = br.readU8() === 1; + + bal.coin += 1; + + if (coin.height !== -1) + bal.confirmed += coin.value; + + if (!spent) + bal.unconfirmed += coin.value; + }); + + batch.put(tlayout.r.encode(acct), serializeBalance(bal)); + + console.log('Updated account balance for %d/%d.', wid, acct); +} + +async function updateWallet(wid) { + const raw = await db.get(layout.w.encode(wid)); + assert(raw); + + console.log('Updating wallet: %d.', wid); + + const br = bio.read(raw, true); + + br.readU32(); // Skip network. + br.readU32(); // Skip wid. + const id = br.readVarString('ascii'); + br.readU8(); // Skip initialized. + const watchOnly = br.readU8() === 1; + const accountDepth = br.readU32(); + const token = br.readBytes(32); + const tokenDepth = br.readU32(); + + // We want to get the key + // _out of_ varint serialization. + let key = br.readVarBytes(); + + const kr = bio.read(key, true); + + // Unencrypted? + if (kr.readU8() === 0) { + const bw = bio.write(); + bw.writeU8(0); + + // Skip useless varint. + kr.readVarint(); + + // Skip HD key params. + kr.seek(13); + + // Read/write chain code. + bw.writeBytes(kr.readBytes(32)); + + // Skip zero byte. + assert(kr.readU8() === 0); + + // Read/write private key. + bw.writeBytes(kr.readBytes(32)); + + // Skip checksum. + kr.seek(4); + + // Include mnemonic. + if (kr.readU8() === 1) { + bw.writeU8(1); + const bits = kr.readU16(); + assert(bits % 32 === 0); + const lang = kr.readU8(); + const entropy = kr.readBytes(bits / 8); + + bw.writeU16(bits); + bw.writeU8(lang); + bw.writeBytes(entropy); + } else { + bw.writeU8(0); + } + + key = bw.render(); + } + + let flags = 0; + + if (watchOnly) + flags |= 1; + + // Concatenate wallet with key. + const bw = bio.write(); + bw.writeU8(flags); + bw.writeU32(accountDepth); + bw.writeBytes(token); + bw.writeU32(tokenDepth); + bw.writeBytes(key); + + parent.put(layout.w.encode(wid), bw.render()); + parent.put(layout.W.encode(wid), fromString(id)); + + console.log('Updating accounts for %d...', wid); + + for (let acct = 0; acct < accountDepth; acct++) + await updateAccount(wid, acct); + + console.log('Updated %d accounts for %d.', accountDepth, wid); + + console.log('Updated wallet: %d.', wid); +} + +async function updateAccount(wid, acct) { + const raw = await db.get(layout.a.encode(wid, acct)); + assert(raw); + + console.log('Updating account: %d/%d...', wid, acct); + + const br = bio.read(raw, true); + + const name = br.readVarString('ascii'); + const initialized = br.readU8() === 1; + const witness = br.readU8() === 1; + const type = br.readU8(); + const m = br.readU8(); + const n = br.readU8(); + br.readU32(); // accountIndex + const receiveDepth = br.readU32(); + const changeDepth = br.readU32(); + const nestedDepth = br.readU32(); + const lookahead = br.readU8(); + const accountKey = { + network: br.readU32BE(), + depth: br.readU8(), + parentFingerPrint: br.readU32BE(), + childIndex: br.readU32BE(), + chainCode: br.readBytes(32), + publicKey: br.readBytes(33), + checksum: br.readU32() + }; + + const count = br.readU8(); + const keys = []; + + for (let i = 0; i < count; i++) { + const key = { + network: br.readU32BE(), + depth: br.readU8(), + parentFingerPrint: br.readU32BE(), + childIndex: br.readU32BE(), + chainCode: br.readBytes(32), + publicKey: br.readBytes(33), + checksum: br.readU32() + }; + keys.push(key); + } + + const bw = bio.write(); + + let flags = 0; + + if (initialized) + flags |= 1; + + if (witness) + flags |= 2; + + bw.writeU8(flags); + bw.writeU8(type); + bw.writeU8(m); + bw.writeU8(n); + bw.writeU32(receiveDepth); + bw.writeU32(changeDepth); + bw.writeU32(nestedDepth); + bw.writeU8(lookahead); + + bw.writeU8(accountKey.depth); + bw.writeU32BE(accountKey.parentFingerPrint); + bw.writeU32BE(accountKey.childIndex); + bw.writeBytes(accountKey.chainCode); + bw.writeBytes(accountKey.publicKey); + + bw.writeU8(keys.length); + + for (const key of keys) { + bw.writeU8(key.depth); + bw.writeU32BE(key.parentFingerPrint); + bw.writeU32BE(key.childIndex); + bw.writeBytes(key.chainCode); + bw.writeBytes(key.publicKey); + } + + parent.put(layout.a.encode(wid, acct), bw.render()); + parent.put(layout.n.encode(wid, acct), fromString(name)); + + console.log('Updated account: %d/%d.', wid, acct); +} + +async function updatePaths() { + const iter = db.iterator({ + gte: layout.P.min(), + lte: layout.P.max(), + keys: true, + values: true + }); + + console.log('Updating paths....'); + + let total = 0; + + await iter.each((key, value) => { + const br = bio.read(value, true); + + const account = br.readU32(); + const keyType = br.readU8(); + + let branch = -1; + let index = -1; + let encrypted = false; + let data = null; + + switch (keyType) { + case 0: + branch = br.readU32(); + index = br.readU32(); + break; + case 1: + encrypted = br.readU8() === 1; + data = br.readVarBytes(); + break; + case 2: + break; + default: + assert(false); + break; + } + + let version = br.readI8(); + + let type = br.readU8(); + + if (type === 129 || type === 130) + type = 4; + + type -= 2; + + const bw = bio.write(); + + bw.writeU32(account); + bw.writeU8(keyType); + + if (version === -1) + version = 0x1f; + + const flags = (version << 3) | type; + + bw.writeU8(flags); + + switch (keyType) { + case 0: + assert(!data); + assert(index !== -1); + bw.writeU32(branch); + bw.writeU32(index); + break; + case 1: + assert(data); + assert(index === -1); + bw.writeU8(encrypted ? 1 : 0); + bw.writeVarBytes(data); + break; + case 2: + assert(!data); + assert(index === -1); + break; + default: + assert(false); + break; + } + + parent.put(key, bw.render()); + + total += 1; + }); + + console.log('Updated %d paths.', total); +} + +async function getDepth() { + const iter = db.iterator({ + gte: layout.w.min(), + lte: layout.w.max(), + reverse: true, + limit: 1 + }); + + if (!await iter.next()) + return 1; + + const {key} = iter; + + await iter.end(); + + const [depth] = layout.w.decode(key); + + return depth + 1; +} + +/* + * Old Records + */ + +class BlockMapRecord { + constructor(height) { + this.height = height != null ? height : -1; + this.txs = new Map(); + } + + fromRaw(data) { + const br = bio.read(data); + const count = br.readU32(); + + for (let i = 0; i < count; i++) { + const hash = br.readHash(); + const tx = TXMapRecord.fromReader(hash, br); + this.txs.set(tx.hash, tx); + } + + return this; + } + + static fromRaw(height, data) { + return new BlockMapRecord(height).fromRaw(data); + } + + getSize() { + let size = 0; + + size += 4; + + for (const tx of this.txs.values()) { + size += 32; + size += tx.getSize(); + } + + return size; + } + + toRaw() { + const size = this.getSize(); + const bw = bio.write(size); + + bw.writeU32(this.txs.size); + + for (const [hash, tx] of this.txs) { + bw.writeHash(hash); + tx.toWriter(bw); + } + + return bw.render(); + } + + add(hash, wid) { + let tx = this.txs.get(hash); + + if (!tx) { + tx = new TXMapRecord(hash); + this.txs.set(hash, tx); + } + + return tx.add(wid); + } + + remove(hash, wid) { + const tx = this.txs.get(hash); + + if (!tx) + return false; + + if (!tx.remove(wid)) + return false; + + if (tx.wids.size === 0) + this.txs.delete(tx.hash); + + return true; + } + + toArray() { + const txs = []; + + for (const tx of this.txs.values()) + txs.push(tx); + + return txs; + } +} + +class TXMapRecord { + constructor(hash, wids) { + this.hash = hash || null; + this.wids = wids || new Set(); + } + + add(wid) { + if (this.wids.has(wid)) + return false; + + this.wids.add(wid); + return true; + } + + remove(wid) { + return this.wids.delete(wid); + } + + toWriter(bw) { + return serializeMap(bw, this.wids); + } + + getSize() { + return sizeMap(this.wids); + } + + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); + } + + fromReader(br) { + this.wids = parseMap(br); + return this; + } + + fromRaw(data) { + return this.fromReader(bio.read(data)); + } + + static fromReader(hash, br) { + return new TXMapRecord(hash).fromReader(br); + } + + static fromRaw(hash, data) { + return new TXMapRecord(hash).fromRaw(data); + } +} + +function parseMap(br) { + const count = br.readU32(); + const wids = new Set(); + + for (let i = 0; i < count; i++) + wids.add(br.readU32()); + + return wids; +} + +function sizeMap(wids) { + return 4 + wids.size * 4; +} + +function serializeMap(bw, wids) { + bw.writeU32(wids.size); + + for (const wid of wids) + bw.writeU32(wid); + + return bw; +} + +/* + * Helpers + */ + +function newBalance() { + return { + tx: 0, + coin: 0, + unconfirmed: 0, + confirmed: 0 + }; +} + +function serializeBalance(bal) { + const bw = bio.write(32); + + bw.writeU64(bal.tx); + bw.writeU64(bal.coin); + bw.writeU64(bal.unconfirmed); + bw.writeU64(bal.confirmed); + + return bw.render(); +} + +function parsep(key) { // p[hash] + assert(Buffer.isBuffer(key)); + assert(key.length >= 21); + return [key.slice(1)]; +} + +function parseP(key) { // P[wid][hash] + assert(Buffer.isBuffer(key)); + assert(key.length >= 25); + return [key.readUInt32BE(1, true), key.slice(5)]; +} + +function parser(key) { // r[wid][index][hash] + assert(Buffer.isBuffer(key)); + assert(key.length >= 29); + return [ + key.readUInt32BE(1, true), + key.readUInt32BE(5, true), + key.slice(9) + ]; +} + +function parsel(key) { // l[id] + assert(Buffer.isBuffer(key)); + assert(key.length >= 1); + return [key.toString('ascii', 1)]; +} + +function parsei(key) { // i[wid][name] + assert(Buffer.isBuffer(key)); + assert(key.length >= 5); + return [key.readUInt32BE(1, true), key.toString('ascii', 5)]; +} + +function fromString(str) { + const buf = Buffer.alloc(1 + str.length); + buf[0] = str.length; + buf.write(str, 1, str.length, 'ascii'); + return buf; +} + +/* + * Execute + */ + +(async () => { + await db.open(); + + console.log('Opened %s.', process.argv[2]); + + parent = db.batch(); + + await updateVersion(); + await updateKeys(); + await updateState(); + await updateBlockMap(); + await updateTXDB(); + await updatePaths(); + + await parent.write(); + await db.close(); +})().then(() => { + console.log('Migration complete.'); + process.exit(0); +}).catch((err) => { + console.error(err.stack); + process.exit(1); +}); diff --git a/package-lock.json b/package-lock.json index e347106aa..52a002041 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6765 +1,267 @@ { "name": "fcoin", - "version": "1.0.1", + "version": "1.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { - "abstract-leveldown": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz", - "integrity": "sha512-2++wDf/DYqkPR3o5tbfdhF96EfMApo1GpPfzOsR/ZYXdkSmELlvOOEAl9iKkRsktMPHdGjO4rtkBpf2I7TiTeA==", - "optional": true, - "requires": { - "xtend": "4.0.1" - } - }, - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "optional": true, - "requires": { - "mime-types": "2.1.24", - "negotiator": "0.6.1" - } - }, - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", - "dev": true - }, - "acorn-dynamic-import": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", - "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", - "dev": true, - "requires": { - "acorn": "4.0.13" - }, - "dependencies": { - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", - "dev": true - } - } - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "3.3.0" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" - }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" - } - }, - "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", - "dev": true - }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" - } - }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "3.1.10", - "normalize-path": "2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "1.1.0" - } - } - } - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "optional": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "1.0.3" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" - }, - "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1" - } - }, - "assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", - "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", - "dev": true, - "requires": { - "util": "0.10.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - } - } - } - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "dev": true, - "requires": { - "lodash": "4.17.11" - } - }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" - } - }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "babel-generator": "6.26.1", - "babel-helpers": "6.24.1", - "babel-messages": "6.23.0", - "babel-register": "6.26.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "convert-source-map": "1.6.0", - "debug": "2.6.9", - "json5": "0.5.1", - "lodash": "4.17.11", - "minimatch": "3.0.4", - "path-is-absolute": "1.0.1", - "private": "0.1.8", - "slash": "1.0.0", - "source-map": "0.5.7" - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "detect-indent": "4.0.0", - "jsesc": "1.3.0", - "lodash": "4.17.11", - "source-map": "0.5.7", - "trim-right": "1.0.1" - } - }, - "babel-helper-builder-binary-assignment-operator-visitor": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", - "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", - "dev": true, - "requires": { - "babel-helper-explode-assignable-expression": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-call-delegate": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", - "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-define-map": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", - "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", - "dev": true, - "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.11" - } - }, - "babel-helper-explode-assignable-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", - "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", - "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", - "dev": true, - "requires": { - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-get-function-arity": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", - "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-hoist-variables": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", - "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-optimise-call-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", - "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-regex": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", - "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.11" - } - }, - "babel-helper-remap-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", - "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", - "dev": true, - "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-replace-supers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", - "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", - "dev": true, - "requires": { - "babel-helper-optimise-call-expression": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-loader": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-7.1.5.tgz", - "integrity": "sha512-iCHfbieL5d1LfOQeeVJEUyD9rTwBcP/fcEbRCfempxTDuqrKpu0AZjLAQHEQa3Yqyj9ORKe2iHfoj4rHLf7xpw==", - "dev": true, - "requires": { - "find-cache-dir": "1.0.0", - "loader-utils": "1.2.3", - "mkdirp": "0.5.1" - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-check-es2015-constants": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", - "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-syntax-async-functions": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", - "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", - "dev": true - }, - "babel-plugin-syntax-exponentiation-operator": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", - "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", - "dev": true - }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", - "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", - "dev": true - }, - "babel-plugin-transform-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", - "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", - "dev": true, - "requires": { - "babel-helper-remap-async-to-generator": "6.24.1", - "babel-plugin-syntax-async-functions": "6.13.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-arrow-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", - "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-block-scoped-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", - "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-block-scoping": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", - "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.11" - } - }, - "babel-plugin-transform-es2015-classes": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", - "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", - "dev": true, - "requires": { - "babel-helper-define-map": "6.26.0", - "babel-helper-function-name": "6.24.1", - "babel-helper-optimise-call-expression": "6.24.1", - "babel-helper-replace-supers": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-computed-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", - "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-destructuring": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", - "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-duplicate-keys": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", - "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-for-of": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", - "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", - "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", - "dev": true, - "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", - "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-amd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", - "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "6.26.2", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", - "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", - "dev": true, - "requires": { - "babel-plugin-transform-strict-mode": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-systemjs": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", - "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-umd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", - "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-object-super": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", - "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", - "dev": true, - "requires": { - "babel-helper-replace-supers": "6.24.1", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-parameters": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", - "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", - "dev": true, - "requires": { - "babel-helper-call-delegate": "6.24.1", - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-shorthand-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", - "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-spread": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", - "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-sticky-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", - "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", - "dev": true, - "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-template-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", - "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-typeof-symbol": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", - "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-unicode-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", - "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", - "dev": true, - "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "regexpu-core": "2.0.0" - } - }, - "babel-plugin-transform-exponentiation-operator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", - "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", - "dev": true, - "requires": { - "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", - "babel-plugin-syntax-exponentiation-operator": "6.13.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-regenerator": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", - "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", - "dev": true, - "requires": { - "regenerator-transform": "0.10.1" - } - }, - "babel-plugin-transform-runtime": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz", - "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-strict-mode": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", - "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-preset-env": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.7.0.tgz", - "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==", - "dev": true, - "requires": { - "babel-plugin-check-es2015-constants": "6.22.0", - "babel-plugin-syntax-trailing-function-commas": "6.22.0", - "babel-plugin-transform-async-to-generator": "6.24.1", - "babel-plugin-transform-es2015-arrow-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoping": "6.26.0", - "babel-plugin-transform-es2015-classes": "6.24.1", - "babel-plugin-transform-es2015-computed-properties": "6.24.1", - "babel-plugin-transform-es2015-destructuring": "6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", - "babel-plugin-transform-es2015-for-of": "6.23.0", - "babel-plugin-transform-es2015-function-name": "6.24.1", - "babel-plugin-transform-es2015-literals": "6.22.0", - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "6.26.2", - "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", - "babel-plugin-transform-es2015-modules-umd": "6.24.1", - "babel-plugin-transform-es2015-object-super": "6.24.1", - "babel-plugin-transform-es2015-parameters": "6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", - "babel-plugin-transform-es2015-spread": "6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "6.24.1", - "babel-plugin-transform-es2015-template-literals": "6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "6.24.1", - "babel-plugin-transform-exponentiation-operator": "6.24.1", - "babel-plugin-transform-regenerator": "6.26.0", - "browserslist": "3.2.8", - "invariant": "2.2.4", - "semver": "5.7.0" - } - }, - "babel-preset-es2015": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", - "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", - "dev": true, - "requires": { - "babel-plugin-check-es2015-constants": "6.22.0", - "babel-plugin-transform-es2015-arrow-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoping": "6.26.0", - "babel-plugin-transform-es2015-classes": "6.24.1", - "babel-plugin-transform-es2015-computed-properties": "6.24.1", - "babel-plugin-transform-es2015-destructuring": "6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", - "babel-plugin-transform-es2015-for-of": "6.23.0", - "babel-plugin-transform-es2015-function-name": "6.24.1", - "babel-plugin-transform-es2015-literals": "6.22.0", - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "6.26.2", - "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", - "babel-plugin-transform-es2015-modules-umd": "6.24.1", - "babel-plugin-transform-es2015-object-super": "6.24.1", - "babel-plugin-transform-es2015-parameters": "6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", - "babel-plugin-transform-es2015-spread": "6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "6.24.1", - "babel-plugin-transform-es2015-template-literals": "6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "6.24.1", - "babel-plugin-transform-regenerator": "6.26.0" - } - }, - "babel-preset-es2016": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-es2016/-/babel-preset-es2016-6.24.1.tgz", - "integrity": "sha1-+QC/k+LrwNJ235uKtZck6/2Vn4s=", - "dev": true, - "requires": { - "babel-plugin-transform-exponentiation-operator": "6.24.1" - } - }, - "babel-preset-es2017": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-es2017/-/babel-preset-es2017-6.24.1.tgz", - "integrity": "sha1-WXvq37n38gi8/YoS6bKym4svFNE=", - "dev": true, - "requires": { - "babel-plugin-syntax-trailing-function-commas": "6.22.0", - "babel-plugin-transform-async-to-generator": "6.24.1" - } - }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "dev": true, - "requires": { - "babel-core": "6.26.3", - "babel-runtime": "6.26.0", - "core-js": "2.6.5", - "home-or-tmp": "2.0.0", - "lodash": "4.17.11", - "mkdirp": "0.5.1", - "source-map-support": "0.4.18" - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.5", - "regenerator-runtime": "0.11.1" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "lodash": "4.17.11" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.4", - "lodash": "4.17.11" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.11", - "to-fast-properties": "1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "optional": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "1.0.1", - "class-utils": "0.3.6", - "component-emitter": "1.2.1", - "define-property": "1.0.0", - "isobject": "3.0.1", - "mixin-deep": "1.3.1", - "pascalcase": "0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" - }, - "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", - "dev": true - }, - "base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", - "optional": true - }, - "bcoin-native": { - "version": "0.0.23", - "resolved": "https://registry.npmjs.org/bcoin-native/-/bcoin-native-0.0.23.tgz", - "integrity": "sha512-bk2XK9EtOcTiqS4cgJ5dy77R2bVJC65dTvLuhH+SxLemjERC3jbf8jadYvOYfZx/x8TF6fuxZzWruhc0OF3Bnw==", - "optional": true, - "requires": { - "bindings": "1.5.0", - "nan": "2.13.2" - } - }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "requires": { - "callsite": "1.0.0" - } - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bip66": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", - "integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=", - "optional": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", - "optional": true, - "requires": { - "readable-stream": "2.3.6", - "safe-buffer": "5.1.2" - } - }, - "blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" - }, - "bluebird": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", - "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==", - "dev": true - }, - "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.3", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" - }, - "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", - "dev": true - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "requires": { - "buffer-xor": "1.0.3", - "cipher-base": "1.0.4", - "create-hash": "1.2.0", - "evp_bytestokey": "1.0.3", - "inherits": "2.0.3", - "safe-buffer": "5.1.2" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "requires": { - "browserify-aes": "1.2.0", - "browserify-des": "1.0.2", - "evp_bytestokey": "1.0.3" - } - }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "des.js": "1.0.0", - "inherits": "2.0.3", - "safe-buffer": "5.1.2" - } - }, - "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "randombytes": "2.1.0" - } - }, - "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "elliptic": "6.4.0", - "inherits": "2.0.3", - "parse-asn1": "5.1.4" - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "1.0.10" - } - }, - "browserslist": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", - "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", - "dev": true, - "requires": { - "caniuse-lite": "1.0.30000962", - "electron-to-chromium": "1.3.125" - } - }, - "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "dev": true, - "requires": { - "base64-js": "1.3.0", - "ieee754": "1.1.13", - "isarray": "1.0.0" - } - }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "optional": true, - "requires": { - "buffer-alloc-unsafe": "1.1.0", - "buffer-fill": "1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "optional": true - }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "optional": true - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "cacache": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", - "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", - "dev": true, - "requires": { - "bluebird": "3.5.4", - "chownr": "1.1.1", - "glob": "7.1.3", - "graceful-fs": "4.1.15", - "lru-cache": "4.1.5", - "mississippi": "2.0.0", - "mkdirp": "0.5.1", - "move-concurrently": "1.0.1", - "promise-inflight": "1.0.1", - "rimraf": "2.6.3", - "ssri": "5.3.0", - "unique-filename": "1.1.1", - "y18n": "4.0.0" - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "1.0.0", - "component-emitter": "1.2.1", - "get-value": "2.0.6", - "has-value": "1.0.0", - "isobject": "3.0.1", - "set-value": "2.0.0", - "to-object-path": "0.3.0", - "union-value": "1.0.0", - "unset-value": "1.0.0" - } - }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "0.2.0" - } - }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" - }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30000962", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000962.tgz", - "integrity": "sha512-WXYsW38HK+6eaj5IZR16Rn91TGhU3OhbwjKZvJ4HN/XBIABLKfbij9Mnd3pM0VEwZSlltWjoWg3I8FQ0DGgNOA==", - "dev": true - }, - "catharsis": { - "version": "0.8.9", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.9.tgz", - "integrity": "sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=", - "dev": true, - "requires": { - "underscore-contrib": "0.3.0" - } - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" - } - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", - "dev": true - }, - "chokidar": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz", - "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==", - "dev": true, - "requires": { - "anymatch": "2.0.0", - "async-each": "1.0.3", - "braces": "2.3.2", - "fsevents": "1.2.8", - "glob-parent": "3.1.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "4.0.1", - "normalize-path": "3.0.0", - "path-is-absolute": "1.0.1", - "readdirp": "2.2.1", - "upath": "1.1.2" - } - }, - "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.2" - } - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "3.1.0", - "define-property": "0.2.5", - "isobject": "3.0.1", - "static-extend": "0.1.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - } - } - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "2.0.0" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", - "wordwrap": "0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true - } - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "1.0.0", - "object-visit": "1.0.1" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": "1.0.1" - } - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "optional": true - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" - }, - "component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "1.1.1", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "typedarray": "0.0.6" - } - }, - "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "dev": true, - "requires": { - "date-now": "0.1.4" - } - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "optional": true - }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "requires": { - "aproba": "1.2.0", - "fs-write-stream-atomic": "1.0.10", - "iferr": "0.1.5", - "mkdirp": "0.5.1", - "rimraf": "2.6.3", - "run-queue": "1.0.3" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "core-js": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", - "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "elliptic": "6.4.0" - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "requires": { - "cipher-base": "1.0.4", - "inherits": "2.0.3", - "md5.js": "1.3.5", - "ripemd160": "2.0.2", - "sha.js": "2.4.11" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "requires": { - "cipher-base": "1.0.4", - "create-hash": "1.2.0", - "inherits": "2.0.3", - "ripemd160": "2.0.2", - "safe-buffer": "5.1.2", - "sha.js": "2.4.11" - } - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "4.1.5", - "shebang-command": "1.2.0", - "which": "1.3.1" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "1.0.1", - "browserify-sign": "4.0.4", - "create-ecdh": "4.0.3", - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "diffie-hellman": "5.0.3", - "inherits": "2.0.3", - "pbkdf2": "3.0.17", - "public-encrypt": "4.0.3", - "randombytes": "2.1.0", - "randomfill": "1.0.4" - } - }, - "cyclist": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", - "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", - "dev": true - }, - "d": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true, - "requires": { - "es5-ext": "0.10.49" - } - }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "optional": true, - "requires": { - "mimic-response": "1.0.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "optional": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "1.0.2", - "isobject": "3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "optional": true - }, - "des.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", - "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1" - } - }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "2.0.1" - } - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "optional": true - }, - "diff": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", - "dev": true - }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "miller-rabin": "4.0.1", - "randombytes": "2.1.0" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "2.0.2" - } - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true - }, - "drbg.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", - "integrity": "sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=", - "optional": true, - "requires": { - "browserify-aes": "1.2.0", - "create-hash": "1.2.0", - "create-hmac": "1.1.7" - } - }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "requires": { - "end-of-stream": "1.4.1", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "stream-shift": "1.0.0" - } - }, - "electron-to-chromium": { - "version": "1.3.125", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.125.tgz", - "integrity": "sha512-XxowpqQxJ4nDwUXHtVtmEhRqBpm2OnjBomZmZtHD0d2Eo0244+Ojezhk3sD/MBSSe2nxCdGQFRXHIsf/LUTL9A==", - "dev": true - }, - "elliptic": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", - "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", - "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0", - "hash.js": "1.1.7", - "hmac-drbg": "1.0.1", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1", - "minimalistic-crypto-utils": "1.0.1" - } - }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true - }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "requires": { - "once": "1.4.0" - } - }, - "engine.io": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.5.tgz", - "integrity": "sha512-D06ivJkYxyRrcEe0bTpNnBQNgP9d3xog+qZlLbui8EsMr/DouQpf5o9FzJnWYHEYE0YsFHllUv2R1dkgYZXHcA==", - "optional": true, - "requires": { - "accepts": "1.3.5", - "base64id": "1.0.0", - "cookie": "0.3.1", - "debug": "3.1.0", - "engine.io-parser": "2.1.3", - "uws": "9.14.0", - "ws": "3.3.3" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "optional": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "engine.io-client": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.6.tgz", - "integrity": "sha512-hnuHsFluXnsKOndS4Hv6SvUrgdYx1pk2NqfaDMW+GWdgfU3+/V25Cj7I8a0x92idSpa5PIhJRKxPvp9mnoLsfg==", - "optional": true, - "requires": { - "component-emitter": "1.2.1", - "component-inherit": "0.0.3", - "debug": "3.1.0", - "engine.io-parser": "2.1.3", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "ws": "3.3.3", - "xmlhttprequest-ssl": "1.5.5", - "yeast": "0.1.2" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "optional": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "engine.io-parser": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", - "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", - "requires": { - "after": "0.8.2", - "arraybuffer.slice": "0.0.7", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.5", - "has-binary2": "1.0.3" - } - }, - "enhanced-resolve": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", - "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", - "dev": true, - "requires": { - "graceful-fs": "4.1.15", - "memory-fs": "0.4.1", - "object-assign": "4.1.1", - "tapable": "0.2.9" - } - }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, - "requires": { - "prr": "1.0.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "0.2.1" - } - }, - "es5-ext": { - "version": "0.10.49", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.49.tgz", - "integrity": "sha512-3NMEhi57E31qdzmYp2jwRArIUsj1HI/RxbQ4bgnSB+AIKIxsAmTiK83bYMifIcpWvEc3P1X30DhUKOqEtF/kvg==", - "dev": true, - "requires": { - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "next-tick": "1.0.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.49", - "es6-symbol": "3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.49", - "es6-iterator": "2.0.3", - "es6-set": "0.1.5", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" - } - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.49", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.49" - } - }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.49", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "requires": { - "es6-map": "0.1.5", - "es6-weak-map": "2.0.2", - "esrecurse": "4.2.1", - "estraverse": "4.2.0" - } - }, - "eslint": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", - "dev": true, - "requires": { - "ajv": "5.5.2", - "babel-code-frame": "6.26.0", - "chalk": "2.4.2", - "concat-stream": "1.6.2", - "cross-spawn": "5.1.0", - "debug": "3.2.6", - "doctrine": "2.1.0", - "eslint-scope": "3.7.3", - "eslint-visitor-keys": "1.0.0", - "espree": "3.5.4", - "esquery": "1.0.1", - "esutils": "2.0.2", - "file-entry-cache": "2.0.0", - "functional-red-black-tree": "1.0.1", - "glob": "7.1.3", - "globals": "11.11.0", - "ignore": "3.3.10", - "imurmurhash": "0.1.4", - "inquirer": "3.3.0", - "is-resolvable": "1.1.0", - "js-yaml": "3.13.1", - "json-stable-stringify-without-jsonify": "1.0.1", - "levn": "0.3.0", - "lodash": "4.17.11", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "natural-compare": "1.4.0", - "optionator": "0.8.2", - "path-is-inside": "1.0.2", - "pluralize": "7.0.0", - "progress": "2.0.3", - "regexpp": "1.1.0", - "require-uncached": "1.0.3", - "semver": "5.7.0", - "strip-ansi": "4.0.0", - "strip-json-comments": "2.0.1", - "table": "4.0.2", - "text-table": "0.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "1.9.3" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.5.0" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "2.1.1" - } - }, - "globals": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", - "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==", - "dev": true - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", - "dev": true, - "requires": { - "esrecurse": "4.2.1", - "estraverse": "4.2.0" - } - }, - "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", - "dev": true - }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "5.7.3", - "acorn-jsx": "3.0.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "dev": true, - "requires": { - "estraverse": "4.2.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "4.2.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.49" - } - }, - "events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", - "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", - "dev": true - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "requires": { - "md5.js": "1.3.5", - "safe-buffer": "5.1.2" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "expand-template": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz", - "integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg==", - "optional": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "1.0.0", - "is-extendable": "1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "2.0.4" - } - } - } - }, - "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", - "dev": true, - "requires": { - "chardet": "0.4.2", - "iconv-lite": "0.4.24", - "tmp": "0.0.33" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true - }, - "fast-future": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fast-future/-/fast-future-1.0.2.tgz", - "integrity": "sha1-hDWpqqAteSSNF9cE52JZMB2ZKAo=", - "optional": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "1.0.5" - } - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, - "requires": { - "flat-cache": "1.3.4", - "object-assign": "4.1.1" - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "find-cache-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", - "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", - "dev": true, - "requires": { - "commondir": "1.0.1", - "make-dir": "1.3.0", - "pkg-dir": "2.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "2.0.0" - } - }, - "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", - "dev": true, - "requires": { - "circular-json": "0.3.3", - "graceful-fs": "4.1.15", - "rimraf": "2.6.3", - "write": "0.2.1" - } - }, - "flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "0.2.2" - } - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" - } - }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "optional": true - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "requires": { - "graceful-fs": "4.1.15", - "iferr": "0.1.5", - "imurmurhash": "0.1.4", - "readable-stream": "2.3.6" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.8.tgz", - "integrity": "sha512-tPvHgPGB7m40CZ68xqFGkKuzN+RnpGmSV+hgeKxhRpbxdqKXUFJGC3yonBOLzQBcJyGpdZFDfCsdOC2KFsXzeA==", - "dev": true, - "optional": true, - "requires": { - "nan": "2.13.2", - "node-pre-gyp": "0.12.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "2.3.5" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.3" - } - }, - "glob": { - "version": "7.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": "2.1.2" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "minipass": { - "version": "2.3.5", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.1.2", - "yallist": "3.0.3" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "2.3.5" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.3.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "4.1.1", - "iconv-lite": "0.4.24", - "sax": "1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.1", - "needle": "2.3.0", - "nopt": "4.0.1", - "npm-packlist": "1.4.1", - "npmlog": "4.1.2", - "rc": "1.2.8", - "rimraf": "2.6.3", - "semver": "5.7.0", - "tar": "4.4.8" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" - } - }, - "npm-bundled": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.6" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "1.1.5", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "0.6.0", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.7.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "1.1.1", - "fs-minipass": "1.2.5", - "minipass": "2.3.5", - "minizlib": "1.2.1", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.2", - "yallist": "3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true, - "dev": true - } - } - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "optional": true, - "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.3" - } - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=", - "optional": true - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "2.1.1" - } - } - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true - }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true - }, - "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", - "dev": true - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", - "requires": { - "isarray": "2.0.1" - }, - "dependencies": { - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" - } - } - }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "optional": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "2.0.6", - "has-values": "1.0.0", - "isobject": "3.0.1" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.2" - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1" - } - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "requires": { - "hash.js": "1.1.7", - "minimalistic-assert": "1.0.1", - "minimalistic-crypto-utils": "1.0.1" - } - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", - "dev": true - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": "2.1.2" - } - }, - "idb-wrapper": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/idb-wrapper/-/idb-wrapper-1.7.2.tgz", - "integrity": "sha512-zfNREywMuf0NzDo9mVsL0yegjsirJxHpKHvWcyRozIqQy89g0a3U+oBPOCN4cc0oCiOuYgZHimzaW/R46G1Mpg==", - "dev": true - }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true - }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "optional": true - }, - "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", - "dev": true, - "requires": { - "ansi-escapes": "3.2.0", - "chalk": "2.4.2", - "cli-cursor": "2.1.0", - "cli-width": "2.2.0", - "external-editor": "2.2.0", - "figures": "2.0.0", - "lodash": "4.17.11", - "mute-stream": "0.0.7", - "run-async": "2.3.0", - "rx-lite": "4.0.8", - "rx-lite-aggregates": "4.0.8", - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "through": "2.3.8" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "1.9.3" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.5.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", - "dev": true - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "1.4.0" - } - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "1.13.1" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "2.1.1" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "3.0.1" - } - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isbuffer": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/isbuffer/-/isbuffer-0.0.0.tgz", - "integrity": "sha1-OMFG2d9Si4v5sHAcPUPPEt8/w5s=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "1.0.10", - "esprima": "4.0.1" - } - }, - "js2xmlparser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-3.0.0.tgz", - "integrity": "sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=", - "dev": true, - "requires": { - "xmlcreate": "1.0.2" - } - }, - "jsdoc": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.5.5.tgz", - "integrity": "sha512-6PxB65TAU4WO0Wzyr/4/YhlGovXl0EVYfpKbpSroSj0qBxT4/xod/l40Opkm38dRHRdQgdeY836M0uVnJQG7kg==", - "dev": true, - "requires": { - "babylon": "7.0.0-beta.19", - "bluebird": "3.5.4", - "catharsis": "0.8.9", - "escape-string-regexp": "1.0.5", - "js2xmlparser": "3.0.0", - "klaw": "2.0.0", - "marked": "0.3.19", - "mkdirp": "0.5.1", - "requizzle": "0.2.1", - "strip-json-comments": "2.0.1", - "taffydb": "2.6.2", - "underscore": "1.8.3" - }, - "dependencies": { - "babylon": { - "version": "7.0.0-beta.19", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.19.tgz", - "integrity": "sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==", - "dev": true - } - } - }, - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - }, - "json-loader": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", - "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - }, - "klaw": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-2.0.0.tgz", - "integrity": "sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY=", - "dev": true, - "requires": { - "graceful-fs": "4.1.15" - } - }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "1.0.0" - } - }, - "level-js": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/level-js/-/level-js-2.2.4.tgz", - "integrity": "sha1-vAVfQYBjXUSJtWHJSG+jcOjBFpc=", - "dev": true, - "requires": { - "abstract-leveldown": "0.12.4", - "idb-wrapper": "1.7.2", - "isbuffer": "0.0.0", - "ltgt": "2.2.1", - "typedarray-to-buffer": "1.0.4", - "xtend": "2.1.2" - }, - "dependencies": { - "abstract-leveldown": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-0.12.4.tgz", - "integrity": "sha1-KeGOYy5g5OIh1YECR4UqY9ey5BA=", - "dev": true, - "requires": { - "xtend": "3.0.0" - }, - "dependencies": { - "xtend": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", - "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", - "dev": true - } - } - }, - "xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", - "dev": true, - "requires": { - "object-keys": "0.4.0" - } - } - } - }, - "leveldown": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-1.7.2.tgz", - "integrity": "sha1-XjRnuyfuJGpKe429j7KxYgam64s=", - "optional": true, - "requires": { - "abstract-leveldown": "2.6.3", - "bindings": "1.2.1", - "fast-future": "1.0.2", - "nan": "2.6.2", - "prebuild-install": "2.5.3" - }, - "dependencies": { - "bindings": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", - "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=", - "optional": true - }, - "nan": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz", - "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=", - "optional": true - } - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "4.1.15", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", - "dev": true - }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "dev": true, - "requires": { - "big.js": "5.2.2", - "emojis-list": "2.1.0", - "json5": "1.0.1" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "1.2.0" - } - } - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dev": true, - "requires": { - "lodash._basecopy": "3.0.1", - "lodash.keys": "3.1.2" - } - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basecreate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", - "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash.create": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", - "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", - "dev": true, - "requires": { - "lodash._baseassign": "3.2.0", - "lodash._basecreate": "3.0.3", - "lodash._isiterateecall": "3.0.9" - } - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "3.9.1", - "lodash.isarguments": "3.1.0", - "lodash.isarray": "3.0.4" - } - }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "3.0.2" - } - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - }, - "ltgt": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", - "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=", - "dev": true - }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "requires": { - "pify": "3.0.0" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "1.0.1" - } - }, - "marked": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", - "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", - "dev": true - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "requires": { - "hash-base": "3.0.4", - "inherits": "2.0.3", - "safe-buffer": "5.1.2" - } - }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "requires": { - "mimic-fn": "1.2.0" - } - }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "requires": { - "errno": "0.1.7", - "readable-stream": "2.3.6" - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.13", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0" - } - }, - "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", - "optional": true - }, - "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", - "optional": true, - "requires": { - "mime-db": "1.40.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "optional": true - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "mississippi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", - "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", - "dev": true, - "requires": { - "concat-stream": "1.6.2", - "duplexify": "3.7.1", - "end-of-stream": "1.4.1", - "flush-write-stream": "1.1.1", - "from2": "2.3.0", - "parallel-transform": "1.1.0", - "pump": "2.0.1", - "pumpify": "1.5.1", - "stream-each": "1.2.3", - "through2": "2.0.5" - } - }, - "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", - "dev": true, - "requires": { - "for-in": "1.0.2", - "is-extendable": "1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } - }, - "mocha": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", - "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", - "dev": true, - "requires": { - "browser-stdout": "1.3.0", - "commander": "2.9.0", - "debug": "2.6.8", - "diff": "3.2.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.1", - "growl": "1.9.2", - "he": "1.1.1", - "json3": "3.3.2", - "lodash.create": "3.1.1", - "mkdirp": "0.5.1", - "supports-color": "3.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "supports-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", - "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, - "requires": { - "aproba": "1.2.0", - "copy-concurrently": "1.0.5", - "fs-write-stream-atomic": "1.0.10", - "mkdirp": "0.5.1", - "rimraf": "2.6.3", - "run-queue": "1.0.3" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "n64": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/n64/-/n64-0.0.18.tgz", - "integrity": "sha512-z5gRoCNFTY5fderO8n5tt6p7+jZiivzN1LhuR4EqrTkciYDCpJy8xkwkd1YO5eyfimeLAjXy/6k3FLC+GQ6lLw==" - }, - "nan": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", - "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "fragment-cache": "0.2.1", - "is-windows": "1.0.2", - "kind-of": "6.0.2", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", - "optional": true - }, - "neo-async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", - "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", - "dev": true - }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true - }, - "node-abi": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.7.1.tgz", - "integrity": "sha512-OV8Bq1OrPh6z+Y4dqwo05HqrRL9YNF7QVMRfq1/pguwKLG+q9UB/Lk0x5qXjO23JjJg+/jqCHSTaG1P3tfKfuw==", - "optional": true, - "requires": { - "semver": "5.7.0" - } - }, - "node-libs-browser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.0.tgz", - "integrity": "sha512-5MQunG/oyOaBdttrL40dA7bUfPORLRWMUJLQtMg7nluxUvk5XwnLdL9twQHFAjRx/y7mIMkLKT9++qPbbk6BZA==", - "dev": true, - "requires": { - "assert": "1.4.1", - "browserify-zlib": "0.2.0", - "buffer": "4.9.1", - "console-browserify": "1.1.0", - "constants-browserify": "1.0.0", - "crypto-browserify": "3.12.0", - "domain-browser": "1.2.0", - "events": "3.0.0", - "https-browserify": "1.0.0", - "os-browserify": "0.3.0", - "path-browserify": "0.0.0", - "process": "0.11.10", - "punycode": "1.4.1", - "querystring-es3": "0.2.1", - "readable-stream": "2.3.6", - "stream-browserify": "2.0.2", - "stream-http": "2.8.3", - "string_decoder": "1.1.1", - "timers-browserify": "2.0.10", - "tty-browserify": "0.0.0", - "url": "0.11.0", - "util": "0.11.1", - "vm-browserify": "0.0.4" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - } - } - }, - "node-loader": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/node-loader/-/node-loader-0.6.0.tgz", - "integrity": "sha1-x5fvUQle1YWZArFX9jhPY2HgWug=", - "dev": true - }, - "noop-logger": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=", - "optional": true - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "2.7.1", - "resolve": "1.10.0", - "semver": "5.7.0", - "validate-npm-package-license": "3.0.4" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "2.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "optional": true, - "requires": { - "are-we-there-yet": "1.1.5", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", - "optional": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "0.1.1", - "define-property": "0.2.5", - "kind-of": "3.2.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - } - } - }, - "object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "3.0.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "3.0.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" - } - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "1.2.0" - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, - "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "1.3.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "pako": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", - "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", - "dev": true - }, - "parallel-transform": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", - "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", - "dev": true, - "requires": { - "cyclist": "0.2.2", - "inherits": "2.0.3", - "readable-stream": "2.3.6" - } - }, - "parse-asn1": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", - "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", - "dev": true, - "requires": { - "asn1.js": "4.10.1", - "browserify-aes": "1.2.0", - "create-hash": "1.2.0", - "evp_bytestokey": "1.0.3", - "pbkdf2": "3.0.17", - "safe-buffer": "5.1.2" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "1.3.2" - } - }, - "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "requires": { - "better-assert": "1.0.2" - } - }, - "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "requires": { - "better-assert": "1.0.2" - } - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", - "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "2.3.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "pbkdf2": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", - "dev": true, - "requires": { - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "ripemd160": "2.0.2", - "safe-buffer": "5.1.2", - "sha.js": "2.4.11" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "2.1.0" - } - }, - "pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", - "dev": true - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "prebuild-install": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.3.tgz", - "integrity": "sha512-/rI36cN2g7vDQnKWN8Uzupi++KjyqS9iS+/fpwG4Ea8d0Pip0PQ5bshUNzVwt+/D2MRfhVAplYMMvWLqWrCF/g==", - "optional": true, - "requires": { - "detect-libc": "1.0.3", - "expand-template": "1.1.1", - "github-from-package": "0.0.0", - "minimist": "1.2.0", - "mkdirp": "0.5.1", - "node-abi": "2.7.1", - "noop-logger": "0.1.1", - "npmlog": "4.1.2", - "os-homedir": "1.0.2", - "pump": "2.0.1", - "rc": "1.2.8", - "simple-get": "2.8.1", - "tar-fs": "1.16.3", - "tunnel-agent": "0.6.0", - "which-pm-runs": "1.0.0" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.2.0", - "parse-asn1": "5.1.4", - "randombytes": "2.1.0", - "safe-buffer": "5.1.2" - } - }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" - } - }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "requires": { - "duplexify": "3.7.1", - "inherits": "2.0.3", - "pump": "2.0.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "requires": { - "randombytes": "2.1.0", - "safe-buffer": "5.1.2" - } - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "optional": true, - "requires": { - "deep-extend": "0.6.0", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.5.0", - "path-type": "2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "requires": { - "graceful-fs": "4.1.15", - "micromatch": "3.1.10", - "readable-stream": "2.3.6" - } - }, - "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "regenerator-transform": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", - "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "private": "0.1.8" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "3.0.2", - "safe-regex": "1.1.0" - } - }, - "regexpp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", - "dev": true - }, - "regexpu-core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", - "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", - "dev": true, - "requires": { - "regenerate": "1.4.0", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" - } - }, - "regjsgen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", - "dev": true - }, - "regjsparser": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", - "dev": true, - "requires": { - "jsesc": "0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - } - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "1.0.2" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "0.1.0", - "resolve-from": "1.0.1" - } - }, - "requizzle": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.1.tgz", - "integrity": "sha1-aUPDUwxNmn5G8c3dUcFY/GcM294=", - "dev": true, - "requires": { - "underscore": "1.6.0" - }, - "dependencies": { - "underscore": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", - "dev": true - } - } - }, - "resolve": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", - "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", - "dev": true, - "requires": { - "path-parse": "1.0.6" - } - }, - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "2.0.1", - "signal-exit": "3.0.2" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "requires": { - "align-text": "0.1.4" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "7.1.3" - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "@oipwg/fclient": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@oipwg/fclient/-/fclient-0.1.7.tgz", + "integrity": "sha512-mrLXFj1sz3CxpLib/mp9vntQnkofUBwECCiSWyb0BKAwM1g68w3fsp0IkF3G7gmcs5XcTVyuuor91O39qTk7Wg==", "requires": { - "hash-base": "3.0.4", - "inherits": "2.0.3" + "bcfg": "~0.1.6", + "bcurl": "~0.1.6", + "bsert": "~0.0.10" } }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, + "bcfg": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/bcfg/-/bcfg-0.1.6.tgz", + "integrity": "sha512-BR2vwQZwu24aRm588XHOnPVjjQtbK8sF0RopRFgMuke63/REJMWnePTa2YHKDBefuBYiVdgkowuB1/e4K7Ue3g==", "requires": { - "is-promise": "2.1.0" + "bsert": "~0.0.10" } }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, + "bcrypto": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/bcrypto/-/bcrypto-3.1.11.tgz", + "integrity": "sha512-XHsle+v0aYjCyHZSwd3Insnu8GUe4cuf3+f07Z/mO+GLq60YqUyEIvpaSv4LAAJ4uf8UNYMSSQ0LslsV0r4tug==", "requires": { - "aproba": "1.2.0" + "bsert": "~0.0.10", + "bufio": "~1.0.6", + "loady": "~0.0.1", + "nan": "^2.13.2" } }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", - "dev": true, + "bcurl": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/bcurl/-/bcurl-0.1.6.tgz", + "integrity": "sha512-noeDhfqFiUcNOUuZKErkXcbZfxBjn6duTfYPEfA4hAYXGr7gwVxkAWvIerxl3ZLLyn8jzh7Oi0nHlrLm1ST9Fw==", "requires": { - "rx-lite": "4.0.8" + "brq": "~0.1.7", + "bsert": "~0.0.10", + "bsock": "~0.1.8" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, + "bdb": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/bdb/-/bdb-1.1.7.tgz", + "integrity": "sha512-jtBnWEyDDK08dBbKL9LJeO2huZyBmbjBQMMVjU9RI1liJo6YDbv86uDHoaD4gniefd9pfxqOM1w6rYuwLVCXlQ==", "requires": { - "ret": "0.1.15" + "bsert": "~0.0.10", + "loady": "~0.0.1" } }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "schema-utils": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", - "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", - "dev": true, + "bdns": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/bdns/-/bdns-0.1.5.tgz", + "integrity": "sha512-LNVkfM7ynlAD0CvPvO9cKxW8YXt1KOCRQZlRsGZWeMyymUWVdHQpZudAzH9chaFAz6HiwAnQxwDemCKDPy6Mag==", "requires": { - "ajv": "6.10.0", - "ajv-keywords": "3.4.0" - }, - "dependencies": { - "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", - "dev": true, - "requires": { - "fast-deep-equal": "2.0.1", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" - } - }, - "ajv-keywords": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", - "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==", - "dev": true - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - } + "bsert": "~0.0.10" } }, - "secp256k1": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.3.0.tgz", - "integrity": "sha512-CbrQoeGG5V0kQ1ohEMGI+J7oKerapLTpivLICBaXR0R4HyQcN3kM9itLsV5fdpV1UR1bD14tOkJ1xughmlDIiQ==", - "optional": true, + "bevent": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/bevent/-/bevent-0.1.5.tgz", + "integrity": "sha512-hs6T3BjndibrAmPSoKTHmKa3tz/c6Qgjv9iZw+tAoxuP6izfTCkzfltBQrW7SuK5xnY22gv9jCEf51+mRH+Qvg==", "requires": { - "bindings": "1.5.0", - "bip66": "1.1.5", - "bn.js": "4.11.8", - "create-hash": "1.2.0", - "drbg.js": "1.0.1", - "elliptic": "6.4.0", - "nan": "2.13.2", - "prebuild-install": "2.5.3", - "safe-buffer": "5.1.2" + "bsert": "~0.0.10" } }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" - }, - "serialize-javascript": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.7.0.tgz", - "integrity": "sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "dev": true, - "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "split-string": "3.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } + "bfile": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/bfile/-/bfile-0.2.1.tgz", + "integrity": "sha512-du2QjKNkqZ1YJweWEQeq3CEqd+nQD/WOnmAMfs52ok5ujBBagWYLZC5ORDuqfV2fuF88of44PZdsnAVfxoH31g==" }, - "setimmediate": { + "bfilter": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.2" - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "simple-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", - "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=", - "optional": true - }, - "simple-get": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", - "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", - "optional": true, - "requires": { - "decompress-response": "3.3.0", - "once": "1.4.0", - "simple-concat": "1.0.0" - } - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - } - } - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "0.11.2", - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "map-cache": "0.2.2", - "source-map": "0.5.7", - "source-map-resolve": "0.5.2", - "use": "3.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "1.0.0", - "isobject": "3.0.1", - "snapdragon-util": "3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "socket.io": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.0.3.tgz", - "integrity": "sha1-Q1nwaiSTOua9CHeYr3jGgOrjReM=", - "optional": true, - "requires": { - "debug": "2.6.9", - "engine.io": "3.1.5", - "object-assign": "4.1.1", - "socket.io-adapter": "1.1.1", - "socket.io-client": "2.0.3", - "socket.io-parser": "3.1.3" - } - }, - "socket.io-adapter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", - "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=", - "optional": true - }, - "socket.io-client": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.0.3.tgz", - "integrity": "sha1-bK9K/5+FsZ/ZG2zhPWmttWT4hzs=", - "optional": true, - "requires": { - "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", - "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "2.6.9", - "engine.io-client": "3.1.6", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "socket.io-parser": "3.1.3", - "to-array": "0.1.4" - } - }, - "socket.io-parser": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.3.tgz", - "integrity": "sha512-g0a2HPqLguqAczs3dMECuA1RgoGFPyvDqcbaDEdCWY9g59kdUAz3YRmaJBNKXflrHNwB7Q12Gkf/0CZXfdHR7g==", - "requires": { - "component-emitter": "1.2.1", - "debug": "3.1.0", - "has-binary2": "1.0.3", - "isarray": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" - } - } - }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "dev": true, - "requires": { - "atob": "2.1.2", - "decode-uri-component": "0.2.0", - "resolve-url": "0.2.1", - "source-map-url": "0.4.0", - "urix": "0.1.0" - } - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "0.5.7" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.4" - } - }, - "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "2.2.0", - "spdx-license-ids": "3.0.4" - } - }, - "spdx-license-ids": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", - "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "3.0.2" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "ssri": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", - "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "0.2.5", - "object-copy": "0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - } - } - }, - "stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" - } - }, - "stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "dev": true, - "requires": { - "end-of-stream": "1.4.1", - "stream-shift": "1.0.0" - } - }, - "stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "requires": { - "builtin-status-codes": "3.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "to-arraybuffer": "1.0.1", - "xtend": "4.0.1" - } - }, - "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "resolved": "https://registry.npmjs.org/bfilter/-/bfilter-1.0.5.tgz", + "integrity": "sha512-GupIidtCvLbKhXnA1sxvrwa+gh95qbjafy7P1U1x/2DHxNabXq4nGW0x3rmgzlJMYlVl+c8fMxoMRIwpKYlgcQ==", "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "bsert": "~0.0.10", + "bufio": "~1.0.6", + "mrmr": "~0.1.6" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", - "dev": true, - "requires": { - "ajv": "5.5.2", - "ajv-keywords": "2.1.1", - "chalk": "2.4.2", - "lodash": "4.17.11", - "slice-ansi": "1.0.0", - "string-width": "2.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "1.9.3" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.5.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", - "dev": true - }, - "tapable": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz", - "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==", - "dev": true - }, - "tar-fs": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", - "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", - "optional": true, - "requires": { - "chownr": "1.1.1", - "mkdirp": "0.5.1", - "pump": "1.0.3", - "tar-stream": "1.6.2" - }, - "dependencies": { - "pump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", - "optional": true, - "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" - } - } - } - }, - "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", - "optional": true, - "requires": { - "bl": "1.2.2", - "buffer-alloc": "1.2.0", - "end-of-stream": "1.4.1", - "fs-constants": "1.0.0", - "readable-stream": "2.3.6", - "to-buffer": "1.1.1", - "xtend": "4.0.1" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "2.3.6", - "xtend": "4.0.1" - } - }, - "timers-browserify": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", - "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", - "dev": true, - "requires": { - "setimmediate": "1.0.5" - } - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "1.0.2" - } - }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", - "optional": true - }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", - "optional": true - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "regex-not": "1.0.2", - "safe-regex": "1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "3.0.0", - "repeat-string": "1.6.1" - } - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "optional": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "1.1.2" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "typedarray-to-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-1.0.4.tgz", - "integrity": "sha1-m7i6DoQfs/TPH+fCRenz+opf6Zw=", - "dev": true - }, - "uglify-es": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", - "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", - "dev": true, - "requires": { - "commander": "2.13.0", - "source-map": "0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, - "uglifyjs-webpack-plugin": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz", - "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==", - "dev": true, - "requires": { - "cacache": "10.0.4", - "find-cache-dir": "1.0.0", - "schema-utils": "0.4.7", - "serialize-javascript": "1.7.0", - "source-map": "0.6.1", - "uglify-es": "3.3.9", - "webpack-sources": "1.3.0", - "worker-farm": "1.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" - }, - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", - "dev": true - }, - "underscore-contrib": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/underscore-contrib/-/underscore-contrib-0.3.0.tgz", - "integrity": "sha1-ZltmwkeD+PorGMn4y7Dix9SMJsc=", - "dev": true, - "requires": { - "underscore": "1.6.0" - }, - "dependencies": { - "underscore": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", - "dev": true - } - } - }, - "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", - "dev": true, - "requires": { - "arr-union": "3.1.0", - "get-value": "2.0.6", - "is-extendable": "0.1.1", - "set-value": "0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "to-object-path": "0.3.0" - } - } - } - }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, + "bheep": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/bheep/-/bheep-0.1.5.tgz", + "integrity": "sha512-0KR5Zi8hgJBKL35+aYzndCTtgSGakOMxrYw2uszd5UmXTIfx3+drPGoETlVbQ6arTdAzSoQYA1j35vbaWpQXBg==", "requires": { - "unique-slug": "2.0.1" + "bsert": "~0.0.10" } }, - "unique-slug": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.1.tgz", - "integrity": "sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg==", - "dev": true, + "binet": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/binet/-/binet-0.3.5.tgz", + "integrity": "sha512-suogkqXrt63Z76ABvwJjI28w+LWrRE53nwItDPz1VwNjHEuh1+rxudGYtoewFmMhkJ9pW/VGGnjoxP+AGzHgLQ==", "requires": { - "imurmurhash": "0.1.4" + "bs32": "~0.1.5", + "bsert": "~0.0.10" } }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, + "blgr": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/blgr/-/blgr-0.1.7.tgz", + "integrity": "sha512-+jSaU2jnYqF+ec9e8nzjfjU7QhZIntkDNG8/AEwoxW7J3mmwvmUfAI5lm9BFYs1OzT+1UgIZTFHa2qWjTBURfw==", "requires": { - "has-value": "0.3.1", - "isobject": "3.0.1" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "2.0.6", - "has-values": "0.1.4", - "isobject": "2.1.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } + "bsert": "~0.0.10" } }, - "upath": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", - "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", - "dev": true - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, + "blru": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/blru/-/blru-0.1.6.tgz", + "integrity": "sha512-34+xZ2u4ys/aUzWCU9m6Eee4nVuN1ywdxbi8b3Z2WULU6qvnfeHvCWEdGzlVfRbbhimG2xxJX6R77GD2cuVO6w==", "requires": { - "punycode": "2.1.1" + "bsert": "~0.0.10" } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, + "blst": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/blst/-/blst-0.1.5.tgz", + "integrity": "sha512-TPl04Cx3CHdPFAJ2x9Xx1Z1FOfpAzmNPfHkfo+pGAaNH4uLhS58ExvamVkZh3jadF+B7V5sMtqvrqdf9mHINYA==", "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } + "bsert": "~0.0.10" } }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "bmocha": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bmocha/-/bmocha-2.1.2.tgz", + "integrity": "sha512-EvcGGQCbRnF3/Uq+gNIrWdNY65eDok17i7AKEY/xTLGFk812GdqEQ/FW8X1X1lyH5IlLshjubDjyC4HRWeCt7g==", "dev": true }, - "util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, + "bmutex": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/bmutex/-/bmutex-0.1.6.tgz", + "integrity": "sha512-nXWOXtQHbfPaMl6jyEF/rmRMrcemj2qn+OCAI/uZYurjfx7Dg3baoXdPzHOL0U8Cfvn8CWxKcnM/rgxL7DR4zw==", "requires": { - "inherits": "2.0.3" + "bsert": "~0.0.10" } }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "uws": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/uws/-/uws-9.14.0.tgz", - "integrity": "sha512-HNMztPP5A1sKuVFmdZ6BPVpBQd5bUjNC8EFMFiICK+oho/OQsAJy5hnIx4btMHiOk8j04f/DbIlqnEZ9d72dqg==", - "optional": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, + "brq": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/brq/-/brq-0.1.8.tgz", + "integrity": "sha512-6SDY1lJMKXgt5TZ6voJQMH2zV1XPWWtm203PSkx3DSg9AYNYuRfOPFSBDkNemabzgpzFW9/neR4YhTvyJml8rQ==", "requires": { - "spdx-correct": "3.1.0", - "spdx-expression-parse": "3.0.0" + "bsert": "~0.0.10" } }, - "vm-browserify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", - "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", - "dev": true, + "bs32": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/bs32/-/bs32-0.1.5.tgz", + "integrity": "sha512-YR9OXFjx8qmW/TpuJKc1RSdzRogpCYh1ygCSMi5Z3fG2QkP+Ra1IfcHsICqd/I/tmFAtc7ov8BpEyN8HJD7jlw==", "requires": { - "indexof": "0.0.1" + "bsert": "~0.0.10" } }, - "watchpack": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", - "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", - "dev": true, - "requires": { - "chokidar": "2.1.5", - "graceful-fs": "4.1.15", - "neo-async": "2.6.0" - } + "bsert": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/bsert/-/bsert-0.0.10.tgz", + "integrity": "sha512-NHNwlac+WPy4t2LoNh8pXk8uaIGH3NSaIUbTTRXGpE2WEbq0te/tDykYHkFK57YKLPjv/aGHmbqvnGeVWDz57Q==" }, - "webpack": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.12.0.tgz", - "integrity": "sha512-Sw7MdIIOv/nkzPzee4o0EdvCuPmxT98+vVpIvwtcwcF1Q4SDSNp92vwcKc4REe7NItH9f1S4ra9FuQ7yuYZ8bQ==", - "dev": true, + "bsip": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/bsip/-/bsip-0.1.9.tgz", + "integrity": "sha512-i7cVEfCthASPB3BYKS/pZN/D4kh4vToIlSAFcVBLNjzYl1UirT3E2PIGSfNnYR2qZ3UW1qnDavrWEHhLeSKt2A==", "requires": { - "acorn": "5.7.3", - "acorn-dynamic-import": "2.0.2", - "ajv": "6.10.0", - "ajv-keywords": "3.4.0", - "async": "2.6.2", - "enhanced-resolve": "3.4.1", - "escope": "3.6.0", - "interpret": "1.2.0", - "json-loader": "0.5.7", - "json5": "0.5.1", - "loader-runner": "2.4.0", - "loader-utils": "1.2.3", - "memory-fs": "0.4.1", - "mkdirp": "0.5.1", - "node-libs-browser": "2.2.0", - "source-map": "0.5.7", - "supports-color": "4.5.0", - "tapable": "0.2.9", - "uglifyjs-webpack-plugin": "0.4.6", - "watchpack": "1.6.0", - "webpack-sources": "1.3.0", - "yargs": "8.0.2" - }, - "dependencies": { - "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", - "dev": true, - "requires": { - "fast-deep-equal": "2.0.1", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" - } - }, - "ajv-keywords": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", - "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==", - "dev": true - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dev": true, - "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" - }, - "dependencies": { - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", - "window-size": "0.1.0" - } - } - } - }, - "uglifyjs-webpack-plugin": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz", - "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", - "dev": true, - "requires": { - "source-map": "0.5.7", - "uglify-js": "2.8.29", - "webpack-sources": "1.3.0" - } - } + "bsert": "~0.0.10", + "loady": "~0.0.1", + "nan": "^2.13.1" } }, - "webpack-sources": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", - "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", - "dev": true, + "bsock": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/bsock/-/bsock-0.1.9.tgz", + "integrity": "sha512-/l9Kg/c5o+n/0AqreMxh2jpzDMl1ikl4gUxT7RFNe3A3YRIyZkiREhwcjmqxiymJSRI/Qhew357xGn1SLw/xEw==", "requires": { - "source-list-map": "2.0.1", - "source-map": "0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "bsert": "~0.0.10" } }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, + "bsocks": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/bsocks/-/bsocks-0.2.5.tgz", + "integrity": "sha512-w1yG8JmfKPIaTDLuR9TIxJM2Ma6nAiInRpLNZ43g3qPnPHjawCC4SV6Bdy84bEJQX1zJWYTgdod/BnQlDhq4Gg==", "requires": { - "isexe": "2.0.0" + "binet": "~0.3.5", + "bsert": "~0.0.10" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "optional": true, + "bstring": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/bstring/-/bstring-0.3.9.tgz", + "integrity": "sha512-D95flI7SXL+UsQi9mW+hH+AK2AFfafIJi+3GbbyTAWMe2FqwR9keBxsjGiGd/JM+77Y9WsC+M4EhZVNVcym9jw==", "requires": { - "string-width": "1.0.2" + "bsert": "~0.0.10", + "loady": "~0.0.1", + "nan": "^2.13.1" } }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true + "btcp": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/btcp/-/btcp-0.1.5.tgz", + "integrity": "sha512-tkrtMDxeJorn5p0KxaLXELneT8AbfZMpOFeoKYZ5qCCMMSluNuwut7pGccLC5YOJqmuk0DR774vNVQLC9sNq/A==" }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true + "buffer-map": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/buffer-map/-/buffer-map-0.0.7.tgz", + "integrity": "sha512-95try3p/vMRkIAAnJDaGkFhGpT/65NoeW6XelEPjAomWYR58RQtW4khn0SwKj34kZoE7uxL7w2koZSwbnszvQQ==" }, - "worker-farm": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", - "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", - "dev": true, - "requires": { - "errno": "0.1.7" - } + "bufio": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/bufio/-/bufio-1.0.6.tgz", + "integrity": "sha512-mjYZFRHmI9bk3Oeexu0rWjHFY+w6hGLabdmwSFzq+EFr4MHHsNOYduDVdYl71NG5pTPL7GGzUCMk9cYuV34/Qw==" }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, + "bupnp": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bupnp/-/bupnp-0.2.6.tgz", + "integrity": "sha512-J6ykzJhZMxXKN78K+1NzFi3v/51X2Mvzp2hW42BWwmxIVfau6PaN99gyABZ8x05e8MObWbsAis23gShhj9qpbw==", "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" + "binet": "~0.3.5", + "brq": "~0.1.7", + "bsert": "~0.0.10" } }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, + "bval": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/bval/-/bval-0.1.6.tgz", + "integrity": "sha512-jxNH9gSx7g749hQtS+nTxXYz/bLxwr4We1RHFkCYalNYcj12RfbW6qYWsKu0RYiKAdFcbNoZRHmWrIuXIyhiQQ==", "requires": { - "mkdirp": "0.5.1" + "bsert": "~0.0.10" } }, - "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "bweb": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/bweb/-/bweb-0.1.9.tgz", + "integrity": "sha512-Ozz6Vq7mC0ydHxnykvkUIiwduKAW4Qewj+au0osTCSssfXfRYrO7O2UluLgZQL0sSrXiifqEdJaGtOQ+GAeC8A==", "requires": { - "async-limiter": "1.0.0", - "safe-buffer": "5.1.2", - "ultron": "1.1.1" + "bsert": "~0.0.10", + "bsock": "~0.1.8" } }, - "xmlcreate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz", - "integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=", - "dev": true - }, - "xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", - "optional": true - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "loady": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/loady/-/loady-0.0.1.tgz", + "integrity": "sha512-PW5Z13Jd0v6ZcA1P6ZVUc3EV8BJwQuAiwUvvT6VQGHoaZ1d/tu7r1QZctuKfQqwy9SFBWeAGfcIdLxhp7ZW3Rw==" }, - "yargs": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", - "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", - "dev": true, + "mrmr": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/mrmr/-/mrmr-0.1.8.tgz", + "integrity": "sha512-lNav10EJsPZvlMqlBOYQ5atIO9jrlvbJ4/7asqunXY89dHooN5c+W6AV7jtvBRw4wFDR7TpEWfmBQgRGRVwBzA==", "requires": { - "camelcase": "4.1.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.3", - "os-locale": "2.1.0", - "read-pkg-up": "2.0.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - } - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - } + "bsert": "~0.0.10", + "loady": "~0.0.1", + "nan": "^2.13.1" } }, - "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", - "dev": true, - "requires": { - "camelcase": "4.1.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - } - } + "n64": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/n64/-/n64-0.2.10.tgz", + "integrity": "sha512-uH9geV4+roR1tohsrrqSOLCJ9Mh1iFcDI+9vUuydDlDxUS1UCAWUfuGb06p3dj3flzywquJNrGsQ7lHP8+4RVQ==" }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "optional": true + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" } } } diff --git a/package.json b/package.json index 761b0e80c..08fb225e3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "fcoin", - "version": "1.0.2", - "description": "Flo bike-shed", + "version": "1.1.0", + "description": "FLO bike-shed", "license": "MIT", "repository": "git://github.com/oipwg/fcoin.git", "homepage": "https://github.com/oipwg/fcoin", @@ -11,7 +11,7 @@ "author": "Fedor Indutny ", "contributors": [ "Christopher Jeffrey ", - "Sky Young " + "Sky Young " ], "keywords": [ "bcoin", @@ -25,56 +25,67 @@ "wallet" ], "engines": { - "node": ">=7.6.0" + "node": ">=8.6.0" }, "dependencies": { - "bn.js": "4.11.8", - "elliptic": "6.4.0", - "n64": "0.0.18" - }, - "optionalDependencies": { - "bcoin-native": "0.0.23", - "leveldown": "1.7.2", - "secp256k1": "3.3.0", - "socket.io": "2.0.3", - "socket.io-client": "2.0.3" + "bcfg": "~0.1.6", + "@oipwg/fclient": "~0.1.7", + "bcrypto": "~3.1.11", + "bdb": "~1.1.7", + "bdns": "~0.1.5", + "bevent": "~0.1.5", + "bfile": "~0.2.1", + "bfilter": "~1.0.5", + "bheep": "~0.1.5", + "binet": "~0.3.5", + "blgr": "~0.1.7", + "blru": "~0.1.6", + "blst": "~0.1.5", + "bmutex": "~0.1.6", + "bsert": "~0.0.10", + "bsip": "~0.1.9", + "bsock": "~0.1.9", + "bsocks": "~0.2.5", + "bstring": "~0.3.9", + "btcp": "~0.1.5", + "buffer-map": "~0.0.7", + "bufio": "~1.0.6", + "bupnp": "~0.2.6", + "bval": "~0.1.6", + "bweb": "~0.1.9", + "mrmr": "~0.1.8", + "n64": "~0.2.10" }, "devDependencies": { - "babel-core": "^6.25.0", - "babel-loader": "^7.1.1", - "babel-plugin-transform-runtime": "^6.23.0", - "babel-preset-es2015": "^6.24.1", - "babel-preset-es2016": "^6.24.1", - "babel-preset-es2017": "^6.24.1", - "babel-preset-env": "^1.6.0", - "eslint": "^4.4.1", - "hash.js": "^1.1.3", - "jsdoc": "^3.5.4", - "level-js": "^2.2.4", - "mocha": "^3.5.0", - "node-loader": "^0.6.0", - "uglifyjs-webpack-plugin": "^1.0.0-beta.2", - "webpack": "^3.5.4" + "bmocha": "^2.1.2" }, "main": "./lib/bcoin.js", "bin": { "fcoin": "./bin/fcoin", - "fcoin-cli": "./bin/cli", "fcoin-node": "./bin/node", - "fcoin-spvnode": "./bin/spvnode" + "fcoin-spvnode": "./bin/spvnode", + "fwallet": "./bin/fwallet", + "fcoin-cli": "./bin/fcoin-cli", + "fwallet-cli": "./bin/fwallet-cli" }, "scripts": { - "clean": "rm -f {browser/,}{bcoin.js,bcoin-worker.js}", + "browserify": "browserify -s fcoin lib/fcoin-browser.js | uglifyjs -c > fcoin.js", + "clean": "rm -f {browser/,}{fcoin.js,fcoin-worker.js,app.js,worker.js}", "docs": "jsdoc -c jsdoc.json", "lint": "eslint $(cat .eslintfiles) || exit 0", + "lint-ci": "eslint $(cat .eslintfiles)", "lint-file": "eslint", - "test": "mocha --reporter spec test/*.js", - "test-browser": "BCOIN_NO_NATIVE=1 BCOIN_NO_SECP256K1=1 mocha --reporter spec test/*.js", - "test-file": "mocha --reporter spec", - "test-file-browser": "BCOIN_NO_NATIVE=1 BCOIN_NO_SECP256K1=1 mocha --reporter spec", - "webpack": "webpack --config webpack.browser.js", - "webpack-browser": "webpack --config webpack.browser.js", - "webpack-compat": "webpack --config webpack.compat.js", - "webpack-node": "webpack --config webpack.node.js" + "test": "bmocha --reporter spec test/*.js", + "test-browser": "NODE_BACKEND=js bmocha --reporter spec test/*.js", + "test-file": "bmocha --reporter spec", + "test-file-browser": "NODE_BACKEND=js bmocha --reporter spec", + "test-ci": "nyc -a -n 'lib/**/*.js' --reporter=lcov --reporter=text npm run test" + }, + "browser": { + "./lib/hd/nfkd": "./lib/hd/nfkd-compat.js", + "./lib/hd/wordlist": "./lib/hd/wordlist-browser.js", + "./lib/workers/child": "./lib/workers/child-browser.js", + "./lib/workers/parent": "./lib/workers/parent-browser.js", + "./lib/fcoin": "./lib/fcoin-browser.js" } } diff --git a/scripts/fuzz.js b/scripts/fuzz.js index 87835e2cb..4011ba1a7 100644 --- a/scripts/fuzz.js +++ b/scripts/fuzz.js @@ -1,6 +1,5 @@ 'use strict'; -const util = require('../lib/utils/util'); const Script = require('../lib/script/script'); const Stack = require('../lib/script/stack'); const Witness = require('../lib/script/witness'); @@ -8,8 +7,8 @@ const Input = require('../lib/primitives/input'); const Output = require('../lib/primitives/output'); const Outpoint = require('../lib/primitives/outpoint'); const TX = require('../lib/primitives/tx'); -const random = require('../lib/crypto/random'); -const secp256k1 = require('../lib/crypto/secp256k1'); +const random = require('bcrypto/lib/random'); +const secp256k1 = require('bcrypto/lib/secp256k1'); const flags = Script.flags; let consensus = null; @@ -21,7 +20,7 @@ try { } if (consensus) - util.log('Running against bitcoinconsensus...'); + console.log('Running against bitcoinconsensus...'); const MANDATORY = flags.MANDATORY_VERIFY_FLAGS | flags.VERIFY_WITNESS; const STANDARD = flags.STANDARD_VERIFY_FLAGS; @@ -39,27 +38,27 @@ function assertConsensus(tx, output, flags, code) { const err = verifyConsensus(tx, 0, output, 0, flags); if (err !== code) { - util.log('bitcoinconsensus mismatch!'); - util.log(`${err} (bitcoin core) !== ${code} (bcoin)`); - util.log(tx); - util.log(output); - util.log(flags); - util.log('TX: %s', tx.toRaw().toString('hex')); - util.log('Output Script: %s', output.toRaw().toString('hex')); + console.log('bitcoinconsensus mismatch!'); + console.log(`${err} (bitcoin core) !== ${code} (bcoin)`); + console.log(tx); + console.log(output); + console.log(flags); + console.log('TX: %s', tx.toRaw().toString('hex')); + console.log('Output Script: %s', output.toRaw().toString('hex')); } } function randomSignature() { - const r = secp256k1.generatePrivateKey(); - const s = secp256k1.generatePrivateKey(); - return secp256k1.toDER(Buffer.concat([r, s])); + const r = secp256k1.privateKeyGenerate(); + const s = secp256k1.privateKeyGenerate(); + return secp256k1.signatureExport(Buffer.concat([r, s])); } function randomKey() { - const x = secp256k1.generatePrivateKey(); - const y = secp256k1.generatePrivateKey(); + const x = secp256k1.privateKeyGenerate(); + const y = secp256k1.privateKeyGenerate(); - if (util.random(0, 2) === 0) { + if (rand(0, 2) === 0) { const p = Buffer.from([2 | (y[y.length - 1] & 1)]); return Buffer.concat([p, x]); } @@ -70,28 +69,28 @@ function randomKey() { function randomOutpoint() { const hash = random.randomBytes(32).toString('hex'); - return new Outpoint(hash, util.random(0, 0xffffffff)); + return new Outpoint(hash, rand(0, 0xffffffff)); } function randomInput() { const input = Input.fromOutpoint(randomOutpoint()); - if (util.random(0, 5) === 0) - input.sequence = util.random(0, 0xffffffff); + if (rand(0, 5) === 0) + input.sequence = rand(0, 0xffffffff); return input; } function randomOutput() { - return Output.fromScript(randomScript(), util.random(0, 1e8)); + return Output.fromScript(randomScript(), rand(0, 1e8)); } function randomTX() { const tx = new TX(); - const inputs = util.random(1, 5); - const outputs = util.random(0, 5); + const inputs = rand(1, 5); + const outputs = rand(0, 5); - tx.version = util.random(0, 0xffffffff); + tx.version = rand(0, 0xffffffff); for (let i = 0; i < inputs; i++) tx.inputs.push(randomInput()); @@ -99,8 +98,8 @@ function randomTX() { for (let i = 0; i < outputs; i++) tx.outputs.push(randomOutput()); - if (util.random(0, 5) === 0) - tx.locktime = util.random(0, 0xffffffff); + if (rand(0, 5) === 0) + tx.locktime = rand(0, 0xffffffff); tx.refresh(); @@ -108,11 +107,11 @@ function randomTX() { } function randomWitness(redeem) { - const size = util.random(1, 100); + const size = rand(1, 100); const witness = new Witness(); for (let i = 0; i < size; i++) { - const len = util.random(0, 100); + const len = rand(0, 100); witness.push(random.randomBytes(len)); } @@ -125,11 +124,11 @@ function randomWitness(redeem) { } function randomInputScript(redeem) { - const size = util.random(1, 100); + const size = rand(1, 100); const script = new Script(); for (let i = 0; i < size; i++) { - const len = util.random(0, 100); + const len = rand(0, 100); script.pushData(random.randomBytes(len)); } @@ -140,7 +139,7 @@ function randomInputScript(redeem) { } function randomOutputScript() { - const size = util.random(1, 10000); + const size = rand(1, 10000); return Script.fromRaw(random.randomBytes(size)); } @@ -165,7 +164,7 @@ function isPushOnly(script) { } function randomPubkey() { - const len = util.random(0, 2) === 0 ? 33 : 65; + const len = rand(0, 2) === 0 ? 33 : 65; return Script.fromPubkey(random.randomBytes(len)); } @@ -174,12 +173,12 @@ function randomPubkeyhash() { } function randomMultisig() { - const n = util.random(1, 16); - const m = util.random(1, n); + const n = rand(1, 16); + const m = rand(1, n); const keys = []; for (let i = 0; i < n; i++) { - const len = util.random(0, 2) === 0 ? 33 : 65; + const len = rand(0, 2) === 0 ? 33 : 65; keys.push(random.randomBytes(len)); } @@ -199,13 +198,13 @@ function randomWitnessScripthash() { } function randomProgram() { - const version = util.random(0, 16); - const size = util.random(2, 41); + const version = rand(0, 16); + const size = rand(2, 41); return Script.fromProgram(version, random.randomBytes(size)); } function randomRedeem() { - switch (util.random(0, 5)) { + switch (rand(0, 5)) { case 0: return randomPubkey(); case 1: @@ -221,7 +220,7 @@ function randomRedeem() { } function randomScript() { - switch (util.random(0, 7)) { + switch (rand(0, 7)) { case 0: return randomPubkey(); case 1: @@ -299,7 +298,7 @@ function randomWitnessNestedContext() { } function randomContext() { - switch (util.random(0, 6)) { + switch (rand(0, 6)) { case 0: return randomPubkeyContext(); case 1: @@ -322,7 +321,7 @@ function fuzzSimple(flags) { for (;;) { if (++total % 1000 === 0) - util.log('Fuzzed %d scripts.', total); + console.log('Fuzzed %d scripts.', total); if (total % 500 === 0) tx = randomTX(); @@ -357,16 +356,16 @@ function fuzzSimple(flags) { if (isPushOnly(output)) continue; - util.log('Produced valid scripts:'); + console.log('Produced valid scripts:'); - util.log('Input:'); - util.log(input); + console.log('Input:'); + console.log(input); - util.log('Output:'); - util.log(output); + console.log('Output:'); + console.log(output); - util.log('Stack:'); - util.log(stack); + console.log('Stack:'); + console.log(stack); break; } @@ -378,7 +377,7 @@ function fuzzVerify(flags) { for (;;) { if (++total % 1000 === 0) - util.log('Fuzzed %d scripts.', total); + console.log('Fuzzed %d scripts.', total); if (total % 500 === 0) tx = randomTX(); @@ -415,16 +414,16 @@ function fuzzVerify(flags) { if (isPushOnly(output)) continue; - util.log('Produced valid scripts:'); + console.log('Produced valid scripts:'); - util.log('Input:'); - util.log(input); + console.log('Input:'); + console.log(input); - util.log('Witness:'); - util.log(witness); + console.log('Witness:'); + console.log(witness); - util.log('Output:'); - util.log(output); + console.log('Output:'); + console.log(output); break; } @@ -436,7 +435,7 @@ function fuzzLess(flags) { for (;;) { if (++total % 1000 === 0) - util.log('Fuzzed %d scripts.', total); + console.log('Fuzzed %d scripts.', total); if (total % 500 === 0) tx = randomTX(); @@ -469,20 +468,20 @@ function fuzzLess(flags) { assertConsensus(tx, ctx.output, flags, 'OK'); - util.log('Produced valid scripts:'); + console.log('Produced valid scripts:'); - util.log('Input:'); - util.log(ctx.input); + console.log('Input:'); + console.log(ctx.input); - util.log('Witness:'); - util.log(ctx.witness); + console.log('Witness:'); + console.log(ctx.witness); - util.log('Output:'); - util.log(ctx.output); + console.log('Output:'); + console.log(ctx.output); if (ctx.redeem) { - util.log('Redeem:'); - util.log(ctx.redeem); + console.log('Redeem:'); + console.log(ctx.redeem); } break; @@ -505,13 +504,17 @@ function main() { fuzzLess(flags); break; default: - util.log('Please select a mode:'); - util.log('simple, verify, less'); - util.log('Optional `--standard` flag.'); + console.log('Please select a mode:'); + console.log('simple, verify, less'); + console.log('Optional `--standard` flag.'); break; } } +function rand(min, max) { + return Math.floor(Math.random() * (max - min)) + min; +} + randomKey; randomSignature; diff --git a/scripts/gen.js b/scripts/gen.js index ecd40355e..4ebd4f156 100644 --- a/scripts/gen.js +++ b/scripts/gen.js @@ -1,8 +1,6 @@ 'use strict'; -const util = require('../lib/utils/util'); const consensus = require('../lib/protocol/consensus'); -const encoding = require('../lib/utils/encoding'); const TX = require('../lib/primitives/tx'); const Block = require('../lib/primitives/block'); const Script = require('../lib/script/script'); @@ -32,7 +30,7 @@ function createGenesisBlock(options) { version: 1, inputs: [{ prevout: { - hash: encoding.NULL_HASH, + hash: consensus.ZERO_HASH, index: 0xffffffff }, script: Script() @@ -51,8 +49,8 @@ function createGenesisBlock(options) { const block = new Block({ version: options.version, - prevBlock: encoding.NULL_HASH, - merkleRoot: tx.hash('hex'), + prevBlock: consensus.ZERO_HASH, + merkleRoot: tx.hash(), time: options.time, bits: options.bits, nonce: options.nonce, @@ -100,18 +98,31 @@ const regtest = createGenesisBlock({ key: Buffer.from('040184710fa689ad5023690c80f3a49c8f13f8d45b8c857fbcbc8bc4a8e4d3eb4b10f4d4604fa08dce601aaf0f470216fe1b51850b4acf21b179c45070ac7b03a9', 'hex') }); -util.log(main); -util.log(''); -util.log(testnet); -util.log(''); -util.log(regtest); -util.log(''); -util.log('main hash: %s', main.rhash()); -util.log('main raw: %s', main.toRaw().toString('hex')); -util.log(''); -util.log('testnet hash: %s', testnet.rhash()); -util.log('testnet raw: %s', testnet.toRaw().toString('hex')); -util.log(''); -util.log('regtest hash: %s', regtest.rhash()); -util.log('regtest raw: %s', regtest.toRaw().toString('hex')); -util.log(''); +console.log(main); +console.log(''); +console.log(testnet); +console.log(''); +console.log(regtest); +console.log(''); +console.log(segnet3); +console.log(''); +console.log(segnet4); +console.log(''); +console.log(''); +console.log('main hash: %s', main.rhash()); +console.log('main raw: %s', main.toRaw().toString('hex')); +console.log(''); +console.log('testnet hash: %s', testnet.rhash()); +console.log('testnet raw: %s', testnet.toRaw().toString('hex')); +console.log(''); +console.log('regtest hash: %s', regtest.rhash()); +console.log('regtest raw: %s', regtest.toRaw().toString('hex')); +console.log(''); +console.log('segnet3 hash: %s', segnet3.rhash()); +console.log('segnet3 raw: %s', segnet3.toRaw().toString('hex')); +console.log(''); +console.log('segnet4 hash: %s', segnet4.rhash()); +console.log('segnet4 raw: %s', segnet4.toRaw().toString('hex')); +console.log(''); +console.log('btcd simnet hash: %s', btcd.rhash()); +console.log('btcd simnet raw: %s', btcd.toRaw().toString('hex')); diff --git a/scripts/reverseHex.js b/scripts/reverseHex.js index bcd6e5b43..0f5c51566 100644 --- a/scripts/reverseHex.js +++ b/scripts/reverseHex.js @@ -1,62 +1,65 @@ var checkpoints = { // Mainnet - // 100000: 'c0fb4e2c6b96e6871a2acfd12299fdbb95c71b0887b03469fb2e313e75a749fd', - // 200000: 'a939b5c2f2a9d7430f833a9ffc1ecd7eb433add3671677809cbf6b8bdc1430ec', - // 300000: '77ec9ac7085e351acd58a103ba3c8b207e9ae3afcc4f348fece25908129040fc', - // 400000: '7ece07533f8ef9366121865219d2cf3d2ec4c4cd1cebc8f80493a66ebc0e2c67', - // 500000: 'fec49695e922d3842feb28bbdd4219ab4704a61083a4d4b87229f27626ba1b0c', - // 600000: '6592f6f6ad67d29c07ce993c8167ed2d4713976b9de701b0edb95da67e5b4fd2', - // 700000: 'ca10962bb25a4184bb3cb6432efaa9b4bb0c743e1130527443912d87c360459a', - // 800000: 'af8cb31e71ba877d0d8c95c580d6980cd14fe605c81e79a35b4acba9980c9307', - // 900000: '8debc9619c2e204fdeaa996b9917b6b95b965f4a2635981cb626d8780c4a43af', - // 1000000: 'b68bd62db4374adf1ba626edc90580aca90e4cad53c1920b697d45c4df0994fd', - // 1100000: 'a6d51d335b9a382ca8683c849b819fe5f9cec43a5f51beaa7e2f44457bf5ed16', - // 1200000: 'f2161c0da5ab6af306bbb8b4da3d5b768d17cd93178d27d414b77d9eb4398f08', - // 1300000: '3751cc16a35f91c61a6e9fcdf3cbdbbf631a92cad2221a9e36e6fb8833162b91', - // 1400000: '705aa04e6bc0a9ecac1fb2e37eccb3cccc0ae1e3f1f558cbe9791cad1fca565f', - // 1500000: '30961f1a45cd0b6a7d58c28769d6b77babc068d09db4934ff9aa4922ee0551f2', - // 1600000: '5b0d109822240bcdaa73c89c21c974563405f3b10949a8ed56d8e6c67e97eeca', - // 1700000: '684818f8534c7f8ef941ce02e7c34cb44d7ede5cace36cb6f2d70532283dbafe', - // 1800000: 'cc560b5cf3ff15d57cc1d34fff827eceee69cd068a949cc8f84fc4a541980390', - // 1900000: '60912e3b6cd7aee10705a683d6653e5862800a5b8e4ea12ea587238db2c75b6d', - // 2000000: '4a8cb5ca397b7c92c270ccfa9139ffb93f7c5b9515e52486c635c7a1dee9d221', - // 2100000: 'ceb4ddb46a9385096bd161bd84b68af29670958d942c7ef85c95ec796cce5f01', - // 2200000: 'ae999db0281b4b0238da15f116b929fc332cb1b19629d4355958220508787558', - // 2300000: 'a0ac730ed5961f231d7312f3d910fa8707e91455405c0232486c974651a10838', - // 2400000: '2dfb9b767428dbd02995074635e3aa54c044fe130dde85dd5e9a533d329be1d2', - // 2500000: '478b768a963bd4357df2a94f633774ffb0e586726c10fedff79bf0293b21512e', - // 2600000: 'a35101676beabefa360239eeab2b99c05a458f2b6bd614154fc740792ed1ba77', - // 2700000: '9e9f0e1b199aa6a506ce359928ca4fbde61b69368bacefa37f8bafa967f48443', - // 2800000: 'fcbc1e5553bd81177477578dd94f567fd4f06442ef94cf04b67d5c343ff1c1bd', - // 2900000: '4e30a7ac9b5f24f57c98f603c41c849ba1eb41fec17fa7d48fa9fda81fd59305', - // 3000000: '5ad3a302e3b1c681f0177411384ea03ee595a80a530c23a61f22839fae948e7f' + 100000: 'c0fb4e2c6b96e6871a2acfd12299fdbb95c71b0887b03469fb2e313e75a749fd', + 200000: 'a939b5c2f2a9d7430f833a9ffc1ecd7eb433add3671677809cbf6b8bdc1430ec', + 300000: '77ec9ac7085e351acd58a103ba3c8b207e9ae3afcc4f348fece25908129040fc', + 400000: '7ece07533f8ef9366121865219d2cf3d2ec4c4cd1cebc8f80493a66ebc0e2c67', + 500000: 'fec49695e922d3842feb28bbdd4219ab4704a61083a4d4b87229f27626ba1b0c', + 600000: '6592f6f6ad67d29c07ce993c8167ed2d4713976b9de701b0edb95da67e5b4fd2', + 700000: 'ca10962bb25a4184bb3cb6432efaa9b4bb0c743e1130527443912d87c360459a', + 800000: 'af8cb31e71ba877d0d8c95c580d6980cd14fe605c81e79a35b4acba9980c9307', + 900000: '8debc9619c2e204fdeaa996b9917b6b95b965f4a2635981cb626d8780c4a43af', + 1000000: 'b68bd62db4374adf1ba626edc90580aca90e4cad53c1920b697d45c4df0994fd', + 1100000: 'a6d51d335b9a382ca8683c849b819fe5f9cec43a5f51beaa7e2f44457bf5ed16', + 1200000: 'f2161c0da5ab6af306bbb8b4da3d5b768d17cd93178d27d414b77d9eb4398f08', + 1300000: '3751cc16a35f91c61a6e9fcdf3cbdbbf631a92cad2221a9e36e6fb8833162b91', + 1400000: '705aa04e6bc0a9ecac1fb2e37eccb3cccc0ae1e3f1f558cbe9791cad1fca565f', + 1500000: '30961f1a45cd0b6a7d58c28769d6b77babc068d09db4934ff9aa4922ee0551f2', + 1600000: '5b0d109822240bcdaa73c89c21c974563405f3b10949a8ed56d8e6c67e97eeca', + 1700000: '684818f8534c7f8ef941ce02e7c34cb44d7ede5cace36cb6f2d70532283dbafe', + 1800000: 'cc560b5cf3ff15d57cc1d34fff827eceee69cd068a949cc8f84fc4a541980390', + 1900000: '60912e3b6cd7aee10705a683d6653e5862800a5b8e4ea12ea587238db2c75b6d', + 2000000: '4a8cb5ca397b7c92c270ccfa9139ffb93f7c5b9515e52486c635c7a1dee9d221', + 2100000: 'ceb4ddb46a9385096bd161bd84b68af29670958d942c7ef85c95ec796cce5f01', + 2200000: 'ae999db0281b4b0238da15f116b929fc332cb1b19629d4355958220508787558', + 2300000: 'a0ac730ed5961f231d7312f3d910fa8707e91455405c0232486c974651a10838', + 2400000: '2dfb9b767428dbd02995074635e3aa54c044fe130dde85dd5e9a533d329be1d2', + 2500000: '478b768a963bd4357df2a94f633774ffb0e586726c10fedff79bf0293b21512e', + 2600000: 'a35101676beabefa360239eeab2b99c05a458f2b6bd614154fc740792ed1ba77', + 2700000: '9e9f0e1b199aa6a506ce359928ca4fbde61b69368bacefa37f8bafa967f48443', + 2800000: 'fcbc1e5553bd81177477578dd94f567fd4f06442ef94cf04b67d5c343ff1c1bd', + 2900000: '4e30a7ac9b5f24f57c98f603c41c849ba1eb41fec17fa7d48fa9fda81fd59305', + 3000000: '5ad3a302e3b1c681f0177411384ea03ee595a80a530c23a61f22839fae948e7f', + 3100000: 'db81ac8d65c4ab92c160e8e7102f547813e588b54d92b8e2a6e7a22bd1bd07fd', + 3200000: '8cb2be5dc584757a669fe2b12cb1648e08417a5f27a8ed2552493a547a7aa3e9', + 3300000: '96cd2d29f76b32c01c7fc63862a5df636e1c6c2418fb2ec8ab57f8f530baf23d', + 3400000: 'dded264a4c804036a05c0ceb4829bb1e2eaea99253958dc69f44f0dc190c533e', + 3500000: '25aa1145525023f7d81f662cb2edfcc6446ab05ec467522207f1203202674401', + 3550000: 'e2a58b96cb04a34a29558242e4454d0abd6304d0da25879fb3081653b3967c24' + // Testnet - 2056: 'd3334db071731beaa651f10624c2fea1a5e8c6f9e50f0e602f86262938374148', - 10000: '9068605f2cd1ec5206f835e337ef53e1e5ac1a0ccb834923064d3b184afd3879', - 50000: '629a1c3f692fd1e4c9a711d85a31a0861e6b551edf993c8c5b20619130f30c07', - 75000: 'a26dbb93c13b1238a406afe378abd3b9a2235dcd0aec0cc16e2d61eb17b4b65a', - 100000: '915b751b06e655fe224f06341cf58cafd90fd0d0f5169dc06f3e1b0276cf1388', - 150000: '2d9ae7c05a962ff10a5904d7d78f36d37cb15b789f7dab580cf09807d5afe1c5', - 200000: 'ac569d0dee91b8e9dd1a428ae7f9be9bbe82b646f9dca89604871c13c220b39f', - 225000: '2735e9c90420163275908fb09a52dd5b5373e13fadaf98f56dc0c5c969147112', - 245000: 'd3bfc53d65985948f09c3c722966981db1755dfdfcd671db60356bfabeb99c15' + // 2056: 'd3334db071731beaa651f10624c2fea1a5e8c6f9e50f0e602f86262938374148', + // 10000: '9068605f2cd1ec5206f835e337ef53e1e5ac1a0ccb834923064d3b184afd3879', + // 50000: '629a1c3f692fd1e4c9a711d85a31a0861e6b551edf993c8c5b20619130f30c07', + // 75000: 'a26dbb93c13b1238a406afe378abd3b9a2235dcd0aec0cc16e2d61eb17b4b65a', + // 100000: '915b751b06e655fe224f06341cf58cafd90fd0d0f5169dc06f3e1b0276cf1388', + // 150000: '2d9ae7c05a962ff10a5904d7d78f36d37cb15b789f7dab580cf09807d5afe1c5', + // 200000: 'ac569d0dee91b8e9dd1a428ae7f9be9bbe82b646f9dca89604871c13c220b39f', + // 225000: '2735e9c90420163275908fb09a52dd5b5373e13fadaf98f56dc0c5c969147112', + // 245000: 'd3bfc53d65985948f09c3c722966981db1755dfdfcd671db60356bfabeb99c15' + // 300000: '244d7e4bb021c6899a1de24bac2ff3d76c815a0244b461d098cdb78e5e5202f6', + // 400000: '0705bd4fa97fca140153b7fafc8f54ca08d1c0bb16b67d563ff8cd34fffc1457', + // 500000: 'ffe2fbe9bc744c26f180906190c53231d186696d682f8dd0b50a7cb3c3dd8789', + // 600000: '1d002a640eb5ad7ceb5c9f84b7495220199d329cda5de126df85e4bc26f5353e', + // 686000: '9ba779f1ab8120d69164b86c41125b7db32c6f3bfee8c0c853f2eedfad5f41ba' } var reversedCheckpoints = {} for (var height in checkpoints){ var checkStr = checkpoints[height]; - var strArray = checkStr.match(/.{1,2}/g); - - var reversedArray = strArray.reverse(); - - var builtStr = ""; - - for (var str of reversedArray){ - builtStr += str; - } - reversedCheckpoints[height] = builtStr; + reversedCheckpoints[height] = Buffer.from(checkStr, 'hex').reverse().toString('hex'); } console.log(reversedCheckpoints) \ No newline at end of file diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 0cf7fd240..b8ca728c7 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -18,4 +18,4 @@ parts: source: . plugin: nodejs build-packages: [python, gcc] - node-engine: 7.9.0 \ No newline at end of file + node-engine: 8.0.0 diff --git a/test/address-test.js b/test/address-test.js index 69ee6621d..76d3f549e 100644 --- a/test/address-test.js +++ b/test/address-test.js @@ -5,7 +5,8 @@ const Address = require('../lib/primitives/address'); const Script = require('../lib/script/script'); -const assert = require('./util/assert'); +const assert = require('bsert'); +const nodejsUtil = require('util'); describe('Address', function() { it('should match mainnet p2pkh address', () => { @@ -13,7 +14,7 @@ describe('Address', function() { const p2pkh = Buffer.from(raw, 'hex'); const addr = Address.fromPubkeyhash(p2pkh); const expectedAddr = 'LfwofMun44rKk766Vcft6v9a7XPWBSppiq'; - assert.strictEqual(addr.toString(), expectedAddr); + assert.strictEqual(addr.toString('main'), expectedAddr); }); it('should match mainnet p2pkh address 2', () => { @@ -21,15 +22,15 @@ describe('Address', function() { const p2pkh = Buffer.from(raw, 'hex'); const addr = Address.fromPubkeyhash(p2pkh); const expectedAddr = 'LLawTSFdhuPKGSotr5KpwS4rGdkB7J9vLq'; - assert.strictEqual(addr.toString(), expectedAddr); + assert.strictEqual(addr.toString('main'), expectedAddr); }); it('should match testnet p2pkh address', () => { const raw = '78b316a08647d5b77283e512d3603f1f1c8de68f'; const p2pkh = Buffer.from(raw, 'hex'); - const addr = Address.fromPubkeyhash(p2pkh, 'testnet'); + const addr = Address.fromPubkeyhash(p2pkh); const expectedAddr = 'mrX9vMRYLfVy1BnZbc5gZjuyaqH3ZW2ZHz'; - assert.strictEqual(addr.toString(), expectedAddr); + assert.strictEqual(addr.toString('testnet'), expectedAddr); }); it('should handle wrong p2pkh hash length', () => { @@ -59,7 +60,7 @@ describe('Address', function() { const script = Script.fromRaw(p2sh); const addr = Address.fromScript(script); const expectedAddr = '3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC'; - assert.strictEqual(addr.toString(), expectedAddr); + assert.strictEqual(addr.toString('main'), expectedAddr); }); it('should match mainnet p2sh address obtained from script hash', () => { @@ -67,7 +68,7 @@ describe('Address', function() { const p2sh = Buffer.from(raw, 'hex'); const addr = Address.fromScripthash(p2sh); const expectedAddr = '3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC'; - assert.strictEqual(addr.toString(), expectedAddr); + assert.strictEqual(addr.toString('main'), expectedAddr); }); it('should match mainnet p2sh address obtained from script 2', () => { @@ -75,15 +76,15 @@ describe('Address', function() { const p2sh = Buffer.from(raw, 'hex'); const addr = Address.fromScripthash(p2sh); const expectedAddr = '3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8'; - assert.strictEqual(addr.toString(), expectedAddr); + assert.strictEqual(addr.toString('main'), expectedAddr); }); it('should match testnet p2sh address', () => { const raw = 'c579342c2c4c9220205e2cdc285617040c924a0a'; const p2sh = Buffer.from(raw, 'hex'); - const addr = Address.fromScripthash(p2sh, 'testnet'); + const addr = Address.fromScripthash(p2sh); const expectedAddr = '2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n'; - assert.strictEqual(addr.toString(), expectedAddr); + assert.strictEqual(addr.toString('testnet'), expectedAddr); }); it('should match mainnet segwit p2wpkh v0 address', () => { @@ -91,7 +92,7 @@ describe('Address', function() { const p2wpkh = Buffer.from(raw, 'hex'); const addr = Address.fromWitnessPubkeyhash(p2wpkh); const expectedAddr = 'ltc1qw508d6qejxtdg4y5r3zarvary0c5xw7kgmn4n9'; - assert.strictEqual(addr.toString(), expectedAddr); + assert.strictEqual(addr.toString('main'), expectedAddr); }); it('should match mainnet segwit p2pwsh v0 address', () => { @@ -101,16 +102,16 @@ describe('Address', function() { + '6c985678cd4d27a1' + 'b8c6329604903262', 'hex'); const addr = Address.fromWitnessScripthash(p2wpkh); - assert.strictEqual(addr.toString(), + assert.strictEqual(addr.toString('main'), 'ltc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qmu8tk5'); }); it('should match testnet segwit p2wpkh v0 address', () => { const raw = '751e76e8199196d454941c45d1b3a323f1433bd6'; const p2wpkh = Buffer.from(raw, 'hex'); - const addr = Address.fromWitnessPubkeyhash(p2wpkh, 'testnet'); + const addr = Address.fromWitnessPubkeyhash(p2wpkh); const expectedAddr = 'tltc1qw508d6qejxtdg4y5r3zarvary0c5xw7klfsuq0'; - assert.strictEqual(addr.toString(), expectedAddr); + assert.strictEqual(addr.toString('testnet'), expectedAddr); }); it('should match testnet segwit p2pwsh v0 address', () => { @@ -119,8 +120,8 @@ describe('Address', function() { + '04bd19203356da13' + '6c985678cd4d27a1' + 'b8c6329604903262', 'hex'); - const addr = Address.fromWitnessScripthash(p2wpkh, 'testnet'); - assert.strictEqual(addr.toString(), + const addr = Address.fromWitnessScripthash(p2wpkh); + assert.strictEqual(addr.toString('testnet'), 'tltc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qsnr4fp'); }); @@ -130,56 +131,64 @@ describe('Address', function() { + '21b2a187905e5266' + '362b99d5e91c6ce2' + '4d165dab93e86433', 'hex'); - const addr = Address.fromWitnessScripthash(p2wpkh, 'testnet'); - assert.strictEqual(addr.toString(), + const addr = Address.fromWitnessScripthash(p2wpkh); + assert.strictEqual(addr.toString('testnet'), 'tltc1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesu9tmgm'); }); it('should handle invalid segwit hrp', () => { const addr = 'tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty'; - assert.throws(() => Address.fromString(addr)); + assert.throws(() => Address.fromString(addr, 'main')); }); it('should handle invalid segwit checksum', () => { const addr = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5'; - assert.throws(() => Address.fromString(addr)); + assert.throws(() => Address.fromString(addr, 'main')); }); it('should handle invalid segwit version', () => { const addr = 'BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2'; - assert.throws(() => Address.fromString(addr)); + assert.throws(() => Address.fromString(addr, 'main')); }); it('should handle invalid segwit program length', () => { const addr = 'bc1rw5uspcuh'; - assert.throws(() => Address.fromString(addr)); + assert.throws(() => Address.fromString(addr, 'main')); }); it('should handle invalid segwit program length 2', () => { const addr = 'bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw5' + '08d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90'; - assert.throws(() => Address.fromString(addr)); + assert.throws(() => Address.fromString(addr, 'main')); }); it('should handle invalid segwit program length for witness v0', () => { const addr = 'tb1pw508d6qejxtdg4y5r3zarqfsj6c3'; - assert.throws(() => Address.fromString(addr)); + assert.throws(() => Address.fromString(addr, 'main')); }); it('should handle segwit mixed case', () => { const addr = 'tb1qrp33g0q5c5txsp9arysrx4k6z' + 'dkfs4nce4xj0gdcccefvpysxf3q0sL5k7'; - assert.throws(() => Address.fromString(addr)); + assert.throws(() => Address.fromString(addr, 'main')); }); it('should handle segwit zero padding of more than 4 bits', () => { const addr = 'tb1pw508d6qejxtdg4y5r3zarqfsj6c3'; - assert.throws(() => Address.fromString(addr)); + assert.throws(() => Address.fromString(addr, 'main')); }); it('should handle segwit non-zero padding in 8-to-5 conversion', () => { const addr = 'tb1qrp33g0q5c5txsp9arysrx4k6' + 'zdkfs4nce4xj0gdcccefvpysxf3pjxtptv'; - assert.throws(() => Address.fromString(addr)); + assert.throws(() => Address.fromString(addr, 'main')); + }); + + it('should inspect', () => { + const obj = new Address(); + const fmt = nodejsUtil.format(obj); + assert(typeof fmt === 'string'); + assert(fmt.includes('Address')); + assert(fmt.includes('str=')); }); }); diff --git a/test/aes-test.js b/test/aes-test.js deleted file mode 100644 index 32c547efa..000000000 --- a/test/aes-test.js +++ /dev/null @@ -1,49 +0,0 @@ -/* eslint-env mocha */ -/* eslint prefer-arrow-callback: "off" */ - -'use strict'; - -const assert = require('./util/assert'); -const aes = require('../lib/crypto/aes'); - -const key = Buffer.from( - '3a0c0bf669694ac7685e6806eeadee8e56c9b9bd22c3caa81c718ed4bbf809a1', - 'hex'); - -const iv = Buffer.from('6dd26d9045b73c377a9ed2ffeca72ffd', 'hex'); - -describe('AES', function() { - it('should encrypt and decrypt with 2 blocks', () => { - const data = Buffer.from( - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - 'hex'); - - const expected = Buffer.from('' - + '83de502a9c83112ca6383f2214a892a0cdad5ab2b3e192e' - + '9921ddb126b25262c41f1dcff4d67ccfb40e4116e5a4569c1', - 'hex'); - - const ciphertext = aes.encipher(data, key, iv); - assert.bufferEqual(ciphertext, expected); - - const plaintext = aes.decipher(ciphertext, key, iv); - assert.bufferEqual(plaintext, data); - }); - - it('should encrypt and decrypt with uneven blocks', () => { - const data = Buffer.from( - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855010203', - 'hex'); - - const expected = Buffer.from('' - + '83de502a9c83112ca6383f2214a892a0cdad5ab2b3e192e9' - + '921ddb126b25262c5211801019a30c0c6f795296923e0af8', - 'hex'); - - const ciphertext = aes.encipher(data, key, iv); - assert.bufferEqual(ciphertext, expected); - - const plaintext = aes.decipher(ciphertext, key, iv); - assert.bufferEqual(plaintext, data); - }); -}); diff --git a/test/bech32-test.js b/test/bech32-test.js index 6b00dfca8..2f8924ea3 100644 --- a/test/bech32-test.js +++ b/test/bech32-test.js @@ -26,19 +26,11 @@ 'use strict'; -const assert = require('./util/assert'); -const bech32 = require('../lib/utils/bech32'); +const assert = require('bsert'); const Address = require('../lib/primitives/address'); -const validChecksums = [ - 'A12UEL5L', - 'an83characterlonghumanreadablepartthatcontains' - + 'thenumber1andtheexcludedcharactersbio1tt5tgs', - 'abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw', - '11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' - + 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j', - 'split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w' -]; +// see https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki +// for test vectors, they include both the valid and invalid addresses const validAddresses = [ [ @@ -91,41 +83,29 @@ const validAddresses = [ ]; const invalidAddresses = [ + // invalid hrp 'tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty', + // invalid checksum 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5', + // invalid witness version 'BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2', + // invalid program length 'bc1rw5uspcuh', + // invalid program length 'bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d' + '6qejxtdg4y5r3zarvary0c5xw7kw5rljs90', + // invalid program length for witness version 0 'BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P', + // mixed case 'tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7', + // zero padding of more than 4 bits 'tb1pw508d6qejxtdg4y5r3zarqfsj6c3', - 'tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv' + // non zero padding in 8 to 5 conversion + 'tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv', + // empty data section + 'bc1gmk9yu' ]; -function fromAddress(hrp, addr) { - const dec = bech32.decode(addr); - - if (dec.hrp !== hrp) - throw new Error('Invalid bech32 prefix or data length.'); - - if (dec.version === 0 && dec.hash.length !== 20 && dec.hash.length !== 32) - throw new Error('Malformed witness program.'); - - return { - version: dec.version, - program: dec.hash - }; -} - -function toAddress(hrp, version, program) { - const ret = bech32.encode(hrp, version, program); - - fromAddress(hrp, ret); - - return ret; -} - function createProgram(version, program) { const data = Buffer.allocUnsafe(2 + program.length); data[0] = version ? version + 0x80 : 0; @@ -135,62 +115,22 @@ function createProgram(version, program) { } describe('Bech32', function() { - for (const addr of validChecksums) { - it(`should have valid checksum for ${addr}`, () => { - assert(bech32.deserialize(addr)); - }); - } - - for (const [addr, script] of validAddresses) { - it(`should have valid address for ${addr}`, () => { - let hrp = 'ltc'; - let ret = null; - - try { - ret = fromAddress(hrp, addr); - } catch (e) { - ret = null; - } - - if (ret === null) { - hrp = 'tltc'; - try { - ret = fromAddress(hrp, addr); - } catch (e) { - ret = null; - } - } - - assert(ret !== null); - - const output = createProgram(ret.version, ret.program); - assert.bufferEqual(output, script); - - const recreate = toAddress(hrp, ret.version, ret.program); - assert.strictEqual(recreate, addr.toLowerCase()); - }); - } - - for (const addr of invalidAddresses) { - it(`should have invalid address for ${addr}`, () => { - assert.throws(() => fromAddress('ltc', addr)); - assert.throws(() => fromAddress('tltc', addr)); - }); - } - for (const [addr, script] of validAddresses) { it(`should have valid address for ${addr}`, () => { let ret = null; + let network = null; try { - ret = Address.fromBech32(addr, 'main'); + network = 'main'; + ret = Address.fromBech32(addr, network); } catch (e) { ret = null; } if (ret === null) { try { - ret = Address.fromBech32(addr, 'testnet'); + network = 'testnet'; + ret = Address.fromBech32(addr, network); } catch (e) { ret = null; } @@ -201,7 +141,7 @@ describe('Bech32', function() { const output = createProgram(ret.version, ret.hash); assert.bufferEqual(output, script); - const recreate = ret.toBech32(); + const recreate = ret.toBech32(network); assert.strictEqual(recreate, addr.toLowerCase()); }); } diff --git a/test/bip150-test.js b/test/bip150-test.js deleted file mode 100644 index b78c1587e..000000000 --- a/test/bip150-test.js +++ /dev/null @@ -1,238 +0,0 @@ -/* eslint-env mocha */ -/* eslint prefer-arrow-callback: "off" */ - -'use strict'; - -const assert = require('./util/assert'); -const secp256k1 = require('../lib/crypto/secp256k1'); -const BIP150 = require('../lib/net/bip150'); -const BIP151 = require('../lib/net/bip151'); - -const db = new BIP150.AuthDB(); -const ck = secp256k1.generatePrivateKey(); -const sk = secp256k1.generatePrivateKey(); - -db.addAuthorized(secp256k1.publicKeyCreate(ck, true)); -db.addKnown('127.0.0.2', secp256k1.publicKeyCreate(sk, true)); - -const client = new BIP151(); -const server = new BIP151(); - -client.bip150 = new BIP150(client, '127.0.0.2', true, db, ck); -server.bip150 = new BIP150(server, '127.0.0.1', false, db, sk); - -function payload() { - return Buffer.from('deadbeef', 'hex'); -} - -describe('BIP150', function() { - it('should do encinit', () => { - const init = server.toEncinit(); - client.encinit(init.publicKey, init.cipher); - - const init2 = client.toEncinit(); - server.encinit(init2.publicKey, init2.cipher); - - assert(!client.handshake); - assert(!server.handshake); - }); - - it('should do encack', () => { - client.encack(server.toEncack().publicKey); - server.encack(client.toEncack().publicKey); - assert(client.handshake); - assert(server.handshake); - }); - - it('should have completed ECDH handshake', () => { - assert(client.isReady()); - assert(server.isReady()); - assert(client.handshake); - assert(server.handshake); - }); - - it('should do BIP150 handshake', () => { - const challenge = client.bip150.toChallenge(); - const reply = server.bip150.challenge(challenge.hash); - const propose = client.bip150.reply(reply); - const challenge2 = server.bip150.propose(propose); - const reply2 = client.bip150.challenge(challenge2); - const result = server.bip150.reply(reply2); - - assert(!result); - assert(client.bip150.auth); - assert(server.bip150.auth); - }); - - it('should encrypt payload from client to server', () => { - const packet = client.packet('fake', payload()); - - let emitted = false; - server.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - server.feed(packet); - - assert(emitted); - }); - - it('should encrypt payload from server to client', () => { - const packet = server.packet('fake', payload()); - - let emitted = false; - client.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - client.feed(packet); - - assert(emitted); - }); - - it('should encrypt payload from client to server (2)', () => { - const packet = client.packet('fake', payload()); - - let emitted = false; - server.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - server.feed(packet); - - assert(emitted); - }); - - it('should encrypt payload from server to client (2)', () => { - const packet = server.packet('fake', payload()); - - let emitted = false; - client.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - client.feed(packet); - - assert(emitted); - }); - - it('client should rekey', () => { - const bytes = client.output.processed; - let rekeyed = false; - - client.once('rekey', () => { - rekeyed = true; - const packet = client.packet('encack', client.toRekey().toRaw()); - let emitted = false; - server.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'encack'); - server.encack(body); - }); - server.feed(packet); - assert(emitted); - }); - - // Force a rekey after 1gb processed. - client.maybeRekey({ length: 1024 * (1 << 20) }); - - assert(rekeyed); - - // Reset so as not to mess up - // the symmetry of client and server. - client.output.processed = bytes + 33 + 31; - }); - - it('should encrypt payload from client to server after rekey', () => { - const packet = client.packet('fake', payload()); - - let emitted = false; - server.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - server.feed(packet); - - assert(emitted); - }); - - it('should encrypt payload from server to client after rekey', () => { - const packet = server.packet('fake', payload()); - - let emitted = false; - client.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - client.feed(packet); - - assert(emitted); - }); - - it('should encrypt payload from client to server after rekey (2)', () => { - const packet = client.packet('fake', payload()); - - let emitted = false; - server.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - server.feed(packet); - - assert(emitted); - }); - - it('should encrypt payload from server to client after rekey (2)', () => { - const packet = server.packet('fake', payload()); - - let emitted = false; - client.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - client.feed(packet); - - assert(emitted); - }); - - it('should encrypt payloads both ways asynchronously', () => { - const spacket = server.packet('fake', payload()); - const cpacket = client.packet('fake', payload()); - - let cemitted = false; - client.once('packet', (cmd, body) => { - cemitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - let semitted = false; - server.once('packet', (cmd, body) => { - semitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - client.feed(spacket); - server.feed(cpacket); - - assert(cemitted); - assert(semitted); - }); -}); diff --git a/test/bip151-test.js b/test/bip151-test.js deleted file mode 100644 index 42e7e86e1..000000000 --- a/test/bip151-test.js +++ /dev/null @@ -1,213 +0,0 @@ -/* eslint-env mocha */ -/* eslint prefer-arrow-callback: "off" */ - -'use strict'; - -const assert = require('./util/assert'); -const BIP151 = require('../lib/net/bip151'); - -const client = new BIP151(); -const server = new BIP151(); - -function payload() { - return Buffer.from('deadbeef', 'hex'); -} - -describe('BIP151', function() { - it('should do encinit', () => { - let init = server.toEncinit(); - client.encinit(init.publicKey, init.cipher); - - init = client.toEncinit(); - server.encinit(init.publicKey, init.cipher); - - assert(!client.handshake); - assert(!server.handshake); - }); - - it('should do encack', () => { - client.encack(server.toEncack().publicKey); - server.encack(client.toEncack().publicKey); - assert(client.handshake); - assert(server.handshake); - }); - - it('should have completed ECDH handshake', () => { - assert(client.isReady()); - assert(server.isReady()); - assert(client.handshake); - assert(server.handshake); - }); - - it('should encrypt payload from client to server', () => { - const packet = client.packet('fake', payload()); - - let emitted = false; - server.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - server.feed(packet); - - assert(emitted); - }); - - it('should encrypt payload from server to client', () => { - const packet = server.packet('fake', payload()); - - let emitted = false; - client.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - client.feed(packet); - - assert(emitted); - }); - - it('should encrypt payload from client to server (2)', () => { - const packet = client.packet('fake', payload()); - - let emitted = false; - server.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - server.feed(packet); - - assert(emitted); - }); - - it('should encrypt payload from server to client (2)', () => { - const packet = server.packet('fake', payload()); - - let emitted = false; - client.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - client.feed(packet); - - assert(emitted); - }); - - it('client should rekey', () => { - const bytes = client.output.processed; - let rekeyed = false; - - client.once('rekey', () => { - rekeyed = true; - const packet = client.packet('encack', client.toRekey().toRaw()); - let emitted = false; - server.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'encack'); - server.encack(body); - }); - server.feed(packet); - assert(emitted); - }); - - // Force a rekey after 1gb processed. - client.maybeRekey({ length: 1024 * (1 << 20) }); - - assert(rekeyed); - - // Reset so as not to mess up - // the symmetry of client and server. - client.output.processed = bytes + 33 + 31; - }); - - it('should encrypt payload from client to server after rekey', () => { - const packet = client.packet('fake', payload()); - - let emitted = false; - server.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - server.feed(packet); - - assert(emitted); - }); - - it('should encrypt payload from server to client after rekey', () => { - const packet = server.packet('fake', payload()); - - let emitted = false; - client.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - client.feed(packet); - - assert(emitted); - }); - - it('should encrypt payload from client to server after rekey (2)', () => { - const packet = client.packet('fake', payload()); - - let emitted = false; - server.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - server.feed(packet); - - assert(emitted); - }); - - it('should encrypt payload from server to client after rekey (2)', () => { - const packet = server.packet('fake', payload()); - - let emitted = false; - client.once('packet', (cmd, body) => { - emitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - client.feed(packet); - - assert(emitted); - }); - - it('should encrypt payloads both ways asynchronously', () => { - const spacket = server.packet('fake', payload()); - const cpacket = client.packet('fake', payload()); - - let cemitted = false; - client.once('packet', (cmd, body) => { - cemitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - let semitted = false; - server.once('packet', (cmd, body) => { - semitted = true; - assert.strictEqual(cmd, 'fake'); - assert.bufferEqual(body, payload()); - }); - - client.feed(spacket); - server.feed(cpacket); - - assert(cemitted); - assert(semitted); - }); -}); diff --git a/test/bip70-test.js b/test/bip70-test.js deleted file mode 100644 index 1e63ce2e5..000000000 --- a/test/bip70-test.js +++ /dev/null @@ -1,215 +0,0 @@ -/* eslint-env mocha */ -/* eslint prefer-arrow-callback: "off" */ - -'use strict'; - -const assert = require('./util/assert'); -const util = require('../lib/utils/util'); -const bip70 = require('../lib/bip70'); -const Address = require('../lib/primitives/address'); -const x509 = bip70.x509; - -const tests = require('./data/bip70.json'); - -tests.valid = Buffer.from(tests.valid, 'hex'); -tests.invalid = Buffer.from(tests.invalid, 'hex'); -tests.untrusted = Buffer.from(tests.untrusted, 'hex'); -tests.ack = Buffer.from(tests.ack, 'hex'); -tests.ca = { - crt: Buffer.from(tests.ca.crt, 'hex'), - priv: Buffer.from(tests.ca.priv, 'hex'), - pub: Buffer.from(tests.ca.pub, 'hex') -}; - -x509.allowUntrusted = true; -x509.trusted.clear(); - -x509.verifyTime = function() { - return true; -}; - -function testRequest(data) { - const req = bip70.PaymentRequest.fromRaw(data); - - assert.strictEqual(req.pkiType, 'x509+sha256'); - assert(req.pkiData); - assert(req.getChain()); - assert(req.paymentDetails); - assert(req.paymentDetails.memo.length !== 0); - assert(req.paymentDetails.paymentUrl.length !== 0); - - assert.bufferEqual(req.toRaw(), data); - assert(req.verify()); -} - -describe('BIP70', function() { - it('should parse and verify a payment request', () => { - testRequest(tests.valid); - testRequest(tests.invalid); - testRequest(tests.untrusted); - }); - - it('should verify cert chain', () => { - const req1 = bip70.PaymentRequest.fromRaw(tests.valid); - - assert.strictEqual(req1.version, 1); - assert.strictEqual(req1.getChain().length, 4); - assert.strictEqual(req1.paymentDetails.paymentUrl, - 'https://test.bitpay.com/i/CMWpuFsjgmQ2ZLiyGfcF1W'); - assert.strictEqual(req1.paymentDetails.network, 'test'); - assert.strictEqual(req1.paymentDetails.time, 1408645830); - assert.strictEqual(req1.paymentDetails.expires, 1408646730); - assert.strictEqual(req1.paymentDetails.outputs.length, 1); - assert(!req1.paymentDetails.merchantData); - assert(req1.paymentDetails.isExpired()); - - assert(req1.verifyChain()); - - const req2 = bip70.PaymentRequest.fromRaw(tests.invalid); - - assert.strictEqual(req2.version, 1); - assert.strictEqual(req2.getChain().length, 3); - assert.strictEqual(req2.paymentDetails.paymentUrl, - 'https://bitpay.com/i/PAQtNxX7KL8BtJBnfXyTaH'); - assert.strictEqual(req2.paymentDetails.network, 'main'); - assert.strictEqual(req2.paymentDetails.time, 1442409238); - assert.strictEqual(req2.paymentDetails.expires, 1442410138); - assert.strictEqual(req2.paymentDetails.outputs.length, 1); - assert.strictEqual(req2.paymentDetails.merchantData.length, 76); - assert(req2.paymentDetails.getData('json')); - assert(req2.paymentDetails.isExpired()); - - assert(req2.verifyChain()); - - req2.paymentDetails.setData({foo:1}, 'json'); - assert.strictEqual(req2.paymentDetails.merchantData.length, 9); - assert.deepStrictEqual(req2.paymentDetails.getData('json'), {foo:1}); - assert(!req2.verify()); - - const req3 = bip70.PaymentRequest.fromRaw(tests.untrusted); - - assert.strictEqual(req3.version, -1); - assert.strictEqual(req3.getChain().length, 2); - assert.strictEqual(req3.paymentDetails.paymentUrl, - 'https://www.coinbase.com/rp/55f9ca703d5d80008c0001f4'); - assert.strictEqual(req3.paymentDetails.network, null); - assert.strictEqual(req3.paymentDetails.time, 1442433682); - assert.strictEqual(req3.paymentDetails.expires, 1442434548); - assert.strictEqual(req3.paymentDetails.outputs.length, 1); - assert.strictEqual(req3.paymentDetails.merchantData.length, 32); - assert.strictEqual(req3.paymentDetails.getData('utf8'), - 'bb79b6f2310e321bd3b1d929edbeb358'); - assert(req3.paymentDetails.isExpired()); - - assert(req3.verifyChain()); - }); - - it('should fail to verify cert signatures when enforcing trust', () => { - x509.allowUntrusted = false; - - const req1 = bip70.PaymentRequest.fromRaw(tests.valid); - assert(!req1.verifyChain()); - - const req2 = bip70.PaymentRequest.fromRaw(tests.invalid); - assert(!req2.verifyChain()); - - const req3 = bip70.PaymentRequest.fromRaw(tests.untrusted); - assert(!req3.verifyChain()); - }); - - it('should verify cert signatures once root cert is added', () => { - const req1 = bip70.PaymentRequest.fromRaw(tests.valid); - x509.setTrust([req1.getChain().pop()]); - assert(req1.verifyChain()); - - const req2 = bip70.PaymentRequest.fromRaw(tests.untrusted); - assert(!req2.verifyChain()); - }); - - it('should still fail to verify cert signatures for invalid', () => { - const req = bip70.PaymentRequest.fromRaw(tests.invalid); - assert(!req.verifyChain()); - }); - - it('should get chain and ca for request', () => { - const req = bip70.PaymentRequest.fromRaw(tests.valid); - assert.strictEqual(req.getChain().length, 4); - assert.strictEqual(req.getCA().name, - 'Go Daddy Class 2 Certification Authority'); - }); - - it('should validate untrusted once again', () => { - const req1 = bip70.PaymentRequest.fromRaw(tests.untrusted); - x509.setTrust([req1.getChain().pop()]); - - const req2 = bip70.PaymentRequest.fromRaw(tests.untrusted); - assert(req2.verifyChain()); - assert.strictEqual(req2.getCA().name, - 'DigiCert SHA2 Extended Validation Server CA'); - }); - - it('should parse a payment ack', () => { - const ack = bip70.PaymentACK.fromRaw(tests.ack); - assert.strictEqual(ack.memo.length, 95); - assert.strictEqual(ack.memo, 'Transaction received by BitPay.' - + ' Invoice will be marked as paid if the transaction is confirmed.'); - assert.bufferEqual(ack.toRaw(), tests.ack); - }); - - it('should create a payment request, sign, and verify', () => { - const req = new bip70.PaymentRequest({ - version: 25, - paymentDetails: { - network: 'testnet', - paymentUrl: 'http://bcoin.io/payment', - memo: 'foobar', - time: util.now(), - expires: util.now() + 3600, - outputs: [ - { value: 10000, address: new Address() }, - { value: 50000, address: new Address() } - ], - merchantData: { foo: 'bar' } - } - }); - - assert.strictEqual(req.pkiType, null); - assert(!req.pkiData); - assert.strictEqual(req.getChain().length, 0); - assert(req.paymentDetails); - assert(req.paymentDetails.memo.length !== 0); - assert(req.paymentDetails.paymentUrl.length !== 0); - assert.deepStrictEqual(req.paymentDetails.getData('json'), {foo:'bar'}); - - assert.strictEqual(req.version, 25); - assert.strictEqual(req.paymentDetails.paymentUrl, - 'http://bcoin.io/payment'); - assert.strictEqual(req.paymentDetails.network, 'testnet'); - assert(req.paymentDetails.time <= util.now()); - assert.strictEqual(req.paymentDetails.expires, - req.paymentDetails.time + 3600); - assert.strictEqual(req.paymentDetails.outputs.length, 2); - assert(req.paymentDetails.merchantData); - assert(!req.paymentDetails.isExpired()); - - assert(!req.pkiData); - req.setChain([tests.ca.crt]); - req.sign(tests.ca.priv); - - assert(req.pkiData); - assert.strictEqual(req.pkiType, 'x509+sha256'); - assert.strictEqual(req.getChain().length, 1); - - assert(req.verify()); - assert(!req.verifyChain()); - - testRequest(req.toRaw()); - - x509.setTrust([tests.ca.crt]); - assert(req.verifyChain()); - assert.strictEqual(req.getCA().name, 'JJs CA'); - - req.version = 24; - assert(!req.verify()); - }); -}); diff --git a/test/block-test.js b/test/block-test.js index 349514bed..96380a77e 100644 --- a/test/block-test.js +++ b/test/block-test.js @@ -3,14 +3,15 @@ 'use strict'; -const assert = require('./util/assert'); +const assert = require('bsert'); const common = require('./util/common'); -const Bloom = require('../lib/utils/bloom'); +const {BloomFilter} = require('bfilter'); +const {BufferMap} = require('buffer-map'); const Block = require('../lib/primitives/block'); const MerkleBlock = require('../lib/primitives/merkleblock'); const consensus = require('../lib/protocol/consensus'); const Script = require('../lib/script/script'); -const encoding = require('../lib/utils/encoding'); +const nodejsUtil = require('util'); const bip152 = require('../lib/net/bip152'); const CompactBlock = bip152.CompactBlock; const TXRequest = bip152.TXRequest; @@ -28,6 +29,9 @@ const compact426884 = common.readCompact('compact426884'); const block898352 = common.readBlock('block898352'); const compact898352 = common.readCompact('compact898352'); +// Small SegWit block test vector +const block482683 = common.readBlock('block482683'); + // Sigops counting test vectors // Format: [name, sigops, weight] const sigopsVectors = [ @@ -53,7 +57,7 @@ describe('Block', function() { const tree = block.getTree(); assert.strictEqual(tree.matches.length, 2); - assert.strictEqual(block.hash('hex'), + assert.strictEqual(block.hash().toString('hex'), '8cc72c02a958de5a8b35a23bb7e3bced8bf840cc0a4e1c820000000000000000'); assert.strictEqual(block.rhash(), '0000000000000000821c4e0acc40f88bedbce3b73ba2358b5ade58a9022cc78c'); @@ -95,23 +99,34 @@ describe('Block', function() { it('should parse JSON', () => { const [block1] = block300025.getBlock(); const block2 = Block.fromJSON(block1.toJSON()); - assert.strictEqual(block2.hash('hex'), + assert.strictEqual(block2.hash().toString('hex'), '8cc72c02a958de5a8b35a23bb7e3bced8bf840cc0a4e1c820000000000000000'); assert.strictEqual(block2.rhash(), '0000000000000000821c4e0acc40f88bedbce3b73ba2358b5ade58a9022cc78c'); - assert.strictEqual(block2.merkleRoot, block2.createMerkleRoot('hex')); + assert.bufferEqual(block2.merkleRoot, block2.createMerkleRoot()); + }); + + it('should inspect a block with a witness commitment', () => { + const [block] = block482683.getBlock(); + const fmt = nodejsUtil.format(block); + assert(typeof fmt === 'string'); + assert(fmt.includes('Block')); + assert(fmt.includes('commitmentHash')); }); it('should create a merkle block', () => { - const filter = Bloom.fromRate(1000, 0.01, Bloom.flags.NONE); + const filter = BloomFilter.fromRate(1000, 0.01, BloomFilter.flags.NONE); - const item1 = '8e7445bbb8abd4b3174d80fa4c409fea6b94d96b'; - const item2 = '047b00000078da0dca3b0ec2300c00d0ab4466ed10' + const item1 = Buffer.from( + '8e7445bbb8abd4b3174d80fa4c409fea6b94d96b', + 'hex'); + + const item2 = Buffer.from('047b00000078da0dca3b0ec2300c00d0ab4466ed10' + 'e763272c6c9ca052972c69e3884a9022084215e2eef' - + '0e6f781656b5d5a87231cd4349e534b6dea55ad4ff55e'; + + '0e6f781656b5d5a87231cd4349e534b6dea55ad4ff55e', 'hex'); - filter.add(item1, 'hex'); - filter.add(item2, 'hex'); + filter.add(item1); + filter.add(item2); const [block1] = block300025.getBlock(); const block2 = MerkleBlock.fromBlock(block1, filter); @@ -158,7 +173,7 @@ describe('Block', function() { it('should fail with a bad merkle root', () => { const [block] = block300025.getBlock(); const merkleRoot = block.merkleRoot; - block.merkleRoot = encoding.NULL_HASH; + block.merkleRoot = consensus.ZERO_HASH; block.refresh(); assert(!block.verifyPOW()); const [, reason] = block.checkBody(); @@ -172,7 +187,7 @@ describe('Block', function() { it('should fail on merkle block with a bad merkle root', () => { const [block] = merkle300025.getBlock(); const merkleRoot = block.merkleRoot; - block.merkleRoot = encoding.NULL_HASH; + block.merkleRoot = consensus.ZERO_HASH; block.refresh(); assert(!block.verifyPOW()); const [, reason] = block.checkBody(); @@ -221,11 +236,11 @@ describe('Block', function() { assert.bufferEqual(cblock1.toRaw(), compact426884.getRaw()); assert.bufferEqual(cblock2.toRaw(), compact426884.getRaw()); - const map = new Map(); + const map = new BufferMap(); for (let i = 1; i < block.txs.length; i++) { const tx = block.txs[i]; - map.set(tx.hash('hex'), { tx }); + map.set(tx.hash(), { tx }); } const full = cblock1.fillMempool(false, { map }); @@ -247,11 +262,11 @@ describe('Block', function() { assert.bufferEqual(cblock1.toRaw(), compact426884.getRaw()); assert.bufferEqual(cblock2.toRaw(), compact426884.getRaw()); - const map = new Map(); + const map = new BufferMap(); for (let i = 1; i < ((block.txs.length + 1) >>> 1); i++) { const tx = block.txs[i]; - map.set(tx.hash('hex'), { tx }); + map.set(tx.hash(), { tx }); } const full = cblock1.fillMempool(false, { map }); @@ -259,7 +274,7 @@ describe('Block', function() { const rawReq = cblock1.toRequest().toRaw(); const req = TXRequest.fromRaw(rawReq); - assert.strictEqual(req.hash, cblock1.hash('hex')); + assert.bufferEqual(req.hash, cblock1.hash()); const rawRes = TXResponse.fromBlock(block, req).toRaw(); const res = TXResponse.fromRaw(rawRes); @@ -285,11 +300,11 @@ describe('Block', function() { assert.strictEqual(cblock1.sid(block.txs[1].hash()), 125673511480291); - const map = new Map(); + const map = new BufferMap(); for (let i = 1; i < block.txs.length; i++) { const tx = block.txs[i]; - map.set(tx.hash('hex'), { tx }); + map.set(tx.hash(), { tx }); } const full = cblock1.fillMempool(false, { map }); @@ -313,11 +328,11 @@ describe('Block', function() { assert.strictEqual(cblock1.sid(block.txs[1].hash()), 125673511480291); - const map = new Map(); + const map = new BufferMap(); for (let i = 1; i < ((block.txs.length + 1) >>> 1); i++) { const tx = block.txs[i]; - map.set(tx.hash('hex'), { tx }); + map.set(tx.hash(), { tx }); } const full = cblock1.fillMempool(false, { map }); @@ -325,7 +340,7 @@ describe('Block', function() { const rawReq = cblock1.toRequest().toRaw(); const req = TXRequest.fromRaw(rawReq); - assert.strictEqual(req.hash, cblock1.hash('hex')); + assert.bufferEqual(req.hash, cblock1.hash()); assert.deepStrictEqual(req.indexes, [5, 6, 7, 8, 9]); const rawRes = TXResponse.fromBlock(block, req).toRaw(); @@ -360,4 +375,91 @@ describe('Block', function() { }); } } + + it('should deserialize with offset positions for txs (witness)', () => { + const [block] = block482683.getBlock(); + + const expected = [ + {offset: 81, size: 217}, + {offset: 298, size: 815}, + {offset: 1113, size: 192}, + {offset: 1305, size: 259}, + {offset: 1564, size: 223}, + {offset: 1787, size: 1223}, + {offset: 3010, size: 486}, + {offset: 3496, size: 665}, + {offset: 4161, size: 3176}, + {offset: 7337, size: 225}, + {offset: 7562, size: 1223}, + {offset: 8785, size: 503} + ]; + + assert.equal(expected.length, block.txs.length); + assert.equal(block.getSize(), expected.reduce((a, b) => a + b.size, 81)); + + for (let i = 0; i < block.txs.length; i++) { + const {offset, size} = block.txs[i].getPosition(); + + assert.strictEqual(offset, expected[i].offset); + assert.strictEqual(size, expected[i].size); + } + }); + + it('should serialize with offset positions for txs (witness)', () => { + const [block] = block482683.getBlock(); + + const expected = [ + {offset: 81, size: 217}, + {offset: 298, size: 815}, + {offset: 1113, size: 192}, + {offset: 1305, size: 259}, + {offset: 1564, size: 223}, + {offset: 1787, size: 1223}, + {offset: 3010, size: 486}, + {offset: 3496, size: 665}, + {offset: 4161, size: 3176}, + {offset: 7337, size: 225}, + {offset: 7562, size: 1223}, + {offset: 8785, size: 503} + ]; + + assert.equal(expected.length, block.txs.length); + assert.equal(block.getSize(), expected.reduce((a, b) => a + b.size, 81)); + + // Reset the offset for all transactions, and clear + // any cached values for the block. + block.refresh(true); + for (let i = 0; i < block.txs.length; i++) + assert.equal(block.txs[i]._offset, -1); + + // Serialize the block, as done before saving to disk. + const raw = block.toRaw(); + assert(raw); + + for (let i = 0; i < block.txs.length; i++) { + const {offset, size} = block.txs[i].getPosition(); + + assert.strictEqual(offset, expected[i].offset); + assert.strictEqual(size, expected[i].size); + } + }); + + it('should deserialize with offset positions for txs', () => { + const [block] = block300025.getBlock(); + + assert.equal(block.txs.length, 461); + + let expect = 83; + let total = 83; + + for (let i = 0; i < block.txs.length; i++) { + const {offset, size} = block.txs[i].getPosition(); + + assert.strictEqual(offset, expect); + expect += size; + total += size; + } + + assert.equal(total, 284231); + }); }); diff --git a/test/blockstore-test.js b/test/blockstore-test.js new file mode 100644 index 000000000..70d8ef839 --- /dev/null +++ b/test/blockstore-test.js @@ -0,0 +1,1249 @@ +/* eslint-env mocha */ +/* eslint prefer-arrow-callback: "off" */ + +'use strict'; + +const Logger = require('blgr'); +const bio = require('bufio'); +const assert = require('bsert'); +const common = require('./util/common'); +const {resolve} = require('path'); +const fs = require('bfile'); +const {rimraf, testdir} = require('./util/common'); +const random = require('bcrypto/lib/random'); + +const vectors = [ + common.readBlock('block300025'), + common.readBlock('block426884'), + common.readBlock('block898352') +]; + +const extra = [ + common.readBlock('block482683') +]; + +const undos = [ + common.readBlock('block300025'), + common.readBlock('block928816'), + common.readBlock('block928828'), + common.readBlock('block928831'), + common.readBlock('block928848'), + common.readBlock('block928849') +]; + +const { + AbstractBlockStore, + FileBlockStore, + LevelBlockStore +} = require('../lib/blockstore'); + +const layout = require('../lib/blockstore/layout'); +const {types} = require('../lib/blockstore/common'); + +const { + BlockRecord, + FileRecord +} = require('../lib/blockstore/records'); + +describe('BlockStore', function() { + describe('Abstract', function() { + let logger = null; + + function context(ctx) { + return {info: () => ctx}; + } + + beforeEach(() => { + logger = Logger.global; + Logger.global = {context}; + }); + + afterEach(() => { + Logger.global = logger; + }); + + it('construct with custom logger', async () => { + const store = new AbstractBlockStore({logger: {context}}); + assert(store.logger); + assert(store.logger.info); + assert.equal(store.logger.info(), 'blockstore'); + }); + + it('construct with default logger', async () => { + const store = new AbstractBlockStore(); + assert(store.logger); + assert(store.logger.info); + assert.equal(store.logger.info(), 'blockstore'); + }); + + it('has unimplemented base methods', async () => { + const methods = ['open', 'close', 'write', 'writeUndo', + 'read', 'readUndo', 'prune', 'pruneUndo', + 'has', 'hasUndo', 'ensure']; + + const store = new AbstractBlockStore(); + + for (const method of methods) { + assert(store[method]); + + let err = null; + try { + await store[method](); + } catch (e) { + err = e; + } + assert(err, `Expected unimplemented method ${method}.`); + assert.equal(err.message, 'Abstract method.'); + } + }); + }); + + describe('Records', function() { + describe('BlockRecord', function() { + function constructError(options) { + let err = null; + + try { + new BlockRecord({ + file: options.file, + position: options.position, + length: options.length + }); + } catch (e) { + err = e; + } + + assert(err); + } + + function toAndFromRaw(options) { + const rec1 = new BlockRecord(options); + assert.equal(rec1.file, options.file); + assert.equal(rec1.position, options.position); + assert.equal(rec1.length, options.length); + + const raw = rec1.toRaw(); + const rec2 = BlockRecord.fromRaw(raw); + assert.equal(rec2.file, options.file); + assert.equal(rec2.position, options.position); + assert.equal(rec2.length, options.length); + } + + it('construct with correct options', () => { + const rec = new BlockRecord({ + file: 12, + position: 23392, + length: 4194304 + }); + assert.equal(rec.file, 12); + assert.equal(rec.position, 23392); + assert.equal(rec.length, 4194304); + }); + + it('construct null record', () => { + const rec = new BlockRecord(); + assert.equal(rec.file, 0); + assert.equal(rec.position, 0); + assert.equal(rec.length, 0); + }); + + it('fail with signed number (file)', () => { + constructError({file: -1, position: 1, length: 1}); + }); + + it('fail with signed number (position)', () => { + constructError({file: 1, position: -1, length: 1}); + }); + + it('fail with signed number (length)', () => { + constructError({file: 1, position: 1, length: -1}); + }); + + it('fail with non-32-bit number (file)', () => { + constructError({file: Math.pow(2, 32), position: 1, length: 1}); + }); + + it('fail with non-32-bit number (position)', () => { + constructError({file: 1, position: Math.pow(2, 32), length: 1}); + }); + + it('fail with non-32-bit number (length)', () => { + constructError({file: 1, position: 1, length: Math.pow(2, 32)}); + }); + + it('construct with max 32-bit numbers', () => { + const max = Math.pow(2, 32) - 1; + + const rec = new BlockRecord({ + file: max, + position: max, + length: max + }); + + assert(rec); + assert.equal(rec.file, max); + assert.equal(rec.position, max); + assert.equal(rec.length, max); + }); + + it('serialize/deserialize file record (min)', () => { + toAndFromRaw({file: 0, position: 0, length: 0}); + }); + + it('serialize/deserialize file record', () => { + toAndFromRaw({file: 12, position: 23392, length: 4194304}); + }); + + it('serialize/deserialize file record (max)', () => { + const max = Math.pow(2, 32) - 1; + toAndFromRaw({file: max, position: max, length: max}); + }); + }); + + describe('FileRecord', function() { + function constructError(options) { + let err = null; + + try { + new FileRecord({ + blocks: options.blocks, + used: options.used, + length: options.length + }); + } catch (e) { + err = e; + } + + assert(err); + } + + function toAndFromRaw(options) { + const rec1 = new FileRecord(options); + assert.equal(rec1.blocks, options.blocks); + assert.equal(rec1.used, options.used); + assert.equal(rec1.length, options.length); + + const raw = rec1.toRaw(); + const rec2 = FileRecord.fromRaw(raw); + assert.equal(rec2.blocks, options.blocks); + assert.equal(rec2.used, options.used); + assert.equal(rec2.length, options.length); + } + + it('construct with correct options', () => { + const rec = new FileRecord({ + blocks: 1, + used: 4194304, + length: 20971520 + }); + assert.equal(rec.blocks, 1); + assert.equal(rec.used, 4194304); + assert.equal(rec.length, 20971520); + }); + + it('fail to with signed number (blocks)', () => { + constructError({blocks: -1, used: 1, length: 1}); + }); + + it('fail to with signed number (used)', () => { + constructError({blocks: 1, used: -1, length: 1}); + }); + + it('fail to with signed number (length)', () => { + constructError({blocks: 1, used: 1, length: -1}); + }); + + it('fail to with non-32-bit number (blocks)', () => { + constructError({blocks: Math.pow(2, 32), used: 1, length: 1}); + }); + + it('fail to with non-32-bit number (used)', () => { + constructError({blocks: 1, used: Math.pow(2, 32), length: 1}); + }); + + it('fail to with non-32-bit number (length)', () => { + constructError({blocks: 1, used: 1, length: Math.pow(2, 32)}); + }); + + it('serialize/deserialize block record (min)', () => { + toAndFromRaw({blocks: 0, used: 0, length: 0}); + }); + + it('serialize/deserialize block record', () => { + toAndFromRaw({blocks: 10, used: 4194304, length: 20971520}); + }); + + it('serialize/deserialize block record (max)', () => { + const max = Math.pow(2, 32) - 1; + toAndFromRaw({blocks: max, used: max, length: max}); + }); + }); + }); + + describe('FileBlockStore (Unit)', function() { + const location = '/tmp/.bcoin/blocks'; + let store = null; + + before(() => { + store = new FileBlockStore({ + location: location, + maxFileLength: 1024 + }); + }); + + describe('constructor', function() { + it('will pass options to super', () => { + const info = () => 'info'; + const logger = { + context: () => { + return {info}; + } + }; + + const store = new FileBlockStore({ + location: '/tmp/.bcoin/blocks', + maxFileLength: 1024, + logger: logger + }); + + assert.strictEqual(store.logger.info, info); + }); + + it('will error with invalid location', () => { + let err = null; + + try { + new FileBlockStore({ + location: 'tmp/.bcoin/blocks', + maxFileLength: 1024 + }); + } catch (e) { + err = e; + } + + assert(err); + assert.equal(err.message, 'Location not absolute.'); + }); + + it('will error with invalid max file length', () => { + let err = null; + + try { + new FileBlockStore({ + location: location, + maxFileLength: 'notanumber' + }); + } catch (e) { + err = e; + } + + assert(err); + assert.equal(err.message, 'Invalid max file length.'); + }); + }); + + describe('allocate', function() { + it('will fail with length above file max', async () => { + let err = null; + try { + await store.allocate(types.BLOCK, 1025); + } catch (e) { + err = e; + } + assert(err); + assert.equal(err.message, 'Block length above max file length.'); + }); + }); + + describe('filepath', function() { + it('will give correct path (0)', () => { + const filepath = store.filepath(types.BLOCK, 0); + assert.equal(filepath, '/tmp/.bcoin/blocks/blk00000.dat'); + }); + + it('will give correct path (1)', () => { + const filepath = store.filepath(types.BLOCK, 7); + assert.equal(filepath, '/tmp/.bcoin/blocks/blk00007.dat'); + }); + + it('will give correct path (2)', () => { + const filepath = store.filepath(types.BLOCK, 23); + assert.equal(filepath, '/tmp/.bcoin/blocks/blk00023.dat'); + }); + + it('will give correct path (3)', () => { + const filepath = store.filepath(types.BLOCK, 456); + assert.equal(filepath, '/tmp/.bcoin/blocks/blk00456.dat'); + }); + + it('will give correct path (4)', () => { + const filepath = store.filepath(types.BLOCK, 8999); + assert.equal(filepath, '/tmp/.bcoin/blocks/blk08999.dat'); + }); + + it('will give correct path (5)', () => { + const filepath = store.filepath(types.BLOCK, 99999); + assert.equal(filepath, '/tmp/.bcoin/blocks/blk99999.dat'); + }); + + it('will fail over max size', () => { + let err = null; + try { + store.filepath(types.BLOCK, 100000); + } catch (e) { + err = e; + } + + assert(err); + assert.equal(err.message, 'File number too large.'); + }); + + it('will give undo type', () => { + const filepath = store.filepath(types.UNDO, 99999); + assert.equal(filepath, '/tmp/.bcoin/blocks/blu99999.dat'); + }); + + it('will fail for unknown prefix', () => { + let err = null; + try { + store.filepath(0, 1234); + } catch (e) { + err = e; + } + + assert(err); + assert.equal(err.message, 'Unknown file prefix.'); + }); + }); + + describe('write', function() { + const write = fs.write; + const open = fs.open; + const close = fs.close; + let allocate = null; + let has = null; + + beforeEach(() => { + allocate = store.allocate; + has = store.db.has; + }); + + afterEach(() => { + // Restore stubbed methods. + fs.write = write; + fs.open = open; + fs.close = close; + store.allocate = allocate; + store.db.has = has; + }); + + it('will error if total magic bytes not written', async () => { + let err = null; + + store.allocate = () => { + return { + fileno: 20, + filerecord: { + used: 0 + }, + filepath: '/tmp/.bcoin/blocks/blk00020.dat' + }; + }; + store.db.has = () => false; + fs.open = () => 7; + fs.close = () => undefined; + fs.write = () => 0; + + try { + const hash = random.randomBytes(128); + const block = random.randomBytes(32); + await store.write(hash, block); + } catch (e) { + err = e; + } + + assert(err, 'Expected error.'); + assert.equal(err.message, 'Could not write block magic.'); + }); + + it('will error if total block bytes not written', async () => { + let err = 0; + + let called = 0; + store.allocate = () => { + return { + fileno: 20, + filerecord: { + used: 0 + }, + filepath: '/tmp/.bcoin/blocks/blk00020.dat' + }; + }; + store.db.has = () => false; + fs.open = () => 7; + fs.close = () => undefined; + fs.write = (fd, buffer, offset, length, position) => { + let written = 0; + + if (called === 0) + written = length; + + called += 1; + + return written; + }; + + try { + const hash = random.randomBytes(128); + const block = random.randomBytes(32); + await store.write(hash, block); + } catch (e) { + err = e; + } + + assert(err, 'Expected error.'); + assert.equal(err.message, 'Could not write block.'); + }); + + it('will close file if write throws', async () => { + let err = null; + let closed = null; + + store.allocate = () => { + return { + fileno: 20, + filerecord: { + used: 0 + }, + filepath: '/tmp/.bcoin/blocks/blk00020.dat' + }; + }; + store.db.has = () => false; + fs.open = () => 7; + fs.close = (fd) => { + closed = fd; + }; + fs.write = () => { + throw new Error('Test.'); + }; + + try { + const hash = random.randomBytes(128); + const block = random.randomBytes(32); + await store.write(hash, block); + } catch (e) { + err = e; + } + + assert(err, 'Expected error.'); + assert.equal(err.message, 'Test.'); + assert.equal(closed, 7); + }); + }); + + describe('read', function() { + const read = fs.read; + const open = fs.open; + const close = fs.close; + let get = null; + let raw = null; + + before(() => { + const record = new BlockRecord({ + file: 1, + position: 8, + length: 100 + }); + raw = record.toRaw(); + }); + + beforeEach(() => { + get = store.db.get; + }); + + afterEach(() => { + // Restore stubbed methods. + store.db.get = get; + fs.read = read; + fs.open = open; + fs.close = close; + }); + + it('will error if total read bytes not correct', async () => { + let err = null; + + store.db.get = () => raw; + fs.open = () => 7; + fs.close = () => undefined; + fs.read = () => 99; + + try { + const hash = random.randomBytes(128); + const block = random.randomBytes(32); + await store.read(hash, block); + } catch (e) { + err = e; + } + + assert(err, 'Expected error.'); + assert.equal(err.message, 'Wrong number of bytes read.'); + }); + + it('will close file if read throws', async () => { + let err = null; + let closed = null; + + store.db.get = () => raw; + fs.open = () => 7; + fs.close = (fd) => { + closed = fd; + }; + fs.read = () => { + throw new Error('Test.'); + }; + + try { + const hash = random.randomBytes(128); + const block = random.randomBytes(32); + await store.read(hash, block); + } catch (e) { + err = e; + } + + assert(err, 'Expected error.'); + assert.equal(err.message, 'Test.'); + assert.equal(closed, 7); + }); + }); + }); + + describe('FileBlockStore (Integration 1)', function() { + const location = testdir('blockstore'); + let store = null; + + beforeEach(async () => { + await rimraf(location); + + store = new FileBlockStore({ + location: location, + maxFileLength: 1024 + }); + + await store.ensure(); + await store.open(); + }); + + afterEach(async () => { + await store.close(); + }); + + after(async () => { + await rimraf(location); + }); + + it('will write and read a block', async () => { + const block1 = random.randomBytes(128); + const hash = random.randomBytes(32); + + await store.write(hash, block1); + + const block2 = await store.read(hash); + + assert.bufferEqual(block1, block2); + }); + + it('will write and read block undo coins', async () => { + const block1 = random.randomBytes(128); + const hash = random.randomBytes(32); + + await store.writeUndo(hash, block1); + + const block2 = await store.readUndo(hash); + + assert.bufferEqual(block1, block2); + }); + + it('will read a block w/ offset and length', async () => { + const block1 = random.randomBytes(128); + const hash = random.randomBytes(32); + + await store.write(hash, block1); + + const offset = 79; + const size = 15; + + const block2 = await store.read(hash, offset, size); + + assert.bufferEqual(block1.slice(offset, offset + size), block2); + }); + + it('will fail to read w/ out-of-bounds length', async () => { + const block1 = random.randomBytes(128); + const hash = random.randomBytes(32); + + await store.write(hash, block1); + + const offset = 79; + const size = 50; + + let err = null; + try { + await store.read(hash, offset, size); + } catch (e) { + err = e; + } + + assert(err); + assert.equal(err.message, 'Out-of-bounds read.'); + }); + + it('will allocate new files', async () => { + const blocks = []; + + for (let i = 0; i < 16; i++) { + const block = random.randomBytes(128); + const hash = random.randomBytes(32); + blocks.push({hash, block}); + await store.write(hash, block); + const block2 = await store.read(hash); + assert.bufferEqual(block2, block); + } + + const first = await fs.stat(store.filepath(types.BLOCK, 0)); + const second = await fs.stat(store.filepath(types.BLOCK, 1)); + const third = await fs.stat(store.filepath(types.BLOCK, 2)); + assert.equal(first.size, 952); + assert.equal(second.size, 952); + assert.equal(third.size, 272); + + const magic = (8 * 16); + const len = first.size + second.size + third.size - magic; + assert.equal(len, 128 * 16); + + for (let i = 0; i < 16; i++) { + const expect = blocks[i]; + const block = await store.read(expect.hash); + assert.bufferEqual(block, expect.block); + } + }); + + it('will allocate new files with block undo coins', async () => { + const blocks = []; + + for (let i = 0; i < 16; i++) { + const block = random.randomBytes(128); + const hash = random.randomBytes(32); + blocks.push({hash, block}); + await store.writeUndo(hash, block); + const block2 = await store.readUndo(hash); + assert.bufferEqual(block2, block); + } + + const first = await fs.stat(store.filepath(types.UNDO, 0)); + const second = await fs.stat(store.filepath(types.UNDO, 1)); + const third = await fs.stat(store.filepath(types.UNDO, 2)); + + const magic = (40 * 16); + const len = first.size + second.size + third.size - magic; + assert.equal(len, 128 * 16); + + for (let i = 0; i < 16; i++) { + const expect = blocks[i]; + const block = await store.readUndo(expect.hash); + assert.bufferEqual(block, expect.block); + } + }); + + it('will recover from interrupt during block write', async () => { + { + const block = random.randomBytes(128); + const hash = random.randomBytes(32); + await store.write(hash, block); + + const block2 = await store.read(hash); + assert.bufferEqual(block2, block); + } + + // Manually insert a partially written block to the + // end of file as would be the case of an untimely + // interrupted write of a block. The file record + // would not be updated to include the used bytes and + // thus this data should be overwritten. + { + const filepath = store.filepath(types.BLOCK, 0); + + const fd = await fs.open(filepath, 'a'); + + const bw = bio.write(8); + bw.writeU32(store.network.magic); + bw.writeU32(73); + const magic = bw.render(); + + const failblock = random.randomBytes(73); + + const mwritten = await fs.write(fd, magic, 0, 8); + const bwritten = await fs.write(fd, failblock, 0, 73); + + await fs.close(fd); + + assert.equal(mwritten, 8); + assert.equal(bwritten, 73); + } + + // Now check that this block has the correct position + // in the file and that it can be read correctly. + { + const block = random.randomBytes(128); + const hash = random.randomBytes(32); + await store.write(hash, block); + + const block2 = await store.read(hash); + assert.bufferEqual(block2, block); + } + }); + + it('will not write blocks at the same position', (done) => { + let err = null; + let finished = 0; + + for (let i = 0; i < 16; i++) { + const block = random.randomBytes(128); + const hash = random.randomBytes(32); + + // Accidentally don't use `await` and attempt to + // write multiple blocks in parallel and at the + // same file position. + (async () => { + try { + await store.write(hash, block); + } catch (e) { + err = e; + } finally { + finished += 1; + if (finished >= 16) { + assert(err); + assert(err.message, 'Already writing.'); + done(); + } + } + })(); + } + }); + + it('will not duplicate a block on disk', async () => { + const block = random.randomBytes(128); + const hash = random.randomBytes(32); + + const first = await store.write(hash, block); + assert.equal(first, true); + const second = await store.write(hash, block); + assert.equal(second, false); + + const pruned = await store.prune(hash); + assert.equal(pruned, true); + + assert.equal(await fs.exists(store.filepath(types.BLOCK, 0)), false); + }); + + it('will return null if block not found', async () => { + const hash = random.randomBytes(32); + const block = await store.read(hash); + assert.strictEqual(block, null); + }); + + it('will check if block exists (false)', async () => { + const hash = random.randomBytes(32); + const exists = await store.has(hash); + assert.strictEqual(exists, false); + }); + + it('will check if block exists (true)', async () => { + const block = random.randomBytes(128); + const hash = random.randomBytes(32); + await store.write(hash, block); + const exists = await store.has(hash); + assert.strictEqual(exists, true); + }); + + it('will check if block undo coins exists (false)', async () => { + const hash = random.randomBytes(32); + const exists = await store.hasUndo(hash); + assert.strictEqual(exists, false); + }); + + it('will check if block undo coins exists (true)', async () => { + const block = random.randomBytes(128); + const hash = random.randomBytes(32); + await store.writeUndo(hash, block); + const exists = await store.hasUndo(hash); + assert.strictEqual(exists, true); + }); + + it('will prune blocks', async () => { + const hashes = []; + for (let i = 0; i < 16; i++) { + const block = random.randomBytes(128); + const hash = random.randomBytes(32); + hashes.push(hash); + await store.write(hash, block); + } + + const first = await fs.stat(store.filepath(types.BLOCK, 0)); + const second = await fs.stat(store.filepath(types.BLOCK, 1)); + const third = await fs.stat(store.filepath(types.BLOCK, 2)); + + const magic = (8 * 16); + const len = first.size + second.size + third.size - magic; + assert.equal(len, 128 * 16); + + for (let i = 0; i < 16; i++) { + const pruned = await store.prune(hashes[i]); + assert.strictEqual(pruned, true); + } + + assert.equal(await fs.exists(store.filepath(types.BLOCK, 0)), false); + assert.equal(await fs.exists(store.filepath(types.BLOCK, 1)), false); + assert.equal(await fs.exists(store.filepath(types.BLOCK, 2)), false); + + for (let i = 0; i < 16; i++) { + const exists = await store.has(hashes[i]); + assert.strictEqual(exists, false); + } + + const exists = await store.db.has(layout.f.encode(types.BLOCK, 0)); + assert.strictEqual(exists, false); + }); + + it('will prune block undo coins', async () => { + const hashes = []; + for (let i = 0; i < 16; i++) { + const block = random.randomBytes(128); + const hash = random.randomBytes(32); + hashes.push(hash); + await store.writeUndo(hash, block); + } + + const first = await fs.stat(store.filepath(types.UNDO, 0)); + const second = await fs.stat(store.filepath(types.UNDO, 1)); + const third = await fs.stat(store.filepath(types.UNDO, 2)); + + const magic = (40 * 16); + const len = first.size + second.size + third.size - magic; + assert.equal(len, 128 * 16); + + for (let i = 0; i < 16; i++) { + const pruned = await store.pruneUndo(hashes[i]); + assert.strictEqual(pruned, true); + } + + assert.equal(await fs.exists(store.filepath(types.UNDO, 0)), false); + assert.equal(await fs.exists(store.filepath(types.UNDO, 1)), false); + assert.equal(await fs.exists(store.filepath(types.UNDO, 2)), false); + + for (let i = 0; i < 16; i++) { + const exists = await store.hasUndo(hashes[i]); + assert.strictEqual(exists, false); + } + + const exists = await store.db.has(layout.f.encode(types.UNDO, 0)); + assert.strictEqual(exists, false); + }); + }); + + describe('FileBlockStore (Integration 2)', function() { + const location = testdir('blockstore'); + let store = null; + + beforeEach(async () => { + await rimraf(location); + + store = new FileBlockStore({ + location: location, + maxFileLength: 1024 * 1024 + }); + + await store.ensure(); + await store.open(); + }); + + afterEach(async () => { + await store.close(); + }); + + after(async () => { + await rimraf(location); + }); + + it('will import from files (e.g. db corruption)', async () => { + const blocks = []; + + for (let i = 0; i < vectors.length; i++) { + const [block] = vectors[i].getBlock(); + const hash = block.hash(); + const raw = block.toRaw(); + + blocks.push({hash, block: raw}); + await store.write(hash, raw); + } + + await store.close(); + + await rimraf(resolve(location, './index')); + + store = new FileBlockStore({ + location: location, + maxFileLength: 1024 + }); + + await store.open(); + + for (let i = 0; i < vectors.length; i++) { + const expect = blocks[i]; + const block = await store.read(expect.hash); + assert.equal(block.length, expect.block.length); + assert.bufferEqual(block, expect.block); + } + }); + + it('will import from files after write interrupt', async () => { + const blocks = []; + + for (let i = 0; i < vectors.length; i++) { + const [block] = vectors[i].getBlock(); + const hash = block.hash(); + const raw = block.toRaw(); + + blocks.push({hash, block: raw}); + await store.write(hash, raw); + } + + await store.close(); + + assert.equal(await fs.exists(store.filepath(types.BLOCK, 0)), true); + assert.equal(await fs.exists(store.filepath(types.BLOCK, 1)), true); + assert.equal(await fs.exists(store.filepath(types.BLOCK, 2)), false); + + // Write partial block as would be the case in a + // block write interrupt. + const [partial] = extra[0].getBlock(); + { + // Include all of the header, but not the block. + let raw = partial.toRaw(); + const actual = raw.length; + const part = raw.length - 1; + raw = raw.slice(0, part); + + const filepath = store.filepath(types.BLOCK, 1); + + const fd = await fs.open(filepath, 'a'); + + const bw = bio.write(8); + bw.writeU32(store.network.magic); + bw.writeU32(actual); + const magic = bw.render(); + + const mwritten = await fs.write(fd, magic, 0, 8); + const bwritten = await fs.write(fd, raw, 0, part); + + await fs.close(fd); + + assert.equal(mwritten, 8); + assert.equal(bwritten, part); + } + + await rimraf(resolve(location, './index')); + + store = new FileBlockStore({ + location: location, + maxFileLength: 1024 + }); + + await store.open(); + + const incomplete = await store.read(partial.hash()); + assert(incomplete === null); + + for (let i = 0; i < vectors.length; i++) { + const expect = blocks[i]; + const block = await store.read(expect.hash); + assert.equal(block.length, expect.block.length); + assert.bufferEqual(block, expect.block); + } + }); + + it('will import undo blocks from files', async () => { + const blocks = []; + + for (let i = 0; i < undos.length; i++) { + const [block] = undos[i].getBlock(); + const raw = undos[i].undoRaw; + const hash = block.hash(); + + blocks.push({hash, block: raw}); + await store.writeUndo(hash, raw); + } + + await store.close(); + + await rimraf(resolve(location, './index')); + + store = new FileBlockStore({ + location: location, + maxFileLength: 1024 + }); + + await store.open(); + + for (let i = 0; i < undos.length; i++) { + const expect = blocks[i]; + const block = await store.readUndo(expect.hash); + assert.equal(block.length, expect.block.length); + assert.bufferEqual(block, expect.block); + } + }); + }); + + describe('LevelBlockStore', function() { + const location = testdir('blockstore'); + let store = null; + + beforeEach(async () => { + await rimraf(location); + + store = new LevelBlockStore({ + location: location + }); + + await store.ensure(); + await store.open(); + }); + + afterEach(async () => { + await store.close(); + }); + + after(async () => { + await rimraf(location); + }); + + it('will write and read a block', async () => { + const block1 = random.randomBytes(128); + const hash = random.randomBytes(32); + + await store.write(hash, block1); + + const block2 = await store.read(hash); + + assert.bufferEqual(block1, block2); + }); + + it('will write and read block undo coins', async () => { + const block1 = random.randomBytes(128); + const hash = random.randomBytes(32); + + await store.writeUndo(hash, block1); + + const block2 = await store.readUndo(hash); + + assert.bufferEqual(block1, block2); + }); + + it('will read a block w/ offset and length', async () => { + const block1 = random.randomBytes(128); + const hash = random.randomBytes(32); + + await store.write(hash, block1); + + const offset = 79; + const size = 15; + + const block2 = await store.read(hash, offset, size); + + assert.bufferEqual(block1.slice(offset, offset + size), block2); + }); + + it('will fail to read w/ out-of-bounds length', async () => { + const block1 = random.randomBytes(128); + const hash = random.randomBytes(32); + + await store.write(hash, block1); + + const offset = 79; + const size = 50; + + let err = null; + try { + await store.read(hash, offset, size); + } catch (e) { + err = e; + } + + assert(err); + assert.equal(err.message, 'Out-of-bounds read.'); + }); + + it('will check if block exists (false)', async () => { + const hash = random.randomBytes(32); + const exists = await store.has(hash); + assert.strictEqual(exists, false); + }); + + it('will check if block exists (true)', async () => { + const block = random.randomBytes(128); + const hash = random.randomBytes(32); + await store.write(hash, block); + const exists = await store.has(hash); + assert.strictEqual(exists, true); + }); + + it('will check if block undo coins exists (false)', async () => { + const hash = random.randomBytes(32); + const exists = await store.has(hash); + assert.strictEqual(exists, false); + }); + + it('will check if block undo coins exists (true)', async () => { + const block = random.randomBytes(128); + const hash = random.randomBytes(32); + await store.writeUndo(hash, block); + const exists = await store.hasUndo(hash); + assert.strictEqual(exists, true); + }); + + it('will prune blocks (true)', async () => { + const block = random.randomBytes(128); + const hash = random.randomBytes(32); + await store.write(hash, block); + const pruned = await store.prune(hash); + assert.strictEqual(pruned, true); + const block2 = await store.read(hash); + assert.strictEqual(block2, null); + }); + + it('will prune blocks (false)', async () => { + const hash = random.randomBytes(32); + const exists = await store.has(hash); + assert.strictEqual(exists, false); + const pruned = await store.prune(hash); + assert.strictEqual(pruned, false); + }); + + it('will prune block undo coins (true)', async () => { + const block = random.randomBytes(128); + const hash = random.randomBytes(32); + await store.writeUndo(hash, block); + const pruned = await store.pruneUndo(hash); + assert.strictEqual(pruned, true); + const block2 = await store.readUndo(hash); + assert.strictEqual(block2, null); + }); + + it('will prune block undo coins (false)', async () => { + const hash = random.randomBytes(32); + const exists = await store.hasUndo(hash); + assert.strictEqual(exists, false); + const pruned = await store.pruneUndo(hash); + assert.strictEqual(pruned, false); + }); + }); +}); diff --git a/test/bloom-test.js b/test/bloom-test.js deleted file mode 100644 index 34b8934d0..000000000 --- a/test/bloom-test.js +++ /dev/null @@ -1,183 +0,0 @@ -/* eslint-env mocha */ -/* eslint prefer-arrow-callback: "off" */ - -'use strict'; - -const assert = require('./util/assert'); -const Bloom = require('../lib/utils/bloom'); -const RollingFilter = require('../lib/utils/rollingfilter'); -const murmur3 = require('../lib/utils/murmur3'); - -function testMurmur(str, seed, expect, enc) { - if (!enc) - enc = 'ascii'; - - const data = Buffer.from(str, enc); - const hash = murmur3(data, seed); - - assert.strictEqual(hash, expect); -} - -describe('Bloom', function() { - this.timeout(20000); - - it('should do proper murmur3', () => { - testMurmur('', 0, 0); - testMurmur('', 0xfba4c795, 0x6a396f08); - testMurmur('00', 0xfba4c795, 0x2a101837); - testMurmur('hello world', 0, 0x5e928f0f); - - testMurmur('', 0x00000000, 0x00000000, 'hex'); - testMurmur('', 0xfba4c795, 0x6a396f08, 'hex'); - testMurmur('', 0xffffffff, 0x81f16f39, 'hex'); - - testMurmur('00', 0x00000000, 0x514e28b7, 'hex'); - testMurmur('00', 0xfba4c795, 0xea3f0b17, 'hex'); - testMurmur('ff', 0x00000000, 0xfd6cf10d, 'hex'); - - testMurmur('0011', 0x00000000, 0x16c6b7ab, 'hex'); - testMurmur('001122', 0x00000000, 0x8eb51c3d, 'hex'); - testMurmur('00112233', 0x00000000, 0xb4471bf8, 'hex'); - testMurmur('0011223344', 0x00000000, 0xe2301fa8, 'hex'); - testMurmur('001122334455', 0x00000000, 0xfc2e4a15, 'hex'); - testMurmur('00112233445566', 0x00000000, 0xb074502c, 'hex'); - testMurmur('0011223344556677', 0x00000000, 0x8034d2a0, 'hex'); - testMurmur('001122334455667788', 0x00000000, 0xb4698def, 'hex'); - }); - - it('should test and add stuff', () => { - const filter = new Bloom(512, 10, 156); - - filter.add('hello', 'ascii'); - assert(filter.test('hello', 'ascii')); - assert(!filter.test('hello!', 'ascii')); - assert(!filter.test('ping', 'ascii')); - - filter.add('hello!', 'ascii'); - assert(filter.test('hello!', 'ascii')); - assert(!filter.test('ping', 'ascii')); - - filter.add('ping', 'ascii'); - assert(filter.test('ping', 'ascii')); - }); - - it('should serialize to the correct format', () => { - const filter = new Bloom(952, 6, 3624314491, Bloom.flags.NONE); - const item1 = '8e7445bbb8abd4b3174d80fa4c409fea6b94d96b'; - const item2 = '047b00000078da0dca3b0ec2300c00d0ab4466ed10' - + 'e763272c6c9ca052972c69e3884a9022084215e2eef' - + '0e6f781656b5d5a87231cd4349e534b6dea55ad4ff55e'; - - const expected = Buffer.from('' - + '000000000000000000000000000000000000000000000000088004000000000000000' - + '000000000200000000000000000000000000000000800000000000000000002000000' - + '000000000000002000000000000000000000000000000000000000000040000200000' - + '0000000001000000800000080000000', - 'hex'); - - filter.add(item1, 'hex'); - filter.add(item2, 'hex'); - - assert.bufferEqual(filter.filter, expected); - }); - - it('should handle 1m ops with regular filter', () => { - const filter = Bloom.fromRate(210000, 0.00001, -1); - - filter.tweak = 0xdeadbeef; - - // ~1m operations - for (let i = 0; i < 1000; i++) { - const str = 'foobar' + i; - let j = i; - filter.add(str, 'ascii'); - do { - const str = 'foobar' + j; - assert(filter.test(str, 'ascii')); - assert(!filter.test(str + '-', 'ascii')); - } while (j--); - } - }); - - it('should handle 1m ops with rolling filter', () => { - const filter = new RollingFilter(210000, 0.00001); - - filter.tweak = 0xdeadbeef; - - // ~1m operations - for (let i = 0; i < 1000; i++) { - const str = 'foobar' + i; - let j = i; - filter.add(str, 'ascii'); - do { - const str = 'foobar' + j; - assert(filter.test(str, 'ascii')); - assert(!filter.test(str + '-', 'ascii')); - } while (j--); - } - }); - - it('should handle rolling generations', () => { - const filter = new RollingFilter(50, 0.00001); - - filter.tweak = 0xdeadbeee; - - for (let i = 0; i < 25; i++) { - const str = 'foobar' + i; - let j = i; - filter.add(str, 'ascii'); - do { - const str = 'foobar' + j; - assert(filter.test(str, 'ascii')); - assert(!filter.test(str + '-', 'ascii')); - } while (j--); - } - - for (let i = 25; i < 50; i++) { - const str = 'foobar' + i; - let j = i; - filter.add(str, 'ascii'); - do { - const str = 'foobar' + j; - assert(filter.test(str, 'ascii')); - assert(!filter.test(str + '-', 'ascii')); - } while (j--); - } - - for (let i = 50; i < 75; i++) { - const str = 'foobar' + i; - let j = i; - filter.add(str, 'ascii'); - do { - const str = 'foobar' + j; - assert(filter.test(str, 'ascii')); - assert(!filter.test(str + '-', 'ascii')); - } while (j--); - } - - for (let i = 75; i < 100; i++) { - const str = 'foobar' + i; - let j = i; - filter.add(str, 'ascii'); - do { - const str = 'foobar' + j; - assert(filter.test(str, 'ascii')); - assert(!filter.test(str + '-', 'ascii')); - } while (j-- > 25); - assert(!filter.test('foobar 24', 'ascii')); - } - - for (let i = 100; i < 125; i++) { - const str = 'foobar' + i; - let j = i; - filter.add(str, 'ascii'); - do { - const str = 'foobar' + j; - assert(filter.test(str, 'ascii')); - assert(!filter.test(str + '-', 'ascii')); - } while (j-- > 50); - } - - assert(!filter.test('foobar 49', 'ascii')); - }); -}); diff --git a/test/chachapoly-test.js b/test/chachapoly-test.js deleted file mode 100644 index 27beb23c1..000000000 --- a/test/chachapoly-test.js +++ /dev/null @@ -1,235 +0,0 @@ -/* eslint-env mocha */ -/* eslint prefer-arrow-callback: "off" */ - -'use strict'; - -const assert = require('./util/assert'); -const ChaCha20 = require('../lib/crypto/chacha20'); -const Poly1305 = require('../lib/crypto/poly1305'); -const AEAD = require('../lib/crypto/aead'); - -function testChaCha(options) { - const key = Buffer.from(options.key, 'hex'); - const nonce = Buffer.from(options.nonce, 'hex'); - const plain = Buffer.from(options.plain, 'hex'); - const ciphertext = Buffer.from(options.ciphertext, 'hex'); - const counter = options.counter; - - const ctx1 = new ChaCha20(); - ctx1.init(key, nonce, counter); - const plainenc = Buffer.from(plain); - ctx1.encrypt(plainenc); - assert.bufferEqual(plainenc, ciphertext); - - const ctx2 = new ChaCha20(); - ctx2.init(key, nonce, counter); - ctx2.encrypt(ciphertext); - assert.bufferEqual(plain, ciphertext); -} - -function testAEAD(options) { - const plain = Buffer.from(options.plain, 'hex'); - const aad = Buffer.from(options.aad, 'hex'); - const key = Buffer.from(options.key, 'hex'); - const nonce = Buffer.from(options.nonce, 'hex'); - const pk = Buffer.from(options.pk, 'hex'); - const ciphertext = Buffer.from(options.ciphertext, 'hex'); - const tag = Buffer.from(options.tag, 'hex'); - - const ctx1 = new AEAD(); - ctx1.init(key, nonce); - assert.strictEqual(ctx1.chacha20.getCounter(), 1); - assert.bufferEqual(ctx1.polyKey, pk); - ctx1.aad(aad); - const plainenc = Buffer.from(plain); - ctx1.encrypt(plainenc); - assert.bufferEqual(plainenc, ciphertext); - assert.bufferEqual(ctx1.finish(), tag); - - const ctx2 = new AEAD(); - ctx2.init(key, nonce); - assert.strictEqual(ctx2.chacha20.getCounter(), 1); - assert.bufferEqual(ctx2.polyKey, pk); - ctx2.aad(aad); - ctx2.decrypt(ciphertext); - assert.bufferEqual(ciphertext, plain); - assert.bufferEqual(ctx2.finish(), tag); -} - -describe('ChaCha20 / Poly1305 / AEAD', function() { - it('should perform chacha20', () => { - testChaCha({ - key: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', - nonce: '000000000000004a00000000', - plain: '' - + '4c616469657320616e642047656e746c656d656e206f6620746865206' - + '36c617373206f66202739393a204966204920636f756c64206f6666657220796' - + 'f75206f6e6c79206f6e652074697020666f7220746865206675747572652c207' - + '3756e73637265656e20776f756c642062652069742e', - ciphertext: '' - + '6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afcc' - + 'fd9fae0bf91b65c5524733ab8f593dabcd62b3571639d624e65152ab' - + '8f530c359f0861d807ca0dbf500d6a6156a38e088a22b65e52bc514d' - + '16ccf806818ce91ab77937365af90bbf74a35be6b40b8eedf2785e42874d', - counter: 1 - }); - }); - - it('should perform chacha20', () => { - testChaCha({ - key: '0000000000000000000000000000000000000000000000000000000000000000', - nonce: '000000000000000000000000', - plain: '' - + '0000000000000000000000000000000000000000000000000000000000000000' - + '0000000000000000000000000000000000000000000000000000000000000000', - ciphertext: '' - + '76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b77' - + '0dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669' - + 'b2ee6586', - counter: 0 - }); - }); - - it('should perform chacha20', () => { - testChaCha({ - key: '0000000000000000000000000000000000000000000000000000000000000001', - nonce: '000000000000000000000002', - plain: '' - + '416e79207375626d697373696f6e20746f20746865204945544620696e' - + '74656e6465642062792074686520436f6e7472696275746f7220666f722' - + '07075626c69636174696f6e20617320616c6c206f722070617274206f' - + '6620616e204945544620496e7465726e65742d4472616674206f7220524' - + '64320616e6420616e792073746174656d656e74206d6164652077697468696' - + 'e2074686520636f6e74657874206f6620616e2049455446206163746976' - + '69747920697320636f6e7369646572656420616e20224945544620436f6' - + 'e747269627574696f6e222e20537563682073746174656d656e747320696e' - + '636c756465206f72616c2073746174656d656e747320696e204945544620' - + '73657373696f6e732c2061732077656c6c206173207772697474656e2061' - + '6e6420656c656374726f6e696320636f6d6d756e69636174696f6e73206d' - + '61646520617420616e792074696d65206f7220706c6163652c2077686963' - + '68206172652061646472657373656420746f', - ciphertext: '' - + 'a3fbf07df3fa2fde4f376ca23e82737041605d9f4f4f57bd8cff2c1d' - + '4b7955ec2a97948bd3722915c8f3d337f7d370050e9e96d647b7c39f' - + '56e031ca5eb6250d4042e02785ececfa4b4bb5e8ead0440e20b6e8db' - + '09d881a7c6132f420e52795042bdfa7773d8a9051447b3291ce1411c' - + '680465552aa6c405b7764d5e87bea85ad00f8449ed8f72d0d662ab05' - + '2691ca66424bc86d2df80ea41f43abf937d3259dc4b2d0dfb48a6c91' - + '39ddd7f76966e928e635553ba76c5c879d7b35d49eb2e62b0871cdac' - + '638939e25e8a1e0ef9d5280fa8ca328b351c3c765989cbcf3daa8b6c' - + 'cc3aaf9f3979c92b3720fc88dc95ed84a1be059c6499b9fda236e7e8' - + '18b04b0bc39c1e876b193bfe5569753f88128cc08aaa9b63d1a16f80' - + 'ef2554d7189c411f5869ca52c5b83fa36ff216b9c1d30062bebcfd' - + '2dc5bce0911934fda79a86f6e698ced759c3ff9b6477338f3da4f9' - + 'cd8514ea9982ccafb341b2384dd902f3d1ab7ac61dd29c6f21ba5b' - + '862f3730e37cfdc4fd806c22f221', - counter: 1 - }); - }); - - it('should perform chacha20', () => { - testChaCha({ - key: '1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0', - nonce: '000000000000000000000002', - plain: '' - + '2754776173206272696c6c69672c20616e642074686520736c6974687920746f76' - + '65730a446964206779726520616e642067696d626c6520696e207468' - + '6520776162653a0a416c6c206d696d73792077657265207468652062' - + '6f726f676f7665732c0a416e6420746865206d6f6d65207261746873' - + '206f757467726162652e', - ciphertext: '' - + '62e6347f95ed87a45ffae7426f27a1df5fb69110044c0d73118effa95b01e5cf16' - + '6d3df2d721caf9b21e5fb14c616871fd84c54f9d65b283196c7fe4f6' - + '0553ebf39c6402c42234e32a356b3e764312a61a5532055716ead696' - + '2568f87d3f3f7704c6a8d1bcd1bf4d50d6154b6da731b187b58dfd72' - + '8afa36757a797ac188d1', - counter: 42 - }); - }); - - it('should perform poly1305', () => { - const expected = Buffer.from('ddb9da7ddd5e52792730ed5cda5f90a4', 'hex'); - const key = Buffer.allocUnsafe(32); - const msg = Buffer.allocUnsafe(73); - - for (let i = 0; i < key.length; i++) - key[i] = i + 221; - - for (let i = 0; i < msg.length; i++) - msg[i] = i + 121; - - const mac = Poly1305.auth(msg, key); - assert(Poly1305.verify(mac, expected)); - assert.bufferEqual(mac, expected); - }); - - it('should perform poly1305', () => { - const key = Buffer.from('' - + '85d6be7857556d337f4452fe42d506a' - + '80103808afb0db2fd4abff6af4149f51b', - 'hex'); - - const msg = Buffer.from('Cryptographic Forum Research Group', 'ascii'); - const tag = Buffer.from('a8061dc1305136c6c22b8baf0c0127a9', 'hex'); - - const mac = Poly1305.auth(msg, key); - - assert(Poly1305.verify(mac, tag)); - - mac[0] = 0; - - assert(!Poly1305.verify(mac, tag)); - }); - - it('should create an AEAD and encrypt', () => { - testAEAD({ - plain: '' - + '4c616469657320616e642047656e746c656d656e206f662074686520636c6' - + '17373206f66202739393a204966204920636f756c64206f666665722' - + '0796f75206f6e6c79206f6e652074697020666f72207468652066757' - + '47572652c2073756e73637265656e20776f756c642062652069742e', - aad: '50515253c0c1c2c3c4c5c6c7', - key: '808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f', - nonce: '07000000' + '4041424344454647', - pk: '7bac2b252db447af09b67a55a4e955840ae1d6731075d9eb2a9375783ed553ff', - ciphertext: '' - + 'd31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee6' - + '2d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a' - + '5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad67' - + '5945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116', - tag: '1ae10b594f09e26a7e902ecbd0600691' - }); - }); - - it('should create an AEAD and encrypt', () => { - testAEAD({ - key: '1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0', - ciphertext: '' - + '64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8' - + 'cb24c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03' - + 'b7f355894cf332f830e710b97ce98c8a84abd0b948114ad176e008d3' - + '3bd60f982b1ff37c8559797a06ef4f0ef61c186324e2b35063836069' - + '07b6a7c02b0f9f6157b53c867e4b9166c767b804d46a59b5216cde7a' - + '4e99040c5a40433225ee282a1b0a06c523eaf4534d7f83fa1155b004' - + '7718cbc546a0d072b04b3564eea1b422273f548271a0bb2316053fa7' - + '6991955ebd63159434ecebb4e466dae5a1073a6727627097a1049e61' - + '7d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6' - + 'f2c29a6ad5cb4022b02709b', - nonce: '000000000102030405060708', - aad: 'f33388860000000000004e91', - tag: 'eead9d67890cbb22392336fea1851f38', - pk: 'bdf04aa95ce4de8995b14bb6a18fecaf26478f50c054f563dbc0a21e261572aa', - plain: '' - + '496e7465726e65742d4472616674732061726520647261667420646f63756' - + 'd656e74732076616c696420666f722061206d6178696d756d206f662' - + '0736978206d6f6e74687320616e64206d61792062652075706461746' - + '5642c207265706c616365642c206f72206f62736f6c6574656420627' - + '9206f7468657220646f63756d656e747320617420616e792074696d6' - + '52e20497420697320696e617070726f70726961746520746f2075736' - + '520496e7465726e65742d447261667473206173207265666572656e6' - + '365206d6174657269616c206f7220746f2063697465207468656d206' - + 'f74686572207468616e206173202fe2809c776f726b20696e2070726' - + 'f67726573732e2fe2809d' - }); - }); -}); diff --git a/test/chain-test.js b/test/chain-test.js index 2b58ae604..25387a368 100644 --- a/test/chain-test.js +++ b/test/chain-test.js @@ -3,30 +3,43 @@ 'use strict'; -const assert = require('./util/assert'); +const assert = require('bsert'); const consensus = require('../lib/protocol/consensus'); -const encoding = require('../lib/utils/encoding'); const Coin = require('../lib/primitives/coin'); const Script = require('../lib/script/script'); const Chain = require('../lib/blockchain/chain'); const WorkerPool = require('../lib/workers/workerpool'); const Miner = require('../lib/mining/miner'); -const MTX = require('../lib/primitives/mtx'); +const {Selector, MTX} = require('../lib/primitives/mtx'); const MemWallet = require('./util/memwallet'); const Network = require('../lib/protocol/network'); const Output = require('../lib/primitives/output'); const common = require('../lib/blockchain/common'); +const nodejsUtil = require('util'); const Opcode = require('../lib/script/opcode'); +const BlockStore = require('../lib/blockstore/level'); const opcodes = Script.opcodes; +const ZERO_KEY = Buffer.alloc(33, 0x00); + +const ONE_HASH = Buffer.alloc(32, 0x00); +ONE_HASH[0] = 0x01; + const network = Network.get('regtest'); const workers = new WorkerPool({ - enabled: true + enabled: true, + size: 2 +}); + +const blocks = new BlockStore({ + memory: true, + network }); const chain = new Chain({ - db: 'memory', + memory: true, + blocks, network, workers }); @@ -107,18 +120,25 @@ chain.on('disconnect', (entry, block) => { }); describe('Chain', function() { - this.timeout(45000); + this.timeout(process.browser ? 1200000 : 60000); - it('should open chain and miner', async () => { + before(async () => { + await blocks.open(); await chain.open(); await miner.open(); - }); + await workers.open(); - it('should add addrs to miner', async () => { miner.addresses.length = 0; miner.addAddress(wallet.getReceive()); }); + after(async () => { + await workers.close(); + await miner.close(); + await chain.close(); + await blocks.close(); + }); + it('should mine 200 blocks', async () => { for (let i = 0; i < 200; i++) { const block = await cpu.mineBlock(); @@ -150,13 +170,13 @@ describe('Chain', function() { const blk1 = await job1.mineAsync(); const blk2 = await job2.mineAsync(); - const hash1 = blk1.hash('hex'); - const hash2 = blk2.hash('hex'); + const hash1 = blk1.hash(); + const hash2 = blk2.hash(); assert(await chain.add(blk1)); assert(await chain.add(blk2)); - assert.strictEqual(chain.tip.hash, hash1); + assert.bufferEqual(chain.tip.hash, hash1); tip1 = await chain.getEntry(hash1); tip2 = await chain.getEntry(hash2); @@ -196,7 +216,7 @@ describe('Chain', function() { assert(await chain.add(block)); assert(forked); - assert.strictEqual(chain.tip.hash, block.hash('hex')); + assert.bufferEqual(chain.tip.hash, block.hash()); assert(chain.tip.chainwork.gt(tip1.chainwork)); }); @@ -220,11 +240,11 @@ describe('Chain', function() { assert(await chain.add(block)); - const hash = block.hash('hex'); + const hash = block.hash(); const entry = await chain.getEntry(hash); assert(entry); - assert.strictEqual(chain.tip.hash, entry.hash); + assert.bufferEqual(chain.tip.hash, entry.hash); const result = await chain.isMainChain(entry); assert(result); @@ -314,7 +334,7 @@ describe('Chain', function() { const tx = block.txs[1]; const output = Coin.fromTX(tx, 2, chain.height); - const coin = await chain.getCoin(tx.hash('hex'), 2); + const coin = await chain.getCoin(tx.hash(), 2); assert.bufferEqual(coin.toRaw(), output.toRaw()); }); @@ -330,7 +350,14 @@ describe('Chain', function() { { const tips = await chain.db.getTips(); - assert.notStrictEqual(tips.indexOf(chain.tip.hash), -1); + let index = -1; + + for (let i = 0; i < tips.length; i++) { + if (tips[i].equals(chain.tip.hash)) + index = i; + } + + assert.notStrictEqual(index, -1); assert.strictEqual(tips.length, 2); } @@ -339,7 +366,14 @@ describe('Chain', function() { { const tips = await chain.db.getTips(); - assert.notStrictEqual(tips.indexOf(chain.tip.hash), -1); + let index = -1; + + for (let i = 0; i < tips.length; i++) { + if (tips[i].equals(chain.tip.hash)) + index = i; + } + + assert.notStrictEqual(index, -1); assert.strictEqual(tips.length, 1); } }); @@ -555,7 +589,7 @@ describe('Chain', function() { const block = await cpu.mineBlock(); const tx = block.txs[0]; const input = tx.inputs[0]; - input.witness.set(0, encoding.ONE_HASH); + input.witness.set(0, ONE_HASH); block.refresh(true); assert.strictEqual(await addBlock(block), 'bad-witness-merkle-match'); }); @@ -574,7 +608,7 @@ describe('Chain', function() { output.script.compile(); block.refresh(true); - block.merkleRoot = block.createMerkleRoot('hex'); + block.merkleRoot = block.createMerkleRoot(); assert.strictEqual(await addBlock(block, flags), 'bad-witness-merkle-match'); @@ -591,7 +625,7 @@ describe('Chain', function() { tx.outputs.pop(); block.refresh(true); - block.merkleRoot = block.createMerkleRoot('hex'); + block.merkleRoot = block.createMerkleRoot(); assert.strictEqual(await addBlock(block, flags), 'unexpected-witness'); }); @@ -631,6 +665,9 @@ describe('Chain', function() { assert(await chain.add(block)); }); + if (process.browser) + return; + it('should mine fail to connect too much weight', async () => { const start = chain.height - 2000; const end = chain.height - 200; @@ -781,6 +818,38 @@ describe('Chain', function() { 'bad-txns-txouttotal-toolarge'); }); + it('should fail to connect total fee toolarge', async () => { + const job = await cpu.createJob(); + const outputs = [{ address: wallet.getAddress(), value: 0 }]; + + Selector.MAX_FEE = 50 * consensus.COIN; + const maxFee = Selector.MAX_FEE; + const maxMoney = consensus.MAX_MONEY; + + try { + const tx1 = await wallet.send({ + outputs: outputs, + hardFee: Selector.MAX_FEE + }); + job.pushTX(tx1.toTX()); + + const tx2 = await wallet.send({ + outputs: outputs, + hardFee: Selector.MAX_FEE + }); + job.pushTX(tx2.toTX()); + + consensus.MAX_MONEY = tx1.getFee() + tx2.getFee() - 1; + + job.refresh(); + assert.strictEqual(await mineBlock(job), + 'bad-txns-accumulated-fee-outofrange'); + } finally { + Selector.MAX_FEE = maxFee; + consensus.MAX_MONEY = maxMoney; + } + }); + it('should mine 111 multisig blocks', async () => { const flags = common.flags.DEFAULT_FLAGS & ~common.flags.VERIFY_POW; @@ -788,7 +857,7 @@ describe('Chain', function() { redeem.pushInt(20); for (let i = 0; i < 20; i++) - redeem.pushData(encoding.ZERO_KEY); + redeem.pushData(ZERO_KEY); redeem.pushInt(20); redeem.pushOp(opcodes.OP_CHECKMULTISIG); @@ -813,7 +882,7 @@ describe('Chain', function() { } block.refresh(true); - block.merkleRoot = block.createMerkleRoot('hex'); + block.merkleRoot = block.createMerkleRoot(); assert(await chain.add(block, flags)); } @@ -831,7 +900,7 @@ describe('Chain', function() { script.pushInt(20); for (let i = 0; i < 20; i++) - script.pushData(encoding.ZERO_KEY); + script.pushData(ZERO_KEY); script.pushInt(20); script.pushOp(opcodes.OP_CHECKMULTISIG); @@ -862,8 +931,52 @@ describe('Chain', function() { assert.strictEqual(await mineBlock(job), 'bad-blk-sigops'); }); - it('should cleanup', async () => { - await miner.close(); - await chain.close(); + it('should inspect ChainEntry', async () => { + const fmt = nodejsUtil.format(tip1); + assert(typeof fmt === 'string'); + assert(fmt.includes('hash')); + assert(fmt.includes('version')); + assert(fmt.includes('chainwork')); + }); + + describe('Checkpoints', function() { + before(async () => { + const entry = await chain.getEntry(chain.tip.height - 5); + assert(Buffer.isBuffer(entry.hash)); + assert(Number.isInteger(entry.height)); + + network.checkpointMap[entry.height] = entry.hash; + network.lastCheckpoint = entry.height; + }); + + after(async () => { + network.checkpointMap = {}; + network.lastCheckpoint = 0; + }); + + it('will reject blocks before last checkpoint', async () => { + const entry = await chain.getEntry(chain.tip.height - 10); + const block = await cpu.mineBlock(entry); + + let err = null; + + try { + await chain.add(block); + } catch (e) { + err = e; + } + + assert(err); + assert.equal(err.type, 'VerifyError'); + assert.equal(err.reason, 'bad-fork-prior-to-checkpoint'); + assert.equal(err.score, 100); + }); + + it('will accept blocks after last checkpoint', async () => { + const entry = await chain.getEntry(chain.tip.height - 4); + const block = await cpu.mineBlock(entry); + + assert(await chain.add(block)); + }); }); }); diff --git a/test/coin-test.js b/test/coin-test.js index e35eb24b2..c6e25a8ff 100644 --- a/test/coin-test.js +++ b/test/coin-test.js @@ -4,8 +4,9 @@ 'use strict'; const Coin = require('../lib/primitives/coin'); -const assert = require('./util/assert'); +const assert = require('bsert'); const common = require('../test/util/common'); +const nodejsUtil = require('util'); const tx1 = common.readTX('tx1'); const coin1 = common.readFile('coin1.raw'); @@ -39,4 +40,12 @@ describe('Coin', function() { assert.strictEqual(coin.coinbase, false); assert.strictEqual(coin.index, 0); }); + + it('should inspect Coin', () => { + const coin = new Coin(); + const fmt = nodejsUtil.format(coin); + assert(typeof fmt === 'string'); + assert(fmt.includes('coinbase')); + assert(fmt.includes('script')); + }); }); diff --git a/test/coins-test.js b/test/coins-test.js index 5c545923a..7e3f48dbe 100644 --- a/test/coins-test.js +++ b/test/coins-test.js @@ -3,14 +3,13 @@ 'use strict'; -const assert = require('./util/assert'); +const bio = require('bufio'); +const assert = require('bsert'); const Output = require('../lib/primitives/output'); const Input = require('../lib/primitives/input'); const Outpoint = require('../lib/primitives/outpoint'); const CoinView = require('../lib/coins/coinview'); const CoinEntry = require('../lib/coins/coinentry'); -const StaticWriter = require('../lib/utils/staticwriter'); -const BufferReader = require('../lib/utils/reader'); const common = require('./util/common'); const tx1 = common.readTX('tx1'); @@ -32,7 +31,7 @@ function deepCoinsEqual(a, b) { describe('Coins', function() { it('should instantiate coinview from tx', () => { const [tx] = tx1.getTX(); - const hash = tx.hash('hex'); + const hash = tx.hash(); const view = new CoinView(); const prevout = new Outpoint(hash, 0); const input = Input.fromOutpoint(prevout); @@ -49,7 +48,7 @@ describe('Coins', function() { assert.strictEqual(entry.height, 1); assert.strictEqual(entry.coinbase, false); assert.strictEqual(entry.raw, null); - assert.instanceOf(entry.output, Output); + assert(entry.output instanceof Output); assert.strictEqual(entry.spent, false); const output = view.getOutputFor(input); @@ -60,7 +59,7 @@ describe('Coins', function() { it('should spend an output', () => { const [tx] = tx1.getTX(); - const hash = tx.hash('hex'); + const hash = tx.hash(); const view = new CoinView(); view.addTX(tx, 1); @@ -88,9 +87,9 @@ describe('Coins', function() { const [tx, view] = tx1.getTX(); const size = view.getSize(tx); - const bw = new StaticWriter(size); + const bw = bio.write(size); const raw = view.toWriter(bw, tx).render(); - const br = new BufferReader(raw); + const br = bio.read(raw); const res = CoinView.fromReader(br, tx); const prev = tx.inputs[0].prevout; diff --git a/test/consensus-test.js b/test/consensus-test.js index 9cd15b37d..03503d442 100644 --- a/test/consensus-test.js +++ b/test/consensus-test.js @@ -3,9 +3,9 @@ 'use strict'; -const assert = require('./util/assert'); +const assert = require('bsert'); const consensus = require('../lib/protocol/consensus'); -const BN = require('../lib/crypto/bn'); +const BN = require('bcrypto/lib/bn.js'); describe('Consensus', function() { it('should calculate reward properly', () => { diff --git a/test/data/README.md b/test/data/README.md new file mode 100644 index 000000000..11073ab86 --- /dev/null +++ b/test/data/README.md @@ -0,0 +1,24 @@ +Multiple test vectors are based on Bitcoin Core. + +The MIT License (MIT) + +Copyright (c) 2009-2017 The Bitcoin Core developers +Copyright (c) 2009-2017 Bitcoin Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 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. diff --git a/test/data/bip69/COPYING b/test/data/bip69/COPYING new file mode 100644 index 000000000..fcf2ae9ff --- /dev/null +++ b/test/data/bip69/COPYING @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2016 Daniel Cousens + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 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. diff --git a/test/data/bip69.json b/test/data/bip69/bip69.json similarity index 100% rename from test/data/bip69.json rename to test/data/bip69/bip69.json diff --git a/test/data/bip70.json b/test/data/bip70.json deleted file mode 100644 index b989691f8..000000000 --- a/test/data/bip70.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "valid": "0801120b783530392b7368613235361a89250aa40a3082052030820408a003020102020727a49d05046d62300d06092a864886f70d01010b05003081b4310b30090603550406130255533110300e060355040813074172697a6f6e61311330110603550407130a53636f74747364616c65311a3018060355040a1311476f44616464792e636f6d2c20496e632e312d302b060355040b1324687474703a2f2f63657274732e676f64616464792e636f6d2f7265706f7369746f72792f313330310603550403132a476f2044616464792053656375726520436572746966696361746520417574686f72697479202d204732301e170d3134303432363132333532365a170d3136303432363132333532365a303a3121301f060355040b1318446f6d61696e20436f6e74726f6c2056616c6964617465643115301306035504030c0c2a2e6269747061792e636f6d30820122300d06092a864886f70d01010105000382010f003082010a0282010100e2a5dd4aea959c1d0fb016e6e05bb7011e741cdc61918c61f9625a2f682f485f0e862ea63db61cc9161753127504de800604df36b10f46cb17ab6cb99dba8aa45a36adfb901a2fc380c89e234bce18de6639b883e9339801673efaee1f2df77eeb82f7c39c96a2f8ef4572b634c203d9be8fd1e0036d32fb38b6b9b5ecd5a0684345c7e9ffc5d26bc6fd69aa6619f77badaa4bfb989478fb2f41aa92782e40b34ba9ac4549a4e6fda76b5fc4a581853bd0de5fb5a2c6dfdc12cdfadb54e9636a6d1223705924b8be566b81ac7921078cf590a146ae397a84908ef4fc83ff5715a44ab59e9258674d90113bb607b8d81eb268e4c6ce849497c76521795b0873950203010001a38201ae308201aa300f0603551d130101ff04053003010100301d0603551d250416301406082b0601050507030106082b06010505070302300e0603551d0f0101ff0404030205a030360603551d1f042f302d302ba029a0278625687474703a2f2f63726c2e676f64616464792e636f6d2f676469673273312d34392e63726c30530603551d20044c304a3048060b6086480186fd6d010717013039303706082b06010505070201162b687474703a2f2f6365727469666963617465732e676f64616464792e636f6d2f7265706f7369746f72792f307606082b06010505070101046a3068302406082b060105050730018618687474703a2f2f6f6373702e676f64616464792e636f6d2f304006082b060105050730028634687474703a2f2f6365727469666963617465732e676f64616464792e636f6d2f7265706f7369746f72792f67646967322e637274301f0603551d2304183016801440c2bd278ecc348330a233d7fb6cb3f0b42c80ce30230603551d11041c301a820c2a2e6269747061792e636f6d820a6269747061792e636f6d301d0603551d0e0416041485454e3b4072e2f58e377438988b5229387e967a300d06092a864886f70d01010b050003820101002d0a7ef97f988905ebbbad4e9ffb690352535211d6792516119838b55f24ff9fa4e93b6187b8517cbb0477457d3378078ef66057abe41bcafeb142ec52443a94b88114fa069f725c6198581d97af16352727f4f35e7f2110faa41a0511bcfdf8e3f4a3a310278c150b10f32a962c81e8f3d5374d9cb56d893027ff4fa4e3c3e6384c1f1557ceea6fca9cbc0c110748c08b82d8f0ed9a579637ee43a2d8fec3b5b04d1f3c8f1a3e2088da2274b6bc60948bbe744a7f8b942b41f0ae9b4afaeefbb7e0f04a0587b52efb6ebfa2d970b9de56a068575e4bf0cf824618dc17bbeaa2cdd25d65970a9f1a06fc9fffb466a10c9568cd651795bc2c7996975027bdbaba0ad409308204d0308203b8a003020102020107300d06092a864886f70d01010b0500308183310b30090603550406130255533110300e060355040813074172697a6f6e61311330110603550407130a53636f74747364616c65311a3018060355040a1311476f44616464792e636f6d2c20496e632e3131302f06035504031328476f20446164647920526f6f7420436572746966696361746520417574686f72697479202d204732301e170d3131303530333037303030305a170d3331303530333037303030305a3081b4310b30090603550406130255533110300e060355040813074172697a6f6e61311330110603550407130a53636f74747364616c65311a3018060355040a1311476f44616464792e636f6d2c20496e632e312d302b060355040b1324687474703a2f2f63657274732e676f64616464792e636f6d2f7265706f7369746f72792f313330310603550403132a476f2044616464792053656375726520436572746966696361746520417574686f72697479202d20473230820122300d06092a864886f70d01010105000382010f003082010a0282010100b9e0cb10d4af76bdd49362eb3064b881086cc304d962178e2fff3e65cf8fce62e63c521cda16454b55ab786b63836290ce0f696c99c81a148b4ccc4533ea88dc9ea3af2bfe80619d7957c4cf2ef43f303c5d47fc9a16bcc3379641518e114b54f828bed08cbef030381ef3b026f86647636dde7126478f384753d1461db4e3dc00ea45acbdbc71d9aa6f00dbdbcd303a794f5f4c47f81def5bc2c49d603bb1b24391d8a4334eeab3d6274fad258aa5c6f4d5d0a6ae7405645788b54455d42d2a3a3ef8b8bde9320a029464c4163a50f14aaee77933af0c20077fe8df0439c269026c6352fa77c11bc87487c8b993185054354b694ebc3bd3492e1fdcc1d252fb0203010001a382011a30820116300f0603551d130101ff040530030101ff300e0603551d0f0101ff040403020106301d0603551d0e0416041440c2bd278ecc348330a233d7fb6cb3f0b42c80ce301f0603551d230418301680143a9a8507106728b6eff6bd05416e20c194da0fde303406082b0601050507010104283026302406082b060105050730018618687474703a2f2f6f6373702e676f64616464792e636f6d2f30350603551d1f042e302c302aa028a0268624687474703a2f2f63726c2e676f64616464792e636f6d2f6764726f6f742d67322e63726c30460603551d20043f303d303b0604551d20003033303106082b06010505070201162568747470733a2f2f63657274732e676f64616464792e636f6d2f7265706f7369746f72792f300d06092a864886f70d01010b05000382010100087e6c9310c838b896a9904bffa15f4f04ef6c3e9c8806c9508fa673f757311bbebce42fdbf8bad35be0b4e7e679620e0ca2d76a637331b5f5a848a43b082da25d90d7b47c254f115630c4b6449d7b2c9de55ee6ef0c61aabfe42a1bee849eb8837dc143ce44a713700d911ff4c813ad8360d9d872a873241eb5ac220eca17896258441bab892501000fcdc41b62db51b4d30f512a9bf4bc73fc76ce36a4cdd9d82ceaae9bf52ab290d14d75188a3f8a4190237d5b4bfea403589b46b2c3606083f87d5041cec2a190c3bbef022fd21554ee4415d90aaea78a33edb12d763626dc04eb9ff7611f15dc876fee469628ada1267d0a09a72e04a38dbcf8bc0430010a81093082047d30820365a00302010202031be715300d06092a864886f70d01010b05003063310b30090603550406130255533121301f060355040a131854686520476f2044616464792047726f75702c20496e632e3131302f060355040b1328476f20446164647920436c61737320322043657274696669636174696f6e20417574686f72697479301e170d3134303130313037303030305a170d3331303533303037303030305a308183310b30090603550406130255533110300e060355040813074172697a6f6e61311330110603550407130a53636f74747364616c65311a3018060355040a1311476f44616464792e636f6d2c20496e632e3131302f06035504031328476f20446164647920526f6f7420436572746966696361746520417574686f72697479202d20473230820122300d06092a864886f70d01010105000382010f003082010a0282010100bf716208f1fa5934f71bc918a3f7804958e9228313a6c52043013b84f1e685499f27eaf6841b4ea0b4db7098c73201b1053e074eeef4fa4f2f593022e7ab19566be28007fcf316758039517be5f935b6744ea98d8213e4b63fa90383faa2be8a156a7fde0bc3b6191405caeac3a804943b467c320df3006622c88d696d368c1118b7d3b21c60b438fa028cced3dd4607de0a3eeb5d7cc87cfbb02b53a4926269512505611a44818c2ca9439623dfac3a819a0e29c51ca9e95d1eb69e9e300a39cef18880fb4b5dcc32ec85624325340256270191b43b702a3f6eb1e89c88017d9fd4f9db536d609dbf2ce758abb85f46fccec41b033c09eb49315c6946b3e0470203010001a382011730820113300f0603551d130101ff040530030101ff300e0603551d0f0101ff040403020106301d0603551d0e041604143a9a8507106728b6eff6bd05416e20c194da0fde301f0603551d23041830168014d2c4b0d291d44c1171b361cb3da1fedda86ad4e3303406082b0601050507010104283026302406082b060105050730018618687474703a2f2f6f6373702e676f64616464792e636f6d2f30320603551d1f042b30293027a025a0238621687474703a2f2f63726c2e676f64616464792e636f6d2f6764726f6f742e63726c30460603551d20043f303d303b0604551d20003033303106082b06010505070201162568747470733a2f2f63657274732e676f64616464792e636f6d2f7265706f7369746f72792f300d06092a864886f70d01010b05000382010100590b53bd928611a7247bed5b31cf1d1f6c70c5b86ebe4ebbf6be9750e1307fba285c6294c2e37e33f7fb427685db951c8c225875090c886567390a1609c5a03897a4c523933fb418a601064491e3a76927b45a257f3ab732cddd84ff2a382933a4dd67b285fea188201c5089c8dc2af64203374ce688dfd5af24f2b1c3dfccb5ece0995eb74954203c94180cc71c521849a46de1b3580bc9d8ecd9ae1c328e28700de2fea6179e840fbd5770b35ae91fa08653bbef7cff690be048c3b7930bc80a54c4ac5d1467376ccaa52f310837aa6e6f8cbc9be2575d2481af97979c84ad6cac374c66f361911120e4be309f7aa42909b0e1345f6477184051df8c30a6af0a840830820400308202e8a003020102020100300d06092a864886f70d01010505003063310b30090603550406130255533121301f060355040a131854686520476f2044616464792047726f75702c20496e632e3131302f060355040b1328476f20446164647920436c61737320322043657274696669636174696f6e20417574686f72697479301e170d3034303632393137303632305a170d3334303632393137303632305a3063310b30090603550406130255533121301f060355040a131854686520476f2044616464792047726f75702c20496e632e3131302f060355040b1328476f20446164647920436c61737320322043657274696669636174696f6e20417574686f7269747930820120300d06092a864886f70d01010105000382010d00308201080282010100de9dd7ea571849a15bebd75f4886eabeddffe4ef671cf46568b35771a05e77bbed9b49e970803d561863086fdaf2ccd03f7f0254225410d8b281d4c0753d4b7fc777c33e78ab1a03b5206b2f6a2bb1c5887ec4bb1eb0c1d845276faa3758f78726d7d82df6a917b71f72364ea6173f659892db2a6e5da2fe88e00bde7fe58d15e1ebcb3ad5e212a2132dd88eaf5f123da0080508b65ca565380445991ea3606074c541a572621b62c51f6f5f1a42be025165a8ae23186afc7803a94d7f80c3faab5afca140a4ca1916feb2c8ef5e730dee77bd9af67998bcb10767a2150ddda058c6447b0a3e62285fba41075358cf117e3874c5f8ffb569908f8474ea971baf020103a381c03081bd301d0603551d0e04160414d2c4b0d291d44c1171b361cb3da1fedda86ad4e330818d0603551d230481853081828014d2c4b0d291d44c1171b361cb3da1fedda86ad4e3a167a4653063310b30090603550406130255533121301f060355040a131854686520476f2044616464792047726f75702c20496e632e3131302f060355040b1328476f20446164647920436c61737320322043657274696669636174696f6e20417574686f72697479820100300c0603551d13040530030101ff300d06092a864886f70d01010505000382010100324bf3b2ca3e91fc12c6a1078c8e77a03306145c901e18f708a63d0a19f98780116e69e4961730ff3491637238eecc1c01a31d9428a431f67ac454d7f6e5315803a2ccce62db944573b5bf45c924b5d58202ad2379698db8b64dcecf4cca3323e81c88aa9d8b416e16c920e5899ecd3bda70f77e992620145425ab6e7385e69b219d0a6c820ea8f8c20cfa101e6c96ef870dc40f618badee832b95f88e92847239eb20ea83ed83cd976e08bceb4e26b6732be4d3f64cfe2671e26111744aff571a870f75482ecf516917a002126195d5d140b2104ceec4ac1043a6a59e0ad595629a0dcf8882c5320ce42b9f45e60d9f289cb1b92a5a57ad370faf1d7fdbbd9f22a1010a0474657374122008c0c9e714121976a914176d7c5d60da6f8c82de86671a1fb776028538ca88ac18c6f5d89f0520cafcd89f052a395061796d656e74207265717565737420666f722042697450617920696e766f69636520434d57707546736a676d51325a4c6979476663463157323068747470733a2f2f746573742e6269747061792e636f6d2f692f434d57707546736a676d51325a4c69794766634631572a80021566366ab78842a514c056ca7ecb76481262cac74cc4c4ccdc82c4980bc3300de67836d61d3e06dc8c90798a7774c21c7ad4fe634b85faa8719d6402411bb720396ae03cbb4e14f06f7894a66b208b99f727fab35d32f4f2148294d24bea1b3f240c159d0fd3ee4a32e5f926bf7c05eb7a3f75e01d9af81254cfbb61606467750ea7e0a1536728358e0898d06f57235e4096d2caf647ae58dff645be80c9b3555fa96c81efa07d421977d26214ad4f1ff642a93d0925656aeab454fa0b60fcbb6c1bc570eb6e43e7613392f37900748635ae381534bfaa558792bc46028b9efce391423a9c1201f76292614b30a14272e837f3813045b035f3d42f4f76f48acd", - "invalid": "0801120b783530392b7368613235361a88210aaa0e308207263082060ea003020102020900ba55847ce9a10f59300d06092a864886f70d01010b05003081b4310b30090603550406130255533110300e060355040813074172697a6f6e61311330110603550407130a53636f74747364616c65311a3018060355040a1311476f44616464792e636f6d2c20496e632e312d302b060355040b1324687474703a2f2f63657274732e676f64616464792e636f6d2f7265706f7369746f72792f313330310603550403132a476f2044616464792053656375726520436572746966696361746520417574686f72697479202d204732301e170d3135303432303231323134365a170d3137303432353139313130305a3081be31133011060b2b0601040182373c0201031302555331193017060b2b0601040182373c020102130844656c6177617265311d301b060355040f131450726976617465204f7267616e697a6174696f6e3110300e0603550405130735313633393636310b30090603550406130255533110300e0603550408130747656f726769613110300e0603550407130741746c616e746131153013060355040a130c4269745061792c20496e632e311330110603550403130a6269747061792e636f6d30820122300d06092a864886f70d01010105000382010f003082010a0282010100e42025369a803c1b44234a279d123b610ec1178452fc7c57f2576492e1123166bed5eec11d12a8b824904f77aa6166ce6e111538204121069b5ecff81fd1ab27b1b9475c49d7680ef5f406f57a1871b77392a7b5c4370f504ba175617f78fe24c9ed76154b46aa677cf6463a202909711b34ceecbe0ee7b9766cfd7018886b67e518f1ac39a3715152d62e59547f34d440ed3dfa01a92a42ed0b221c093144ed63f86c2300b8cf192b75929732af63c41526e4fd69735f0062c61c2dbcb56f6439ff9bbe62507da52752c1d99f24afbb3a6b5c98989d2196fb6bd9cc83a5ab21f060e955e7f45a01c9efe782f15cd48ab824d6272db2b71c8988d02071e805df0203010001a382032d30820329300c0603551d130101ff04023000301d0603551d250416301406082b0601050507030106082b06010505070302300e0603551d0f0101ff0404030205a030350603551d1f042e302c302aa028a0268624687474703a2f2f63726c2e676f64616464792e636f6d2f676469673273332d312e63726c30530603551d20044c304a3048060b6086480186fd6d010717033039303706082b06010505070201162b687474703a2f2f6365727469666963617465732e676f64616464792e636f6d2f7265706f7369746f72792f307606082b06010505070101046a3068302406082b060105050730018618687474703a2f2f6f6373702e676f64616464792e636f6d2f304006082b060105050730028634687474703a2f2f6365727469666963617465732e676f64616464792e636f6d2f7265706f7369746f72792f67646967322e637274301f0603551d2304183016801440c2bd278ecc348330a233d7fb6cb3f0b42c80ce30250603551d11041e301c820a6269747061792e636f6d820e7777772e6269747061792e636f6d301d0603551d0e04160414a5698a70daa1405dbcdd1a02e95db8a51aaadd7f3082017d060a2b06010401d6790204020482016d0482016901670076005614069a2fd7c2ecd3f5e1bd44b23ec74676b9bc99115cc0ef949855d689d0dd0000014cd8b6d73a000004030047304502210095e5768670ea27ed35075e484b7c42cd20176c7263b4e879deb418639144079d02204911ae1cf0fcda5a2b6b3cec528ccd689e7371a34c99460c2ca063b44ba6fbca00750068f698f81f6482be3a8ceeb9281d4cfc71515d6793d444d10a67acbb4f4ffbc40000014cd8b6d93e0000040300463044022030c89215d7081b372a1b033eb77ff5abbe9c6822b38c9a0f5e4b166c7532fabf02200a481b330414d0076aa0d925dc84096b8cb7585fc0b1ff28c744cfa85e17ab8e007600a4b90990b418581487bb13a2cc67700a3c359804f91bdfb8e377cd0ec80ddc100000014cd8b6dc34000004030047304502201f085d25b292b34185a182e314a3c64c9b5162526c90cfc3ff7136b137bc98ee022100b28a7fbaa53c2fff07bdf23300551e0b52ff1d823bb89df9eeb9079327adfe2c300d06092a864886f70d01010b0500038201010013eeaf33325800bb0f252a091b1a9cdf2e10de3abfb9f566b94a60e63401598f68c4086cf170881f1b856e51dc45edce992f589a7c4acbfb7c28a10c28c1ce4d1425c5b795b764367f0446dde5bb5710e5a1994a02f0b02334458e4950a876e48363aee4a32d1904d6d9c6af2906f4e4e03122e35bf943cf17b0e05a8ebaa5154a86f40676e779bb1dcd2672d75887e1bcb51186596d749bc6c1ecfedcf60b24eb503ac9460dc3682fe74e7acf16e9ab0014f6719322de534e14e90e7638e854546087c6a33d7743f58ee3b589dbd8fa5800af9b0ecdef0c041a2bf2126b908e3bf84881c1a323929bca34f0830373a8b28f62b2133880ff6f20ffb19c38a7280ad409308204d0308203b8a003020102020107300d06092a864886f70d01010b0500308183310b30090603550406130255533110300e060355040813074172697a6f6e61311330110603550407130a53636f74747364616c65311a3018060355040a1311476f44616464792e636f6d2c20496e632e3131302f06035504031328476f20446164647920526f6f7420436572746966696361746520417574686f72697479202d204732301e170d3131303530333037303030305a170d3331303530333037303030305a3081b4310b30090603550406130255533110300e060355040813074172697a6f6e61311330110603550407130a53636f74747364616c65311a3018060355040a1311476f44616464792e636f6d2c20496e632e312d302b060355040b1324687474703a2f2f63657274732e676f64616464792e636f6d2f7265706f7369746f72792f313330310603550403132a476f2044616464792053656375726520436572746966696361746520417574686f72697479202d20473230820122300d06092a864886f70d01010105000382010f003082010a0282010100b9e0cb10d4af76bdd49362eb3064b881086cc304d962178e2fff3e65cf8fce62e63c521cda16454b55ab786b63836290ce0f696c99c81a148b4ccc4533ea88dc9ea3af2bfe80619d7957c4cf2ef43f303c5d47fc9a16bcc3379641518e114b54f828bed08cbef030381ef3b026f86647636dde7126478f384753d1461db4e3dc00ea45acbdbc71d9aa6f00dbdbcd303a794f5f4c47f81def5bc2c49d603bb1b24391d8a4334eeab3d6274fad258aa5c6f4d5d0a6ae7405645788b54455d42d2a3a3ef8b8bde9320a029464c4163a50f14aaee77933af0c20077fe8df0439c269026c6352fa77c11bc87487c8b993185054354b694ebc3bd3492e1fdcc1d252fb0203010001a382011a30820116300f0603551d130101ff040530030101ff300e0603551d0f0101ff040403020106301d0603551d0e0416041440c2bd278ecc348330a233d7fb6cb3f0b42c80ce301f0603551d230418301680143a9a8507106728b6eff6bd05416e20c194da0fde303406082b0601050507010104283026302406082b060105050730018618687474703a2f2f6f6373702e676f64616464792e636f6d2f30350603551d1f042e302c302aa028a0268624687474703a2f2f63726c2e676f64616464792e636f6d2f6764726f6f742d67322e63726c30460603551d20043f303d303b0604551d20003033303106082b06010505070201162568747470733a2f2f63657274732e676f64616464792e636f6d2f7265706f7369746f72792f300d06092a864886f70d01010b05000382010100087e6c9310c838b896a9904bffa15f4f04ef6c3e9c8806c9508fa673f757311bbebce42fdbf8bad35be0b4e7e679620e0ca2d76a637331b5f5a848a43b082da25d90d7b47c254f115630c4b6449d7b2c9de55ee6ef0c61aabfe42a1bee849eb8837dc143ce44a713700d911ff4c813ad8360d9d872a873241eb5ac220eca17896258441bab892501000fcdc41b62db51b4d30f512a9bf4bc73fc76ce36a4cdd9d82ceaae9bf52ab290d14d75188a3f8a4190237d5b4bfea403589b46b2c3606083f87d5041cec2a190c3bbef022fd21554ee4415d90aaea78a33edb12d763626dc04eb9ff7611f15dc876fee469628ada1267d0a09a72e04a38dbcf8bc0430010a81093082047d30820365a00302010202031be715300d06092a864886f70d01010b05003063310b30090603550406130255533121301f060355040a131854686520476f2044616464792047726f75702c20496e632e3131302f060355040b1328476f20446164647920436c61737320322043657274696669636174696f6e20417574686f72697479301e170d3134303130313037303030305a170d3331303533303037303030305a308183310b30090603550406130255533110300e060355040813074172697a6f6e61311330110603550407130a53636f74747364616c65311a3018060355040a1311476f44616464792e636f6d2c20496e632e3131302f06035504031328476f20446164647920526f6f7420436572746966696361746520417574686f72697479202d20473230820122300d06092a864886f70d01010105000382010f003082010a0282010100bf716208f1fa5934f71bc918a3f7804958e9228313a6c52043013b84f1e685499f27eaf6841b4ea0b4db7098c73201b1053e074eeef4fa4f2f593022e7ab19566be28007fcf316758039517be5f935b6744ea98d8213e4b63fa90383faa2be8a156a7fde0bc3b6191405caeac3a804943b467c320df3006622c88d696d368c1118b7d3b21c60b438fa028cced3dd4607de0a3eeb5d7cc87cfbb02b53a4926269512505611a44818c2ca9439623dfac3a819a0e29c51ca9e95d1eb69e9e300a39cef18880fb4b5dcc32ec85624325340256270191b43b702a3f6eb1e89c88017d9fd4f9db536d609dbf2ce758abb85f46fccec41b033c09eb49315c6946b3e0470203010001a382011730820113300f0603551d130101ff040530030101ff300e0603551d0f0101ff040403020106301d0603551d0e041604143a9a8507106728b6eff6bd05416e20c194da0fde301f0603551d23041830168014d2c4b0d291d44c1171b361cb3da1fedda86ad4e3303406082b0601050507010104283026302406082b060105050730018618687474703a2f2f6f6373702e676f64616464792e636f6d2f30320603551d1f042b30293027a025a0238621687474703a2f2f63726c2e676f64616464792e636f6d2f6764726f6f742e63726c30460603551d20043f303d303b0604551d20003033303106082b06010505070201162568747470733a2f2f63657274732e676f64616464792e636f6d2f7265706f7369746f72792f300d06092a864886f70d01010b05000382010100590b53bd928611a7247bed5b31cf1d1f6c70c5b86ebe4ebbf6be9750e1307fba285c6294c2e37e33f7fb427685db951c8c225875090c886567390a1609c5a03897a4c523933fb418a601064491e3a76927b45a257f3ab732cddd84ff2a382933a4dd67b285fea188201c5089c8dc2af64203374ce688dfd5af24f2b1c3dfccb5ece0995eb74954203c94180cc71c521849a46de1b3580bc9d8ecd9ae1c328e28700de2fea6179e840fbd5770b35ae91fa08653bbef7cff690be048c3b7930bc80a54c4ac5d1467376ccaa52f310837aa6e6f8cbc9be2575d2481af97979c84ad6cac374c66f361911120e4be309f7aa42909b0e1345f6477184051df8c30a6af2289020a046d61696e121f08c0843d121976a914e4ea045fc0f08208d452187ad401ab2a874d0b6d88ac1896d6e5af05209adde5af052a595061796d656e74207265717565737420666f722042697450617920696e766f69636520504151744e7858374b4c3842744a426e66587954614820666f72206d65726368616e74204269744769766520466f756e646174696f6e322b68747470733a2f2f6269747061792e636f6d2f692f504151744e7858374b4c3842744a426e6658795461483a4c7b22696e766f6963654964223a22504151744e7858374b4c3842744a426e665879546148222c226d65726368616e744964223a2254785a35527943686d5a773269734b6a4a5747684263227d2a8002dca72518e1951edeb832ad680b5b99f29e0d5d9b9bf4c6d3a6e327436a8c9e44328b9c4738703b3407033f13cfc90ac7201dbbb612ee00f81a94169b4f4994b310c177948877e440d1347a64aa55c351fd08a3ce9af7598b8d103b24a0928a23cba0067d35aa86de6fc664c017233cd22db0ec204cacb00c87dd58abfdeae7c2fc61e5666a41c1be2f633d925f97a088b1fabc0b6634fff7f0034437a8d4cf52d94d739dc67327a1b3757358f2799bda1e0af4e897a8fae0ec77f2baa8fb6a9b315bf1a19e09dc806d3269e5544c98da2a3bfd1fcc70389539b1c9ad0c16cd34f62cd1533e3b491115814746c47914bd2c45cf947ac239b1e1251907cfd99036", - "untrusted": "120b783530392b7368613235361abe150afe0b308205fa308204e2a0030201020210090b35ca5c5bf1b98b3d8f9f4a7755d6300d06092a864886f70d01010b05003075310b300906035504061302555331153013060355040a130c446967694365727420496e6331193017060355040b13107777772e64696769636572742e636f6d313430320603550403132b4469676943657274205348413220457874656e6465642056616c69646174696f6e20536572766572204341301e170d3134303530393030303030305a170d3136303531333132303030305a30820105311d301b060355040f0c1450726976617465204f7267616e697a6174696f6e31133011060b2b0601040182373c0201031302555331193017060b2b0601040182373c020102130844656c61776172653110300e0603550405130735313534333137310f300d06035504090c06233233303038311730150603550409130e353438204d61726b65742053742e310e300c060355041113053934313034310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d53616e204672616e636973636f31173015060355040a130e436f696e626173652c20496e632e311530130603550403130c636f696e626173652e636f6d30820122300d06092a864886f70d01010105000382010f003082010a0282010100b45e3ff380667aa14d5a12fc2fc983fc6618b55499933c3bde15c01d838846b4caf9848e7c40e5fa7c67ef9b5b1efe26ee5571c5fa2eff759052454701ad8931557d697b139e5d19abb3e439675f31db7f2ef1a5d97db07c1f6966266380eb4fcfa8e1471a6ecc2fbebf3e67b3eaa84d0fbe063e60380dcdb7a20203d29a94059ef7f20d472cc25783ab2a1db6a394ecc07b4024974100bcfd470f59ef3b572365213209609fad229994b4923c1df3a18c41e3e7bc1f192ba6e7e5c32ae155107e21903eff7bce9fc594b49d9f6ae7901fa191fcbae8a2cf09c3bfc24377d717b6010080c5681a7dbc6e1d52987b7ebbe95e7af4202da436e67a88472aacedc90203010001a38201f2308201ee301f0603551d230418301680143dd350a5d6a0adeef34a600a65d321d4f8f8d60f301d0603551d0e041604146d33b9743a61b7499423d1a89d085d0148680bba30290603551d1104223020820c636f696e626173652e636f6d82107777772e636f696e626173652e636f6d300e0603551d0f0101ff0404030205a0301d0603551d250416301406082b0601050507030106082b0601050507030230750603551d1f046e306c3034a032a030862e687474703a2f2f63726c332e64696769636572742e636f6d2f736861322d65762d7365727665722d67312e63726c3034a032a030862e687474703a2f2f63726c342e64696769636572742e636f6d2f736861322d65762d7365727665722d67312e63726c30420603551d20043b3039303706096086480186fd6c0201302a302806082b06010505070201161c68747470733a2f2f7777772e64696769636572742e636f6d2f43505330818806082b06010505070101047c307a302406082b060105050730018618687474703a2f2f6f6373702e64696769636572742e636f6d305206082b060105050730028646687474703a2f2f636163657274732e64696769636572742e636f6d2f446967694365727453484132457874656e64656456616c69646174696f6e53657276657243412e637274300c0603551d130101ff04023000300d06092a864886f70d01010b05000382010100aadfcf94050ed938e3114a640af3d9b04276da00f5215d7148f9f16d4cac0c77bd5349ec2f47299d03c900f70146752da72829290ac50a77992f01537ab2689392ce0bfeb7efa49f4c4fe4e1e43ca1fcfb1626ce554da4f6e7fa34a597e401f215c43afd0ba777ad587eb0afacd71f7a6af7752814f7ab4c202ed76d33defd1289d541803fed01ac80a3cacfdaae29279e5de14d460475f4baf27eab693379d39120e7477bf3ec719664c7b6cb5e557556e5bbddd9c9d1ebc9f835e9da5b3dbb72fe8d94ac05eab3c479987520ade3a1d275e1e2fe725698d2f7cb1390a9d40ea6cbf21a73bddccd1ad61aa249ce8e2885a3730b7d53bd075f55099d2960f3cc0aba09308204b63082039ea00302010202100c79a944b08c11952092615fe26b1d83300d06092a864886f70d01010b0500306c310b300906035504061302555331153013060355040a130c446967694365727420496e6331193017060355040b13107777772e64696769636572742e636f6d312b30290603550403132244696769436572742048696768204173737572616e636520455620526f6f74204341301e170d3133313032323132303030305a170d3238313032323132303030305a3075310b300906035504061302555331153013060355040a130c446967694365727420496e6331193017060355040b13107777772e64696769636572742e636f6d313430320603550403132b4469676943657274205348413220457874656e6465642056616c69646174696f6e2053657276657220434130820122300d06092a864886f70d01010105000382010f003082010a0282010100d753a40451f899a616484b6727aa9349d039ed0cb0b00087f1672886858c8e63dabcb14038e2d3f5eca50518b83d3ec5991732ec188cfaf10ca6642185cb071034b052882b1f689bd2b18f12b0b3d2e7881f1fef387754535f80793f2e1aaaa81e4b2b0dabb763b935b77d14bc594bdf514ad2a1e20ce29082876aaeead764d69855e8fdaf1a506c54bc11f2fd4af29dbb7f0ef4d5be8e16891255d8c07134eef6dc2decc48725868dd821e4b04d0c89dc392617ddf6d79485d80421709d6f6fff5cba19e145cb5657287e1c0d4157aab7b827bbb1e4fa2aef2123751aad2d9b86358c9c77b573add8942de4f30c9deec14e627e17c0719e2cdef1f9102819330203010001a38201493082014530120603551d130101ff040830060101ff020100300e0603551d0f0101ff040403020186301d0603551d250416301406082b0601050507030106082b06010505070302303406082b0601050507010104283026302406082b060105050730018618687474703a2f2f6f6373702e64696769636572742e636f6d304b0603551d1f044430423040a03ea03c863a687474703a2f2f63726c342e64696769636572742e636f6d2f4469676943657274486967684173737572616e63654556526f6f7443412e63726c303d0603551d200436303430320604551d2000302a302806082b06010505070201161c68747470733a2f2f7777772e64696769636572742e636f6d2f435053301d0603551d0e041604143dd350a5d6a0adeef34a600a65d321d4f8f8d60f301f0603551d23041830168014b13ec36903f8bf4701d498261a0802ef63642bc3300d06092a864886f70d01010b050003820101009db6d09086e18602edc5a0f0341c74c18d76cc860aa8f04a8a42d63fc8a94dad7c08ade6b650b8a21a4d8807b12921dce7dac63c21e0e3114970ac7a1d01a4ca113a57ab7d572a4074fdd31d851850df574775a17d55202e473750728c7f821bd2628f2d035adac3c8a1ce2c52a20063eb73ba71c84927239764859e380ead63683cba52815879a32c0cdfde6deb31f2baa07c6cf12cd4e1bd77843703ce32b5c89a811a4a924e3b469a85fe83a2f99e8ca3cc0d5eb33dcf04788f14147b329cc700a65cc4b5a1558d5a5668a42270aa3c8171d99da8453bf4e5f6a251ddc77b62e86f0c74ebb8daf8bf870d795091909b183b915927f1352813ab267ed5f77a22b801121f08d8e51a121976a914f5fa7bb3c2245e6469254c074c062af292ce008c88ac189295e7af0520f49be7af052a315061796d656e74207265717565737420666f7220436f696e62617365206f7264657220636f64653a205738375858335753323468747470733a2f2f7777772e636f696e626173652e636f6d2f72702f3535663963613730336435643830303038633030303166343a2062623739623666323331306533323162643362316439323965646265623335382a8002b45d9b5d48d566d577ec4973b4b57a4930f173be6545d9fc367f9b55f90a669504085db971107a09c25d1e30dcaf54ce86f177685751feebd5c66c40d7d9daad08129fe10450518d48e49ae4e03a8c4939010f9f222de60b9e5df210e066115cbc2ad92f51fcdc35eaa3ee2430687ff46053e460b6f93deede2ee09a8841eec11b6f5d1bbcf26708e5c12d427959569d9af629f8940736ac3941f78eafb2c2e400dfcc76e729d13c4a9d0ec834515cf7287188589e5e0e505a7a7daab1fa8ac4f6745a0eae2daa61d73a7e3fc56ca94457e2aa164a959b05bb91b797828cb7c4d8b05395840a40ef51a0a0f93d13dce15e5f5f9ede08b3510701a1cac6a99c06", - "ack": "0a00125f5472616e73616374696f6e207265636569766564206279204269745061792e20496e766f6963652077696c6c206265206d61726b6564206173207061696420696620746865207472616e73616374696f6e20697320636f6e6669726d65642e", - "ca": { - "crt": "308201ef30820158020900f951e277bfb8b405300d06092a864886f70d01010b0500303c310b3009060355040613025553310b300906035504080c024341310f300d060355040a0c064a4a73204341310f300d06035504030c064a4a73204341301e170d3136303732333132333035365a170d3137303732333132333035365a303c310b3009060355040613025553310b300906035504080c024341310f300d060355040a0c064a4a73204341310f300d06035504030c064a4a7320434130819f300d06092a864886f70d010101050003818d0030818902818100dfa1f53be0a5473f33a69f69290bdb78786980a70d0d68f098eac64b227b268174f798a7a89baca0ee71e8577c91c79025db5888be26a05377e63665a99e10cda0fdba4f8214d5f57807fb482a7c29d90a36f0d37bbe80d0a878922efd9723b511aeaa258cac18213ff966a9e4fac97bbefb4feb0b2a7f02a95aa3b3ac3fbac30203010001300d06092a864886f70d01010b05000381810066f891a7ac6ef88e7ae4ca11b6a4a573397bb5309844136eaea578e843eacb4af3ccae12e9158e7db906b9b386bc118254e696e21fde537dc2a547dc8cdaa98af0e44025fb2fe6b23676455f577b054b284c0d04df9a7bc49214f385b655e31c0b9a47012780e6cca26b6e20e00a485213d4a7aa65b99d011f232f0da6227f95", - "priv": "3082025e02010002818100dfa1f53be0a5473f33a69f69290bdb78786980a70d0d68f098eac64b227b268174f798a7a89baca0ee71e8577c91c79025db5888be26a05377e63665a99e10cda0fdba4f8214d5f57807fb482a7c29d90a36f0d37bbe80d0a878922efd9723b511aeaa258cac18213ff966a9e4fac97bbefb4feb0b2a7f02a95aa3b3ac3fbac3020301000102818100ab2cd8fd8032829a89fa9c426e3cb93d394de83ff5e9b1cd97fefcd13e0f6da5e1d6336f01cc4712d7f1309ae6aadf4a22f00690849c24543f8e634ce5cc2ba70af366c6fb2c8820c977aa5843d380a9b63bfe29fb26177b5c53012bd68fdcdd69609f54cc0a0fe37bae1acf29382950fcba134a77416ad684423345df4feca1024100fec773455b21efe7ef0cfac567723c21080cd1d463233a4679303093ba6c6b3681f053418f33b575f151f7146a1a306e7854299ad03869f2d806ffcae6c18d5f024100e0b44c7f89a0c296b3ddb0981519b49393a8f738c6d4de23dae74f42d59c9131591126ca4b1623475dff1e1be655c6b5c13cf0c468874fb574286da16f61a91d024100a7303ee65e5d30cf244182361422146f209739028463d393f766b619db15d66d186b9a56c5200b8defe3f79d7fdadbb9426108443d2b27eb61f385dba1fd954502410092b3c3d9e45dcb8d1e63171f436f63e0919fd7bffeb80d4bb5681cec5290048f064a575bed47c7c29950ad34a3ddce7ced6b3ef9cc10c36e8126bfe48cd662d502404cceb50ffca2ed6ead5557e76c4cc5b57a3dad3c14038f11eec2dae285c722a28008ea312dcf5552fbf9090e95b1570cea569aed0f3ce3ab55378ec9300a2ee5", - "pub": "30818902818100dfa1f53be0a5473f33a69f69290bdb78786980a70d0d68f098eac64b227b268174f798a7a89baca0ee71e8577c91c79025db5888be26a05377e63665a99e10cda0fdba4f8214d5f57807fb482a7c29d90a36f0d37bbe80d0a878922efd9723b511aeaa258cac18213ff966a9e4fac97bbefb4feb0b2a7f02a95aa3b3ac3fbac30203010001" - } -} \ No newline at end of file diff --git a/test/data/block482683.raw b/test/data/block482683.raw new file mode 100644 index 000000000..52cddffe6 Binary files /dev/null and b/test/data/block482683.raw differ diff --git a/test/data/core-data/COPYING b/test/data/core-data/COPYING new file mode 100644 index 000000000..9704b9c94 --- /dev/null +++ b/test/data/core-data/COPYING @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2009-2018 The Bitcoin Core developers +Copyright (c) 2009-2018 Bitcoin Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 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. diff --git a/test/data/script-tests.json b/test/data/core-data/script-tests.json similarity index 99% rename from test/data/script-tests.json rename to test/data/core-data/script-tests.json index 698e89823..9b320b694 100644 --- a/test/data/script-tests.json +++ b/test/data/core-data/script-tests.json @@ -168,6 +168,18 @@ ["1 0 BOOLOR", "NOP", "P2SH,STRICTENC", "OK"], ["0 1 BOOLOR", "NOP", "P2SH,STRICTENC", "OK"], ["0 0 BOOLOR", "NOT", "P2SH,STRICTENC", "OK"], +["0x01 0x80", "DUP BOOLOR", "P2SH,STRICTENC", "EVAL_FALSE", "negative-0 negative-0 BOOLOR"], +["0x01 0x00", "DUP BOOLOR", "P2SH,STRICTENC", "EVAL_FALSE", " non-minimal-0 non-minimal-0 BOOLOR"], +["0x01 0x81", "DUP BOOLOR", "P2SH,STRICTENC", "OK", "-1 -1 BOOLOR"], +["0x01 0x80", "DUP BOOLAND", "P2SH,STRICTENC", "EVAL_FALSE", "negative-0 negative-0 BOOLAND"], +["0x01 0x00", "DUP BOOLAND", "P2SH,STRICTENC", "EVAL_FALSE", " non-minimal-0 non-minimal-0 BOOLAND"], +["0x01 0x81", "DUP BOOLAND", "P2SH,STRICTENC", "OK", "-1 -1 BOOLAND"], +["0x01 0x00", "NOT", "P2SH,STRICTENC", "OK", "non-minimal-0 NOT"], +["0x01 0x80", "NOT", "P2SH,STRICTENC", "OK", "negative-0 NOT"], +["0x01 0x81", "NOT", "P2SH,STRICTENC", "EVAL_FALSE", "negative 1 NOT"], +["0x01 0x80 0", "NUMEQUAL", "P2SH", "OK", "-0 0 NUMEQUAL"], +["0x01 0x00 0", "NUMEQUAL", "P2SH", "OK", "non-minimal-0 0 NUMEQUAL"], +["0x02 0x00 0x00 0", "NUMEQUAL", "P2SH", "OK", "non-minimal-0 0 NUMEQUAL"], ["16 17 BOOLOR", "NOP", "P2SH,STRICTENC", "OK"], ["11 10 1 ADD", "NUMEQUAL", "P2SH,STRICTENC", "OK"], ["11 10 1 ADD", "NUMEQUALVERIFY 1", "P2SH,STRICTENC", "OK"], @@ -700,7 +712,7 @@ ["0x17 0x3014020002107777777777777777777777777777777701", "0 CHECKSIG NOT", "", "OK", "Zero-length R is correctly encoded"], ["0x17 0x3014021077777777777777777777777777777777020001", "0 CHECKSIG NOT", "", "OK", "Zero-length S is correctly encoded for DERSIG"], ["0x27 0x302402107777777777777777777777777777777702108777777777777777777777777777777701", "0 CHECKSIG NOT", "", "OK", "Negative S is correctly encoded"], - + ["2147483648", "CHECKSEQUENCEVERIFY", "CHECKSEQUENCEVERIFY", "OK", "CSV passes if stack top bit 1 << 31 is set"], ["", "DEPTH", "P2SH,STRICTENC", "EVAL_FALSE", "Test the test: we should have an empty stack after scriptSig evaluation"], @@ -862,8 +874,6 @@ ["Ensure 100% coverage of discouraged NOPS"], ["1", "NOP1", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"], -["1", "CHECKLOCKTIMEVERIFY", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"], -["1", "CHECKSEQUENCEVERIFY", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"], ["1", "NOP4", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"], ["1", "NOP5", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"], ["1", "NOP6", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"], @@ -2543,22 +2553,22 @@ [["01", "635168", 0.00000001], "", "0 0x20 0xc7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "P2SH,WITNESS", "OK"], [["02", "635168", 0.00000001], "", "0 0x20 0xc7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "P2SH,WITNESS", "OK"], [["0100", "635168", 0.00000001], "", "0 0x20 0xc7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "P2SH,WITNESS", "OK"], -[["", "635168", 0.00000001], "", "0 0x20 0xc7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "P2SH,WITNESS", "EVAL_FALSE"], -[["00", "635168", 0.00000001], "", "0 0x20 0xc7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "P2SH,WITNESS", "EVAL_FALSE"], +[["", "635168", 0.00000001], "", "0 0x20 0xc7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "P2SH,WITNESS", "CLEANSTACK"], +[["00", "635168", 0.00000001], "", "0 0x20 0xc7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "P2SH,WITNESS", "CLEANSTACK"], [["01", "635168", 0.00000001], "", "0 0x20 0xc7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "P2SH,WITNESS,MINIMALIF", "OK"], [["02", "635168", 0.00000001], "", "0 0x20 0xc7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "P2SH,WITNESS,MINIMALIF", "MINIMALIF"], [["0100", "635168", 0.00000001], "", "0 0x20 0xc7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "P2SH,WITNESS,MINIMALIF", "MINIMALIF"], -[["", "635168", 0.00000001], "", "0 0x20 0xc7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "P2SH,WITNESS,MINIMALIF", "EVAL_FALSE"], +[["", "635168", 0.00000001], "", "0 0x20 0xc7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "P2SH,WITNESS,MINIMALIF", "CLEANSTACK"], [["00", "635168", 0.00000001], "", "0 0x20 0xc7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "P2SH,WITNESS,MINIMALIF", "MINIMALIF"], [["635168", 0.00000001], "", "0 0x20 0xc7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "P2SH,WITNESS", "UNBALANCED_CONDITIONAL"], [["635168", 0.00000001], "", "0 0x20 0xc7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "P2SH,WITNESS,MINIMALIF", "UNBALANCED_CONDITIONAL"], ["P2WSH NOTIF 1 ENDIF"], -[["01", "645168", 0.00000001], "", "0 0x20 0xf913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "P2SH,WITNESS", "EVAL_FALSE"], -[["02", "645168", 0.00000001], "", "0 0x20 0xf913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "P2SH,WITNESS", "EVAL_FALSE"], -[["0100", "645168", 0.00000001], "", "0 0x20 0xf913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "P2SH,WITNESS", "EVAL_FALSE"], +[["01", "645168", 0.00000001], "", "0 0x20 0xf913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "P2SH,WITNESS", "CLEANSTACK"], +[["02", "645168", 0.00000001], "", "0 0x20 0xf913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "P2SH,WITNESS", "CLEANSTACK"], +[["0100", "645168", 0.00000001], "", "0 0x20 0xf913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "P2SH,WITNESS", "CLEANSTACK"], [["", "645168", 0.00000001], "", "0 0x20 0xf913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "P2SH,WITNESS", "OK"], [["00", "645168", 0.00000001], "", "0 0x20 0xf913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "P2SH,WITNESS", "OK"], -[["01", "645168", 0.00000001], "", "0 0x20 0xf913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "P2SH,WITNESS,MINIMALIF", "EVAL_FALSE"], +[["01", "645168", 0.00000001], "", "0 0x20 0xf913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "P2SH,WITNESS,MINIMALIF", "CLEANSTACK"], [["02", "645168", 0.00000001], "", "0 0x20 0xf913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "P2SH,WITNESS,MINIMALIF", "MINIMALIF"], [["0100", "645168", 0.00000001], "", "0 0x20 0xf913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "P2SH,WITNESS,MINIMALIF", "MINIMALIF"], [["", "645168", 0.00000001], "", "0 0x20 0xf913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "P2SH,WITNESS,MINIMALIF", "OK"], @@ -2572,22 +2582,22 @@ [["01", "635168", 0.00000001], "0x22 0x0020c7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "HASH160 0x14 0x9b27ee6d9010c21bf837b334d043be5d150e7ba7 EQUAL", "P2SH,WITNESS", "OK"], [["02", "635168", 0.00000001], "0x22 0x0020c7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "HASH160 0x14 0x9b27ee6d9010c21bf837b334d043be5d150e7ba7 EQUAL", "P2SH,WITNESS", "OK"], [["0100", "635168", 0.00000001], "0x22 0x0020c7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "HASH160 0x14 0x9b27ee6d9010c21bf837b334d043be5d150e7ba7 EQUAL", "P2SH,WITNESS", "OK"], -[["", "635168", 0.00000001], "0x22 0x0020c7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "HASH160 0x14 0x9b27ee6d9010c21bf837b334d043be5d150e7ba7 EQUAL", "P2SH,WITNESS", "EVAL_FALSE"], -[["00", "635168", 0.00000001], "0x22 0x0020c7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "HASH160 0x14 0x9b27ee6d9010c21bf837b334d043be5d150e7ba7 EQUAL", "P2SH,WITNESS", "EVAL_FALSE"], +[["", "635168", 0.00000001], "0x22 0x0020c7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "HASH160 0x14 0x9b27ee6d9010c21bf837b334d043be5d150e7ba7 EQUAL", "P2SH,WITNESS", "CLEANSTACK"], +[["00", "635168", 0.00000001], "0x22 0x0020c7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "HASH160 0x14 0x9b27ee6d9010c21bf837b334d043be5d150e7ba7 EQUAL", "P2SH,WITNESS", "CLEANSTACK"], [["01", "635168", 0.00000001], "0x22 0x0020c7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "HASH160 0x14 0x9b27ee6d9010c21bf837b334d043be5d150e7ba7 EQUAL", "P2SH,WITNESS,MINIMALIF", "OK"], [["02", "635168", 0.00000001], "0x22 0x0020c7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "HASH160 0x14 0x9b27ee6d9010c21bf837b334d043be5d150e7ba7 EQUAL", "P2SH,WITNESS,MINIMALIF", "MINIMALIF"], [["0100", "635168", 0.00000001], "0x22 0x0020c7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "HASH160 0x14 0x9b27ee6d9010c21bf837b334d043be5d150e7ba7 EQUAL", "P2SH,WITNESS,MINIMALIF", "MINIMALIF"], -[["", "635168", 0.00000001], "0x22 0x0020c7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "HASH160 0x14 0x9b27ee6d9010c21bf837b334d043be5d150e7ba7 EQUAL", "P2SH,WITNESS,MINIMALIF", "EVAL_FALSE"], +[["", "635168", 0.00000001], "0x22 0x0020c7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "HASH160 0x14 0x9b27ee6d9010c21bf837b334d043be5d150e7ba7 EQUAL", "P2SH,WITNESS,MINIMALIF", "CLEANSTACK"], [["00", "635168", 0.00000001], "0x22 0x0020c7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "HASH160 0x14 0x9b27ee6d9010c21bf837b334d043be5d150e7ba7 EQUAL", "P2SH,WITNESS,MINIMALIF", "MINIMALIF"], [["635168", 0.00000001], "0x22 0x0020c7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "HASH160 0x14 0x9b27ee6d9010c21bf837b334d043be5d150e7ba7 EQUAL", "P2SH,WITNESS", "UNBALANCED_CONDITIONAL"], [["635168", 0.00000001], "0x22 0x0020c7eaf06d5ae01a58e376e126eb1e6fab2036076922b96b2711ffbec1e590665d", "HASH160 0x14 0x9b27ee6d9010c21bf837b334d043be5d150e7ba7 EQUAL", "P2SH,WITNESS,MINIMALIF", "UNBALANCED_CONDITIONAL"], ["P2SH-P2WSH NOTIF 1 ENDIF"], -[["01", "645168", 0.00000001], "0x22 0x0020f913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "HASH160 0x14 0xdbb7d1c0a56b7a9c423300c8cca6e6e065baf1dc EQUAL", "P2SH,WITNESS", "EVAL_FALSE"], -[["02", "645168", 0.00000001], "0x22 0x0020f913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "HASH160 0x14 0xdbb7d1c0a56b7a9c423300c8cca6e6e065baf1dc EQUAL", "P2SH,WITNESS", "EVAL_FALSE"], -[["0100", "645168", 0.00000001], "0x22 0x0020f913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "HASH160 0x14 0xdbb7d1c0a56b7a9c423300c8cca6e6e065baf1dc EQUAL", "P2SH,WITNESS", "EVAL_FALSE"], +[["01", "645168", 0.00000001], "0x22 0x0020f913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "HASH160 0x14 0xdbb7d1c0a56b7a9c423300c8cca6e6e065baf1dc EQUAL", "P2SH,WITNESS", "CLEANSTACK"], +[["02", "645168", 0.00000001], "0x22 0x0020f913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "HASH160 0x14 0xdbb7d1c0a56b7a9c423300c8cca6e6e065baf1dc EQUAL", "P2SH,WITNESS", "CLEANSTACK"], +[["0100", "645168", 0.00000001], "0x22 0x0020f913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "HASH160 0x14 0xdbb7d1c0a56b7a9c423300c8cca6e6e065baf1dc EQUAL", "P2SH,WITNESS", "CLEANSTACK"], [["", "645168", 0.00000001], "0x22 0x0020f913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "HASH160 0x14 0xdbb7d1c0a56b7a9c423300c8cca6e6e065baf1dc EQUAL", "P2SH,WITNESS", "OK"], [["00", "645168", 0.00000001], "0x22 0x0020f913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "HASH160 0x14 0xdbb7d1c0a56b7a9c423300c8cca6e6e065baf1dc EQUAL", "P2SH,WITNESS", "OK"], -[["01", "645168", 0.00000001], "0x22 0x0020f913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "HASH160 0x14 0xdbb7d1c0a56b7a9c423300c8cca6e6e065baf1dc EQUAL", "P2SH,WITNESS,MINIMALIF", "EVAL_FALSE"], +[["01", "645168", 0.00000001], "0x22 0x0020f913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "HASH160 0x14 0xdbb7d1c0a56b7a9c423300c8cca6e6e065baf1dc EQUAL", "P2SH,WITNESS,MINIMALIF", "CLEANSTACK"], [["02", "645168", 0.00000001], "0x22 0x0020f913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "HASH160 0x14 0xdbb7d1c0a56b7a9c423300c8cca6e6e065baf1dc EQUAL", "P2SH,WITNESS,MINIMALIF", "MINIMALIF"], [["0100", "645168", 0.00000001], "0x22 0x0020f913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "HASH160 0x14 0xdbb7d1c0a56b7a9c423300c8cca6e6e065baf1dc EQUAL", "P2SH,WITNESS,MINIMALIF", "MINIMALIF"], [["", "645168", 0.00000001], "0x22 0x0020f913eacf2e38a5d6fc3a8311d72ae704cb83866350a984dd3e5eb76d2a8c28e8", "HASH160 0x14 0xdbb7d1c0a56b7a9c423300c8cca6e6e065baf1dc EQUAL", "P2SH,WITNESS,MINIMALIF", "OK"], diff --git a/test/data/sighash-tests.json b/test/data/core-data/sighash-tests.json similarity index 100% rename from test/data/sighash-tests.json rename to test/data/core-data/sighash-tests.json diff --git a/test/data/tx-invalid.json b/test/data/core-data/tx-invalid.json similarity index 80% rename from test/data/tx-invalid.json rename to test/data/core-data/tx-invalid.json index 2235bd0ae..d22175d66 100644 --- a/test/data/tx-invalid.json +++ b/test/data/core-data/tx-invalid.json @@ -92,11 +92,11 @@ [[["60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1", 0, "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"]], "0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a010047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", "P2SH,NULLDUMMY"], -["As above, but using a OP_1"], +["As above, but using an OP_1"], [[["60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1", 0, "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"]], "0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000495147304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", "P2SH,NULLDUMMY"], -["As above, but using a OP_1NEGATE"], +["As above, but using an OP_1NEGATE"], [[["60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1", 0, "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"]], "0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000494f47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", "P2SH,NULLDUMMY"], @@ -205,7 +205,7 @@ [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4259839 CHECKSEQUENCEVERIFY 1"]], "020000000100010000000000000000000000000000000000000000000000000000000000000000000000feff40000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], -["By-time locks, with argument just beyond txin.nSequence (but within numerical boundries)"], +["By-time locks, with argument just beyond txin.nSequence (but within numerical boundaries)"], [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4194305 CHECKSEQUENCEVERIFY 1"]], "020000000100010000000000000000000000000000000000000000000000000000000000000000000000000040000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4259839 CHECKSEQUENCEVERIFY 1"]], @@ -321,7 +321,7 @@ ["where the pubkey is obtained through key recovery with sig and the wrong sighash."], ["This is to show that FindAndDelete is applied only to non-segwit scripts"], ["To show that the tests are 'correctly wrong', they should pass by modifying OP_CHECKSIG under interpreter.cpp"], -["by replacing (sigversion == SIGVERSION_BASE) with (sigversion != SIGVERSION_BASE)"], +["by replacing (sigversion == SigVersion::BASE) with (sigversion != SigVersion::BASE)"], ["Non-segwit: wrong sighash (without FindAndDelete) = 1ba1fe3bc90c5d1265460e684ce6774e324f0fabdf67619eda729e64e8b6bc08"], [[["f18783ace138abac5d3a7a5cf08e88fe6912f267ef936452e0c27d090621c169", 7000, "HASH160 0x14 0x0c746489e2d83cdbb5b90b432773342ba809c134 EQUAL", 200000]], "010000000169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f1581b0000b64830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e012103b12a1ec8428fc74166926318c15e17408fea82dbb157575e16a8c365f546248f4aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e01ffffffff0101000000000000000000000000", "P2SH,WITNESS"], @@ -332,7 +332,7 @@ ["Script is 2 CHECKMULTISIGVERIFY DROP"], ["52af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960175"], ["Signature is 0 2 "], -["Should pass by replacing (sigversion == SIGVERSION_BASE) with (sigversion != SIGVERSION_BASE) under OP_CHECKMULTISIG"], +["Should pass by replacing (sigversion == SigVersion::BASE) with (sigversion != SigVersion::BASE) under OP_CHECKMULTISIG"], ["Non-segwit: wrong sighash (without FindAndDelete) = 4bc6a53e8e16ef508c19e38bba08831daba85228b0211f323d4cb0999cf2a5e8"], [[["9628667ad48219a169b41b020800162287d2c0f713c04157e95c484a8dcb7592", 7000, "HASH160 0x14 0x5748407f5ca5cdca53ba30b79040260770c9ee1b EQUAL", 200000]], "01000000019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a662896581b0000fd6f01004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596015221023fd5dd42b44769c5653cbc5947ff30ab8871f240ad0c0e7432aefe84b5b4ff3421039d52178dbde360b83f19cf348deb04fa8360e1bf5634577be8e50fafc2b0e4ef4c9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960175ffffffff0101000000000000000000000000", "P2SH,WITNESS"], @@ -340,5 +340,53 @@ [[["9628667ad48219a169b41b020800162287d2c0f713c04157e95c484a8dcb7592", 7500, "0x00 0x20 0x9b66c15b4e0b4eb49fa877982cafded24859fe5b0e2dbfbe4f0df1de7743fd52", 200000]], "010000000001019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a6628964c1d000000ffffffff0101000000000000000007004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c03959601010221023cb6055f4b57a1580c5a753e19610cafaedf7e0ff377731c77837fd666eae1712102c1b1db303ac232ffa8e5e7cc2cf5f96c6e40d3e6914061204c0541cb2043a0969552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596017500000000", "P2SH,WITNESS"], +["SCRIPT_VERIFY_CONST_SCRIPTCODE tests"], +["All transactions are copied from OP_CODESEPARATOR tests in tx_valid.json"], + +[[["bc7fd132fcf817918334822ee6d9bd95c889099c96e07ca2c1eb2cc70db63224", 0, "CODESEPARATOR 0x21 0x038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041 CHECKSIG"]], + "01000000012432b60dc72cebc1a27ce0969c0989c895bdd9e62e8234839117f8fc32d17fbc000000004a493046022100a576b52051962c25e642c0fd3d77ee6c92487048e5d90818bcf5b51abaccd7900221008204f8fb121be4ec3b24483b1f92d89b1b0548513a134e345c5442e86e8617a501ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], +[[["83e194f90b6ef21fa2e3a365b63794fb5daa844bdc9b25de30899fcfe7b01047", 0, "CODESEPARATOR CODESEPARATOR 0x21 0x038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041 CHECKSIG"]], + "01000000014710b0e7cf9f8930de259bdc4b84aa5dfb9437b665a3e3a21ff26e0bf994e183000000004a493046022100a166121a61b4eeb19d8f922b978ff6ab58ead8a5a5552bf9be73dc9c156873ea02210092ad9bc43ee647da4f6652c320800debcf08ec20a094a0aaf085f63ecb37a17201ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +[[["326882a7f22b5191f1a0cc9962ca4b878cd969cf3b3a70887aece4d801a0ba5e", 0, "0x21 0x038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041 CODESEPARATOR CHECKSIG"]], + "01000000015ebaa001d8e4ec7a88703a3bcf69d98c874bca6299cca0f191512bf2a7826832000000004948304502203bf754d1c6732fbf87c5dcd81258aefd30f2060d7bd8ac4a5696f7927091dad1022100f5bcb726c4cf5ed0ed34cc13dadeedf628ae1045b7cb34421bc60b89f4cecae701ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +[[["a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944", 0, "0x21 0x038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041 CHECKSIGVERIFY CODESEPARATOR 0x21 0x038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041 CHECKSIGVERIFY CODESEPARATOR 1"]], + "010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a900000000924830450221009c0a27f886a1d8cb87f6f595fbc3163d28f7a81ec3c4b252ee7f3ac77fd13ffa02203caa8dfa09713c8c4d7ef575c75ed97812072405d932bd11e6a1593a98b679370148304502201e3861ef39a526406bad1e20ecad06be7375ad40ddb582c9be42d26c3a0d7b240221009d0a3985e96522e59635d19cc4448547477396ce0ef17a58e7d74c3ef464292301ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +["CODESEPARATOR in an unexecuted IF block is still invalid"], +[[["a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944", 0, "IF CODESEPARATOR ENDIF 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 CHECKSIGVERIFY CODESEPARATOR 1"]], + "010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a48304502207a6974a77c591fa13dff60cabbb85a0de9e025c09c65a4b2285e47ce8e22f761022100f0efaac9ff8ac36b10721e0aae1fb975c90500b50c56e8a0cc52b0403f0425dd0100ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +["CODESEPARATOR in an executed IF block is invalid"], +[[["a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944", 0, "IF CODESEPARATOR ENDIF 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 CHECKSIGVERIFY CODESEPARATOR 1"]], + "010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a483045022100fa4a74ba9fd59c59f46c3960cf90cbe0d2b743c471d24a3d5d6db6002af5eebb02204d70ec490fd0f7055a7c45f86514336e3a7f03503dacecabb247fc23f15c83510151ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + + +["Using CHECKSIG with signatures in scriptSigs will trigger FindAndDelete, which is invalid"], +[[["ccf7f4053a02e653c36ac75c891b7496d0dc5ce5214f6c913d9cf8f1329ebee0", 0, "DUP HASH160 0x14 0xee5a6aa40facefb2655ac23c0c28c57c65c41f9b EQUALVERIFY CHECKSIG"]], + "0100000001e0be9e32f1f89c3d916c4f21e55cdcd096741b895cc76ac353e6023a05f4f7cc00000000d86149304602210086e5f736a2c3622ebb62bd9d93d8e5d76508b98be922b97160edc3dcca6d8c47022100b23c312ac232a4473f19d2aeb95ab7bdf2b65518911a0d72d50e38b5dd31dc820121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac4730440220508fa761865c8abd81244a168392876ee1d94e8ed83897066b5e2df2400dad24022043f5ee7538e87e9c6aef7ef55133d3e51da7cc522830a9c4d736977a76ef755c0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +["OP_CODESEPARATOR in scriptSig is invalid"], +[[["10c9f0effe83e97f80f067de2b11c6a00c3088a4bce42c5ae761519af9306f3c", 1, "DUP HASH160 0x14 0xee5a6aa40facefb2655ac23c0c28c57c65c41f9b EQUALVERIFY CHECKSIG"]], + "01000000013c6f30f99a5161e75a2ce4bca488300ca0c6112bde67f0807fe983feeff0c91001000000e608646561646265656675ab61493046022100ce18d384221a731c993939015e3d1bcebafb16e8c0b5b5d14097ec8177ae6f28022100bcab227af90bab33c3fe0a9abfee03ba976ee25dc6ce542526e9b2e56e14b7f10121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac493046022100c3b93edcc0fd6250eb32f2dd8a0bba1754b0f6c3be8ed4100ed582f3db73eba2022100bf75b5bd2eff4d6bf2bda2e34a40fcc07d4aa3cf862ceaa77b47b81eff829f9a01ab21038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +["Again, FindAndDelete() in scriptSig"], +[[["6056ebd549003b10cbbd915cea0d82209fe40b8617104be917a26fa92cbe3d6f", 0, "DUP HASH160 0x14 0xee5a6aa40facefb2655ac23c0c28c57c65c41f9b EQUALVERIFY CHECKSIG"]], + "01000000016f3dbe2ca96fa217e94b1017860be49f20820dea5c91bdcb103b0049d5eb566000000000fd1d0147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac47304402203757e937ba807e4a5da8534c17f9d121176056406a6465054bdd260457515c1a02200f02eccf1bec0f3a0d65df37889143c2e88ab7acec61a7b6f5aa264139141a2b0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +[[["5a6b0021a6042a686b6b94abc36b387bef9109847774e8b1e51eb8cc55c53921", 1, "DUP HASH160 0x14 0xee5a6aa40facefb2655ac23c0c28c57c65c41f9b EQUALVERIFY CHECKSIG"]], + "01000000012139c555ccb81ee5b1e87477840991ef7b386bc3ab946b6b682a04a621006b5a01000000fdb40148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f2204148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390175ac4830450220646b72c35beeec51f4d5bc1cbae01863825750d7f490864af354e6ea4f625e9c022100f04b98432df3a9641719dbced53393022e7249fb59db993af1118539830aab870148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a580039017521038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +["FindAndDelete() in redeemScript"], +[[["b5b598de91787439afd5938116654e0b16b7a0d0f82742ba37564219c5afcbf9", 0, "DUP HASH160 0x14 0xf6f365c40f0739b61de827a44751e5e99032ed8f EQUALVERIFY CHECKSIG"], + ["ab9805c6d57d7070d9a42c5176e47bb705023e6b67249fb6760880548298e742", 0, "HASH160 0x14 0xd8dacdadb7462ae15cd906f1878706d0da8660e6 EQUAL"]], + "0100000002f9cbafc519425637ba4227f8d0a0b7160b4e65168193d5af39747891de98b5b5000000006b4830450221008dd619c563e527c47d9bd53534a770b102e40faa87f61433580e04e271ef2f960220029886434e18122b53d5decd25f1f4acb2480659fea20aabd856987ba3c3907e0121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffff42e7988254800876b69f24676b3e0205b77be476512ca4d970707dd5c60598ab00000000fd260100483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a53034930460221008431bdfa72bc67f9d41fe72e94c88fb8f359ffa30b33c72c121c5a877d922e1002210089ef5fc22dd8bfc6bf9ffdb01a9862d27687d424d1fefbab9e9c7176844a187a014c9052483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c7153aeffffffff01a08601000000000017a914d8dacdadb7462ae15cd906f1878706d0da8660e68700000000", "P2SH,CONST_SCRIPTCODE"], + +["FindAndDelete() in bare CHECKMULTISIG"], +[[["ceafe58e0f6e7d67c0409fbbf673c84c166e3c5d3c24af58f7175b18df3bb3db", 0, "DUP HASH160 0x14 0xf6f365c40f0739b61de827a44751e5e99032ed8f EQUALVERIFY CHECKSIG"], + ["ceafe58e0f6e7d67c0409fbbf673c84c166e3c5d3c24af58f7175b18df3bb3db", 1, "2 0x48 0x3045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 3 CHECKMULTISIG"]], + "0100000002dbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce000000006b4830450221009627444320dc5ef8d7f68f35010b4c050a6ed0d96b67a84db99fda9c9de58b1e02203e4b4aaa019e012e65d69b487fdf8719df72f488fa91506a80c49a33929f1fd50121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffffdbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce010000009300483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303ffffffff01a0860100000000001976a9149bc0bbdd3024da4d0c38ed1aecf5c68dd1d3fa1288ac00000000", "P2SH,CONST_SCRIPTCODE"], + ["Make diffs cleaner by leaving a comment here without comma at the end"] ] diff --git a/test/data/tx-valid.json b/test/data/core-data/tx-valid.json similarity index 96% rename from test/data/tx-valid.json rename to test/data/core-data/tx-valid.json index e6b382af1..4a1c77166 100644 --- a/test/data/tx-valid.json +++ b/test/data/core-data/tx-valid.json @@ -23,11 +23,11 @@ [[["60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1", 0, "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"]], "0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a01ff47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", "P2SH"], -["As above, but using a OP_1"], +["As above, but using an OP_1"], [[["60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1", 0, "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"]], "0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000495147304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", "P2SH"], -["As above, but using a OP_1NEGATE"], +["As above, but using an OP_1NEGATE"], [[["60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1", 0, "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"]], "0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000494f47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", "P2SH"], @@ -45,7 +45,7 @@ "01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000", "P2SH"], ["The following is f7fdd091fa6d8f5e7a8c2458f5c38faffff2d3f1406b6e4fe2c99dcc0d2d1cbb"], -["It caught a bug in the workaround for 23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63 in an overly simple implementation"], +["It caught a bug in the workaround for 23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63 in an overly simple implementation. In a signature, it contains an ASN1 integer which isn't strict-DER conformant due to being negative, which doesn't make sense in a signature. Before BIP66 activated, it was a valid signature. After it activated, it's not valid any more."], [[["b464e85df2a238416f8bdae11d120add610380ea07f4ef19c5f9dfd472f96c3d", 0, "DUP HASH160 0x14 0xbef80ecf3a44500fda1bc92176e442891662aed2 EQUALVERIFY CHECKSIG"], ["b7978cc96e59a8b13e0865d3f95657561a7f725be952438637475920bac9eb21", 1, "DUP HASH160 0x14 0xbef80ecf3a44500fda1bc92176e442891662aed2 EQUALVERIFY CHECKSIG"]], "01000000023d6cf972d4dff9c519eff407ea800361dd0a121de1da8b6f4138a2f25de864b4000000008a4730440220ffda47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e022049cffa1cdc102a0b56e0e04913606c70af702a1149dc3b305ab9439288fee090014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff21ebc9ba20594737864352e95b727f1a565756f9d365083eb1a8596ec98c97b7010000008a4730440220503ff10e9f1e0de731407a4a245531c9ff17676eda461f8ceeb8c06049fa2c810220c008ac34694510298fa60b3f000df01caa244f165b727d4896eb84f81e46bcc4014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff01f0da5200000000001976a914857ccd42dded6df32949d4646dfa10a92458cfaa88ac00000000", "P2SH"], @@ -56,6 +56,12 @@ [[["0000000000000000000000000000000000000000000000000000000000000200", 0, "1"], ["0000000000000000000000000000000000000000000000000000000000000100", 0, "DUP HASH160 0x14 0xe52b482f2faa8ecbf0db344f93c84ac908557f33 EQUALVERIFY CHECKSIG"]], "01000000020002000000000000000000000000000000000000000000000000000000000000000000000151ffffffff0001000000000000000000000000000000000000000000000000000000000000000000006b483045022100c9cdd08798a28af9d1baf44a6c77bcc7e279f47dc487c8c899911bc48feaffcc0220503c5c50ae3998a733263c5c0f7061b483e2b56c4c41b456e7d2f5a78a74c077032102d5c25adb51b61339d2b05315791e21bbe80ea470a49db0135720983c905aace0ffffffff010000000000000000015100000000", "P2SH"], +["The following tests SIGHASH_SINGLE|SIGHASHANYONECANPAY inputs"], +[[["437a1002eb125dec0f93f635763e0ae45f28ff8e81d82945753d0107611cd390", 1, "DUP HASH160 0x14 0x383fb81cb0a3fc724b5e08cf8bbd404336d711f6 EQUALVERIFY CHECKSIG"], + ["2d48d32ccad087bcda0ac5b31555bd58d1d2568184cbc8e752dd2be2684af03f", 1, "DUP HASH160 0x14 0x275ec2a233e5b23d43fa19e7bf9beb0cb3996117 EQUALVERIFY CHECKSIG"], + ["c76168ef1a272a4f176e55e73157ecfce040cfad16a5272f6296eb7089dca846", 1, "DUP HASH160 0x14 0x34fea2c5a75414fd945273ae2d029ce1f28dafcf EQUALVERIFY CHECKSIG"]], +"010000000390d31c6107013d754529d8818eff285fe40a3e7635f6930fec5d12eb02107a43010000006b483045022100f40815ae3c81a0dd851cc8d376d6fd226c88416671346a9033468cca2cdcc6c202204f764623903e6c4bed1b734b75d82c40f1725e4471a55ad4f51218f86130ac038321033d710ab45bb54ac99618ad23b3c1da661631aa25f23bfe9d22b41876f1d46e4effffffff3ff04a68e22bdd52e7c8cb848156d2d158bd5515b3c50adabc87d0ca2cd3482d010000006a4730440220598d263c107004008e9e26baa1e770be30fd31ee55ded1898f7c00da05a75977022045536bead322ca246779698b9c3df3003377090f41afeca7fb2ce9e328ec4af2832102b738b531def73020bd637f32935924cc88549c8206976226d968edd3a42fc2d7ffffffff46a8dc8970eb96622f27a516adcf40e0fcec5731e7556e174f2a271aef6861c7010000006b483045022100c5b90a777a9fdc90c208dbef7290d1fc1be651f47151ee4ccff646872a454cf90220640cfbc4550446968fbbe9d12528f3adf7d87b31541569c59e790db8a220482583210391332546e22bbe8fe3af54addfad6f8b83d05fa4f5e047593d4c07ae938795beffffffff028036be26000000001976a914ddfb29efad43a667465ac59ff14dc6442a1adfca88ac3d5cba01000000001976a914b64dde7a505a13ca986c40e86e984a8dc81368b688ac00000000", "P2SH"], + ["An invalid P2SH Transaction"], [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7a052c840ba73af26755de42cf01cc9e0a49fef0 EQUAL"]], "010000000100010000000000000000000000000000000000000000000000000000000000000000000009085768617420697320ffffffff010000000000000000015100000000", "NONE"], @@ -471,17 +477,17 @@ ["BIP143 example: P2WSH with OP_CODESEPARATOR and out-of-range SIGHASH_SINGLE."], [[["6eb316926b1c5d567cd6f5e6a84fec606fc53d7b474526d1fff3948020c93dfe", 0, "0x21 0x036d5c20fa14fb2f635474c1dc4ef5909d4568e5569b79fc94d3448486e14685f8 CHECKSIG", 156250000], ["f825690aee1b3dc247da796cacb12687a5e802429fd291cfd63e010f02cf1508", 0, "0x00 0x20 0x5d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0", 4900000000]], -"01000000000102fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e000000004847304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac000347304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503473044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e27034721026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac00000000", "P2SH,WITNESS"], +"01000000000102fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e000000004847304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac000347304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503473044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e27034721026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac00000000", "P2SH,WITNESS,CONST_SCRIPTCODE"], ["BIP143 example: P2WSH with unexecuted OP_CODESEPARATOR and SINGLE|ANYONECANPAY"], [[["01c0cf7fba650638e55eb91261b183251fbb466f90dff17f10086817c542b5e9", 0, "0x00 0x20 0xba468eea561b26301e4cf69fa34bde4ad60c81e70f059f045ca9a79931004a4d", 16777215], ["1b2a9a426ba603ba357ce7773cb5805cb9c7c2b386d100d1fc9263513188e680", 0, "0x00 0x20 0xd9bbfbe56af7c4b7f960a70d7ea107156913d9e5a26b0a71429df5e097ca6537", 16777215]], -"01000000000102e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000", "P2SH,WITNESS"], +"01000000000102e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000", "P2SH,WITNESS,CONST_SCRIPTCODE"], ["BIP143 example: Same as the previous example with input-output pairs swapped"], [[["1b2a9a426ba603ba357ce7773cb5805cb9c7c2b386d100d1fc9263513188e680", 0, "0x00 0x20 0xd9bbfbe56af7c4b7f960a70d7ea107156913d9e5a26b0a71429df5e097ca6537", 16777215], ["01c0cf7fba650638e55eb91261b183251fbb466f90dff17f10086817c542b5e9", 0, "0x00 0x20 0xba468eea561b26301e4cf69fa34bde4ad60c81e70f059f045ca9a79931004a4d", 16777215]], -"0100000000010280e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffffe9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff0280969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac80969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000", "P2SH,WITNESS"], +"0100000000010280e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffffe9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff0280969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac80969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000", "P2SH,WITNESS,CONST_SCRIPTCODE"], ["BIP143 example: P2SH-P2WSH 6-of-6 multisig signed with 6 different SIGHASH types"], [[["6eb98797a21c6c10aa74edf29d618be109f48a8e94c694f3701e08ca69186436", 1, "HASH160 0x14 0x9993a429037b5d912407a71c252019287b8d27a5 EQUAL", 987654321]], @@ -498,7 +504,7 @@ "010000000169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f1581b0000b64830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0121037a3fb04bcdb09eba90f69961ba1692a3528e45e67c85b200df820212d7594d334aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e01ffffffff0101000000000000000000000000", "P2SH,WITNESS"], ["BIP143: correct sighash (without FindAndDelete) = 71c9cd9b2869b9c70b01b1f0360c148f42dee72297db312638df136f43311f23"], [[["f18783ace138abac5d3a7a5cf08e88fe6912f267ef936452e0c27d090621c169", 7500, "0x00 0x20 0x9e1be07558ea5cc8e02ed1d80c0911048afad949affa36d5c3951e3159dbea19", 200000]], -"0100000000010169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f14c1d000000ffffffff01010000000000000000034830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e012102a9781d66b61fb5a7ef00ac5ad5bc6ffc78be7b44a566e3c87870e1079368df4c4aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0100000000", "P2SH,WITNESS"], +"0100000000010169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f14c1d000000ffffffff01010000000000000000034830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e012102a9781d66b61fb5a7ef00ac5ad5bc6ffc78be7b44a566e3c87870e1079368df4c4aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0100000000", "P2SH,WITNESS,CONST_SCRIPTCODE"], ["This is multisig version of the FindAndDelete tests"], ["Script is 2 CHECKMULTISIGVERIFY DROP"], ["52af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960175"], @@ -508,7 +514,11 @@ "01000000019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a662896581b0000fd6f01004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c03959601522102cd74a2809ffeeed0092bc124fd79836706e41f048db3f6ae9df8708cefb83a1c2102e615999372426e46fd107b76eaf007156a507584aa2cc21de9eee3bdbd26d36c4c9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960175ffffffff0101000000000000000000000000", "P2SH,WITNESS"], ["BIP143: correct sighash (without FindAndDelete) = c1628a1e7c67f14ca0c27c06e4fdeec2e6d1a73c7a91d7c046ff83e835aebb72"], [[["9628667ad48219a169b41b020800162287d2c0f713c04157e95c484a8dcb7592", 7500, "0x00 0x20 0x9b66c15b4e0b4eb49fa877982cafded24859fe5b0e2dbfbe4f0df1de7743fd52", 200000]], -"010000000001019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a6628964c1d000000ffffffff0101000000000000000007004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960101022102966f109c54e85d3aee8321301136cedeb9fc710fdef58a9de8a73942f8e567c021034ffc99dd9a79dd3cb31e2ab3e0b09e0e67db41ac068c625cd1f491576016c84e9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596017500000000", "P2SH,WITNESS"], +"010000000001019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a6628964c1d000000ffffffff0101000000000000000007004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960101022102966f109c54e85d3aee8321301136cedeb9fc710fdef58a9de8a73942f8e567c021034ffc99dd9a79dd3cb31e2ab3e0b09e0e67db41ac068c625cd1f491576016c84e9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596017500000000", "P2SH,WITNESS,CONST_SCRIPTCODE"], + +["Test long outputs, which are streamed using length-prefixed bitcoin strings. This might be surprising."], +[[["1111111111111111111111111111111111111111111111111111111111111111", 0, "0x00 0x14 0x751e76e8199196d454941c45d1b3a323f1433bd6", 5000000]], +"0100000000010111111111111111111111111111111111111111111111111111111111111111110000000000ffffffff0130244c0000000000fd02014cdc1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111175210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac02483045022100c1a4a6581996a7fdfea77d58d537955a5655c1d619b6f3ab6874f28bb2e19708022056402db6fede03caae045a3be616a1a2d0919a475ed4be828dc9ff21f24063aa01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179800000000", "P2SH,WITNESS"], ["Make diffs cleaner by leaving a comment here without comma at the end"] ] diff --git a/test/gcs-test.js b/test/gcs-test.js index a4a947628..e69de29bb 100644 --- a/test/gcs-test.js +++ b/test/gcs-test.js @@ -1,177 +0,0 @@ -/* eslint-env mocha */ -/* eslint prefer-arrow-callback: "off" */ - -'use strict'; - -const assert = require('./util/assert'); -const GCSFilter = require('../lib/utils/gcs'); -const random = require('../lib/crypto/random'); -const Outpoint = require('../lib/primitives/outpoint'); -const Address = require('../lib/primitives/address'); -const common = require('./util/common'); - -const block928927 = common.readBlock('block928927'); -const [block] = block928927.getBlock(); - -const key = random.randomBytes(16); -const P = 20; - -const contents1 = [ - Buffer.from('Alex', 'ascii'), - Buffer.from('Bob', 'ascii'), - Buffer.from('Charlie', 'ascii'), - Buffer.from('Dick', 'ascii'), - Buffer.from('Ed', 'ascii'), - Buffer.from('Frank', 'ascii'), - Buffer.from('George', 'ascii'), - Buffer.from('Harry', 'ascii'), - Buffer.from('Ilya', 'ascii'), - Buffer.from('John', 'ascii'), - Buffer.from('Kevin', 'ascii'), - Buffer.from('Larry', 'ascii'), - Buffer.from('Michael', 'ascii'), - Buffer.from('Nate', 'ascii'), - Buffer.from('Owen', 'ascii'), - Buffer.from('Paul', 'ascii'), - Buffer.from('Quentin', 'ascii') -]; - -const contents2 = [ - Buffer.from('Alice', 'ascii'), - Buffer.from('Betty', 'ascii'), - Buffer.from('Charmaine', 'ascii'), - Buffer.from('Donna', 'ascii'), - Buffer.from('Edith', 'ascii'), - Buffer.from('Faina', 'ascii'), - Buffer.from('Georgia', 'ascii'), - Buffer.from('Hannah', 'ascii'), - Buffer.from('Ilsbeth', 'ascii'), - Buffer.from('Jennifer', 'ascii'), - Buffer.from('Kayla', 'ascii'), - Buffer.from('Lena', 'ascii'), - Buffer.from('Michelle', 'ascii'), - Buffer.from('Natalie', 'ascii'), - Buffer.from('Ophelia', 'ascii'), - Buffer.from('Peggy', 'ascii'), - Buffer.from('Queenie', 'ascii') -]; - -const op1 = new Outpoint( - '4cba1d1753ed19dbeafffb1a6c805d20e4af00b194a8f85353163cef83319c2c', - 4); - -const op2 = new Outpoint( - 'b7c3c4bce1a23baef2da05f9b7e4bff813449ec7e80f980ec7e4cacfadcd3314', - 3); - -const op3 = new Outpoint( - '4cba1d1753ed19dbeafffb1a6c805d20e4af00b194a8f85353163cef83319c2c', - 400); - -const op4 = new Outpoint( - 'b7c3c4bce1a23baef2da05f9b7e4bff813449ec7e80f980ec7e4cacfadcd3314', - 300); - -const addr1 = new Address('ltc1qmyrddmxglk49ye2wd29wefaavw7es8k5fgws8k'); -const addr2 = new Address('ltc1q4645ycu0l9pnvxaxnhemushv0w4cd9fljudpvz'); - -let filter1 = null; -let filter2 = null; -let filter3 = null; -let filter4 = null; -let filter5 = null; - -describe('GCS', function() { - it('should test GCS filter build', () => { - filter1 = GCSFilter.fromItems(P, key, contents1); - assert(filter1); - }); - - it('should test GCS filter copy', () => { - filter2 = GCSFilter.fromBytes(filter1.n, P, filter1.toBytes()); - assert(filter2); - filter3 = GCSFilter.fromNBytes(P, filter1.toNBytes()); - assert(filter3); - filter4 = GCSFilter.fromPBytes(filter1.n, filter1.toPBytes()); - assert(filter4); - filter5 = GCSFilter.fromNPBytes(filter1.toNPBytes()); - assert(filter5); - }); - - it('should test GCS filter metadata', () => { - assert.strictEqual(filter1.p, P); - assert.strictEqual(filter1.n, contents1.length); - assert.strictEqual(filter1.p, filter2.p); - assert.strictEqual(filter1.n, filter2.n); - assert.bufferEqual(filter1.data, filter2.data); - assert.strictEqual(filter1.p, filter3.p); - assert.strictEqual(filter1.n, filter3.n); - assert.bufferEqual(filter1.data, filter3.data); - assert.strictEqual(filter1.p, filter4.p); - assert.strictEqual(filter1.n, filter4.n); - assert.bufferEqual(filter1.data, filter4.data); - assert.strictEqual(filter1.p, filter5.p); - assert.strictEqual(filter1.n, filter5.n); - assert.bufferEqual(filter1.data, filter5.data); - }); - - it('should test GCS filter match', () => { - let match = filter1.match(key, Buffer.from('Nate')); - assert(match); - match = filter2.match(key, Buffer.from('Nate')); - assert(match); - match = filter1.match(key, Buffer.from('Quentin')); - assert(match); - match = filter2.match(key, Buffer.from('Quentin')); - assert(match); - - match = filter1.match(key, Buffer.from('Nates')); - assert(!match); - match = filter2.match(key, Buffer.from('Nates')); - assert(!match); - match = filter1.match(key, Buffer.from('Quentins')); - assert(!match); - match = filter2.match(key, Buffer.from('Quentins')); - assert(!match); - }); - - it('should test GCS filter matchAny', () => { - let match = filter1.matchAny(key, contents2); - assert(!match); - match = filter2.matchAny(key, contents2); - assert(!match); - - const contents = contents2.slice(); - contents.push(Buffer.from('Nate')); - - match = filter1.matchAny(key, contents); - assert(match); - match = filter2.matchAny(key, contents); - assert(match); - }); - - it('should test GCS filter fromBlock', () => { - const key = block.hash().slice(0, 16); - const filter = GCSFilter.fromBlock(block); - assert(filter.match(key, op1.toRaw())); - assert(filter.match(key, op2.toRaw())); - assert(!filter.match(key, op3.toRaw())); - assert(!filter.match(key, op4.toRaw())); - assert(filter.match(key, addr1.hash)); - assert(filter.match(key, addr2.hash)); - assert(filter.matchAny(key, [op1.toRaw(), addr1.hash])); - assert(filter.matchAny(key, [op1.toRaw(), op3.toRaw()])); - assert(!filter.matchAny(key, [op3.toRaw(), op4.toRaw()])); - }); - - it('should test GCS filter fromExtended', () => { - const key = block.hash().slice(0, 16); - const filter = GCSFilter.fromExtended(block); - assert(!filter.match(key, op1.toRaw())); - assert(filter.match(key, block.txs[0].hash())); - assert(filter.match(key, block.txs[1].hash())); - assert(filter.matchAny(key, [block.txs[0].hash(), block.txs[1].hash()])); - assert(filter.matchAny(key, [op1.toRaw(), block.txs[1].hash()])); - assert(!filter.matchAny(key, [op1.toRaw(), op2.toRaw()])); - }); -}); diff --git a/test/hd-test.js b/test/hd-test.js index 7b1293c59..d92cf97a9 100644 --- a/test/hd-test.js +++ b/test/hd-test.js @@ -3,13 +3,15 @@ 'use strict'; -const assert = require('./util/assert'); +const {base58} = require('bstring'); +const pbkdf2 = require('bcrypto/lib/pbkdf2'); +const sha512 = require('bcrypto/lib/sha512'); +const assert = require('bsert'); const HD = require('../lib/hd'); -const base58 = require('../lib/utils/base58'); -const pbkdf2 = require('../lib/crypto/pbkdf2'); const vectors = require('./data/hd.json'); const vector1 = vectors.vector1; const vector2 = vectors.vector2; +const nodejsUtil = require('util'); let master = null; let child = null; @@ -21,57 +23,60 @@ function base58Equal(a, b) { describe('HD', function() { it('should create a pbkdf2 seed', () => { - const seed = pbkdf2.derive( - vectors.phrase, 'mnemonicfoo', 2048, 64, 'sha512'); + const seed = pbkdf2.derive(sha512, + Buffer.from(vectors.phrase), + Buffer.from('mnemonicfoo'), + 2048, + 64); assert.strictEqual(seed.toString('hex'), vectors.seed); }); it('should create master private key', () => { const seed = Buffer.from(vectors.seed, 'hex'); const key = HD.PrivateKey.fromSeed(seed); - assert.strictEqual(key.toBase58(), vectors.master_priv); - assert.strictEqual(key.toPublic().toBase58(), vectors.master_pub); + assert.strictEqual(key.toBase58('main'), vectors.master_priv); + assert.strictEqual(key.toPublic().toBase58('main'), vectors.master_pub); master = key; }); it('should derive(0) child from master', () => { const child1 = master.derive(0); - assert.strictEqual(child1.toBase58(), vectors.child1_priv); - assert.strictEqual(child1.toPublic().toBase58(), vectors.child1_pub); + assert.strictEqual(child1.toBase58('main'), vectors.child1_priv); + assert.strictEqual(child1.toPublic().toBase58('main'), vectors.child1_pub); }); it('should derive(1) child from master public key', () => { const child2 = master.toPublic().derive(1); - assert.strictEqual(child2.toBase58(), vectors.child2_pub); + assert.strictEqual(child2.toBase58('main'), vectors.child2_pub); }); it('should derive(1) child from master', () => { const child3 = master.derive(1); - assert.strictEqual(child3.toBase58(), vectors.child3_priv); - assert.strictEqual(child3.toPublic().toBase58(), vectors.child3_pub); + assert.strictEqual(child3.toBase58('main'), vectors.child3_priv); + assert.strictEqual(child3.toPublic().toBase58('main'), vectors.child3_pub); }); it('should derive(2) child from master', () => { const child4 = master.derive(2); - assert.strictEqual(child4.toBase58(), vectors.child4_priv); - assert.strictEqual(child4.toPublic().toBase58(), vectors.child4_pub); + assert.strictEqual(child4.toBase58('main'), vectors.child4_priv); + assert.strictEqual(child4.toPublic().toBase58('main'), vectors.child4_pub); child = child4; }); it('should derive(0) child from child(2)', () => { const child5 = child.derive(0); - assert.strictEqual(child5.toBase58(), vectors.child5_priv); - assert.strictEqual(child5.toPublic().toBase58(), vectors.child5_pub); + assert.strictEqual(child5.toBase58('main'), vectors.child5_priv); + assert.strictEqual(child5.toPublic().toBase58('main'), vectors.child5_pub); }); it('should derive(1) child from child(2)', () => { const child6 = child.derive(1); - assert.strictEqual(child6.toBase58(), vectors.child6_priv); - assert.strictEqual(child6.toPublic().toBase58(), vectors.child6_pub); + assert.strictEqual(child6.toBase58('main'), vectors.child6_priv); + assert.strictEqual(child6.toPublic().toBase58('main'), vectors.child6_pub); }); it('should derive correctly when private key has leading zeros', () => { - const key = HD.PrivateKey.fromBase58(vectors.zero_priv); + const key = HD.PrivateKey.fromBase58(vectors.zero_priv, 'main'); assert.strictEqual(key.privateKey.toString('hex'), '00000055378cf5fafb56c711c674143f9b0ee82ab0ba2924f19b64f5ae7cdbfd'); @@ -82,17 +87,27 @@ describe('HD', function() { }); it('should deserialize master private key', () => { - HD.PrivateKey.fromBase58(master.toBase58()); + HD.PrivateKey.fromBase58(master.toBase58('main'), 'main'); }); it('should deserialize master public key', () => { - HD.PublicKey.fromBase58(master.toPublic().toBase58()); + HD.PublicKey.fromBase58(master.toPublic().toBase58('main'), 'main'); }); it('should deserialize and reserialize json', () => { const key = HD.generate(); const json = key.toJSON(); - base58Equal(HD.fromJSON(json).toBase58(), key.toBase58()); + base58Equal( + HD.fromJSON(json, 'main').toBase58('main'), + key.toBase58('main')); + }); + + it('should inspect Mnemonic', () => { + const mne = new HD.Mnemonic(); + const fmt = nodejsUtil.format(mne); + assert(typeof fmt === 'string'); + assert(fmt.includes('Mnemonic')); + assert.strictEqual(fmt.split(' ').length, 13); }); for (const vector of [vector1, vector2]) { @@ -103,8 +118,8 @@ describe('HD', function() { const key = HD.PrivateKey.fromSeed(seed); const pub = key.toPublic(); - base58Equal(key.toBase58(), vector.m.prv); - base58Equal(pub.toBase58(), vector.m.pub); + base58Equal(key.toBase58('main'), vector.m.prv); + base58Equal(pub.toBase58('main'), vector.m.pub); master = key; }); @@ -118,8 +133,8 @@ describe('HD', function() { it(`should derive ${path} from master`, () => { const key = master.derivePath(path); const pub = key.toPublic(); - base58Equal(key.toBase58(), kp.prv); - base58Equal(pub.toBase58(), kp.pub); + base58Equal(key.toBase58('main'), kp.prv); + base58Equal(pub.toBase58('main'), kp.pub); }); } } diff --git a/test/headers-test.js b/test/headers-test.js index 41b17745c..338283bf0 100644 --- a/test/headers-test.js +++ b/test/headers-test.js @@ -4,7 +4,7 @@ 'use strict'; const Headers = require('../lib/primitives/headers'); -const assert = require('./util/assert'); +const assert = require('bsert'); const common = require('./util/common'); const block1 = common.readBlock('block1'); @@ -26,9 +26,9 @@ describe('Headers', function() { assert.strictEqual(headers.nonce, 2573394689); assert.strictEqual(headers.version, 1); - assert.strictEqual(headers.prevBlock, + assert.strictEqual(headers.prevBlock.toString('hex'), '6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000'); - assert.strictEqual(headers.merkleRoot, + assert.strictEqual(headers.merkleRoot.toString('hex'), '982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e'); assert.strictEqual(headers.rhash(), '00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048'); @@ -45,9 +45,9 @@ describe('Headers', function() { assert.strictEqual(headers.nonce, 2573394689); assert.strictEqual(headers.version, 1); - assert.strictEqual(headers.prevBlock, + assert.strictEqual(headers.prevBlock.toString('hex'), '6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000'); - assert.strictEqual(headers.merkleRoot, + assert.strictEqual(headers.merkleRoot.toString('hex'), '982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e'); assert.strictEqual(headers.rhash(), '00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048'); diff --git a/test/hkdf-test.js b/test/hkdf-test.js deleted file mode 100644 index 8a43dcc6f..000000000 --- a/test/hkdf-test.js +++ /dev/null @@ -1,84 +0,0 @@ -/* eslint-env mocha */ -/* eslint prefer-arrow-callback: "off" */ - -'use strict'; - -const assert = require('./util/assert'); -const hkdf = require('../lib/crypto/hkdf'); - -describe('HKDF', function() { - it('should do proper hkdf (1)', () => { - // https://tools.ietf.org/html/rfc5869 - const alg = 'sha256'; - const ikm = Buffer.from( - '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'); - const salt = Buffer.from('000102030405060708090a0b0c', 'hex'); - const info = Buffer.from('f0f1f2f3f4f5f6f7f8f9', 'hex'); - const len = 42; - - const prkE = Buffer.from( - '077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5', - 'hex'); - - const okmE = Buffer.from('' - + '3cb25f25faacd57a90434f64d0362f2a2d2d0a90' - + 'cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865', - 'hex'); - - const prk = hkdf.extract(ikm, salt, alg); - const okm = hkdf.expand(prk, info, len, alg); - - assert.bufferEqual(prk, prkE); - assert.bufferEqual(okm, okmE); - }); - - it('should do proper hkdf (2)', () => { - const alg = 'sha256'; - - const ikm = Buffer.from('' - + '000102030405060708090a0b0c0d0e0f' - + '101112131415161718191a1b1c1d1e1f' - + '202122232425262728292a2b2c2d2e2f' - + '303132333435363738393a3b3c3d3e3f' - + '404142434445464748494a4b4c4d4e4f', - 'hex'); - - const salt = Buffer.from('' - + '606162636465666768696a6b6c6d6e6f' - + '707172737475767778797a7b7c7d7e7f' - + '808182838485868788898a8b8c8d8e8f' - + '909192939495969798999a9b9c9d9e9f' - + 'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf', - 'hex'); - - const info = Buffer.from('' - + 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' - + 'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' - + 'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf' - + 'e0e1e2e3e4e5e6e7e8e9eaebecedeeef' - + 'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff', - 'hex'); - - const len = 82; - - const prkE = Buffer.from('' - + '06a6b88c5853361a06104c9ceb35b45c' - + 'ef760014904671014a193f40c15fc244', - 'hex'); - - const okmE = Buffer.from('' - + 'b11e398dc80327a1c8e7f78c596a4934' - + '4f012eda2d4efad8a050cc4c19afa97c' - + '59045a99cac7827271cb41c65e590e09' - + 'da3275600c2f09b8367793a9aca3db71' - + 'cc30c58179ec3e87c14c01d5c1f3434f' - + '1d87', - 'hex'); - - const prk = hkdf.extract(ikm, salt, alg); - const okm = hkdf.expand(prk, info, len, alg); - - assert.bufferEqual(prk, prkE); - assert.bufferEqual(okm, okmE); - }); -}); diff --git a/test/http-test.js b/test/http-test.js index bc187d7b3..5b1d13468 100644 --- a/test/http-test.js +++ b/test/http-test.js @@ -3,72 +3,115 @@ 'use strict'; -const assert = require('./util/assert'); +const {BloomFilter} = require('bfilter'); +const assert = require('bsert'); const consensus = require('../lib/protocol/consensus'); -const encoding = require('../lib/utils/encoding'); -const co = require('../lib/utils/co'); const Address = require('../lib/primitives/address'); const Script = require('../lib/script/script'); const Outpoint = require('../lib/primitives/outpoint'); const MTX = require('../lib/primitives/mtx'); -const HTTP = require('../lib/http'); const FullNode = require('../lib/node/fullnode'); +const ChainEntry = require('../lib/blockchain/chainentry'); const pkg = require('../lib/pkg'); +if (process.browser) + return; + +const ports = { + p2p: 49331, + node: 49332, + wallet: 49333 +}; + const node = new FullNode({ network: 'regtest', apiKey: 'foo', walletAuth: true, - db: 'memory', + memory: true, workers: true, - plugins: [require('../lib/wallet/plugin')] + workersSize: 2, + plugins: [require('../lib/wallet/plugin')], + port: ports.p2p, + httpPort: ports.node, + env: { + 'BCOIN_WALLET_HTTP_PORT': ports.wallet.toString() + }}); + +const {NodeClient, WalletClient} = require('@oipwg/fclient'); + +const nclient = new NodeClient({ + port: ports.node, + apiKey: 'foo', + timeout: 15000 }); -const wallet = new HTTP.Wallet({ - network: 'regtest', +const wclient = new WalletClient({ + port: ports.wallet, apiKey: 'foo' }); -const wdb = node.require('walletdb'); +let wallet = null; + +const {wdb} = node.require('walletdb'); let addr = null; let hash = null; +let blocks = null; describe('HTTP', function() { this.timeout(15000); + // m/44'/1'/0'/0/{0,1} + const pubkeys = [ + Buffer.from('02a7451395735369f2ecdfc829c0f' + + '774e88ef1303dfe5b2f04dbaab30a535dfdd6', 'hex'), + Buffer.from('03589ae7c835ce76e23cf8feb32f1a' + + 'df4a7f2ba0ed2ad70801802b0bcd70e99c1c', 'hex') + ]; + it('should open node', async () => { consensus.COINBASE_MATURITY = 0; await node.open(); + await nclient.open(); + await wclient.open(); }); it('should create wallet', async () => { - const info = await wallet.create({ id: 'test' }); + const info = await wclient.createWallet('test'); assert.strictEqual(info.id, 'test'); + wallet = wclient.wallet('test', info.token); + await wallet.open(); }); it('should get info', async () => { - const info = await wallet.client.getInfo(); + const info = await nclient.getInfo(); assert.strictEqual(info.network, node.network.type); assert.strictEqual(info.version, pkg.version); - assert.typeOf(info.pool, 'object'); + assert(typeof info.pool === 'object'); assert.strictEqual(info.pool.agent, node.pool.options.agent); - assert.typeOf(info.chain, 'object'); + assert(typeof info.chain === 'object'); assert.strictEqual(info.chain.height, 0); + assert(typeof info.indexes === 'object'); + assert(typeof info.indexes.addr === 'object'); + assert.equal(info.indexes.addr.enabled, false); + assert.equal(info.indexes.addr.height, 0); + assert(typeof info.indexes.tx === 'object'); + assert.equal(info.indexes.addr.enabled, false); + assert.equal(info.indexes.tx.height, 0); }); it('should get wallet info', async () => { const info = await wallet.getInfo(); assert.strictEqual(info.id, 'test'); - assert.typeOf(info.account, 'object'); - const str = info.account.receiveAddress; - assert.typeOf(str, 'string'); - addr = Address.fromString(str); + const acct = await wallet.getAccount('default'); + const str = acct.receiveAddress; + assert(typeof str === 'string'); + addr = Address.fromString(str, node.network); }); it('should fill with funds', async () => { const mtx = new MTX(); - mtx.addOutpoint(new Outpoint(encoding.NULL_HASH, 0)); + mtx.addOutpoint(new Outpoint(consensus.ZERO_HASH, 0)); mtx.addOutput(addr, 50460); mtx.addOutput(addr, 50460); mtx.addOutput(addr, 50460); @@ -92,10 +135,10 @@ describe('HTTP', function() { }); await wdb.addTX(tx); - await co.timeout(300); + await new Promise(r => setTimeout(r, 300)); assert(receive); - assert.strictEqual(receive.id, 'test'); + assert.strictEqual(receive.name, 'default'); assert.strictEqual(receive.type, 'pubkeyhash'); assert.strictEqual(receive.branch, 0); assert(balance); @@ -116,7 +159,7 @@ describe('HTTP', function() { rate: 10000, outputs: [{ value: 10000, - address: addr.toString() + address: addr.toString(node.network) }] }; @@ -143,9 +186,9 @@ describe('HTTP', function() { it('should generate new api key', async () => { const old = wallet.token.toString('hex'); - const token = await wallet.retoken(null); - assert.strictEqual(token.length, 64); - assert.notStrictEqual(token, old); + const result = await wallet.retoken(null); + assert.strictEqual(result.token.length, 64); + assert.notStrictEqual(result.token, old); }); it('should get balance', async () => { @@ -154,12 +197,12 @@ describe('HTTP', function() { }); it('should execute an rpc call', async () => { - const info = await wallet.client.rpc.execute('getblockchaininfo', []); + const info = await nclient.execute('getblockchaininfo', []); assert.strictEqual(info.blocks, 0); }); it('should execute an rpc call with bool parameter', async () => { - const info = await wallet.client.rpc.execute('getrawmempool', [true]); + const info = await nclient.execute('getrawmempool', [true]); assert.deepStrictEqual(info, {}); }); @@ -188,12 +231,14 @@ describe('HTTP', function() { }); it('should get a block template', async () => { - const json = await wallet.client.rpc.execute('getblocktemplate', []); + const json = await nclient.execute('getblocktemplate', [{ + rules: ['segwit'] + }]); assert.deepStrictEqual(json, { capabilities: ['proposal'], mutable: ['time', 'transactions', 'prevblock'], version: 536870912, - rules: [], + rules: ['!segwit'], vbavailable: {}, vbrequired: 0, height: 1, @@ -207,15 +252,19 @@ describe('HTTP', function() { mintime: 1296688603, maxtime: json.maxtime, expires: json.expires, - sigoplimit: 20000, - sizelimit: 1000000, + sigoplimit: 80000, + sizelimit: 4000000, + weightlimit: 4000000, longpollid: '530827f38f93b43ed12af0b3ad25a288dc02ed74d6d7857862df51fc56c416f9' + '0000000000', submitold: false, coinbaseaux: { flags: '6d696e65642062792062636f696e' }, coinbasevalue: 5000000000, - transactions: [] + transactions: [], + default_witness_commitment: + '6a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48beb' + + 'd836974e8cf9' }); }); @@ -223,7 +272,7 @@ describe('HTTP', function() { const attempt = await node.miner.createBlock(); const block = attempt.toBlock(); const hex = block.toRaw().toString('hex'); - const json = await wallet.client.rpc.execute('getblocktemplate', [{ + const json = await nclient.execute('getblocktemplate', [{ mode: 'proposal', data: hex }]); @@ -231,21 +280,226 @@ describe('HTTP', function() { }); it('should validate an address', async () => { - const json = await wallet.client.rpc.execute('validateaddress', [ - addr.toString() + const json = await nclient.execute('validateaddress', [ + addr.toString(node.network) ]); assert.deepStrictEqual(json, { isvalid: true, - address: addr.toString(), + address: addr.toString(node.network), scriptPubKey: Script.fromAddress(addr).toRaw().toString('hex'), - ismine: false, - iswatchonly: false + iswitness: false, + isscript: false + }); + }); + + it('should not validate invalid address', async () => { + // Valid Mainnet P2WPKH from + // https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki + const json = await nclient.execute('validateaddress', [ + 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4' + ]); + + // Sending an address from the incorrect network + // should result in an invalid address + assert.deepStrictEqual(json, { + isvalid: false + }); + }); + + it('should validate a p2wpkh address', async () => { + const address = 'bcrt1q8gk5z3dy7zv9ywe7synlrk58elz4hrnegvpv6m'; + const addr = Address.fromString(address); + const script = Script.fromAddress(addr); + + const json = await nclient.execute('validateaddress', [ + address + ]); + + assert.deepStrictEqual(json, { + isvalid: true, + iswitness: true, + address: address, + isscript: addr.isScripthash(), + scriptPubKey: script.toJSON(), + witness_version: addr.version, + witness_program: addr.hash.toString('hex') + }); + }); + + it('should validate a p2sh address', async () => { + const script = Script.fromMultisig(2, 2, pubkeys); + const address = Address.fromScript(script); + + // Test the valid case - render the address to the + // correct network + { + const json = await nclient.execute('validateaddress', [ + address.toString(node.network) + ]); + + assert.deepEqual(json, { + isvalid: true, + address: address.toString(node.network), + scriptPubKey: Script.fromAddress(address).toJSON(), + isscript: true, + iswitness: false + }); + } + + // Test the invalid case - render the address to the + // incorrect network, making it an invalid address + { + const json = await nclient.execute('validateaddress', [ + address.toString('main') + ]); + + assert.deepEqual(json, { + isvalid: false + }); + } + }); + + it('should validate a p2wsh address', async () => { + const script = Script.fromMultisig(2, 2, pubkeys); + const scriptPubKey = script.forWitness(); + const program = script.sha256(); + const address = Address.fromProgram(0, program); + + const json = await nclient.execute('validateaddress', [ + address.toString(node.network) + ]); + + assert.deepEqual(json, { + isvalid: true, + address: address.toString(node.network), + scriptPubKey: scriptPubKey.toJSON(), + isscript: true, + iswitness: true, + witness_version: 0, + witness_program: program.toString('hex') + }); + }); + + for (const template of [true, false]) { + const suffix = template ? 'with template' : 'without template'; + it(`should create and sign transaction ${suffix}`, async () => { + const change = await wallet.createChange('default'); + const tx = await wallet.createTX({ + template: template, // should not matter, sign = true + sign: true, + outputs: [{ + address: change.address, + value: 50000 + }] + }); + const mtx = MTX.fromJSON(tx); + + for (const input of tx.inputs) { + const script = input.script; + + assert.notStrictEqual(script, '', + 'Input must be signed.'); + } + + assert.strictEqual(mtx.verify(), true, + 'Transaction must be signed.'); + }); + } + + it('should create transaction without template', async () => { + const change = await wallet.createChange('default'); + const tx = await wallet.createTX({ + sign: false, + outputs: [{ + address: change.address, + value: 50000 + }] }); + + for (const input of tx.inputs) { + const script = input.script; + + assert.strictEqual(script.length, 0, + 'Input must not be templated.'); + } + }); + + it('should create transaction with template', async () => { + const change = await wallet.createChange('default'); + const tx = await wallet.createTX({ + sign: false, + template: true, + outputs: [{ + address: change.address, + value: 20000 + }] + }); + + for (const input of tx.inputs) { + const script = Buffer.from(input.script, 'hex'); + + // p2pkh + // 1 (OP_0 placeholder) + 1 (length) + 33 (pubkey) + assert.strictEqual(script.length, 35); + assert.strictEqual(script[0], 0x00, + 'First item in stack must be a placeholder OP_0'); + } + }); + + it('should generate 10 blocks from RPC call', async () => { + blocks = await nclient.execute( + 'generatetoaddress', + [10, addr.toString('regtest')] + ); + assert.strictEqual(blocks.length, 10); + }); + + it('should initiate rescan from socket without a bloom filter', async () => { + // Rescan from height 5. Without a filter loaded = no response, but no error + const response = await nclient.call('rescan', 5); + assert.strictEqual(null, response); + }); + + it('should initiate rescan from socket WITH a bloom filter', async () => { + // Create an SPV-standard Bloom filter and add one of our wallet addresses + const filter = BloomFilter.fromRate(20000, 0.001, BloomFilter.flags.ALL); + const walletAddr = addr.toString('regtest'); + filter.add(walletAddr, 'ascii'); + + // Send Bloom filter to server + await nclient.call('set filter', filter.filter); + + // `rescan` commands the node server to check blocks against a bloom filter. + // When the server matches a transaction in a block to the filter, it + // sends a socket call BACK to the client with the ChainEntry of the block, + // and an array of matched transactions. Because of this callback, the + // CLIENT MUST have a `block rescan` hook in place or the server will throw. + const matchingBlocks = []; + nclient.hook('block rescan', (entry, txs) => { + // Coinbase transactions were mined to our watch address, matching filter. + assert.strictEqual(txs.length, 1); + const cbtx = MTX.fromRaw(txs[0]); + assert.strictEqual( + cbtx.outputs[0].getAddress().toString('regtest'), + walletAddr + ); + + // Blocks are returned as raw ChainEntry + matchingBlocks.push( + ChainEntry.fromRaw(entry).rhash().toString('hex') + ); + }); + + // Rescan from height 5 -- should return blocks 5 through 10, inclusive. + await nclient.call('rescan', 5); + assert.deepStrictEqual(matchingBlocks, blocks.slice(4)); }); it('should cleanup', async () => { consensus.COINBASE_MATURITY = 100; await wallet.close(); + await wclient.close(); + await nclient.close(); await node.close(); }); }); diff --git a/test/indexer-test.js b/test/indexer-test.js new file mode 100644 index 000000000..fc2b4ff62 --- /dev/null +++ b/test/indexer-test.js @@ -0,0 +1,1240 @@ +/* eslint-env mocha */ +/* eslint prefer-arrow-callback: "off" */ + +'use strict'; + +const assert = require('bsert'); +const EventEmitter = require('events'); +const reorg = require('./util/reorg'); +const Script = require('../lib/script/script'); +const Opcode = require('../lib/script/opcode'); +const Address = require('../lib/primitives/address'); +const Block = require('../lib/primitives/block'); +const Chain = require('../lib/blockchain/chain'); +const WorkerPool = require('../lib/workers/workerpool'); +const Miner = require('../lib/mining/miner'); +const MemWallet = require('./util/memwallet'); +const TXIndexer = require('../lib/indexer/txindexer'); +const AddrIndexer = require('../lib/indexer/addrindexer'); +const BlockStore = require('../lib/blockstore/level'); +const FullNode = require('../lib/node/fullnode'); +const SPVNode = require('../lib/node/spvnode'); +const Network = require('../lib/protocol/network'); +const network = Network.get('regtest'); +const {NodeClient, WalletClient} = require('@oipwg/fclient'); +const {forValue, testdir, rimraf} = require('./util/common'); + +const ports = { + p2p: 49331, + node: 49332, + wallet: 49333 +}; + +const vectors = [ + // Secret for the public key vectors: + // cVDJUtDjdaM25yNVVDLLX3hcHUfth4c7tY3rSc4hy9e8ibtCuj6G + { + addr: 'bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h', + amount: 19.99, + label: 'p2wpkh' + }, + { + addr: 'muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi', + amount: 1.99, + label: 'p2pkh' + }, + // Secrets for 1 of 2 multisig vectors: + // cVDJUtDjdaM25yNVVDLLX3hcHUfth4c7tY3rSc4hy9e8ibtCuj6G + // 93KCDD4LdP4BDTNBXrvKUCVES2jo9dAKKvhyWpNEMstuxDauHty + { + addr: 'bcrt1q2nj8e2nhmsa4hl9qw3xas7l5n2547h5uhlj47nc3pqfxaeq5rtjs9g328g', + amount: 0.99, + label: 'p2wsh' + }, + { + addr: '2Muy8nSQaMsMFAZwPyiXSEMTVFJv9iYuhwT', + amount: 0.11, + label: 'p2sh' + } +]; + +const workers = new WorkerPool({ + enabled: true, + size: 2 +}); + +const blocks = new BlockStore({ + memory: true, + network +}); + +const chain = new Chain({ + memory: true, + network, + workers, + blocks +}); + +const miner = new Miner({ + chain, + version: 4, + workers +}); + +const cpu = miner.cpu; + +const wallet = new MemWallet({ + network +}); + +const txindexer = new TXIndexer({ + memory: true, + network, + chain, + blocks +}); + +const addrindexer = new AddrIndexer({ + memory: true, + network, + chain, + blocks +}); + +describe('Indexer', function() { + this.timeout(120000); + + before(async () => { + await blocks.open(); + await chain.open(); + await miner.open(); + await txindexer.open(); + await addrindexer.open(); + await workers.open(); + }); + + after(async () => { + await workers.close(); + await blocks.close(); + await chain.close(); + await miner.close(); + await txindexer.close(); + await addrindexer.close(); + }); + + describe('Unit', function() { + it('should connect block', async () => { + const indexer = new AddrIndexer({ + blocks: {}, + chain: {} + }); + + indexer.height = 9; + + indexer.getBlockMeta = (height) => { + return { + hash: Buffer.alloc(32, 0x00), + height: height + }; + }; + + let called = false; + indexer._addBlock = async () => { + called = true; + }; + + const meta = {height: 10}; + const block = {prevBlock: Buffer.alloc(32, 0x00)}; + const view = {}; + + const connected = await indexer._syncBlock(meta, block, view); + assert.equal(connected, true); + assert.equal(called, true); + }); + + it('should not connect block', async () => { + const indexer = new AddrIndexer({ + blocks: {}, + chain: {} + }); + + indexer.height = 9; + + indexer.getBlockMeta = (height) => { + return { + hash: Buffer.alloc(32, 0x02), + height: height + }; + }; + + let called = false; + indexer._addBlock = async () => { + called = true; + }; + + const meta = {height: 10}; + const block = {prevBlock: Buffer.alloc(32, 0x01)}; + const view = {}; + + const connected = await indexer._syncBlock(meta, block, view); + assert.equal(connected, false); + assert.equal(called, false); + }); + + it('should disconnect block', async () => { + const indexer = new AddrIndexer({ + blocks: {}, + chain: {} + }); + + indexer.height = 9; + + indexer.getBlockMeta = (height) => { + return { + hash: Buffer.alloc(32, 0x00), + height: height + }; + }; + + let called = false; + indexer._removeBlock = async () => { + called = true; + }; + + const meta = {height: 9}; + const block = {hash: () => Buffer.alloc(32, 0x00)}; + const view = {}; + + const connected = await indexer._syncBlock(meta, block, view); + assert.equal(connected, true); + assert.equal(called, true); + }); + + it('should not disconnect block', async () => { + const indexer = new AddrIndexer({ + blocks: {}, + chain: {} + }); + + indexer.height = 9; + + indexer.getBlockMeta = (height) => { + return { + hash: Buffer.alloc(32, 0x01), + height: height + }; + }; + + let called = false; + indexer._removeBlock = async () => { + called = true; + }; + + const meta = {height: 9}; + const block = {hash: () => Buffer.alloc(32, 0x02)}; + const view = {}; + + const connected = await indexer._syncBlock(meta, block, view); + assert.equal(connected, false); + assert.equal(called, false); + }); + + it('should not index transaction w/ invalid address', async () => { + const indexer = new AddrIndexer({ + blocks: {}, + chain: {} + }); + + const ops = []; + + indexer.put = (key, value) => ops.push([key, value]); + indexer.del = (key, value) => ops.push([key, value]); + + // Create a witness program version 1 with + // 40 byte data push. + const script = new Script(); + script.push(Opcode.fromSmall(1)); + script.push(Opcode.fromData(Buffer.alloc(40))); + script.compile(); + const addr = Address.fromScript(script); + + const tx = { + getAddresses: () => [addr], + hash: () => Buffer.alloc(32) + }; + + const entry = {height: 323549}; + const block = {txs: [tx]}; + const view = {}; + + indexer.indexBlock(entry, block, view); + indexer.unindexBlock(entry, block, view); + + assert.equal(ops.length, 0); + }); + + it('should index transaction w/ valid address', async () => { + const indexer = new AddrIndexer({ + blocks: {}, + chain: {} + }); + + const ops = []; + + indexer.put = (key, value) => ops.push([key, value]); + indexer.del = (key, value) => ops.push([key, value]); + + // Create a witness program version 0 with + // 20 byte data push. + const script = new Script(); + script.push(Opcode.fromSmall(0)); + script.push(Opcode.fromData(Buffer.alloc(20))); + script.compile(); + const addr = Address.fromScript(script); + + const tx = { + getAddresses: () => [addr], + hash: () => Buffer.alloc(32) + }; + + const entry = {height: 323549}; + const block = {txs: [tx]}; + const view = {}; + + indexer.indexBlock(entry, block, view); + indexer.unindexBlock(entry, block, view); + + assert.equal(ops.length, 6); + }); + + it('should error with limits', async () => { + const indexer = new AddrIndexer({ + blocks: {}, + chain: {}, + maxTxs: 10 + }); + + await assert.rejects(async () => { + await indexer.getHashesByAddress(vectors[0].addr, {limit: 11}); + }, { + name: 'Error', + message: 'Limit above max of 10.' + }); + }); + + it('should track bound chain events and remove on close', async () => { + const indexer = new AddrIndexer({ + blocks: {}, + chain: new EventEmitter() + }); + + const events = ['connect', 'disconnect', 'reset']; + + await indexer.open(); + + for (const event of events) + assert.equal(indexer.chain.listeners(event).length, 1); + + await indexer.close(); + + for (const event of events) + assert.equal(indexer.chain.listeners(event).length, 0); + }); + }); + + describe('Index 10 blocks', function() { + let addr = null; + + before(async () => { + miner.addresses.length = 0; + miner.addAddress(wallet.getReceive()); + + addr = miner.getAddress(); + + for (let i = 0; i < 10; i++) { + const block = await cpu.mineBlock(); + assert(block); + assert(await chain.add(block)); + } + + assert.strictEqual(chain.height, 10); + assert.strictEqual(txindexer.height, 10); + assert.strictEqual(addrindexer.height, 10); + }); + + it('should get txs by address', async () => { + const hashes = await addrindexer.getHashesByAddress(miner.getAddress()); + assert.strictEqual(hashes.length, 10); + }); + + it('should get txs by address (limit)', async () => { + const hashes = await addrindexer.getHashesByAddress(addr, {limit: 1}); + assert.strictEqual(hashes.length, 1); + }); + + it('should get txs by address (reverse)', async () => { + const hashes = await addrindexer.getHashesByAddress( + addr, {reverse: false}); + + assert.strictEqual(hashes.length, 10); + + const reversed = await addrindexer.getHashesByAddress( + addr, {reverse: true}); + + assert.strictEqual(reversed.length, 10); + + for (let i = 0; i < 10; i++) + assert.deepEqual(hashes[i], reversed[9 - i]); + }); + + it('should get txs by address after txid', async () => { + const hashes = await addrindexer.getHashesByAddress(addr, {limit: 5}); + + assert.strictEqual(hashes.length, 5); + + const txid = hashes[4]; + + const next = await addrindexer.getHashesByAddress( + addr, {after: txid, limit: 5}); + + assert.strictEqual(next.length, 5); + + const all = await addrindexer.getHashesByAddress(addr); + assert.strictEqual(all.length, 10); + + assert.deepEqual(hashes.concat(next), all); + }); + + it('should get txs by address after txid (reverse)', async () => { + const hashes = await addrindexer.getHashesByAddress( + addr, {limit: 5, reverse: true}); + + assert.strictEqual(hashes.length, 5); + + const txid = hashes[4]; + + const next = await addrindexer.getHashesByAddress( + addr, {after: txid, limit: 5, reverse: true}); + + assert.strictEqual(next.length, 5); + + const all = await addrindexer.getHashesByAddress( + addr, {reverse: true}); + + assert.strictEqual(all.length, 10); + + assert.deepEqual(hashes.concat(next), all); + }); + + it('should get tx and meta', async () => { + const hashes = await addrindexer.getHashesByAddress(addr, {limit: 1}); + assert.equal(hashes.length, 1); + const hash = hashes[0]; + + const tx = await txindexer.getTX(hash); + const meta = await txindexer.getMeta(hash); + + assert(meta.height); + assert(meta.block); + assert(meta.time); + + assert.deepEqual(meta.tx, tx); + }); + + it('should get null if not found for tx and meta', async () => { + const hash = Buffer.alloc(32); + + const tx = await txindexer.getTX(hash); + const meta = await txindexer.getMeta(hash); + + assert.strictEqual(tx, null); + assert.strictEqual(meta, null); + }); + + it('should get unspendable genesis tx', async () => { + const block = Block.fromRaw(Buffer.from(network.genesisBlock, 'hex')); + const hash = block.txs[0].hash(); + + const tx = await txindexer.getTX(hash); + const meta = await txindexer.getMeta(hash); + + assert(meta); + assert.equal(meta.height, 0); + assert(meta.block); + assert(meta.time); + + assert.deepEqual(meta.tx, tx); + }); + }); + + describe('Reorg and rescan', function() { + it('should rescan and reindex 10 missed blocks', async () => { + for (let i = 0; i < 10; i++) { + const block = await cpu.mineBlock(); + assert(block); + assert(await chain.add(block)); + } + + assert.strictEqual(chain.height, 20); + assert.strictEqual(txindexer.height, 20); + assert.strictEqual(addrindexer.height, 20); + + const hashes = await addrindexer.getHashesByAddress(miner.getAddress()); + assert.strictEqual(hashes.length, 20); + + for (const hash of hashes) { + const meta = await txindexer.getMeta(hash); + assert.bufferEqual(meta.tx.hash(), hash); + } + }); + + it('should handle indexing a reorg', async () => { + await reorg(chain, cpu, 10); + + assert.strictEqual(txindexer.height, 31); + assert.strictEqual(addrindexer.height, 31); + + const hashes = await addrindexer.getHashesByAddress(miner.getAddress()); + assert.strictEqual(hashes.length, 31); + + for (const hash of hashes) { + const meta = await txindexer.getMeta(hash); + assert.bufferEqual(meta.tx.hash(), hash); + } + }); + + describe('Integration', function() { + const prefix = testdir('indexer'); + + beforeEach(async () => { + await rimraf(prefix); + }); + + after(async () => { + await rimraf(prefix); + }); + + it('will enable indexes retroactively', async () => { + let node, nclient = null; + + try { + node = new FullNode({ + prefix: prefix, + network: 'regtest', + apiKey: 'foo', + memory: false, + indexTX: false, + indexAddress: false, + port: ports.p2p, + httpPort: ports.node + }); + + await node.ensure(); + await node.open(); + + nclient = new NodeClient({ + port: ports.node, + apiKey: 'foo', + timeout: 120000 + }); + + await nclient.open(); + + const blocks = await nclient.execute( + 'generatetoaddress', [150, vectors[0].addr]); + + assert.equal(blocks.length, 150); + + await forValue(node.chain, 'height', 150); + + const info = await nclient.request('GET', '/'); + + assert.equal(info.chain.height, 150); + assert.equal(info.indexes.addr.enabled, false); + assert.equal(info.indexes.addr.height, 0); + assert.equal(info.indexes.tx.enabled, false); + assert.equal(info.indexes.tx.height, 0); + } finally { + if (nclient) + await nclient.close(); + + if (node) + await node.close(); + } + + try { + node = new FullNode({ + prefix: prefix, + network: 'regtest', + memory: false, + indexTX: true, + indexAddress: false, + port: ports.p2p, + httpPort: ports.node + }); + + await node.ensure(); + await node.open(); + + assert(node.txindex); + assert.equal(node.txindex.height, 0); + + node.startSync(); + + await forValue(node.txindex, 'height', 150); + } finally { + if (node) + await node.close(); + } + }); + + it('will sync if disabled during reorganization', async () => { + let node, nclient, wclient = null; + + try { + // Generate initial set of blocks that are are spending + // coins and therefore data in undo blocks. + node = new FullNode({ + prefix: prefix, + network: 'regtest', + apiKey: 'foo', + memory: false, + indexTX: true, + indexAddress: false, + port: ports.p2p, + httpPort: ports.node, + plugins: [require('../lib/wallet/plugin')], + env: { + 'BCOIN_WALLET_HTTP_PORT': ports.wallet.toString() + }, + logLevel: 'none' + }); + + await node.ensure(); + await node.open(); + + nclient = new NodeClient({ + port: ports.node, + apiKey: 'foo', + timeout: 120000 + }); + + await nclient.open(); + + wclient = new WalletClient({ + port: ports.wallet, + apiKey: 'foo', + timeout: 120000 + }); + + await wclient.open(); + + const coinbase = await wclient.execute( + 'getnewaddress', ['default']); + + const blocks = await nclient.execute( + 'generatetoaddress', [150, coinbase]); + + assert.equal(blocks.length, 150); + + for (let i = 0; i < 10; i++) { + for (const v of vectors) + await wclient.execute('sendtoaddress', [v.addr, v.amount]); + + const blocks = await nclient.execute( + 'generatetoaddress', [1, coinbase]); + + assert.equal(blocks.length, 1); + } + + await forValue(node.chain, 'height', 160); + await forValue(node.txindex, 'height', 160); + } finally { + if (wclient) + await wclient.close(); + + if (nclient) + await nclient.close(); + + if (node) + await node.close(); + } + + try { + // Now create a reorganization in the chain while + // the indexer is disabled. + node = new FullNode({ + prefix: prefix, + network: 'regtest', + apiKey: 'foo', + memory: false, + indexTX: false, + indexAddress: false, + port: ports.p2p, + httpPort: ports.node, + logLevel: 'none' + }); + + await node.ensure(); + await node.open(); + + nclient = new NodeClient({ + port: ports.node, + apiKey: 'foo', + timeout: 120000 + }); + + await nclient.open(); + + for (let i = 0; i < 10; i++) { + const hash = await nclient.execute('getbestblockhash'); + await nclient.execute('invalidateblock', [hash]); + } + + await forValue(node.chain, 'height', 150); + + const blocks = await nclient.execute( + 'generatetoaddress', [20, vectors[0].addr]); + + assert.equal(blocks.length, 20); + + await forValue(node.chain, 'height', 170); + } finally { + if (nclient) + await nclient.close(); + + if (node) + await node.close(); + } + + try { + // Now turn the indexer back on and check that it + // is able to disconnect blocks and add the new blocks. + node = new FullNode({ + prefix: prefix, + network: 'regtest', + apiKey: 'foo', + memory: false, + indexTX: true, + indexAddress: false, + port: ports.p2p, + httpPort: ports.node, + logLevel: 'none' + }); + + await node.ensure(); + await node.open(); + + assert(node.txindex); + assert.equal(node.txindex.height, 160); + + node.txindex.sync(); + + await forValue(node.txindex, 'height', 170, 5000); + } finally { + if (node) + await node.close(); + } + }); + + it('will reset indexes', async () => { + let node, nclient = null; + + try { + node = new FullNode({ + prefix: prefix, + network: 'regtest', + apiKey: 'foo', + memory: false, + indexTX: true, + indexAddress: false, + port: ports.p2p, + httpPort: ports.node, + logLevel: 'none' + }); + + await node.ensure(); + await node.open(); + + nclient = new NodeClient({ + port: ports.node, + apiKey: 'foo', + timeout: 120000 + }); + + await nclient.open(); + + const blocks = await nclient.execute( + 'generatetoaddress', [150, vectors[0].addr]); + + assert.equal(blocks.length, 150); + + await forValue(node.txindex, 'height', 150); + + await node.chain.reset(0); + + await forValue(node.txindex, 'height', 1); + } finally { + if (nclient) + await nclient.close(); + + if (node) + await node.close(); + } + }); + + it('will not index if pruned', async () => { + let err = null; + + try { + new FullNode({ + prefix: prefix, + network: 'regtest', + apiKey: 'foo', + memory: false, + prune: true, + indexTX: true, + indexAddress: true, + port: ports.p2p, + httpPort: ports.node + }); + } catch (e) { + err = e; + } + + assert(err); + assert.equal(err.message, 'Can not index while pruned.'); + }); + + it('will not index if spv', async () => { + const node = new SPVNode({ + prefix: prefix, + network: 'regtest', + apiKey: 'foo', + memory: false, + indexTX: true, + indexAddress: true, + port: ports.p2p, + httpPort: ports.node + }); + + assert.equal(node.txindex, null); + assert.equal(node.addrindex, null); + }); + }); + }); + + describe('HTTP', function() { + this.timeout(120000); + + let node, nclient, wclient = null; + + const confirmed = []; + const unconfirmed = []; + + function sanitize(txs) { + return txs.map((tx) => { + // Remove mtime from the results for deep + // comparisons as it can be variable. + delete tx.mtime; + return tx; + }); + } + + before(async () => { + this.timeout(120000); + + // Setup a testing node with txindex and addrindex + // both enabled. + node = new FullNode({ + network: 'regtest', + apiKey: 'foo', + walletAuth: true, + memory: true, + workers: true, + workersSize: 2, + indexTX: true, + indexAddress: true, + port: ports.p2p, + httpPort: ports.node, + plugins: [require('../lib/wallet/plugin')], + env: { + 'BCOIN_WALLET_HTTP_PORT': ports.wallet.toString() + } + }); + + await node.open(); + + // Setup the node client to make calls to the node + // to generate blocks and other tasks. + nclient = new NodeClient({ + port: ports.node, + apiKey: 'foo', + timeout: 120000 + }); + + await nclient.open(); + + // Setup a test wallet to generate transactions for + // testing various scenarios. + wclient = new WalletClient({ + port: ports.wallet, + apiKey: 'foo', + timeout: 120000 + }); + + await wclient.open(); + + // Generate initial set of transactions and + // send the coinbase to alice. + const coinbase = await wclient.execute( + 'getnewaddress', ['default']); + + const blocks = await nclient.execute( + 'generatetoaddress', [150, coinbase]); + + assert.equal(blocks.length, 150); + + // Send to the vector addresses for several blocks. + for (let i = 0; i < 10; i++) { + for (const v of vectors) { + const txid = await wclient.execute( + 'sendtoaddress', [v.addr, v.amount]); + + confirmed.push(txid); + } + + const blocks = await nclient.execute( + 'generatetoaddress', [1, coinbase]); + + assert.equal(blocks.length, 1); + } + + await forValue(node.chain, 'height', 160); + + // Send unconfirmed to the vector addresses. + for (let i = 0; i < 5; i++) { + for (const v of vectors) { + const txid = await wclient.execute( + 'sendtoaddress', [v.addr, v.amount]); + + unconfirmed.push(txid); + } + } + + await forValue(node.mempool.map, 'size', 20); + }); + + after(async () => { + await nclient.close(); + await wclient.close(); + await node.close(); + }); + + for (const v of vectors) { + it(`txs by ${v.label} addr`, async () => { + const res = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {}); + + assert.equal(res.length, 15); + + for (let i = 0; i < 10; i++) + assert(confirmed.includes(res[i].hash)); + + for (let i = 10; i < 15; i++) + assert(unconfirmed.includes(res[i].hash)); + }); + + it(`txs by ${v.label} addr (limit)`, async () => { + const res = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {limit: 3}); + + assert.equal(res.length, 3); + + for (const tx of res) + assert(confirmed.includes(tx.hash)); + }); + + it(`txs by ${v.label} addr (limit w/ unconf)`, async () => { + const res = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {limit: 11}); + + assert.equal(res.length, 11); + + for (let i = 0; i < 10; i++) + assert(confirmed.includes(res[i].hash)); + + for (let i = 10; i < 11; i++) + assert(unconfirmed.includes(res[i].hash)); + }); + + it(`txs by ${v.label} addr (reverse)`, async () => { + const asc = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {reverse: false}); + + assert.equal(asc.length, 15); + + const dsc = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {reverse: true}); + + assert.equal(dsc.length, 15); + + for (let i = 0; i < 10; i++) + assert(confirmed.includes(asc[i].hash)); + + for (let i = 10; i < 15; i++) + assert(unconfirmed.includes(asc[i].hash)); + + // Check the the results are reverse + // of each other. + for (let i = 0; i < dsc.length; i++) { + const atx = asc[i]; + const dtx = dsc[dsc.length - i - 1]; + assert.equal(atx.hash, dtx.hash); + } + }); + + it(`txs by ${v.label} addr (after)`, async () => { + const one = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {limit: 3}); + assert.strictEqual(one.length, 3); + + for (let i = 0; i < 3; i++) + assert(confirmed.includes(one[i].hash)); + + // The after hash is within the + // confirmed transactions. + const hash = one[2].hash; + + const two = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {after: hash, limit: 3}); + assert.strictEqual(one.length, 3); + + const all = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {limit: 6}); + assert.strictEqual(one.length, 3); + + assert.deepEqual(sanitize(one.concat(two)), sanitize(all)); + }); + + it(`txs by ${v.label} addr (after w/ unconf)`, async () => { + const one = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {limit: 11}); + assert.strictEqual(one.length, 11); + + for (let i = 0; i < 10; i++) + assert(confirmed.includes(one[i].hash)); + + for (let i = 10; i < 11; i++) + assert(unconfirmed.includes(one[i].hash)); + + // The after hash is within the + // unconfirmed transactions. + const hash = one[10].hash; + + const two = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {after: hash, limit: 1}); + assert.strictEqual(two.length, 1); + assert(unconfirmed.includes(two[0].hash)); + + const all = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {limit: 12}); + assert.strictEqual(all.length, 12); + + assert.deepEqual(sanitize(one.concat(two)), sanitize(all)); + }); + + it(`txs by ${v.label} addr (after w/ unconf 2)`, async () => { + const one = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {limit: 12}); + assert.strictEqual(one.length, 12); + + for (let i = 0; i < 10; i++) + assert(confirmed.includes(one[i].hash)); + + for (let i = 10; i < 12; i++) + assert(unconfirmed.includes(one[i].hash)); + + const hash = one[11].hash; + + const two = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {after: hash, limit: 10}); + assert.strictEqual(two.length, 3); + + for (let i = 0; i < 3; i++) + assert(unconfirmed.includes(two[i].hash)); + + const all = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {limit: 100}); + assert.strictEqual(all.length, 15); + + assert.deepEqual(sanitize(one.concat(two)), sanitize(all)); + }); + + it(`txs by ${v.label} addr (after w/ unconf 3)`, async () => { + const one = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {limit: 13}); + assert.strictEqual(one.length, 13); + + for (let i = 0; i < 10; i++) + assert(confirmed.includes(one[i].hash)); + + for (let i = 10; i < 13; i++) + assert(unconfirmed.includes(one[i].hash)); + + const hash = one[12].hash; + + const two = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {after: hash, limit: 1}); + assert.strictEqual(two.length, 1); + assert(unconfirmed.includes(two[0].hash)); + + const all = await nclient.request( + 'GET', `/tx/address/${v.addr}`, {limit: 14}); + assert.strictEqual(all.length, 14); + + assert.deepEqual(sanitize(one.concat(two)), sanitize(all)); + }); + + it(`txs by ${v.label} addr (after, reverse)`, async () => { + const one = await nclient.request( + 'GET', `/tx/address/${v.addr}`, + {limit: 8, reverse: true}); + + assert.strictEqual(one.length, 8); + + for (let i = 0; i < 5; i++) + assert(unconfirmed.includes(one[i].hash)); + + for (let i = 5; i < 8; i++) + assert(confirmed.includes(one[i].hash)); + + // The after hash is within the + // confirmed transactions. + const hash = one[7].hash; + + const two = await nclient.request( + 'GET', `/tx/address/${v.addr}`, + {after: hash, limit: 3, reverse: true}); + + assert.strictEqual(two.length, 3); + + for (let i = 0; i < 3; i++) + assert(confirmed.includes(two[i].hash)); + + const all = await nclient.request( + 'GET', `/tx/address/${v.addr}`, + {limit: 11, reverse: true}); + + assert.strictEqual(all.length, 11); + + for (let i = 0; i < 5; i++) + assert(unconfirmed.includes(all[i].hash)); + + for (let i = 5; i < 11; i++) + assert(confirmed.includes(all[i].hash)); + + assert.deepEqual(sanitize(one.concat(two)), sanitize(all)); + }); + + it(`txs by ${v.label} addr (after, reverse w/ unconf)`, async () => { + const one = await nclient.request( + 'GET', `/tx/address/${v.addr}`, + {limit: 5, reverse: true}); + + assert.strictEqual(one.length, 5); + for (let i = 0; i < 5; i++) + assert(unconfirmed.includes(one[i].hash)); + + // The after hash is within the + // unconfirmed transactions. + const hash = one[4].hash; + + const two = await nclient.request( + 'GET', `/tx/address/${v.addr}`, + {after: hash, limit: 3, reverse: true}); + + assert.strictEqual(two.length, 3); + + for (let i = 0; i < 3; i++) + assert(confirmed.includes(two[i].hash)); + + const all = await nclient.request( + 'GET', `/tx/address/${v.addr}`, + {limit: 8, reverse: true}); + + assert.strictEqual(all.length, 8); + + for (let i = 0; i < 5; i++) + assert(unconfirmed.includes(all[i].hash)); + + for (let i = 5; i < 8; i++) + assert(confirmed.includes(all[i].hash)); + + assert.deepEqual(sanitize(one.concat(two)), sanitize(all)); + }); + + it(`txs by ${v.label} addr (after, reverse w/ unconf 2)`, async () => { + const one = await nclient.request( + 'GET', `/tx/address/${v.addr}`, + {limit: 3, reverse: true}); + + assert.strictEqual(one.length, 3); + for (let i = 0; i < 3; i++) + assert(unconfirmed.includes(one[i].hash)); + + const hash = one[2].hash; + + const two = await nclient.request( + 'GET', `/tx/address/${v.addr}`, + {after: hash, limit: 1, reverse: true}); + + assert.strictEqual(two.length, 1); + assert(unconfirmed.includes(two[0].hash)); + + const all = await nclient.request( + 'GET', `/tx/address/${v.addr}`, + {limit: 4, reverse: true}); + + assert.strictEqual(all.length, 4); + + for (let i = 0; i < 4; i++) + assert(unconfirmed.includes(all[i].hash)); + + assert.deepEqual(sanitize(one.concat(two)), sanitize(all)); + }); + } + + describe('Errors', function() { + it('will give error if limit is exceeded', async () => { + await assert.rejects(async () => { + await nclient.request( + 'GET', `/tx/address/${vectors[0].addr}`, {limit: 101}); + }, { + name: 'Error', + message: 'Limit above max of 100.' + }); + }); + + it('will give error with invalid after hash', async () => { + await assert.rejects(async () => { + await nclient.request( + 'GET', `/tx/address/${vectors[0].addr}`, {after: 'deadbeef'}); + }, { + name: 'Error', + message: 'after must be a hex string.' + }); + }); + + it('will give error with invalid reverse', async () => { + await assert.rejects(async () => { + await nclient.request( + 'GET', `/tx/address/${vectors[0].addr}`, {reverse: 'sure'}); + }, { + name: 'Error', + message: 'reverse must be a boolean.' + }); + }); + }); + }); +}); diff --git a/test/input-test.js b/test/input-test.js index d707ada24..2c0edaf21 100644 --- a/test/input-test.js +++ b/test/input-test.js @@ -3,10 +3,10 @@ 'use strict'; -const Input = require('../lib/primitives/input'); +const bio = require('bufio'); const util = require('../lib/utils/util'); -const BufferReader = require('../lib/utils/reader'); -const assert = require('./util/assert'); +const Input = require('../lib/primitives/input'); +const assert = require('bsert'); const common = require('./util/common'); // Take input rawbytes from the raw data format @@ -22,7 +22,8 @@ const input2 = tx2.getRaw().slice(152, 339); const tx3 = common.readTX('tx4'); const input3 = tx3.getRaw().slice(5, 266); -const bip69tests = require('./data/bip69'); +// test files: https://github.com/bitcoinjs/bip69/blob/master/test/fixtures.json +const bip69tests = require('./data/bip69/bip69.json'); describe('Input', function() { it('should return same raw', () => { @@ -37,7 +38,7 @@ describe('Input', function() { it('should return same raw on fromReader', () => { [input1, input2, input3].forEach((rawinput) => { const raw = rawinput.slice(); - const input = Input.fromReader(new BufferReader(raw)); + const input = Input.fromReader(bio.read(raw)); assert.bufferEqual(raw, input.toRaw()); }); @@ -51,7 +52,7 @@ describe('Input', function() { const input = Input.fromRaw(raw); const type = input.getType(); - const addr = input.getAddress().toBase58(); + const addr = input.getAddress().toBase58('main'); const prevout = input.prevout.toRaw(); assert.strictEqual(type, 'pubkeyhash'); @@ -100,7 +101,7 @@ describe('Input', function() { const type = input.getType(); const subtype = input.getSubtype(); - const addr = input.getAddress().toBase58(); + const addr = input.getAddress().toBase58('main'); const prevout = input.prevout.toRaw(); const redeem = input.getRedeem().toRaw(); @@ -216,8 +217,9 @@ describe('Input', function() { const options = { prevout: { - hash: '8759d7397a86d6c42dfe2c55612e523d' + - '171e51708fec9e289118deb5ba994001', + hash: Buffer.from( + '8759d7397a86d6c42dfe2c55612e523d' + + '171e51708fec9e289118deb5ba994001', 'hex'), index: 1 }, script: rawscript, @@ -236,7 +238,7 @@ describe('Input', function() { const inputs = test.inputs.map((prevout, i) => { const input = Input.fromOptions({ prevout: { - hash: util.revHex(prevout.txId), + hash: util.fromRev(prevout.txId), index: prevout.vout } }); diff --git a/test/keyring-test.js b/test/keyring-test.js index 05fed287b..ba5d8a0fd 100644 --- a/test/keyring-test.js +++ b/test/keyring-test.js @@ -3,14 +3,15 @@ 'use strict'; -const assert = require('./util/assert'); +const assert = require('bsert'); const KeyRing = require('../lib/primitives/keyring'); +const Script = require('../lib/script/script'); const uncompressed = KeyRing.fromSecret( - '6vrJ6bnKwaSuimkkRLpNNziSjqwZCG59kfFC9P2kjbUUs5Y6Cw9'); + '6vrJ6bnKwaSuimkkRLpNNziSjqwZCG59kfFC9P2kjbUUs5Y6Cw9', 'main'); const compressed = KeyRing.fromSecret( - 'TAgaTiX4btdMhNY6eSU5N5jvc71o6hXKdhoeBzEk31AHykGDou8i'); + 'TAgaTiX4btdMhNY6eSU5N5jvc71o6hXKdhoeBzEk31AHykGDou8i', 'main'); describe('KeyRing', function() { it('should get uncompressed public key', () => { @@ -23,13 +24,13 @@ describe('KeyRing', function() { it('should get uncompressed public key address', () => { assert.strictEqual( 'Lbnu1x4UfToiiFGU8MvPrLpj2GSrtUrxFH', - uncompressed.getKeyAddress('base58')); + uncompressed.getKeyAddress('base58', 'main')); }); it('should get uncompressed WIF', () => { assert.strictEqual( '6vrJ6bnKwaSuimkkRLpNNziSjqwZCG59kfFC9P2kjbUUs5Y6Cw9', - uncompressed.toSecret()); + uncompressed.toSecret('main')); }); it('should get compressed public key', () => { @@ -41,12 +42,25 @@ describe('KeyRing', function() { it('should get compressed public key address', () => { assert.strictEqual( 'LZGpRyQPybaDjbRGoB87YH2ebFnmKYmRui', - compressed.getKeyAddress('base58')); + compressed.getKeyAddress('base58', 'main')); }); it('should get compressed WIF', () => { assert.strictEqual( 'TAgaTiX4btdMhNY6eSU5N5jvc71o6hXKdhoeBzEk31AHykGDou8i', - compressed.toSecret()); + compressed.toSecret('main')); + }); + + it('should get keys from multisig', () => { + const script = Script.fromMultisig(1, 2, [ + compressed.getPublicKey(), + uncompressed.getPublicKey()]); + + assert.strictEqual( + compressed.getPublicKey(), + KeyRing.fromMultisigScript(script, 1).getPublicKey()); + assert.strictEqual( + uncompressed.getPublicKey(), + KeyRing.fromMultisigScript(script, 2).getPublicKey()); }); }); diff --git a/test/mempool-test.js b/test/mempool-test.js index 1af9a72f2..2fbb2e845 100644 --- a/test/mempool-test.js +++ b/test/mempool-test.js @@ -3,11 +3,13 @@ 'use strict'; -const assert = require('./util/assert'); -const encoding = require('../lib/utils/encoding'); -const random = require('../lib/crypto/random'); +const assert = require('bsert'); +const random = require('bcrypto/lib/random'); +const common = require('../lib/blockchain/common'); +const Block = require('../lib/primitives/block'); const MempoolEntry = require('../lib/mempool/mempoolentry'); const Mempool = require('../lib/mempool/mempool'); +const AddrIndexer = require('../lib/mempool/addrindexer'); const WorkerPool = require('../lib/workers/workerpool'); const Chain = require('../lib/blockchain/chain'); const MTX = require('../lib/primitives/mtx'); @@ -15,23 +17,39 @@ const Coin = require('../lib/primitives/coin'); const KeyRing = require('../lib/primitives/keyring'); const Address = require('../lib/primitives/address'); const Outpoint = require('../lib/primitives/outpoint'); +const Input = require('../lib/primitives/input'); const Script = require('../lib/script/script'); +const Opcode = require('../lib/script/opcode'); +const opcodes = Script.opcodes; const Witness = require('../lib/script/witness'); const MemWallet = require('./util/memwallet'); +const BlockStore = require('../lib/blockstore/level'); +const {BufferSet} = require('buffer-map'); + const ALL = Script.hashType.ALL; +const VERIFY_NONE = common.flags.VERIFY_NONE; + +const ONE_HASH = Buffer.alloc(32, 0x00); +ONE_HASH[0] = 0x01; const workers = new WorkerPool({ - enabled: true + enabled: true, + size: 2 +}); + +const blocks = new BlockStore({ + memory: true }); const chain = new Chain({ - db: 'memory', - workers + memory: true, + workers, + blocks }); const mempool = new Mempool({ chain, - db: 'memory', + memory: true, workers }); @@ -60,15 +78,41 @@ function dummyInput(script, hash) { return Coin.fromTX(fund, 0, -1); } +async function getMockBlock(chain, txs = [], cb = true) { + if (cb) { + const raddr = KeyRing.generate().getAddress(); + const mtx = new MTX(); + mtx.addInput(new Input()); + mtx.addOutput(raddr, 0); + + txs = [mtx.toTX(), ...txs]; + } + + const now = Math.floor(Date.now() / 1000); + const time = chain.tip.time <= now ? chain.tip.time + 1 : now; + + const block = new Block(); + block.txs = txs; + block.prevBlock = chain.tip.hash; + block.time = time; + block.bits = await chain.getTarget(block.time, chain.tip); + + return block; +} + describe('Mempool', function() { this.timeout(5000); it('should open mempool', async () => { + await workers.open(); + await blocks.open(); + await chain.open(); await mempool.open(); chain.state.flags |= Script.flags.VERIFY_WITNESS; }); it('should handle incoming orphans and TXs', async () => { + this.timeout(20000); const key = KeyRing.generate(); const t1 = new MTX(); @@ -77,7 +121,7 @@ describe('Mempool', function() { const script = Script.fromPubkey(key.publicKey); - t1.addCoin(dummyInput(script, encoding.ONE_HASH.toString('hex'))); + t1.addCoin(dummyInput(script, ONE_HASH)); const sig = t1.signature(0, script, 700000, key.privateKey, ALL, 0); @@ -127,7 +171,7 @@ describe('Mempool', function() { // Fake signature const input = fake.inputs[0]; - input.script.setData(0, encoding.ZERO_SIG); + input.script.setData(0, Buffer.alloc(73, 0x00)); input.script.compile(); // balance: 110000 @@ -169,10 +213,60 @@ describe('Mempool', function() { const txs = mempool.getHistory(); assert(txs.some((tx) => { - return tx.hash('hex') === f1.hash('hex'); + return tx.hash().equals(f1.hash()); })); }); + it('should get spend coins and reflect in coinview', async () => { + const wallet = new MemWallet(); + const script = Script.fromAddress(wallet.getAddress()); + const dummyCoin = dummyInput(script, random.randomBytes(32)); + + // spend first output + const mtx1 = new MTX(); + mtx1.addOutput(wallet.getAddress(), 50000); + mtx1.addCoin(dummyCoin); + wallet.sign(mtx1); + + // spend second tx + const tx1 = mtx1.toTX(); + const coin1 = Coin.fromTX(tx1, 0, -1); + const mtx2 = new MTX(); + + mtx2.addOutput(wallet.getAddress(), 10000); + mtx2.addOutput(wallet.getAddress(), 30000); // 10k fee.. + mtx2.addCoin(coin1); + + wallet.sign(mtx2); + + const tx2 = mtx2.toTX(); + + await mempool.addTX(tx1); + + { + const view = await mempool.getCoinView(tx2); + assert(view.hasEntry(coin1)); + } + + await mempool.addTX(tx2); + + // we should not have coins available in the mempool for these txs. + { + const view = await mempool.getCoinView(tx1); + const sview = await mempool.getSpentView(tx1); + + assert(!view.hasEntry(dummyCoin)); + assert(sview.hasEntry(dummyCoin)); + } + + { + const view = await mempool.getCoinView(tx2); + const sview = await mempool.getSpentView(tx2); + assert(!view.hasEntry(coin1)); + assert(sview.hasEntry(coin1)); + } + }); + it('should handle locktime', async () => { const key = KeyRing.generate(); @@ -181,7 +275,7 @@ describe('Mempool', function() { tx.addOutput(wallet.getAddress(), 100000); const prev = Script.fromPubkey(key.publicKey); - const prevHash = random.randomBytes(32).toString('hex'); + const prevHash = random.randomBytes(32); tx.addCoin(dummyInput(prev, prevHash)); tx.setLocktime(200); @@ -203,7 +297,7 @@ describe('Mempool', function() { tx.addOutput(wallet.getAddress(), 100000); const prev = Script.fromPubkey(key.publicKey); - const prevHash = random.randomBytes(32).toString('hex'); + const prevHash = random.randomBytes(32); tx.addCoin(dummyInput(prev, prevHash)); tx.setLocktime(200); @@ -234,7 +328,7 @@ describe('Mempool', function() { tx.addOutput(wallet.getAddress(), 100000); const prev = Script.fromProgram(0, key.getKeyHash()); - const prevHash = random.randomBytes(32).toString('hex'); + const prevHash = random.randomBytes(32); tx.addCoin(dummyInput(prev, prevHash)); @@ -264,7 +358,7 @@ describe('Mempool', function() { tx.addOutput(wallet.getAddress(), 100000); const prev = Script.fromPubkey(key.publicKey); - const prevHash = random.randomBytes(32).toString('hex'); + const prevHash = random.randomBytes(32); tx.addCoin(dummyInput(prev, prevHash)); @@ -283,6 +377,54 @@ describe('Mempool', function() { assert(!mempool.hasReject(tx.hash())); }); + it('should cache a non-malleated tx with non-empty stack', async () => { + // Wrap in P2SH, so we pass standardness checks. + const key = KeyRing.generate(); + + { + const script = new Script(); + script.pushOp(opcodes.OP_1); + script.compile(); + key.script = script; + } + + const wallet = new MemWallet(); + const script = Script.fromAddress(wallet.getAddress()); + const dummyCoin = dummyInput(script, random.randomBytes(32)); + + // spend first output + const t1 = new MTX(); + t1.addOutput(key.getAddress(), 50000); + t1.addCoin(dummyCoin); + wallet.sign(t1); + + const t2 = new MTX(); + t2.addCoin(Coin.fromTX(t1, 0, 0)); + t2.addOutput(wallet.getAddress(), 40000); + + { + const script = new Script(); + script.pushOp(opcodes.OP_1); + script.pushData(key.script.toRaw()); + script.compile(); + + t2.inputs[0].script = script; + } + + await mempool.addTX(t1.toTX()); + + let err; + try { + await mempool.addTX(t2.toTX()); + } catch (e) { + err = e; + } + + assert(err); + assert(!err.malleated); + assert(mempool.hasReject(t2.hash())); + }); + it('should not cache a malleated wtx with wit removed', async () => { const key = KeyRing.generate(); @@ -293,7 +435,7 @@ describe('Mempool', function() { tx.addOutput(wallet.getAddress(), 100000); const prev = Script.fromProgram(0, key.getKeyHash()); - const prevHash = random.randomBytes(32).toString('hex'); + const prevHash = random.randomBytes(32); tx.addCoin(dummyInput(prev, prevHash)); @@ -317,7 +459,7 @@ describe('Mempool', function() { tx.addOutput(wallet.getAddress(), 100000); const prev = Script.fromPubkey(key.publicKey); - const prevHash = random.randomBytes(32).toString('hex'); + const prevHash = random.randomBytes(32); tx.addCoin(dummyInput(prev, prevHash)); @@ -349,5 +491,568 @@ describe('Mempool', function() { it('should destroy mempool', async () => { await mempool.close(); + await chain.close(); + await blocks.close(); + await workers.close(); + }); + + describe('Index', function () { + const workers = new WorkerPool({ + enabled: true, + size: 2 + }); + + const blocks = new BlockStore({ + memory: true + }); + + const chain = new Chain({ + memory: true, + workers, + blocks + }); + + const mempool = new Mempool({ + chain, + workers, + memory: true, + indexAddress: true + }); + + before(async () => { + await blocks.open(); + await mempool.open(); + await chain.open(); + await workers.open(); + }); + + after(async () => { + await workers.close(); + await chain.close(); + await mempool.close(); + await blocks.close(); + }); + + // Number of coins available in + // chaincoins (100k satoshi per coin). + const N = 100; + const chaincoins = new MemWallet(); + const wallet = new MemWallet(); + + it('should create coins in chain', async () => { + const mtx = new MTX(); + mtx.addInput(new Input()); + + for (let i = 0; i < N; i++) { + const addr = chaincoins.createReceive().getAddress(); + mtx.addOutput(addr, 100000); + } + + const cb = mtx.toTX(); + const block = await getMockBlock(chain, [cb], false); + const entry = await chain.add(block, VERIFY_NONE); + + await mempool._addBlock(entry, block.txs); + + // Add 100 blocks so we don't get + // premature spend of coinbase. + for (let i = 0; i < 100; i++) { + const block = await getMockBlock(chain); + const entry = await chain.add(block, VERIFY_NONE); + + await mempool._addBlock(entry, block.txs); + } + + chaincoins.addTX(cb); + }); + + it('should spend txs and coins in the mempool', async () => { + // Verify coins are removed from the coin index. + const coin = chaincoins.getCoins()[0]; + const addr = wallet.createReceive().getAddress(); + + const mtx1 = new MTX(); + + mtx1.addCoin(coin); + mtx1.addOutput(addr, 90000); + + chaincoins.sign(mtx1); + + const tx1 = mtx1.toTX(); + + chaincoins.addTX(tx1, -1); + wallet.addTX(tx1, -1); + + { + const missing = await mempool.addTX(tx1); + assert.strictEqual(missing, null); + } + + assert(mempool.hasCoin(tx1.hash(), 0)); + + { + const txs = mempool.getTXByAddress(addr); + const metas = mempool.getMetaByAddress(addr); + + assert.strictEqual(txs.length, 1); + assert.strictEqual(metas.length, 1); + + assert.bufferEqual(txs[0].hash(), tx1.hash()); + } + + const mtx2 = new MTX(); + + mtx2.addTX(tx1, 0, -1); + mtx2.addOutput(addr, 80000); + + wallet.sign(mtx2); + + const tx2 = mtx2.toTX(); + + { + const missing = await mempool.addTX(tx2); + assert.strictEqual(missing, null); + } + + wallet.addTX(tx2, -1); + + assert(!mempool.hasCoin(tx1.hash(), 0)); + assert(mempool.hasCoin(tx2.hash(), 0)); + + { + const txs = mempool.getTXByAddress(addr); + + assert.strictEqual(txs.length, 2); + } + }); + + it('should spend resolved orphans', async () => { + const coin = chaincoins.getCoins()[0]; + const addr = wallet.createReceive().getAddress(); + + const pmtx = new MTX(); + + pmtx.addOutput(addr, 90000); + pmtx.addCoin(coin); + + chaincoins.sign(pmtx); + + const parentTX = pmtx.toTX(); + + const cmtx = new MTX(); + + cmtx.addTX(pmtx.toTX(), 0, -1); + cmtx.addOutput(addr, 80000); + + wallet.sign(cmtx); + + const childTX = cmtx.toTX(); + + { + // Create orphan tx. + const missing = await mempool.addTX(childTX); + + // We only have one input missing. + assert.strictEqual(missing.length, 1); + } + + { + const txs = mempool.getTXByAddress(addr); + + assert.strictEqual(txs.length, 0); + } + + { + // Orphans are not coins. + const childCoin = mempool.getCoin(childTX.hash(), 0); + assert.strictEqual(childCoin, null); + } + + { + // Orphans should be resolved. + const missing = await mempool.addTX(parentTX); + assert.strictEqual(missing, null); + + // Coins should be available once they are resolved. + const parentCoin = mempool.getCoin(parentTX.hash(), 0); + + // We spent this. + assert.strictEqual(parentCoin, null); + + const childCoin = mempool.getCoin(childTX.hash(), 0); + assert(childCoin); + } + + { + const txs = mempool.getTXByAddress(addr); + assert.strictEqual(txs.length, 2); + } + + // Update coins in wallets. + for (const tx of [parentTX, childTX]) { + chaincoins.addTX(tx); + wallet.addTX(tx); + } + }); + + it('should remove double spend tx from mempool', async () => { + const coin = chaincoins.getCoins()[0]; + const addr = wallet.createReceive().getAddress(); + const randomAddress = KeyRing.generate().getAddress(); + + // We check double spending our mempool tx. + const mtx1 = new MTX(); + + mtx1.addCoin(coin); + mtx1.addOutput(addr, 90000); + + chaincoins.sign(mtx1); + + // This will double spend in block. + const mtx2 = new MTX(); + + mtx2.addCoin(coin); + mtx2.addOutput(randomAddress, 90000); + + chaincoins.sign(mtx2); + + const tx1 = mtx1.toTX(); + const tx2 = mtx2.toTX(); + + { + const missing = await mempool.addTX(tx1); + assert.strictEqual(missing, null); + } + + { + const txs = mempool.getTXByAddress(addr); + assert.strictEqual(txs.length, 1); + } + + assert(mempool.hasCoin(tx1.hash(), 0)); + + const block = await getMockBlock(chain, [tx2]); + const entry = await chain.add(block, VERIFY_NONE); + + await mempool._addBlock(entry, block.txs); + + { + const txs = mempool.getTXByAddress(addr); + assert.strictEqual(txs.length, 0); + } + + assert(!mempool.hasCoin(tx1.hash(), 0)); + + chaincoins.addTX(tx2); + }); + + it('should remove confirmed txs from mempool', async () => { + const coin = chaincoins.getCoins()[0]; + const addr = wallet.createReceive().getAddress(); + + const mtx = new MTX(); + + mtx.addCoin(coin); + mtx.addOutput(addr, 90000); + + chaincoins.sign(mtx); + + const tx = mtx.toTX(); + + await mempool.addTX(tx); + + assert(mempool.hasCoin(tx.hash(), 0)); + + { + const txs = mempool.getTXByAddress(addr); + assert.strictEqual(txs.length, 1); + } + + const block = await getMockBlock(chain, [tx]); + const entry = await chain.add(block, VERIFY_NONE); + + await mempool._addBlock(entry, block.txs); + + { + const txs = mempool.getTXByAddress(addr); + assert.strictEqual(txs.length, 0); + } + + assert(!mempool.hasCoin(tx.hash(), 0)); + + chaincoins.addTX(tx); + wallet.addTX(tx); + }); + }); + + describe('AddrIndexer', function () { + it('will not get key for witness program v1', function() { + const addrindex = new AddrIndexer(); + + // Create a witness program version 1 with + // 40 byte data push. + const script = new Script(); + script.push(Opcode.fromSmall(1)); + script.push(Opcode.fromData(Buffer.alloc(40))); + script.compile(); + const addr = Address.fromScript(script); + + const key = addrindex.getKey(addr); + + assert.strictEqual(key, null); + }); + + it('will get key for witness program v0', function() { + const addrindex = new AddrIndexer(); + + // Create a witness program version 0 with + // 32 byte data push. + const script = new Script(); + script.push(Opcode.fromSmall(0)); + script.push(Opcode.fromData(Buffer.alloc(32))); + script.compile(); + const addr = Address.fromScript(script); + + const key = addrindex.getKey(addr); + + assert.bufferEqual(key, Buffer.from('0a' + '00'.repeat(32), 'hex')); + }); + }); + + describe('Mempool persistent cache', function () { + const workers = new WorkerPool({ + enabled: true, + size: 2 + }); + + const blocks = new BlockStore({ + memory: true + }); + + const chain = new Chain({ + memory: true, + workers, + blocks + }); + + const mempool = new Mempool({ + chain, + workers, + memory: true, + indexAddress: true, + persistent: true + }); + + before(async () => { + await blocks.open(); + await mempool.open(); + await chain.open(); + await workers.open(); + }); + + after(async () => { + await workers.close(); + await chain.close(); + await mempool.close(); + await blocks.close(); + }); + + // Number of coins available in + // chaincoins (100k satoshi per coin). + const N = 100; + const chaincoins = new MemWallet(); + const wallet = new MemWallet(); + + it('should create txs in chain', async () => { + const mtx = new MTX(); + mtx.addInput(new Input()); + + for (let i = 0; i < N; i++) { + const addr = chaincoins.createReceive().getAddress(); + mtx.addOutput(addr, 100000); + } + + const cb = mtx.toTX(); + const block = await getMockBlock(chain, [cb], false); + const entry = await chain.add(block, VERIFY_NONE); + + await mempool._addBlock(entry, block.txs); + + // Add 100 blocks so we don't get premature + // spend of coinbase. + for (let i = 0; i < 100; i++) { + const block = await getMockBlock(chain); + const entry = await chain.add(block, VERIFY_NONE); + + await mempool._addBlock(entry, block.txs); + } + + chaincoins.addTX(cb); + }); + + it('should restore txs in the mempool', async () => { + this.timeout(20000); + const coins = chaincoins.getCoins(); + + assert.strictEqual(coins.length, N); + + const addrs = []; + const txs = 20; + const spend = 5; + + for (let i = 0; i < txs; i++) + addrs.push(wallet.createReceive().getAddress()); + + const mempoolTXs = new BufferSet(); + const mempoolCoins = new BufferSet(); + + // Send 15 txs to the wallet. + for (let i = 0; i < txs - spend; i++) { + const mtx = new MTX(); + + mtx.addCoin(coins[i]); + mtx.addOutput(addrs[i], 90000); + + chaincoins.sign(mtx); + + const tx = mtx.toTX(); + const missing = await mempool.addTX(tx); + + assert.strictEqual(missing, null); + assert(mempool.hasCoin(tx.hash(), 0)); + + // Indexer checks. + { + const txs = mempool.getTXByAddress(addrs[i]); + + assert.strictEqual(txs.length, 1); + assert.bufferEqual(txs[0].hash(), tx.hash()); + } + + wallet.addTX(tx); + + mempoolTXs.add(tx.hash()); + mempoolCoins.add(Outpoint.fromTX(tx, 0).toKey()); + } + + // Spend first 5 coins from the mempool. + for (let i = 0; i < spend; i++) { + const coin = wallet.getCoins()[0]; + const addr = addrs[txs - spend + i]; + const mtx = new MTX(); + + mtx.addCoin(coin); + mtx.addOutput(addr, 80000); + + wallet.sign(mtx); + + const tx = mtx.toTX(); + const missing = await mempool.addTX(tx); + + assert.strictEqual(missing, null); + assert(!mempool.hasCoin(coin.hash, 0)); + assert(mempool.hasCoin(tx.hash(), 0)); + + { + const txs = mempool.getTXByAddress(addr); + assert.strictEqual(txs.length, 1); + } + + { + const txs = mempool.getTXByAddress(addrs[i]); + assert.strictEqual(txs.length, 2); + } + + mempoolTXs.add(tx.hash()); + mempoolCoins.delete(coin.toKey()); + mempoolCoins.add(Outpoint.fromTX(tx, 0).toKey()); + + wallet.addTX(tx); + } + + const verifyMempoolState = (mempool) => { + // Verify general state of the mempool. + assert.strictEqual(mempool.map.size, txs); + assert.strictEqual(mempool.spents.size, txs); + + assert.strictEqual(mempool.addrindex.map.size, txs); + + // Verify txs are same. + for (const val of mempoolTXs.values()) + assert(mempool.getTX(val)); + + for (const opkey of mempoolCoins.values()) { + const outpoint = Outpoint.fromRaw(opkey); + assert(mempool.hasCoin(outpoint.hash, outpoint.index)); + } + + // Coins in these txs are spent. + for (let i = 0; i < spend; i++) { + const addr = addrs[i]; + + const txs = mempool.getTXByAddress(addr); + assert.strictEqual(txs.length, 2); + } + + // These txs are untouched. + for (let i = spend; i < txs - spend; i++) { + const addr = addrs[i]; + + const txs = mempool.getTXByAddress(addr); + assert.strictEqual(txs.length, 1); + } + + // These are txs spending mempool txs. + for (let i = txs - spend; i < txs; i++) { + const addr = addrs[i]; + + const txs = mempool.getTXByAddress(addr); + assert.strictEqual(txs.length, 1); + } + }; + + verifyMempoolState(mempool); + + // Hack to get in memory cache in new mempool. + const cache = mempool.cache; + + // We need to manually sync because when first block + // was mined there were no mempool txs. + await cache.sync(chain.tip.hash); + + // Apply batch to the memdb. + await cache.flush(); + await mempool.close(); + + let err; + { + const mempool = new Mempool({ + chain, + workers, + memory: true, + indexAddress: true, + persistent: true + }); + + mempool.cache = cache; + + await mempool.open(); + + try { + verifyMempoolState(mempool); + } catch (e) { + err = e; + } finally { + await cache.wipe(); + await mempool.close(); + } + } + + // Reopen for after cleanup. + await mempool.open(); + + if (err) + throw err; + }); }); }); diff --git a/test/mnemonic-test.js b/test/mnemonic-test.js index 10429cee3..b0e670df1 100644 --- a/test/mnemonic-test.js +++ b/test/mnemonic-test.js @@ -3,7 +3,7 @@ 'use strict'; -const assert = require('./util/assert'); +const assert = require('bsert'); const Mnemonic = require('../lib/hd/mnemonic'); const HDPrivateKey = require('../lib/hd/private'); @@ -27,31 +27,29 @@ describe('Mnemonic', function() { it(`should create a ${language} mnemonic from entropy (${i})`, () => { const mnemonic = new Mnemonic({ language, - entropy, - passphrase + entropy }); assert.strictEqual(mnemonic.getPhrase(), phrase); assert.bufferEqual(mnemonic.getEntropy(), entropy); - assert.bufferEqual(mnemonic.toSeed(), seed); + assert.bufferEqual(mnemonic.toSeed(passphrase), seed); - const key = HDPrivateKey.fromMnemonic(mnemonic); - assert.strictEqual(key.toBase58(), xpriv); + const key = HDPrivateKey.fromMnemonic(mnemonic, passphrase); + assert.strictEqual(key.toBase58('main'), xpriv); }); it(`should create a ${language} mnemonic from phrase (${i})`, () => { const mnemonic = new Mnemonic({ language, - phrase, - passphrase + phrase }); assert.strictEqual(mnemonic.getPhrase(), phrase); assert.bufferEqual(mnemonic.getEntropy(), entropy); - assert.bufferEqual(mnemonic.toSeed(), seed); + assert.bufferEqual(mnemonic.toSeed(passphrase), seed); - const key = HDPrivateKey.fromMnemonic(mnemonic); - assert.strictEqual(key.toBase58(), xpriv); + const key = HDPrivateKey.fromMnemonic(mnemonic, passphrase); + assert.strictEqual(key.toBase58('main'), xpriv); }); i += 1; diff --git a/test/net-test.js b/test/net-test.js new file mode 100644 index 000000000..54f571b9b --- /dev/null +++ b/test/net-test.js @@ -0,0 +1,2226 @@ +/* eslint-env mocha */ +/* eslint prefer-arrow-callback: "off" */ + +'use strict'; + +const assert = require('bsert'); +const test = require('./util/common'); +const {BloomFilter} = require('bfilter'); +const Logger = require('blgr'); +const common = require('../lib/net/common'); +const services = common.services; +const Framer = require('../lib/net/framer'); +const packets = require('../lib/net/packets'); +const NetAddress = require('../lib/net/netaddress'); +const {CompactBlock, TXRequest, TXResponse} = require('../lib/net/bip152'); +const Peer = require('../lib/net/peer'); +const Pool = require('../lib/net/pool'); +const InvItem = require('../lib/primitives/invitem'); +const Headers = require('../lib/primitives/headers'); +const MerkleBlock = require('../lib/primitives/merkleblock'); +const ChainEntry = require('../lib/blockchain/chainentry'); +const Network = require('../lib/protocol/network'); +const {VerifyError} = require('../lib/protocol/errors'); +const consensus = require('../lib/protocol/consensus'); +const util = require('../lib/utils/util'); + +// Block test vectors +const block300025 = test.readBlock('block300025'); + +// Merkle block test vectors +const merkle300025 = test.readMerkle('merkle300025'); + +// Small SegWit block test vector +const block482683 = test.readBlock('block482683'); + +describe('Net', function() { + describe('Packets', function() { + it('version', () => { + const check = (pkt) => { + assert.equal(pkt.cmd, 'version'); + assert.equal(pkt.type, packets.types.VERSION); + assert.equal(pkt.version, 70012); + assert.equal(pkt.services, 10); + assert.equal(pkt.time, 1558405603); + assert.equal(pkt.remote.host, '127.0.0.1'); + assert.equal(pkt.remote.port, 8334); + assert.equal(pkt.local.host, '127.0.0.1'); + assert.equal(pkt.local.port, 8335); + assert.bufferEqual(pkt.nonce, Buffer.alloc(8, 0x00)); + assert.equal(pkt.agent, 'bcoin'); + assert.equal(pkt.height, 500000); + assert.equal(pkt.noRelay, true); + }; + + let pkt = new packets.VersionPacket({ + version: 70012, + services: 10, + time: 1558405603, + remote: { + host: '127.0.0.1', + port: 8334 + }, + local: { + host: '127.0.0.1', + port: 8335 + }, + nonce: Buffer.alloc(8, 0x00), + agent: 'bcoin', + height: 500000, + noRelay: true + }); + + check(pkt); + + pkt = packets.VersionPacket.fromRaw(pkt.toRaw()); + + check(pkt); + }); + + it('verack', () => { + const check = (pkt) => { + assert.equal(pkt.cmd, 'verack'); + assert.equal(pkt.type, packets.types.VERACK); + }; + + let pkt = new packets.VerackPacket(); + check(pkt); + + pkt = packets.VerackPacket.fromRaw(pkt.toRaw()); + check(pkt); + }); + + it('ping', () => { + const check = (pkt, nonce) => { + assert.equal(pkt.cmd, 'ping'); + assert.equal(pkt.type, packets.types.PING); + if (nonce) + assert.bufferEqual(pkt.nonce, Buffer.alloc(8, 0x01)); + }; + + let pkt = new packets.PingPacket(Buffer.alloc(8, 0x01)); + check(pkt, true); + + pkt = packets.PingPacket.fromRaw(pkt.toRaw()); + check(pkt, true); + + pkt = new packets.PingPacket(); + check(pkt, false); + + pkt = packets.PingPacket.fromRaw(pkt.toRaw()); + check(pkt, false); + }); + + it('pong', () => { + const check = (pkt, nonce) => { + assert.equal(pkt.cmd, 'pong'); + assert.equal(pkt.type, packets.types.PONG); + if (nonce) + assert.bufferEqual(pkt.nonce, Buffer.alloc(8, 0x01)); + else { + assert.bufferEqual(pkt.nonce, Buffer.alloc(8, 0x00)); + } + }; + + let pkt = new packets.PongPacket(Buffer.alloc(8, 0x01)); + check(pkt, true); + + pkt = packets.PongPacket.fromRaw(pkt.toRaw()); + check(pkt, true); + + pkt = new packets.PongPacket(); + check(pkt, false); + + pkt = packets.PongPacket.fromRaw(pkt.toRaw()); + check(pkt, false); + }); + + it('getaddr', () => { + const check = (pkt) => { + assert.equal(pkt.cmd, 'getaddr'); + assert.equal(pkt.type, packets.types.GETADDR); + }; + + let pkt = new packets.GetAddrPacket(); + check(pkt); + + pkt = packets.GetAddrPacket.fromRaw(pkt.toRaw()); + check(pkt); + }); + + it('addr', () => { + const check = (pkt) => { + assert.equal(pkt.cmd, 'addr'); + assert.equal(pkt.type, packets.types.ADDR); + + let addr = pkt.items[0]; + assert.equal(addr.host, '127.0.0.2'); + assert.equal(addr.port, 8334); + assert.equal(addr.services, 101); + assert.equal(addr.time, 1558405603); + + addr = pkt.items[1]; + assert.equal(addr.host, '127.0.0.3'); + assert.equal(addr.port, 8333); + assert.equal(addr.services, 102); + assert.equal(addr.time, 1558405602); + }; + + const items = [ + new NetAddress({ + host: '127.0.0.2', + port: 8334, + services: 101, + time: 1558405603 + }), + new NetAddress({ + host: '127.0.0.3', + port: 8333, + services: 102, + time: 1558405602 + }) + ]; + + let pkt = new packets.AddrPacket(items); + check(pkt); + + pkt = packets.AddrPacket.fromRaw(pkt.toRaw()); + check(pkt); + }); + + it('inv', () => { + const check = (pkt, many) => { + assert.equal(pkt.cmd, 'inv'); + assert.equal(pkt.type, packets.types.INV); + + let item = pkt.items[0]; + assert.equal(item.type, 1); + assert.bufferEqual(item.hash, Buffer.alloc(32, 0x01)); + + item = pkt.items[1]; + assert.equal(item.type, 1); + assert.bufferEqual(item.hash, Buffer.alloc(32, 0x02)); + + if (many) { + for (let i = 2; i < 254; i++) { + item = pkt.items[i]; + assert.equal(item.type, 1); + assert.bufferEqual(item.hash, Buffer.alloc(32, 0x03)); + } + } + }; + + const items = [ + new InvItem(InvItem.types.TX, Buffer.alloc(32, 0x01)), + new InvItem(InvItem.types.TX, Buffer.alloc(32, 0x02)) + ]; + + let pkt = new packets.InvPacket(items); + check(pkt, false); + + pkt = packets.InvPacket.fromRaw(pkt.toRaw()); + check(pkt, false); + + while (items.length < 254) + items.push(new InvItem(InvItem.types.TX, Buffer.alloc(32, 0x03))); + + pkt = new packets.InvPacket(items); + check(pkt, true); + + pkt = packets.InvPacket.fromRaw(pkt.toRaw()); + check(pkt, true); + }); + + it('getdata', () => { + const check = (pkt) => { + assert.equal(pkt.cmd, 'getdata'); + assert.equal(pkt.type, packets.types.GETDATA); + + let item = pkt.items[0]; + assert.equal(item.type, 1); + assert.bufferEqual(item.hash, Buffer.alloc(32, 0x01)); + + item = pkt.items[1]; + assert.equal(item.type, 1); + assert.bufferEqual(item.hash, Buffer.alloc(32, 0x02)); + }; + + const items = [ + new InvItem(InvItem.types.TX, Buffer.alloc(32, 0x01)), + new InvItem(InvItem.types.TX, Buffer.alloc(32, 0x02)) + ]; + + let pkt = new packets.GetDataPacket(items); + check(pkt); + + pkt = packets.GetDataPacket.fromRaw(pkt.toRaw()); + check(pkt); + }); + + it('notfound', () => { + const check = (pkt) => { + assert.equal(pkt.cmd, 'notfound'); + assert.equal(pkt.type, packets.types.NOTFOUND); + + let item = pkt.items[0]; + assert.equal(item.type, 1); + assert.bufferEqual(item.hash, Buffer.alloc(32, 0x01)); + + item = pkt.items[1]; + assert.equal(item.type, 1); + assert.bufferEqual(item.hash, Buffer.alloc(32, 0x02)); + }; + + const items = [ + new InvItem(InvItem.types.TX, Buffer.alloc(32, 0x01)), + new InvItem(InvItem.types.TX, Buffer.alloc(32, 0x02)) + ]; + + let pkt = new packets.NotFoundPacket(items); + check(pkt); + + pkt = packets.NotFoundPacket.fromRaw(pkt.toRaw()); + check(pkt); + }); + + it('getblocks', () => { + const check = (pkt, values) => { + assert.equal(pkt.cmd, 'getblocks'); + assert.equal(pkt.type, packets.types.GETBLOCKS); + + if (values) { + assert.equal(pkt.locator.length, 2); + assert.bufferEqual(pkt.locator[0], Buffer.alloc(32, 0x01)); + assert.bufferEqual(pkt.locator[1], Buffer.alloc(32, 0x02)); + assert.bufferEqual(pkt.stop, Buffer.alloc(32, 0x03)); + } else { + assert.equal(pkt.locator.length, 0); + assert.strictEqual(pkt.stop, null); + } + }; + + const locator = [ + Buffer.alloc(32, 0x01), + Buffer.alloc(32, 0x02) + ]; + + const stop = Buffer.alloc(32, 0x03); + + let pkt = new packets.GetBlocksPacket(locator, stop); + check(pkt, true); + + pkt = packets.GetBlocksPacket.fromRaw(pkt.toRaw()); + check(pkt, true); + + pkt = new packets.GetBlocksPacket(); + check(pkt, false); + + pkt = packets.GetBlocksPacket.fromRaw(pkt.toRaw()); + check(pkt, false); + }); + + it('getheaders', () => { + const check = (pkt, values) => { + assert.equal(pkt.cmd, 'getheaders'); + assert.equal(pkt.type, packets.types.GETHEADERS); + + if (values) { + assert.equal(pkt.locator.length, 2); + assert.bufferEqual(pkt.locator[0], Buffer.alloc(32, 0x01)); + assert.bufferEqual(pkt.locator[1], Buffer.alloc(32, 0x02)); + assert.bufferEqual(pkt.stop, Buffer.alloc(32, 0x03)); + } else { + assert.equal(pkt.locator.length, 0); + assert.strictEqual(pkt.stop, null); + } + }; + + const locator = [ + Buffer.alloc(32, 0x01), + Buffer.alloc(32, 0x02) + ]; + + const stop = Buffer.alloc(32, 0x03); + + let pkt = new packets.GetHeadersPacket(locator, stop); + check(pkt, true); + + pkt = packets.GetHeadersPacket.fromRaw(pkt.toRaw()); + check(pkt, true); + + pkt = new packets.GetHeadersPacket(); + check(pkt, false); + + pkt = packets.GetHeadersPacket.fromRaw(pkt.toRaw()); + check(pkt, false); + }); + + it('headers', () => { + const check = (pkt, values, many) => { + assert.equal(pkt.cmd, 'headers'); + assert.equal(pkt.type, packets.types.HEADERS); + + assert.equal(pkt.items[0].version, 1); + assert.bufferEqual(pkt.items[0].prevBlock, Buffer.alloc(32, 0x01)); + assert.bufferEqual(pkt.items[0].merkleRoot, Buffer.alloc(32, 0x02)); + assert.equal(pkt.items[0].time, 1558405603); + assert.equal(pkt.items[0].bits, 403014710); + assert.equal(pkt.items[0].nonce, 101); + + assert.equal(pkt.items[1].version, 2); + assert.bufferEqual(pkt.items[1].prevBlock, Buffer.alloc(32, 0x02)); + assert.bufferEqual(pkt.items[1].merkleRoot, Buffer.alloc(32, 0x03)); + assert.equal(pkt.items[1].time, 1558405604); + assert.equal(pkt.items[1].bits, 403014711); + assert.equal(pkt.items[1].nonce, 102); + + if (many) { + for (let i = 2; i < 254; i++) { + const item = pkt.items[i]; + assert.equal(item.version, 3); + assert.bufferEqual(pkt.items[1].prevBlock, Buffer.alloc(32, 0x04)); + assert.bufferEqual(pkt.items[1].merkleRoot, Buffer.alloc(32, 0x05)); + assert.equal(pkt.items[1].time, 1558405605); + assert.equal(pkt.items[1].bits, 403014712); + assert.equal(pkt.items[1].nonce, 103); + } + } + }; + + const items = [ + new Headers({ + version: 1, + prevBlock: Buffer.alloc(32, 0x01), + merkleRoot: Buffer.alloc(32, 0x02), + time: 1558405603, + bits: 403014710, + nonce: 101 + }), + new Headers({ + version: 2, + prevBlock: Buffer.alloc(32, 0x02), + merkleRoot: Buffer.alloc(32, 0x03), + time: 1558405604, + bits: 403014711, + nonce: 102 + }) + ]; + + let pkt = new packets.HeadersPacket(items); + check(pkt, false); + + pkt = packets.HeadersPacket.fromRaw(pkt.toRaw()); + check(pkt, false); + + while (items.length < 254) { + items.push(new Headers({ + version: 3, + prevBlock: Buffer.alloc(32, 0x04), + merkleRoot: Buffer.alloc(32, 0x05), + time: 1558405605, + bits: 403014712, + nonce: 103 + })); + } + + pkt = new packets.HeadersPacket(items); + check(pkt, true); + + pkt = packets.HeadersPacket.fromRaw(pkt.toRaw()); + check(pkt, true); + }); + + it('sendheaders (BIP130)', () => { + const check = (pkt) => { + assert.equal(pkt.cmd, 'sendheaders'); + assert.equal(pkt.type, packets.types.SENDHEADERS); + }; + + let pkt = new packets.SendHeadersPacket(); + check(pkt); + + pkt = packets.SendHeadersPacket.fromRaw(pkt.toRaw()); + check(pkt); + }); + + it('block', () => { + const [block] = block300025.getBlock(); + const [witnessBlock] = block482683.getBlock(); + + const check = (pkt, witness, read) => { + assert.equal(pkt.cmd, 'block'); + assert.equal(pkt.type, packets.types.BLOCK); + + if (witness) { + assert.bufferEqual(pkt.block.hash(), witnessBlock.hash()); + if (!read) + assert.equal(pkt.witness, true); + } else { + assert.bufferEqual(pkt.block.hash(), block.hash()); + assert.equal(pkt.witness, false); + } + }; + + let pkt = new packets.BlockPacket(block, false); + check(pkt, false); + + pkt = packets.BlockPacket.fromRaw(pkt.toRaw()); + check(pkt, false); + + pkt = new packets.BlockPacket(witnessBlock, true); + check(pkt, true); + + pkt = packets.BlockPacket.fromRaw(pkt.toRaw()); + check(pkt, true, true); + }); + + it('tx', () => { + const [block] = block482683.getBlock(); + + const tx = block.txs[9]; + const witnessTx = block.txs[10]; + + const check = (pkt, witness, read) => { + assert.equal(pkt.cmd, 'tx'); + assert.equal(pkt.type, packets.types.TX); + + if (witness) { + assert.bufferEqual(pkt.tx.hash(), witnessTx.hash()); + if (!read) + assert.equal(pkt.witness, true); + } else { + assert.bufferEqual(pkt.tx.hash(), tx.hash()); + assert.equal(pkt.witness, false); + } + }; + + let pkt = new packets.TXPacket(tx, false); + check(pkt, false); + + pkt = packets.TXPacket.fromRaw(pkt.toRaw()); + check(pkt, false); + + pkt = new packets.TXPacket(witnessTx, true); + check(pkt, true); + + pkt = packets.TXPacket.fromRaw(pkt.toRaw()); + check(pkt, true, true); + }); + + it('reject', () => { + const check = (pkt) => { + assert.equal(pkt.cmd, 'reject'); + assert.equal(pkt.type, packets.types.REJECT); + + assert.equal(pkt.code, 1); + assert.equal(pkt.reason, 'test-reason'); + assert.equal(pkt.message, 'block'); + + assert.equal(pkt.getCode(), 'malformed'); + + assert.bufferEqual(pkt.hash, Buffer.alloc(32, 0x01)); + }; + + let pkt = new packets.RejectPacket({ + message: 'block', + code: 1, + reason: 'test-reason', + hash: Buffer.alloc(32, 0x01) + }); + + check(pkt); + + pkt = packets.RejectPacket.fromRaw(pkt.toRaw()); + check(pkt); + + pkt = packets.RejectPacket.fromReason( + 'malformed', + 'test-reason', + 'block', + Buffer.alloc(32, 0x01) + ); + + check(pkt); + + pkt = packets.RejectPacket.fromRaw(pkt.toRaw()); + check(pkt); + }); + + it('mempool (BIP35)', () => { + const check = (pkt) => { + assert.equal(pkt.cmd, 'mempool'); + assert.equal(pkt.type, packets.types.MEMPOOL); + }; + + let pkt = new packets.MempoolPacket(); + check(pkt); + + pkt = packets.MempoolPacket.fromRaw(pkt.toRaw()); + }); + + it('filterload (BIP37)', () => { + const check = (pkt) => { + assert.equal(pkt.cmd, 'filterload'); + assert.equal(pkt.type, packets.types.FILTERLOAD); + assert.equal(pkt.filter.test(Buffer.alloc(32, 0x01)), true); + }; + + const filter = BloomFilter.fromRate( + 20000, 0.001, BloomFilter.flags.ALL); + + filter.add(Buffer.alloc(32, 0x01)); + + let pkt = new packets.FilterLoadPacket(filter); + check(pkt); + + pkt = packets.FilterLoadPacket.fromRaw(pkt.toRaw()); + check(pkt); + }); + + it('filteradd (BIP37)', () => { + const check = (pkt) => { + assert.equal(pkt.cmd, 'filteradd'); + assert.equal(pkt.type, packets.types.FILTERADD); + assert.bufferEqual(pkt.data, Buffer.alloc(32, 0x02)); + }; + + let pkt = new packets.FilterAddPacket(Buffer.alloc(32, 0x02)); + check(pkt); + + pkt = packets.FilterAddPacket.fromRaw(pkt.toRaw()); + check(pkt); + }); + + it('filterclear (BIP37)', () => { + const check = (pkt) => { + assert.equal(pkt.cmd, 'filterclear'); + assert.equal(pkt.type, packets.types.FILTERCLEAR); + }; + + let pkt = new packets.FilterClearPacket(); + check(pkt); + + pkt = packets.FilterClearPacket.fromRaw(pkt.toRaw()); + check(pkt); + }); + + it('merkleblock (BIP37)', () => { + const [block] = merkle300025.getBlock(); + + const check = (pkt) => { + assert.equal(pkt.cmd, 'merkleblock'); + assert.equal(pkt.type, packets.types.MERKLEBLOCK); + + assert.bufferEqual(pkt.block.hash(), block.hash()); + }; + + let pkt = new packets.MerkleBlockPacket(block); + check(pkt); + + pkt = packets.MerkleBlockPacket.fromRaw(pkt.toRaw()); + check(pkt); + }); + + it('feefilter (BIP133)', () => { + const check = (pkt) => { + assert.equal(pkt.cmd, 'feefilter'); + assert.equal(pkt.type, packets.types.FEEFILTER); + + assert.equal(pkt.rate, 120000); + }; + + let pkt = new packets.FeeFilterPacket(120000); + check(pkt); + + pkt = packets.FeeFilterPacket.fromRaw(pkt.toRaw()); + check(pkt); + }); + + it('sendcmpct (BIP152)', () => { + const check = (pkt, mode, version) => { + assert.equal(pkt.cmd, 'sendcmpct'); + assert.equal(pkt.type, packets.types.SENDCMPCT); + + assert.equal(pkt.mode, mode); + assert.equal(pkt.version, version); + }; + + let pkt = new packets.SendCmpctPacket(); + check(pkt, 0, 1); + + pkt = packets.SendCmpctPacket.fromRaw(pkt.toRaw()); + check(pkt, 0, 1); + + pkt = new packets.SendCmpctPacket(1, 2); + check(pkt, 1, 2); + + pkt = packets.SendCmpctPacket.fromRaw(pkt.toRaw()); + check(pkt, 1, 2); + }); + + it('cmpctblock (BIP152)', () => { + const [block] = block300025.getBlock(); + const [witnessBlock] = block482683.getBlock(); + + const check = (pkt, witness, read) => { + assert.equal(pkt.cmd, 'cmpctblock'); + assert.equal(pkt.type, packets.types.CMPCTBLOCK); + + if (witness) { + assert.bufferEqual(pkt.block.hash(), witnessBlock.hash()); + if (!read) + assert.equal(pkt.witness, true); + } else { + assert.bufferEqual(pkt.block.hash(), block.hash()); + assert.equal(pkt.witness, false); + } + }; + + const compact = CompactBlock.fromBlock(block); + + let pkt = new packets.CmpctBlockPacket(compact); + check(pkt, false); + + pkt = packets.CmpctBlockPacket.fromRaw(pkt.toRaw()); + check(pkt, false); + + const witnessCompact = CompactBlock.fromBlock(witnessBlock); + + pkt = new packets.CmpctBlockPacket(witnessCompact, true); + check(pkt, true); + + pkt = packets.CmpctBlockPacket.fromRaw(pkt.toRaw()); + check(pkt, true, true); + }); + + it('getblocktxn (BIP152)', () => { + const check = (pkt) => { + assert.equal(pkt.cmd, 'getblocktxn'); + assert.equal(pkt.type, packets.types.GETBLOCKTXN); + + assert.bufferEqual(pkt.request.hash, Buffer.alloc(32, 0x01)); + assert.deepEqual(pkt.request.indexes, [2, 3, 5, 7, 11]); + }; + + const request = new TXRequest({ + hash: Buffer.alloc(32, 0x01), + indexes: [2, 3, 5, 7, 11] + }); + + let pkt = new packets.GetBlockTxnPacket(request); + check(pkt); + + pkt = packets.GetBlockTxnPacket.fromRaw(pkt.toRaw()); + check(pkt); + }); + + it('blocktxn (BIP152)', () => { + const [block] = block482683.getBlock(); + + const tx = block.txs[9]; + const witnessTx = block.txs[10]; + + const check = (pkt, witness, read) => { + assert.equal(pkt.cmd, 'blocktxn'); + assert.equal(pkt.type, packets.types.BLOCKTXN); + + assert.bufferEqual(pkt.response.hash, Buffer.alloc(32, 0x01)); + if (witness) { + assert.bufferEqual(pkt.response.txs[0].hash(), witnessTx.hash()); + if (!read) + assert.equal(pkt.witness, true); + } else { + assert.bufferEqual(pkt.response.txs[0].hash(), tx.hash()); + assert.equal(pkt.witness, false); + } + }; + + const response = new TXResponse({ + hash: Buffer.alloc(32, 0x01), + txs: [tx] + }); + + let pkt = new packets.BlockTxnPacket(response); + check(pkt, false); + + pkt = packets.BlockTxnPacket.fromRaw(pkt.toRaw()); + check(pkt, false); + + const witnessResponse = new TXResponse({ + hash: Buffer.alloc(32, 0x01), + txs: [witnessTx] + }); + + pkt = new packets.BlockTxnPacket(witnessResponse, true); + check(pkt, true); + + pkt = packets.BlockTxnPacket.fromRaw(pkt.toRaw()); + check(pkt, true, true); + }); + + it('unknown', () => { + const check = (pkt) => { + assert.equal(pkt.cmd, 'cmd'); + assert.equal(pkt.type, packets.types.UNKNOWN); + }; + + let pkt = new packets.UnknownPacket('cmd', Buffer.alloc(12, 0x01)); + check(pkt); + + pkt = packets.UnknownPacket.fromRaw('cmd', pkt.toRaw()); + check(pkt); + }); + }); + + describe('Peer', function() { + describe('handlePacket', function() { + it('will throw if destroyed', async () => { + const peer = Peer.fromOptions({}); + let err = null; + + peer.destroyed = true; + + try { + await peer.handlePacket(); + } catch(e) { + err = e; + } + + assert(err); + assert(err.message, 'Destroyed peer sent a packet.'); + }); + + it('will handle types correctly', async () => { + const map = new Map(); + map.set(packets.types.VERSION, 'handleVersion'); + map.set(packets.types.VERACK, 'handleVerack'); + map.set(packets.types.PING, 'handlePing'); + map.set(packets.types.PONG, 'handlePong'); + map.set(packets.types.GETADDR, false); + map.set(packets.types.ADDR, false); + map.set(packets.types.INV, false); + map.set(packets.types.GETDATA, false); + map.set(packets.types.NOTFOUND, false); + map.set(packets.types.GETBLOCKS, false); + map.set(packets.types.GETHEADERS, false); + map.set(packets.types.HEADERS, false); + map.set(packets.types.SENDHEADERS, 'handleSendHeaders'); + map.set(packets.types.BLOCK, false); + map.set(packets.types.TX, false); + map.set(packets.types.REJECT, false); + map.set(packets.types.MEMPOOL, false); + map.set(packets.types.FILTERLOAD, 'handleFilterLoad'); + map.set(packets.types.FILTERADD, 'handleFilterAdd'); + map.set(packets.types.FILTERCLEAR, 'handleFilterClear'); + map.set(packets.types.MERKLEBLOCK, false); + map.set(packets.types.FEEFILTER, 'handleFeeFilter'); + map.set(packets.types.SENDCMPCT, 'handleSendCmpct'); + map.set(packets.types.CMPCTBLOCK, false); + map.set(packets.types.GETBLOCKTXN, false); + map.set(packets.types.BLOCKTXN, false); + map.set(packets.types.UNKNOWN, false); + map.set(packets.types.INTERNAL, false); + map.set(packets.types.DATA, false); + + const wrap = (type, handler) => { + const peer = Peer.fromOptions({}); + const result = {count: 0, peer}; + + for (const fn of map.values()) { + if (fn) { + peer[fn] = (packet) => { + assert.equal(fn, handler); + assert(packet); + assert.equal(packet.type, type); + result.count += 1; + }; + } + } + + return result; + }; + + for (const [type, handler] of map) { + const stub = wrap(type, handler); + const packet = {type}; + + await stub.peer.handlePacket(packet); + if (handler) + assert.equal(stub.count, 1); + else + assert.equal(stub.count, 0); + } + }); + }); + + describe('handleVersion', function() { + it('will error if already sent version', async () => { + const peer = Peer.fromOptions({}); + peer.version = 1000; + const pkt = new packets.VersionPacket(); + let err = null; + + try { + await peer.handleVersion(pkt); + } catch (e) { + err = e; + } + assert(err); + }); + + it('will not connect to self', async () => { + const peer = Peer.fromOptions({}); + peer.options.hasNonce = () => true; + + const pkt = new packets.VersionPacket(); + let err = null; + + try { + await peer.handleVersion(pkt); + } catch (e) { + err = e; + } + assert(err); + assert.equal(err.message, 'We connected to ourself. Oops.'); + }); + + it('will error if below min version', async () => { + const peer = Peer.fromOptions({}); + + const pkt = new packets.VersionPacket({version: 70000}); + let err = null; + + try { + await peer.handleVersion(pkt); + } catch (e) { + err = e; + } + assert(err); + const msg = 'Peer does not support required protocol version.'; + assert.equal(err.message, msg); + }); + + it('will error if w/o network service (outbound)', async () => { + const peer = Peer.fromOptions({}); + peer.outbound = true; + + const pkt = new packets.VersionPacket({services: 0}); + + let err = null; + + try { + await peer.handleVersion(pkt); + } catch (e) { + err = e; + } + assert(err); + const msg = 'Peer does not support network services.'; + assert.equal(err.message, msg); + }); + + it('will error if w/o bloom service (outbound)', async () => { + const peer = Peer.fromOptions({spv: true}); + peer.outbound = true; + + const pkt = new packets.VersionPacket({ + services: 0 | services.NETWORK + }); + + let err = null; + + try { + await peer.handleVersion(pkt); + } catch (e) { + err = e; + } + assert(err); + const msg = 'Peer does not support BIP37.'; + assert.equal(err.message, msg); + }); + + it('will error if w/o bloom version (outbound)', async () => { + const peer = Peer.fromOptions({spv: true}); + peer.outbound = true; + + const pkt = new packets.VersionPacket({ + services: 0 | services.NETWORK | services.BLOOM, + version: common.BLOOM_VERSION - 1 + }); + + let err = null; + + try { + await peer.handleVersion(pkt); + } catch (e) { + err = e; + } + assert(err); + const msg = 'Peer does not support BIP37.'; + assert.equal(err.message, msg); + }); + + it('will error if w/o witness service (outbound)', async () => { + const peer = Peer.fromOptions({}); + peer.outbound = true; + + const pkt = new packets.VersionPacket({ + services: 0 | services.NETWORK + }); + + let err = null; + + try { + await peer.handleVersion(pkt); + } catch (e) { + err = e; + } + assert(err); + const msg = 'Peer does not support segregated witness.'; + assert.equal(err.message, msg); + }); + + it('will send ack (outbound)', async () => { + const peer = Peer.fromOptions({}); + peer.outbound = true; + + const pkt = new packets.VersionPacket({ + services: 0 | services.NETWORK | services.WITNESS + }); + + let called = false; + + peer.send = (packet) => { + assert(packet); + assert.equal(packet.type, packets.types.VERACK); + called = true; + }; + + await peer.handleVersion(pkt); + assert(called); + }); + + it('will send ack (outbound=false)', async () => { + const peer = Peer.fromOptions({}); + peer.outbound = false; + + const pkt = new packets.VersionPacket(); + + let called = false; + + peer.send = (packet) => { + assert(packet); + assert.equal(packet.type, packets.types.VERACK); + called = true; + }; + + await peer.handleVersion(pkt); + assert(called); + }); + }); + + describe('handleVerack', function() { + it('will set ack', async () => { + const peer = Peer.fromOptions({}); + assert.equal(peer.ack, false); + const pkt = new packets.VerackPacket(); + await peer.handleVerack(pkt); + assert.equal(peer.ack, true); + }); + }); + + describe('handlePing', function() { + it('will not send pong without nonce', async () => { + const peer = Peer.fromOptions({}); + const pkt = new packets.PingPacket(); + + let called = false; + peer.send = (packet) => { + called = true; + }; + + await peer.handlePing(pkt); + assert.equal(called, false); + }); + + it('will send pong', async () => { + const peer = Peer.fromOptions({}); + const nonce = common.nonce(); + const pkt = new packets.PingPacket(nonce); + + let called = false; + peer.send = (packet) => { + assert(packet); + assert.equal(packet.type, packets.types.PONG); + assert.bufferEqual(packet.nonce, nonce); + called = true; + }; + + await peer.handlePing(pkt); + assert.equal(called, true); + }); + }); + + describe('handlePong', function() { + it('will not update last pong w/o challenge', async () => { + const peer = Peer.fromOptions({}); + peer.challenge = null; + peer.lastPong = -1; + peer.minPing = -1; + + const pkt = new packets.PongPacket(); + await peer.handlePong(pkt); + + assert.equal(peer.lastPong, -1); + assert.equal(peer.minPing, -1); + }); + + it('will not update last pong w/ wrong nonce', async () => { + const peer = Peer.fromOptions({}); + peer.challenge = common.nonce(); + peer.lastPong = -1; + peer.minPing = -1; + + const pkt = new packets.PongPacket(common.nonce()); + await peer.handlePong(pkt); + + assert.equal(peer.lastPong, -1); + assert.equal(peer.minPing, -1); + }); + + it('will update last pong and min ping', async () => { + const now = Date.now(); + + const peer = Peer.fromOptions({}); + const nonce = common.nonce(); + peer.challenge = nonce; + peer.lastPong = -1; + peer.minPing = -1; + + const pkt = new packets.PongPacket(nonce); + await peer.handlePong(pkt); + + assert(peer.lastPong >= now); + assert(peer.minPing >= now - 1); + }); + }); + + describe('handleSendHeaders (BIP130)', function() { + it('will set prefer headers', async () => { + const peer = Peer.fromOptions({}); + const pkt = new packets.SendHeadersPacket(); + await peer.handleSendHeaders(pkt); + assert.equal(peer.preferHeaders, true); + }); + }); + + describe('handleFilterLoad (BIP37)', function() { + it('will load spv filter', async () => { + const peer = Peer.fromOptions({}); + const filter = new BloomFilter(); + const pkt = new packets.FilterLoadPacket(filter); + peer.handleFilterLoad(pkt); + assert.strictEqual(peer.spvFilter, filter); + }); + + it('will increase ban if not within constraints', async () => { + const peer = Peer.fromOptions({}); + const filter = new BloomFilter(); + const pkt = new packets.FilterLoadPacket(filter); + + let called = false; + peer.increaseBan = (score) => { + assert.equal(score, 100); + called = true; + }; + + pkt.isWithinConstraints = () => false; + await peer.handleFilterLoad(pkt); + + assert.equal(called, true); + assert.strictEqual(peer.spvFilter, null); + }); + }); + + describe('handleFilterAdd (BIP37)', function() { + it('will add to spv filter', async () => { + const peer = Peer.fromOptions({}); + peer.spvFilter = BloomFilter.fromRate( + 20000, 0.001, BloomFilter.flags.ALL); + + const data = Buffer.alloc(32, 0x01); + + const pkt = new packets.FilterAddPacket(data); + await peer.handleFilterAdd(pkt); + + assert.equal(peer.spvFilter.test(data), true); + assert.equal(peer.noRelay, false); + + peer.spvFilter = null; + + await peer.handleFilterAdd(pkt); + assert.equal(peer.spvFilter, null); + }); + + it('will increase ban with max push', async () => { + const peer = Peer.fromOptions({}); + peer.noRelay = true; + peer.spvFilter = BloomFilter.fromRate( + 20000, 0.001, BloomFilter.flags.ALL); + + let called = false; + peer.increaseBan = (score) => { + assert.equal(score, 100); + called = true; + }; + + const data = Buffer.alloc(521, 0x01); + + const pkt = new packets.FilterAddPacket(data); + await peer.handleFilterAdd(pkt); + + assert(called); + assert.equal(peer.spvFilter.test(data), false); + assert.equal(peer.noRelay, true); + }); + }); + + describe('handleFilterClear (BIP37)', function() { + it('will reset spv filter', async () => { + const peer = Peer.fromOptions({}); + peer.spvFilter = BloomFilter.fromRate( + 20000, 0.001, BloomFilter.flags.ALL); + + const data = Buffer.alloc(32, 0x01); + peer.spvFilter.add(data); + assert.equal(peer.spvFilter.test(data), true); + + const pkt = new packets.FilterClearPacket(); + await peer.handleFilterClear(pkt); + + assert.equal(peer.spvFilter.test(data), false); + assert.equal(peer.noRelay, false); + }); + + it('will clear if not set', async () => { + const peer = Peer.fromOptions({}); + peer.spvFilter = null; + + const pkt = new packets.FilterClearPacket(); + await peer.handleFilterClear(pkt); + + assert.equal(peer.spvFilter, null); + assert.equal(peer.noRelay, false); + }); + }); + + describe('handleFeeFilter (BIP133)', function() { + it('will set fee rate', async () => { + const peer = Peer.fromOptions({}); + const pkt = new packets.FeeFilterPacket(120000); + await peer.handleFeeFilter(pkt); + assert.equal(peer.feeRate, 120000); + }); + + it('will increase ban if > max money or negative', async () => { + const peer = Peer.fromOptions({}); + let called = 0; + + peer.increaseBan = (score) => { + assert.equal(score, 100); + called += 1; + }; + + let pkt = new packets.FeeFilterPacket(consensus.MAX_MONEY + 1); + await peer.handleFeeFilter(pkt); + assert.equal(called, 1); + + pkt = new packets.FeeFilterPacket(-100); + await peer.handleFeeFilter(pkt); + assert.equal(called, 2); + }); + }); + + describe('handleSendCmpct (BIP152)', function() { + it('will not set compact mode (already set)', async () => { + const peer = Peer.fromOptions({}); + const pkt = new packets.SendCmpctPacket(1, 1); + peer.compactMode = 2; + await peer.handleSendCmpct(pkt); + assert.equal(peer.compactMode, 2); + }); + + it('will set low-bandwidth mode (mode=0)', async () => { + const peer = Peer.fromOptions({}); + const pkt = new packets.SendCmpctPacket(0, 1); + await peer.handleSendCmpct(pkt); + assert.equal(peer.compactMode, 0); + }); + + it('will set high-bandwidth mode (mode=1)', async () => { + const peer = Peer.fromOptions({}); + const pkt = new packets.SendCmpctPacket(1, 1); + await peer.handleSendCmpct(pkt); + assert.equal(peer.compactMode, 1); + }); + + it('will not set compact mode (mode=2)', async () => { + const peer = Peer.fromOptions({}); + const pkt = new packets.SendCmpctPacket(2, 1); + await peer.handleSendCmpct(pkt); + assert.equal(peer.compactMode, -1); + assert.equal(peer.compactWitness, false); + }); + + it('will set witness=false (version=1)', async () => { + const peer = Peer.fromOptions({}); + const pkt = new packets.SendCmpctPacket(0, 1); + await peer.handleSendCmpct(pkt); + assert.equal(peer.compactWitness, false); + }); + + it('will set witness=true (version=2)', async () => { + const peer = Peer.fromOptions({}); + const pkt = new packets.SendCmpctPacket(0, 2); + await peer.handleSendCmpct(pkt); + assert.equal(peer.compactWitness, true); + }); + + it('will not set compact mode (version=3)', async () => { + const peer = Peer.fromOptions({}); + const pkt = new packets.SendCmpctPacket(0, 3); + await peer.handleSendCmpct(pkt); + assert.equal(peer.compactMode, -1); + assert.equal(peer.compactWitness, false); + }); + }); + }); + + describe('Pool', function() { + describe('handleVersion', function() { + it('will update pool time and nonce data', async () => { + const network = Network.get('regtest'); + + const pool = new Pool({ + logger: Logger.global, + chain: {network, options: {checkpoints: true}, on: () => {}} + }); + + const peer = Peer.fromOptions(pool.options); + + const pkt = new packets.VersionPacket({ + time: util.now(), + remote: { + host: '127.0.0.1', + port: 8334 + }, + local: { + host: '127.0.0.1', + port: 8335 + }, + nonce: Buffer.alloc(8, 0x00) + }); + + assert(!pool.network.time.known.has(peer.hostname())); + const nonce = pool.nonces.alloc(peer.hostname()); + assert(pool.nonces.has(nonce)); + + await pool.handleVersion(peer, pkt); + + assert(pool.network.time.known.has(peer.hostname())); + assert(!pool.nonces.has(nonce)); + }); + + it('will update local address score', async () => { + const network = Network.get('regtest'); + + const pool = new Pool({ + logger: Logger.global, + chain: {network, options: {checkpoints: true}, on: () => {}} + }); + + let called = false; + + const local = new NetAddress({ + host: '5.19.5.127', + port: 8334 + }); + + pool.hosts = { + markLocal: (addr) => { + assert(addr); + assert.equal(addr.host, local.host); + assert.equal(addr.port, local.port); + called = true; + } + }; + + const peer = Peer.fromOptions(pool.options); + + // The remote address in this case is the address + // of the receiver of the message, it is the local. + const pkt = new packets.VersionPacket({remote: local}); + + await pool.handleVersion(peer, pkt); + + assert(called); + }); + }); + + describe('handleAddr', function() { + it('will add addrs to hosts list', async () => { + const network = Network.get('regtest'); + + const pool = new Pool({ + logger: Logger.global, + chain: { + network, + options: { + checkpoints: true + }, + on: () => {}, + state: { + hasWitness: () => true + } + } + }); + + const peer = Peer.fromOptions(pool.options); + + const items = [ + // Routable and has required services + new NetAddress({ + host: '5.19.5.127', + port: 8334, + services: 0 | services.NETWORK | services.WITNESS, + time: 1558405603 + }), + // Routable and missing services + new NetAddress({ + host: '5.29.139.120', + port: 8335, + services: 0 | services.NETWORK, + time: 1558405603 + }), + // Not routable + new NetAddress({ + host: '127.0.0.3', + port: 8333, + services: 0 | services.NETWORK | services.WITNESS, + time: 1558405602 + }) + ]; + + const pkt = new packets.AddrPacket(items); + + assert.equal(pool.hosts.totalFresh, 0); + + await pool.handleAddr(peer, pkt); + + assert.equal(pool.hosts.totalFresh, 1); + }); + }); + + describe('handleInv', function() { + const network = Network.get('regtest'); + + it('will ban with too many inv', async () => { + const pool = new Pool({ + logger: Logger.global, + chain: {network, options: {checkpoints: true}, on: () => {}} + }); + + let handleTXInv = false; + pool.handleTXInv = () => { + handleTXInv = true; + }; + + let increaseBan = false; + const peer = Peer.fromOptions(pool.options); + peer.increaseBan = (score) => { + assert.equal(score, 100); + increaseBan = true; + }; + + const items = []; + for (let i = 0; i < common.MAX_INV + 1; i++) + items.push(new InvItem(InvItem.types.TX, Buffer.alloc(32, 0x01))); + + const pkt = new packets.InvPacket(items); + + await pool.handleInv(peer, pkt); + + assert(increaseBan); + assert(!handleTXInv); + }); + + it('will handle block inventory', async () => { + const pool = new Pool({ + logger: Logger.global, + chain: {network, options: {checkpoints: true}, on: () => {}} + }); + + const peer = Peer.fromOptions(pool.options); + + let handleBlockInv = false; + pool.handleBlockInv = (_peer, blocks) => { + assert.strictEqual(_peer, peer); + assert.bufferEqual(blocks[0], Buffer.alloc(32, 0x01)); + handleBlockInv = true;; + }; + + const pkt = new packets.InvPacket([ + new InvItem(InvItem.types.BLOCK, Buffer.alloc(32, 0x01)) + ]); + + await pool.handleInv(peer, pkt); + + assert(handleBlockInv); + }); + + it('will handle tx inventory', async () => { + const pool = new Pool({ + logger: Logger.global, + chain: {network, options: {checkpoints: true}, on: () => {}} + }); + + const peer = Peer.fromOptions(pool.options); + + let handleTXInv = false; + pool.handleTXInv = (_peer, txs) => { + assert.strictEqual(_peer, peer); + assert.bufferEqual(txs[0], Buffer.alloc(32, 0x01)); + handleTXInv = true;; + }; + + const pkt = new packets.InvPacket([ + new InvItem(InvItem.types.TX, Buffer.alloc(32, 0x01)) + ]); + + await pool.handleInv(peer, pkt); + + assert(handleTXInv); + }); + }); + + describe('handleGetData', function() { + const network = Network.get('regtest'); + + it('will ban with too many items', async () => { + const pool = new Pool({ + logger: Logger.global, + chain: {network, options: {checkpoints: true}, on: () => {}} + }); + + let increaseBan = false; + let destroy = false; + const peer = Peer.fromOptions(pool.options); + peer.increaseBan = (score) => { + assert.equal(score, 100); + increaseBan = true; + }; + peer.destroy = () => { + destroy = true; + }; + + const items = []; + for (let i = 0; i < common.MAX_INV + 1; i++) + items.push(new InvItem(InvItem.types.TX, Buffer.alloc(32, 0x01))); + + const pkt = new packets.GetDataPacket(items); + + await pool.handleGetData(peer, pkt); + + assert(increaseBan); + assert(destroy); + }); + + it('will send tx packets', async () => { + const [block] = block482683.getBlock(); + + const tx = block.txs[10]; + + const pool = new Pool({ + logger: Logger.global, + chain: {network, options: {checkpoints: true}, on: () => {}} + }); + + const item = new InvItem(InvItem.types.TX, tx.hash()); + + const pkt = new packets.GetDataPacket([item]); + const peer = Peer.fromOptions(pool.options); + + pool.getItem = (_peer, _item) => { + assert(_peer, peer); + + if (_item.hash.compare(tx.hash()) === 0) + return tx; + + return null; + }; + + let called = false; + peer.send = (packet) => { + assert.equal(packet.type, packets.types.TX); + assert.strictEqual(packet.tx, tx); + called = true; + }; + + await pool.handleGetData(peer, pkt); + + assert(called); + }); + + it('will send tx not found', async () => { + const pool = new Pool({ + logger: Logger.global, + chain: {network, options: {checkpoints: true}, on: () => {}} + }); + + pool.getItem = () => null; + + const pkt = new packets.GetDataPacket([ + new InvItem(InvItem.types.TX, Buffer.alloc(32, 0x01)) + ]); + + const peer = Peer.fromOptions(pool.options); + + let called = false; + peer.send = (packet) => { + assert.equal(packet.type, packets.types.NOTFOUND); + assert.equal(packet.items[0].type, InvItem.types.TX); + assert.bufferEqual(packet.items[0].hash, Buffer.alloc(32, 0x01)); + called = true; + }; + + await pool.handleGetData(peer, pkt); + + assert(called); + }); + + it('will send block (witness)', async () => { + const [block] = block482683.getBlock(); + + const pool = new Pool({ + logger: Logger.global, + chain: { + network, + options: {checkpoints: true}, + on: () => {}, + state: { + hasWitness: () => true + }, + getRawBlock: (hash) => { + assert.bufferEqual(hash, block.hash()); + return block.toRaw(); + } + } + }); + + const pkt = new packets.GetDataPacket([ + new InvItem(InvItem.types.WITNESS_BLOCK, block.hash()) + ]); + + const peer = Peer.fromOptions(pool.options); + + let called = false; + peer.sendRaw = (cmd, body) => { + assert.equal(cmd, 'block'); + assert.bufferEqual(body, block.toRaw()); + called = true; + }; + + await pool.handleGetData(peer, pkt); + + assert(called); + }); + + it('will send block (non-witness)', async () => { + const [block] = block482683.getBlock(); + + const pool = new Pool({ + logger: Logger.global, + chain: { + network, + options: {checkpoints: true}, + on: () => {}, + state: { + hasWitness: () => true + }, + getBlock: (hash) => { + assert.bufferEqual(hash, block.hash()); + return block; + } + } + }); + + const pkt = new packets.GetDataPacket([ + new InvItem(InvItem.types.BLOCK, block.hash()) + ]); + + const peer = Peer.fromOptions(pool.options); + + let called = false; + peer.send = (packet) => { + assert.equal(packet.type, packets.types.BLOCK); + assert.strictEqual(packet.block, block); + assert.equal(packet.witness, false); + called = true; + }; + + await pool.handleGetData(peer, pkt); + + assert(called); + }); + + it('will send block not found', async () => { + const pool = new Pool({ + logger: Logger.global, + chain: { + network, + options: {checkpoints: true}, + on: () => {}, + state: { + hasWitness: () => true + }, + getRawBlock: () => null + } + }); + + const pkt = new packets.GetDataPacket([ + new InvItem(InvItem.types.WITNESS_BLOCK, Buffer.alloc(32, 0x01)) + ]); + + const peer = Peer.fromOptions(pool.options); + + let called = false; + peer.send = (packet) => { + assert.equal(packet.type, packets.types.NOTFOUND); + assert.equal(packet.items[0].type, InvItem.types.WITNESS_BLOCK); + assert.bufferEqual(packet.items[0].hash, Buffer.alloc(32, 0x01)); + called = true; + }; + + await pool.handleGetData(peer, pkt); + + assert(called); + }); + + it('will destroy if filtered block (bip37=false)', async () => { + const pool = new Pool({ + logger: Logger.global, + chain: {network, options: {checkpoints: true}, on: () => {}}, + bip37: false + }); + + const pkt = new packets.GetDataPacket([ + new InvItem(InvItem.types.WITNESS_FILTERED_BLOCK, + Buffer.alloc(32, 0x01)) + ]); + + const peer = Peer.fromOptions(pool.options); + + let called = false; + peer.destroy = () => { + called = true; + }; + + await pool.handleGetData(peer, pkt); + + assert(called); + }); + + it('will send filtered block and txs', async () => { + const [block] = block300025.getBlock(); + + const pool = new Pool({ + logger: Logger.global, + chain: {network, options: {checkpoints: true}, on: () => {}}, + bip37: true + }); + + const filter = BloomFilter.fromRate(20000, 0.001, + BloomFilter.flags.ALL); + filter.add(block.txs[10].hash()); + filter.add(block.txs[12].hash()); + + const peer = Peer.fromOptions(pool.options); + peer.spvFilter = filter; + + const item = new InvItem(InvItem.types.WITNESS_FILTERED_BLOCK, + block.hash()); + + pool.getItem = (_peer, _item) => { + assert(_peer, peer); + + if (_item.hash.compare(block.hash()) === 0) + return block; + + return null; + }; + + const pkt = new packets.GetDataPacket([item]); + + let called = 0; + peer.send = (packet) => { + switch (called) { + case 0: + assert.equal(packet.type, packets.types.MERKLEBLOCK); + assert.bufferEqual(packet.block.hash(), block.hash()); + break; + case 1: + assert.equal(packet.type, packets.types.TX); + assert.bufferEqual(packet.tx.hash(), block.txs[10].hash()); + break; + case 2: + assert.equal(packet.type, packets.types.TX); + assert.bufferEqual(packet.tx.hash(), block.txs[12].hash()); + break; + } + + called += 1; + }; + + await pool.handleGetData(peer, pkt); + + assert.equal(called, 3); + }); + + it('will send filtered block not found', async () => { + const pool = new Pool({ + logger: Logger.global, + chain: {network, options: {checkpoints: true}, on: () => {}}, + bip37: true + }); + + pool.getItem = () => null; + + const pkt = new packets.GetDataPacket([ + new InvItem(InvItem.types.WITNESS_FILTERED_BLOCK, + Buffer.alloc(32, 0x01)) + ]); + + const peer = Peer.fromOptions(pool.options); + + let called = false; + peer.send = (packet) => { + assert.equal(packet.type, packets.types.NOTFOUND); + assert.equal(packet.items[0].type, + InvItem.types.WITNESS_FILTERED_BLOCK); + assert.bufferEqual(packet.items[0].hash, Buffer.alloc(32, 0x01)); + called = true; + }; + + await pool.handleGetData(peer, pkt); + + assert(called); + }); + + it('will send compact block not found', async () => { + const pool = new Pool({ + logger: Logger.global, + chain: { + network, + options: {checkpoints: true}, + on: () => {}, + getHeight: () => 500001, + tip: { + height: 500001 + } + } + }); + + pool.getItem = () => null; + + const pkt = new packets.GetDataPacket([ + new InvItem(InvItem.types.CMPCT_BLOCK, Buffer.alloc(32, 0x01)) + ]); + + const peer = Peer.fromOptions(pool.options); + + let called = false; + peer.send = (packet) => { + assert.equal(packet.type, packets.types.NOTFOUND); + assert.equal(packet.items[0].type, InvItem.types.CMPCT_BLOCK); + assert.bufferEqual(packet.items[0].hash, Buffer.alloc(32, 0x01)); + called = true; + }; + + await pool.handleGetData(peer, pkt); + + assert(called); + }); + + it('will send compact block', async () => { + const [block] = block300025.getBlock(); + + const pool = new Pool({ + logger: Logger.global, + chain: { + network, + options: {checkpoints: true}, + on: () => {}, + getHeight: () => 500001, + tip: { + height: 500001 + } + } + }); + + const item = new InvItem(InvItem.types.CMPCT_BLOCK, block.hash()); + const peer = Peer.fromOptions(pool.options); + + pool.getItem = (_peer, _item) => { + assert(_peer, peer); + + if (_item.hash.compare(block.hash()) === 0) + return block; + + return null; + }; + + const pkt = new packets.GetDataPacket([item]); + + let called = false; + peer.send = (packet) => { + assert.equal(packet.type, packets.types.CMPCTBLOCK); + assert.bufferEqual(packet.block.hash(), block.hash()); + called = true; + }; + + await pool.handleGetData(peer, pkt); + + assert(called); + }); + }); + + describe('handleGetHeaders', function() { + function mockHash(height) { + const hash = Buffer.alloc(32, 0x00); + hash.writeUInt32LE(height); + return hash; + } + + const network = Network.get('regtest'); + let pool = null; + + before(async () => { + pool = new Pool({ + logger: Logger.global, + chain: { + network, + options: {checkpoints: true}, + on: () => {}, + synced: true, + findLocator: (locators) => { + assert.bufferEqual(locators[0], mockHash(0)); + + return locators[0]; + }, + getNextHash: (hash) => { + assert.bufferEqual(hash, mockHash(0)); + + return mockHash(1); + }, + getEntry: (hash) => { + assert.bufferEqual(hash, mockHash(1)); + + return new ChainEntry({ + version: 1, + hash: mockHash(1), + prevBlock: mockHash(0), + merkleRoot: Buffer.alloc(32, 0x00), + time: 1558629632, + bits: 486604799, + nonce: 10, + height: 1 + }); + }, + getNext: (entry) => { + const height = entry.height + 1; + + return new ChainEntry({ + version: 1, + hash: mockHash(height), + prevBlock: entry.hash, + merkleRoot: Buffer.alloc(32, 0x00), + time: 1558629632, + bits: 486604799, + nonce: 10, + height: height + }); + } + } + }); + }); + + it('will send max headers from chain', async () => { + const peer = Peer.fromOptions(pool.options); + peer.handshake = true; + + const locators = [mockHash(0)]; + const stop = mockHash(7500); + + const pkt = new packets.GetHeadersPacket(locators, stop); + + let called = false; + peer.send = (packet) => { + assert.equal(packet.type, packets.types.HEADERS); + assert.equal(packet.items.length, 2000); + called = true; + }; + + await pool.handleGetHeaders(peer, pkt); + assert(called); + }); + + it('will continue until stop point', async () => { + const peer = Peer.fromOptions(pool.options); + peer.handshake = true; + + const locators = [mockHash(0)]; + const stop = mockHash(1500); + + const pkt = new packets.GetHeadersPacket(locators, stop); + + let called = false; + peer.send = (packet) => { + assert.equal(packet.type, packets.types.HEADERS); + assert.equal(packet.items.length, 1500); + assert.bufferEqual(packet.items[0].hash(), mockHash(1)); + assert.bufferEqual(packet.items[1499].hash(), mockHash(1500)); + called = true; + }; + + await pool.handleGetHeaders(peer, pkt); + assert(called); + }); + }); + + describe('handleTX', function() { + const [block] = block300025.getBlock(); + + const network = Network.get('regtest'); + + it('will destroy if unrequested', async () => { + const pool = new Pool({ + logger: Logger.global, + chain: {network, options: {checkpoints: true}, on: () => {}} + }); + + const peer = Peer.fromOptions(pool.options); + + pool.resolveTX = (_peer, hash) => { + assert.strictEqual(_peer, peer); + assert.bufferEqual(hash, block.txs[10].hash()); + return false; + }; + + let called = false; + peer.destroy = () => { + called = true; + }; + + const pkt = new packets.TXPacket(block.txs[10]); + + await pool.handleTX(peer, pkt); + assert(called); + }); + + it('will add tx to mempool', async () => { + let added = false; + + const pool = new Pool({ + logger: Logger.global, + chain: { + network, + options: { + checkpoints: true + }, + on: () => {} + } + }); + + const peer = Peer.fromOptions(pool.options); + + pool.resolveTX = () => true; + pool.mempool = { + addTX: (tx, id) => { + assert.strictEqual(tx, block.txs[10]); + assert.equal(id, peer.id); + added = true; + return false; + }, + on: () => {} + }; + + const pkt = new packets.TXPacket(block.txs[10]); + + await pool.handleTX(peer, pkt); + assert(added); + }); + + it('will increase ban if invalid', async () => { + const pool = new Pool({ + logger: Logger.global, + chain: { + network, + options: { + checkpoints: true + }, + on: () => {} + }, + mempool: { + addTX: (tx) => { + throw new VerifyError(tx, 'invalid', 'test-reason', 10); + }, + on: () => {} + } + }); + + const peer = Peer.fromOptions(pool.options); + + let increaseBan = false; + peer.increaseBan = (score) => { + assert.equal(score, 10); + increaseBan = true; + }; + + let send = false; + peer.send = (packet) => { + assert.equal(packet.type, packets.types.REJECT); + send = true; + }; + + pool.resolveTX = () => true; + + const pkt = new packets.TXPacket(block.txs[10]); + + await pool.handleTX(peer, pkt); + assert(increaseBan); + assert(send); + }); + }); + + describe('handleMerkleBlock/handleTX (BIP37)', function() { + const network = Network.get('regtest'); + + it('will add block w/ merkle block and txs', async () => { + const [block] = block300025.getBlock(); + + const pool = new Pool({ + logger: Logger.global, + chain: { + network, + options: { + checkpoints: true, + spv: true + }, + on: () => {} + }, + spv: true + }); + pool.syncing = true; + + const filter = BloomFilter.fromRate(20000, 0.001, + BloomFilter.flags.ALL); + filter.add(block.txs[10].hash()); + filter.add(block.txs[12].hash()); + + // Serialize the block as the txs are not included + // over the network, however the txs are included in + // the data structure. + const merkle = MerkleBlock.fromRaw(block.toMerkle(filter).toRaw()); + + const blkpkt = new packets.MerkleBlockPacket(merkle); + const tx1pkt = new packets.TXPacket(block.txs[10]); + const tx2pkt = new packets.TXPacket(block.txs[12]); + + const peer = Peer.fromOptions(pool.options); + peer.blockMap.set(block.hash(), Date.now()); + + let called = false; + + pool._addBlock = (_peer, _block, flags) => { + assert.strictEqual(_peer, peer); + assert.bufferEqual(_block.hash(), block.hash()); + assert.equal(_block.txs.length, 2); + assert.strictEqual(_block.txs[0], block.txs[10]); + assert.strictEqual(_block.txs[1], block.txs[12]); + called = true; + }; + + await pool.handleMerkleBlock(peer, blkpkt); + await pool.handleTX(peer, tx1pkt); + await pool.handleTX(peer, tx2pkt); + + assert(called); + }); + }); + }); + + describe('Framer', function() { + it('will construct with network (primary)', () => { + const framer = new Framer(); + assert.strictEqual(framer.network, Network.get('main')); + }); + + it('will construct with network (custom)', () => { + const framer = new Framer('regtest'); + assert.strictEqual(framer.network, Network.get('regtest')); + }); + + it('throw with long command', () => { + const framer = new Framer('regtest'); + let err = null; + + try { + framer.packet('abcdefghijklm', Buffer.alloc(2, 0x00)); + } catch (e) { + err = e; + } + assert(err); + assert(err.type, 'AssertionError'); + }); + + it('will frame payload with header', () => { + const framer = new Framer('regtest'); + + const pkt = framer.packet('cmd', Buffer.alloc(2, 0x00)); + + const magic = pkt.slice(0, 4); + assert.bufferEqual(magic, Buffer.from('fabfb5da', 'hex')); + + const cmd = pkt.slice(4, 16); + const cmdbuf = Buffer.from('636d64000000000000000000', 'hex'); + assert.bufferEqual(cmd, cmdbuf); + + const length = pkt.slice(16, 20); + assert.bufferEqual(length, Buffer.from('02000000', 'hex')); + + const checksum = pkt.slice(20, 24); + assert.bufferEqual(checksum, Buffer.from('407feb4a', 'hex')); + + const payload = pkt.slice(24, 26); + assert.bufferEqual(payload, Buffer.from('0000', 'hex')); + }); + + it('will frame payload with header (w/ checksum)', () => { + const framer = new Framer('regtest'); + + const payload = Buffer.alloc(2, 0x00); + const checksum = Buffer.alloc(4, 0x00); + + const pkt = framer.packet('cmd', payload, checksum); + + assert.bufferEqual(pkt.slice(20, 24), Buffer.from('00000000', 'hex')); + }); + }); + + describe('Common', function() { + it('will give nonce', async () => { + const n = common.nonce(); + assert(Buffer.isBuffer(n)); + assert.equal(n.length, 8); + }); + }); +}); diff --git a/test/node-rpc-test.js b/test/node-rpc-test.js new file mode 100644 index 000000000..931272c9a --- /dev/null +++ b/test/node-rpc-test.js @@ -0,0 +1,63 @@ +/* eslint-env mocha */ +/* eslint prefer-arrow-callback: "off" */ + +'use strict'; + +const assert = require('bsert'); +const FullNode = require('../lib/node/fullnode'); + +const ports = { + p2p: 49331, + node: 49332, + wallet: 49333 +}; + +const node = new FullNode({ + network: 'regtest', + apiKey: 'foo', + walletAuth: true, + memory: true, + workers: true, + workersSize: 2, + plugins: [require('../lib/wallet/plugin')], + port: ports.p2p, + httpPort: ports.node, + env: { + 'BCOIN_WALLET_HTTP_PORT': ports.wallet.toString() + }}); + +const {NodeClient} = require('@oipwg/fclient'); + +const nclient = new NodeClient({ + port: ports.node, + apiKey: 'foo', + timeout: 15000 +}); + +describe('RPC', function() { + this.timeout(15000); + + before(async () => { + await node.open(); + }); + + after(async () => { + await node.close(); + }); + + it('should rpc help', async () => { + assert(await nclient.execute('help', [])); + + await assert.rejects(async () => { + await nclient.execute('help', ['getinfo']); + }, { + name: 'Error', + message: /^getinfo/ + }); + }); + + it('should rpc getinfo', async () => { + const info = await nclient.execute('getinfo', []); + assert.strictEqual(info.blocks, 0); + }); +}); diff --git a/test/node-test.js b/test/node-test.js index 73d0021ce..5696fd77e 100644 --- a/test/node-test.js +++ b/test/node-test.js @@ -3,9 +3,8 @@ 'use strict'; -const assert = require('./util/assert'); +const assert = require('bsert'); const consensus = require('../lib/protocol/consensus'); -const co = require('../lib/utils/co'); const Coin = require('../lib/primitives/coin'); const Script = require('../lib/script/script'); const Opcode = require('../lib/script/opcode'); @@ -13,18 +12,35 @@ const FullNode = require('../lib/node/fullnode'); const MTX = require('../lib/primitives/mtx'); const TX = require('../lib/primitives/tx'); const Address = require('../lib/primitives/address'); +const Peer = require('../lib/net/peer'); +const InvItem = require('../lib/primitives/invitem'); +const invTypes = InvItem.types; + +const ports = { + p2p: 49331, + node: 49332, + wallet: 49333 +}; const node = new FullNode({ - db: 'memory', + memory: true, apiKey: 'foo', network: 'regtest', workers: true, - plugins: [require('../lib/wallet/plugin')] + workersSize: 2, + plugins: [require('../lib/wallet/plugin')], + indexTX: true, + indexAddress: true, + port: ports.p2p, + httpPort: ports.node, + env: { + 'BCOIN_WALLET_HTTP_PORT': ports.wallet.toString() + } }); const chain = node.chain; const miner = node.miner; -const wdb = node.require('walletdb'); +const {wdb} = node.require('walletdb'); let wallet = null; let tip1 = null; @@ -44,8 +60,8 @@ async function mineBlock(tip, tx) { spend.addTX(tx, 0); - spend.addOutput(wallet.getReceive(), 25 * 1e8); - spend.addOutput(wallet.getChange(), 5 * 1e8); + spend.addOutput(await wallet.receiveAddress(), 25 * 1e8); + spend.addOutput(await wallet.changeAddress(), 5 * 1e8); spend.setLocktime(chain.height); @@ -83,7 +99,10 @@ async function mineCSV(fund) { } describe('Node', function() { - this.timeout(5000); + this.timeout(process.browser ? 20000 : 5000); + + if (process.browser) + return; it('should open chain and miner', async () => { miner.mempool = null; @@ -94,7 +113,7 @@ describe('Node', function() { it('should open walletdb', async () => { wallet = await wdb.create(); miner.addresses.length = 0; - miner.addAddress(wallet.getReceive()); + miner.addAddress(await wallet.receiveAddress()); }); it('should mine a block', async () => { @@ -103,7 +122,8 @@ describe('Node', function() { await chain.add(block); }); - it('should mine competing chains', async () => { + it('should mine competing chains', async function () { + this.timeout(20000); for (let i = 0; i < 10; i++) { const block1 = await mineBlock(tip1, cb1); cb1 = block1.txs[0]; @@ -115,17 +135,17 @@ describe('Node', function() { await chain.add(block2); - assert.strictEqual(chain.tip.hash, block1.hash('hex')); + assert.bufferEqual(chain.tip.hash, block1.hash()); - tip1 = await chain.getEntry(block1.hash('hex')); - tip2 = await chain.getEntry(block2.hash('hex')); + tip1 = await chain.getEntry(block1.hash()); + tip2 = await chain.getEntry(block2.hash()); assert(tip1); assert(tip2); assert(!await chain.isMainChain(tip2)); - await co.wait(); + await new Promise(setImmediate); } }); @@ -136,7 +156,7 @@ describe('Node', function() { }); it('should have correct balance', async () => { - await co.timeout(100); + await new Promise(r => setTimeout(r, 100)); const balance = await wallet.getBalance(); assert.strictEqual(balance.unconfirmed, 550 * 1e8); @@ -162,7 +182,7 @@ describe('Node', function() { await chain.add(block); assert(forked); - assert.strictEqual(chain.tip.hash, block.hash('hex')); + assert.bufferEqual(chain.tip.hash, block.hash()); assert(chain.tip.chainwork.gt(tip1.chainwork)); }); @@ -173,7 +193,7 @@ describe('Node', function() { }); it('should have correct balance', async () => { - await co.timeout(100); + await new Promise(r => setTimeout(r, 100)); const balance = await wallet.getBalance(); assert.strictEqual(balance.unconfirmed, 1100 * 1e8); @@ -190,9 +210,9 @@ describe('Node', function() { await chain.add(block); - const entry = await chain.getEntry(block.hash('hex')); + const entry = await chain.getEntry(block.hash()); assert(entry); - assert.strictEqual(chain.tip.hash, entry.hash); + assert.bufferEqual(chain.tip.hash, entry.hash); const result = await chain.isMainChain(entry); assert(result); @@ -246,20 +266,20 @@ describe('Node', function() { const tx = block2.txs[1]; const output = Coin.fromTX(tx, 1, chain.height); - const coin = await chain.getCoin(tx.hash('hex'), 1); + const coin = await chain.getCoin(tx.hash(), 1); assert.bufferEqual(coin.toRaw(), output.toRaw()); }); it('should get balance', async () => { - await co.timeout(100); + await new Promise(r => setTimeout(r, 100)); const balance = await wallet.getBalance(); assert.strictEqual(balance.unconfirmed, 1250 * 1e8); assert.strictEqual(balance.confirmed, 750 * 1e8); - assert(wallet.account.receiveDepth >= 7); - assert(wallet.account.changeDepth >= 6); + assert((await wallet.receiveDepth()) >= 7); + assert((await wallet.changeDepth()) >= 6); assert.strictEqual(wdb.state.height, chain.height); @@ -271,7 +291,14 @@ describe('Node', function() { { const tips = await chain.db.getTips(); - assert.notStrictEqual(tips.indexOf(chain.tip.hash), -1); + let index = -1; + + for (let i = 0; i < tips.length; i++) { + if (tips[i].equals(chain.tip.hash)) + index = i; + } + + assert.notStrictEqual(index, -1); assert.strictEqual(tips.length, 2); } @@ -280,7 +307,14 @@ describe('Node', function() { { const tips = await chain.db.getTips(); - assert.notStrictEqual(tips.indexOf(chain.tip.hash), -1); + let index = -1; + + for (let i = 0; i < tips.length; i++) { + if (tips[i].equals(chain.tip.hash)) + index = i; + } + + assert.notStrictEqual(index, -1); assert.strictEqual(tips.length, 1); } }); @@ -447,7 +481,7 @@ describe('Node', function() { it('should rescan for transactions', async () => { await wdb.rescan(0); - assert.strictEqual(wallet.txdb.state.confirmed, 1289250000000); + assert.strictEqual((await wallet.getBalance()).confirmed, 1289250000000); }); it('should reset miner mempool', async () => { @@ -471,11 +505,11 @@ describe('Node', function() { id: '1' }, {}); - assert.typeOf(json.result, 'object'); - assert.typeOf(json.result.curtime, 'number'); - assert.typeOf(json.result.mintime, 'number'); - assert.typeOf(json.result.maxtime, 'number'); - assert.typeOf(json.result.expires, 'number'); + assert(typeof json.result === 'object'); + assert(Number.isInteger(json.result.curtime)); + assert(Number.isInteger(json.result.mintime)); + assert(Number.isInteger(json.result.maxtime)); + assert(Number.isInteger(json.result.expires)); assert.deepStrictEqual(json, { result: { @@ -498,7 +532,7 @@ describe('Node', function() { sigoplimit: 80000, sizelimit: 4000000, weightlimit: 4000000, - longpollid: node.chain.tip.rhash() + '0000000000', + longpollid: node.chain.tip.rhash() + '00000000', submitold: false, coinbaseaux: { flags: '6d696e65642062792062636f696e' }, coinbasevalue: 1250000000, @@ -545,25 +579,23 @@ describe('Node', function() { assert(!json.error); assert.strictEqual(json.result, null); - assert.strictEqual(node.chain.tip.hash, block.hash('hex')); + assert.bufferEqual(node.chain.tip.hash, block.hash()); }); it('should validate an address', async () => { const addr = new Address(); - addr.network = node.network; - const json = await node.rpc.call({ method: 'validateaddress', - params: [addr.toString()] + params: [addr.toString(node.network)] }, {}); assert.deepStrictEqual(json.result, { isvalid: true, - address: addr.toString(), - scriptPubKey: Script.fromAddress(addr).toJSON(), - ismine: false, - iswatchonly: false + address: addr.toString(node.network), + scriptPubKey: Script.fromAddress(addr, node.network).toJSON(), + isscript: false, + iswitness: false }); }); @@ -572,7 +604,7 @@ describe('Node', function() { rate: 100000, outputs: [{ value: 100000, - address: wallet.getAddress() + address: await wallet.receiveAddress() }] }); @@ -582,7 +614,7 @@ describe('Node', function() { const tx = mtx.toTX(); - await wallet.db.addTX(tx); + await wdb.addTX(tx); const missing = await node.mempool.addTX(tx); assert(!missing); @@ -597,7 +629,7 @@ describe('Node', function() { rate: 1000, outputs: [{ value: 50000, - address: wallet.getAddress() + address: await wallet.receiveAddress() }] }); @@ -607,7 +639,7 @@ describe('Node', function() { const tx = mtx.toTX(); - await wallet.db.addTX(tx); + await wdb.addTX(tx); const missing = await node.mempool.addTX(tx); assert(!missing); @@ -704,6 +736,46 @@ describe('Node', function() { assert.strictEqual(result.coinbasevalue, 125e7 + fees); }); + it('should broadcast a tx from inventory', async () => { + const rawTX1 = + '01000000011d06bce42b67f1de811a3444353fab5d400d82728a5bbf9c89978be37ad' + + '3eba9000000006a47304402200de4fd4ecc365ea90f93dbc85d219d7f1bd92ec87436' + + '48acb48b6602977e0b4302203ca2eeabed8e6f457234652a92711d66dd8eda71ed90f' + + 'b6c49b3c12ce809a5d401210257654e1b0de2d8b08d514e51af5d770e9ef617ca2b25' + + '4d84dd26685fbc609ec3ffffffff0280969800000000001976a914a4ecde9642f8070' + + '241451c5851431be9b658a7fe88acc4506a94000000001976a914b9825cafc838c5b5' + + 'befb70ecded7871d011af89d88ac00000000'; + const tx1 = TX.fromRaw(rawTX1, 'hex'); + const dummyPeer = Peer.fromOptions({ + network: 'regtest', + agent: 'my-subversion', + hasWitness: () => { + return false; + } + }); + const txItem = new InvItem(invTypes.TX, tx1.hash()); + await node.sendTX(tx1); // add TX to inventory + const tx2 = node.pool.getBroadcasted(dummyPeer, txItem); + assert.strictEqual(tx1.txid(), tx2.txid()); + }); + + it('should get tx by hash', async () => { + const block = await mineBlock(); + await chain.add(block); + + const tx = block.txs[0]; + const hash = tx.hash(); + const hasTX = await node.hasTX(hash); + + assert.strictEqual(hasTX, true); + + const tx2 = await node.getTX(hash); + assert.strictEqual(tx.txid(), tx2.txid()); + + const meta = await node.getMeta(hash); + assert.strictEqual(meta.tx.txid(), tx2.txid()); + }); + it('should cleanup', async () => { consensus.COINBASE_MATURITY = 100; await node.close(); diff --git a/test/outpoint-test.js b/test/outpoint-test.js new file mode 100644 index 000000000..1276cb333 --- /dev/null +++ b/test/outpoint-test.js @@ -0,0 +1,129 @@ +/* eslint-env mocha */ + +'use strict'; + +const Outpoint = require('../lib/primitives/outpoint'); +const assert = require('bsert'); +const common = require('./util/common'); +const util = require('../lib/utils/util'); +const TX = require('../lib/primitives/tx'); +const nodejsUtil = require('util'); +const OUTPOINT_SIZE = 36; + +describe('Outpoint', () => { + let raw1, tx1, out1; + beforeEach(() => { + tx1 = common.readTX('tx1').getRaw(); + raw1 = tx1.slice(5, 5+OUTPOINT_SIZE); + out1 = Outpoint.fromRaw(raw1); + }); + + it('should clone the outpoint correctly', () => { + const out1 = Outpoint.fromRaw(raw1); + const clone = out1.clone(); + const equals = out1.equals(clone); + + assert.strictEqual(out1 !== clone, true); + assert.strictEqual(equals, true); + }); + + it('should create outpoint from options object', () => { + const options = {}; + options.hash = out1.hash; + options.index = out1.index; + + const newOut = Outpoint.fromOptions(options); + assert(newOut.equals(out1), true); + }); + + it('should check hash and index are equal', () => { + const out1Clone = Outpoint.fromOptions(Object.assign(out1, {})); + + assert(out1Clone.hash === out1.hash); + assert(out1Clone.index === out1.index); + }); + + it('should compare the indexes between outpoints', () => { + const out1RevHash = out1.clone(); + out1RevHash.hash = Buffer.from(out1RevHash.hash); + out1RevHash.hash[0] = 0; + + const out1AdjIndex = out1.clone(); + out1AdjIndex.index += 1; + + const index1 = out1.index; + const index2 = out1AdjIndex.index; + // assert that it compares txid first + assert(out1.compare(out1RevHash) !== 0, 'txid wasn\'t compared correctly'); + assert(out1.compare(out1) === 0); + assert(out1.compare(out1AdjIndex) === index1 - index2); + }); + + it('should detect if the outpoint is null', () => { + const rawHash = '00000000000000000000000000000000000000000000' + + '00000000000000000000'; + const rawIndex = 'ffffffff'; + const nullOut = Outpoint.fromRaw(Buffer.from(rawHash + rawIndex, 'hex')); + assert(nullOut.isNull(), true); + }); + + it('should retrieve little endian hash', () => { + assert.strictEqual(out1.rhash(), util.revHex(out1.hash)); + assert.strictEqual(out1.txid(), util.revHex(out1.hash)); + }); + + it('should serialize to a key suitable for hash table', () => { + const expected = out1.toRaw(); + const actual = out1.toKey(); + assert.bufferEqual(expected, actual); + }); + + it('should inject properties from hash table key', () => { + const key = out1.toKey(); + const fromKey = Outpoint.fromKey(key); + assert(out1.equals(fromKey), true); + }); + + it('should return a size of 36', () => { + assert(out1.getSize() === OUTPOINT_SIZE, true); + }); + + it('should create an outpoint from JSON', () => { + const json = { + hash: out1.txid(), + index: out1.index + }; + const fromJSON = Outpoint.fromJSON(json); + + assert.deepEqual(out1, fromJSON); + }); + + it('should return an object with reversed hash', () => { + const hash = out1.hash; + const index = out1.index; + + const expected = { + hash: util.revHex(hash), + index + }; + assert.deepEqual(expected, out1.toJSON()); + }); + + it('should instantiate an outpoint from a tx', () => { + const tx = TX.fromRaw(tx1); + const index = 0; + const fromTX = Outpoint.fromTX(tx, index); + + assert.bufferEqual(fromTX.hash, tx.hash()); + assert.strictEqual(fromTX.index, index); + }); + + it('should inspect Outpoint', () => { + const outpoint = new Outpoint(); + const fmt = nodejsUtil.format(outpoint); + assert(typeof fmt === 'string'); + assert(fmt.includes('Outpoint')); + assert(fmt.includes( + '0000000000000000000000000000000000000000000000000000000000000000')); + }); +}); diff --git a/test/pow-test.js b/test/pow-test.js new file mode 100644 index 000000000..041ca1c10 --- /dev/null +++ b/test/pow-test.js @@ -0,0 +1,96 @@ +/* eslint-env mocha */ +/* eslint prefer-arrow-callback: "off" */ + +'use strict'; + +const assert = require('bsert'); +const Chain = require('../lib/blockchain/chain'); +const ChainEntry = require('../lib/blockchain/chainentry'); +const Network = require('../lib/protocol/network'); +const BlockStore = require('../lib/blockstore/level'); + +const network = Network.get('main'); + +function random(max) { + return Math.floor(Math.random() * max); +} + +const blocks = new BlockStore({ + memory: true, + network +}); + +const chain = new Chain({ + memory: true, + network, + blocks +}); + +describe('Difficulty', function() { + it('should open chain', async () => { + await blocks.open(); + await chain.open(); + }); + + it('should get next work', async () => { + const prev = new ChainEntry(); + prev.time = 1262152739; + prev.bits = 0x1d00ffff; + prev.height = 32255; + const first = new ChainEntry(); + first.time = 1261130161; + assert.strictEqual(chain.retarget(prev, first), 0x1d00d86a); + }); + + it('should get next work pow limit', async () => { + const prev = new ChainEntry(); + prev.time = 1233061996; + prev.bits = 0x1d00ffff; + prev.height = 2015; + const first = new ChainEntry(); + first.time = 1231006505; + assert.strictEqual(chain.retarget(prev, first), 0x1d00ffff); + }); + + it('should get next work lower limit actual', async () => { + const prev = new ChainEntry(); + prev.time = 1279297671; + prev.bits = 0x1c05a3f4; + prev.height = 68543; + const first = new ChainEntry(); + first.time = 1279008237; + assert.strictEqual(chain.retarget(prev, first), 0x1c0168fd); + }); + + it('should get next work upper limit actual', async () => { + const prev = new ChainEntry(); + prev.time = 1269211443; + prev.bits = 0x1c387f6f; + prev.height = 46367; + const first = new ChainEntry(); + first.time = 1263163443; + assert.strictEqual(chain.retarget(prev, first), 0x1d00e1fd); + }); + + it('should get block proof equivalent time', async () => { + const blocks = []; + for (let i = 0; i < 10000; i++) { + const prev = new ChainEntry(); + prev.height = i; + prev.time = 1269211443 + i * network.pow.targetSpacing; + prev.bits = 0x207fffff; + if (i > 0) + prev.chainwork = prev.getProof().addn(blocks[i-1].chainwork.toNumber()); + blocks[i] = prev; + } + + chain.tip = blocks[blocks.length - 1]; + for (let j = 0; j < 1000; j++) { + const p1 = blocks[random(blocks.length)]; + const p2 = blocks[random(blocks.length)]; + + const tdiff = chain.getProofTime(p1, p2); + assert.ok(tdiff === p1.time - p2.time); + } + }); +}); diff --git a/test/protocol-test.js b/test/protocol-test.js index 4f4082111..ff1e8a39d 100644 --- a/test/protocol-test.js +++ b/test/protocol-test.js @@ -4,10 +4,10 @@ 'use strict'; -const assert = require('./util/assert'); +const assert = require('bsert'); const Network = require('../lib/protocol/network'); const util = require('../lib/utils/util'); -const NetAddress = require('../lib/primitives/netaddress'); +const NetAddress = require('../lib/net/netaddress'); const TX = require('../lib/primitives/tx'); const Framer = require('../lib/net/framer'); const Parser = require('../lib/net/parser'); @@ -51,7 +51,7 @@ describe('Protocol', function() { time: network.now(), remote: new NetAddress(), local: new NetAddress(), - nonce: util.nonce(), + nonce: Buffer.allocUnsafe(8), agent: agent, height: 0, noRelay: false @@ -70,7 +70,7 @@ describe('Protocol', function() { time: network.now(), remote: new NetAddress(), local: new NetAddress(), - nonce: util.nonce(), + nonce: Buffer.allocUnsafe(8), agent: agent, height: 10, noRelay: true @@ -102,15 +102,15 @@ describe('Protocol', function() { ]; packetTest('addr', new packets.AddrPacket(hosts), (payload) => { - assert.typeOf(payload.items, 'array'); + assert(Array.isArray(payload.items)); assert.strictEqual(payload.items.length, 2); - assert.typeOf(payload.items[0].time, 'number'); + assert(Number.isInteger(payload.items[0].time)); assert.strictEqual(payload.items[0].services, 1); assert.strictEqual(payload.items[0].host, hosts[0].host); assert.strictEqual(payload.items[0].port, hosts[0].port); - assert.typeOf(payload.items[1].time, 'number'); + assert(Number.isInteger(payload.items[1].time)); assert.strictEqual(payload.items[1].services, 1); assert.strictEqual(payload.items[1].host, hosts[1].host); assert.strictEqual(payload.items[1].port, hosts[1].port); diff --git a/test/schnorr-test.js b/test/schnorr-test.js deleted file mode 100644 index 0934fd564..000000000 --- a/test/schnorr-test.js +++ /dev/null @@ -1,20 +0,0 @@ -/* eslint-env mocha */ -/* eslint prefer-arrow-callback: "off" */ - -'use strict'; - -const assert = require('./util/assert'); -const secp256k1 = require('../lib/crypto/secp256k1'); -const digest = require('../lib/crypto/digest'); -const schnorr = require('../lib/crypto/schnorr'); - -describe('Schnorr', function() { - it('should do proper schnorr', () => { - const key = secp256k1.generatePrivateKey(); - const pub = secp256k1.publicKeyCreate(key, true); - const msg = digest.hash256(Buffer.from('foo', 'ascii')); - const sig = schnorr.sign(msg, key); - assert.strictEqual(schnorr.verify(msg, sig, pub), true); - assert.bufferEqual(schnorr.recover(sig, msg), pub); - }); -}); diff --git a/test/script-test.js b/test/script-test.js index a50ff9b42..9623168c5 100644 --- a/test/script-test.js +++ b/test/script-test.js @@ -3,16 +3,17 @@ 'use strict'; -const assert = require('./util/assert'); +const assert = require('bsert'); const Script = require('../lib/script/script'); const Witness = require('../lib/script/witness'); const Stack = require('../lib/script/stack'); const Opcode = require('../lib/script/opcode'); const TX = require('../lib/primitives/tx'); -const util = require('../lib/utils/util'); -const encoding = require('../lib/utils/encoding'); +const consensus = require('../lib/protocol/consensus'); +const {fromFloat} = require('../lib/utils/fixed'); -const scripts = require('./data/script-tests.json'); +// test files: https://github.com/bitcoin/bitcoin/tree/master/src/test/data +const scripts = require('./data/core-data/script-tests.json'); function isSuccess(stack) { if (stack.length === 0) @@ -39,7 +40,7 @@ function parseScriptTest(data) { let value = 0; if (witArr.length > 0) - value = util.fromFloat(witArr.pop(), 8); + value = fromFloat(witArr.pop(), 8); const witness = Witness.fromString(witArr); const input = Script.fromString(inpHex); @@ -103,7 +104,7 @@ describe('Script', function() { input.execute(stack); output.execute(stack); - assert.deepEqual(stack.items, [[1], [3], [5]]); + assert.deepEqual(stack.items, [[1], [3], [5]].map(a => Buffer.from(a))); } { @@ -128,7 +129,7 @@ describe('Script', function() { input.execute(stack); output.execute(stack); - assert.deepEqual(stack.items, [[1], [4], [5]]); + assert.deepEqual(stack.items, [[1], [4], [5]].map(a => Buffer.from(a))); } { @@ -151,7 +152,7 @@ describe('Script', function() { input.execute(stack); output.execute(stack); - assert.deepEqual(stack.items, [[1], [3], [5]]); + assert.deepEqual(stack.items, [[1], [3], [5]].map(a => Buffer.from(a))); } { @@ -174,7 +175,7 @@ describe('Script', function() { input.execute(stack); output.execute(stack); - assert.deepEqual(stack.items, [[1], [5]]); + assert.deepEqual(stack.items, [[1], [5]].map(a => Buffer.from(a))); } { @@ -197,7 +198,7 @@ describe('Script', function() { input.execute(stack); output.execute(stack); - assert.deepEqual(stack.items, [[1], [3], [5]]); + assert.deepEqual(stack.items, [[1], [3], [5]].map(a => Buffer.from(a))); } }); @@ -286,7 +287,7 @@ describe('Script', function() { version: 1, inputs: [{ prevout: { - hash: encoding.NULL_HASH, + hash: consensus.ZERO_HASH, index: 0xffffffff }, script: [ @@ -308,7 +309,7 @@ describe('Script', function() { version: 1, inputs: [{ prevout: { - hash: prev.hash('hex'), + hash: prev.hash(), index: 0 }, script: input, @@ -335,7 +336,7 @@ describe('Script', function() { } if (expected !== 'OK') { - assert.typeOf(err, 'error'); + assert(err instanceof Error); assert.strictEqual(err.code, expected); return; } diff --git a/test/scrypt-test.js b/test/scrypt-test.js deleted file mode 100644 index 93f13e1f3..000000000 --- a/test/scrypt-test.js +++ /dev/null @@ -1,93 +0,0 @@ -/* eslint-env mocha */ -/* eslint prefer-arrow-callback: "off" */ - -'use strict'; - -const assert = require('./util/assert'); -const scrypt = require('../lib/crypto/scrypt'); - -describe('Scrypt', function() { - this.timeout(20000); - - it('should perform scrypt with N=16', () => { - const pass = Buffer.from(''); - const salt = Buffer.from(''); - const result = scrypt.derive(pass, salt, 16, 1, 1, 64); - assert.strictEqual(result.toString('hex'), '' - + '77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3f' - + 'ede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628' - + 'cf35e20c38d18906'); - }); - - it('should perform scrypt with N=1024', () => { - const pass = Buffer.from('password'); - const salt = Buffer.from('NaCl'); - const result = scrypt.derive(pass, salt, 1024, 8, 16, 64); - assert.strictEqual(result.toString('hex'), '' - + 'fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e773' - + '76634b3731622eaf30d92e22a3886ff109279d9830dac727afb9' - + '4a83ee6d8360cbdfa2cc0640'); - }); - - it('should perform scrypt with N=16384', () => { - const pass = Buffer.from('pleaseletmein'); - const salt = Buffer.from('SodiumChloride'); - const result = scrypt.derive(pass, salt, 16384, 8, 1, 64); - assert.strictEqual(result.toString('hex'), '' - + '7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b54' - + '3f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d' - + '651e40dfcf017b45575887'); - }); - - // Only enable if you want to wait a while. - // it('should perform scrypt with N=1048576', () => { - // let pass = Buffer.from('pleaseletmein'); - // let salt = Buffer.from('SodiumChloride'); - // let result = scrypt.derive(pass, salt, 1048576, 8, 1, 64); - // assert.strictEqual(result.toString('hex'), '' - // + '2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5' - // + 'ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049' - // + 'e8a952fbcbf45c6fa77a41a4'); - // }); - - it('should perform scrypt with N=16 (async)', async () => { - const pass = Buffer.from(''); - const salt = Buffer.from(''); - const result = await scrypt.deriveAsync(pass, salt, 16, 1, 1, 64); - assert.strictEqual(result.toString('hex'), '' - + '77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3f' - + 'ede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628' - + 'cf35e20c38d18906'); - }); - - it('should perform scrypt with N=1024 (async)', async () => { - const pass = Buffer.from('password'); - const salt = Buffer.from('NaCl'); - const result = await scrypt.deriveAsync(pass, salt, 1024, 8, 16, 64); - assert.strictEqual(result.toString('hex'), '' - + 'fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e773' - + '76634b3731622eaf30d92e22a3886ff109279d9830dac727afb9' - + '4a83ee6d8360cbdfa2cc0640'); - }); - - it('should perform scrypt with N=16384 (async)', async () => { - const pass = Buffer.from('pleaseletmein'); - const salt = Buffer.from('SodiumChloride'); - const result = await scrypt.deriveAsync(pass, salt, 16384, 8, 1, 64); - assert.strictEqual(result.toString('hex'), '' - + '7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b54' - + '3f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d' - + '651e40dfcf017b45575887'); - }); - - // Only enable if you want to wait a while. - // it('should perform scrypt with N=1048576 (async)', async () => { - // let pass = Buffer.from('pleaseletmein'); - // let salt = Buffer.from('SodiumChloride'); - // let result = await scrypt.deriveAsync(pass, salt, 1048576, 8, 1, 64); - // assert.strictEqual(result.toString('hex'), '' - // + '2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5' - // + 'ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049' - // + 'e8a952fbcbf45c6fa77a41a4'); - // }); -}); diff --git a/test/siphash-test.js b/test/siphash-test.js deleted file mode 100644 index 76aa89323..000000000 --- a/test/siphash-test.js +++ /dev/null @@ -1,30 +0,0 @@ -/* eslint-env mocha */ -/* eslint prefer-arrow-callback: "off" */ - -'use strict'; - -const assert = require('./util/assert'); -const siphash = require('../lib/crypto/siphash'); -const siphash256 = siphash.siphash256; - -describe('SipHash', function() { - it('should perform siphash with no data', () => { - const data = Buffer.alloc(0); - const key = Buffer.from('000102030405060708090a0b0c0d0e0f', 'hex'); - assert.deepStrictEqual(siphash256(data, key), [1919933255, -586281423]); - }); - - it('should perform siphash with data', () => { - const data = Buffer.from('0001020304050607', 'hex'); - const key = Buffer.from('000102030405060708090a0b0c0d0e0f', 'hex'); - assert.deepStrictEqual(siphash256(data, key), [-1812597383, -1701632926]); - }); - - it('should perform siphash with uint256', () => { - const data = Buffer.from( - '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', - 'hex'); - const key = Buffer.from('000102030405060708090a0b0c0d0e0f', 'hex'); - assert.deepStrictEqual(siphash256(data, key), [1898402095, 1928494286]); - }); -}); diff --git a/test/tx-test.js b/test/tx-test.js index 5de5b4657..77f05f77d 100644 --- a/test/tx-test.js +++ b/test/tx-test.js @@ -3,12 +3,14 @@ 'use strict'; -const assert = require('./util/assert'); +const {inspect} = require('util'); +const {encoding} = require('bufio'); +const assert = require('bsert'); +const random = require('bcrypto/lib/random'); const util = require('../lib/utils/util'); -const encoding = require('../lib/utils/encoding'); -const random = require('../lib/crypto/random'); const consensus = require('../lib/protocol/consensus'); const TX = require('../lib/primitives/tx'); +const MTX = require('../lib/primitives/mtx'); const Output = require('../lib/primitives/output'); const Outpoint = require('../lib/primitives/outpoint'); const Script = require('../lib/script/script'); @@ -17,11 +19,15 @@ const Opcode = require('../lib/script/opcode'); const Input = require('../lib/primitives/input'); const CoinView = require('../lib/coins/coinview'); const KeyRing = require('../lib/primitives/keyring'); +const Address = require('../lib/primitives/address'); +const BufferWriter = require('bufio').BufferWriter; const common = require('./util/common'); +const nodejsUtil = require('util'); -const validTests = require('./data/tx-valid.json'); -const invalidTests = require('./data/tx-invalid.json'); -const sighashTests = require('./data/sighash-tests.json'); +// test files: https://github.com/bitcoin/bitcoin/tree/master/src/test/data +const validTests = require('./data/core-data/tx-valid.json'); +const invalidTests = require('./data/core-data/tx-invalid.json'); +const sighashTests = require('./data/core-data/sighash-tests.json'); const tx1 = common.readTX('tx1'); const tx2 = common.readTX('tx2'); @@ -65,7 +71,7 @@ function parseTXTest(data) { const view = new CoinView(); for (const [txid, index, str, amount] of coins) { - const hash = util.revHex(txid); + const hash = util.fromRev(txid); const script = Script.fromString(str); const value = parseInt(amount || '0', 10); @@ -90,7 +96,7 @@ function parseTXTest(data) { flags: flags, view: view, comments: coin - ? util.inspectify(coin.script, false) + ? inspect(coin.script) : 'coinbase', data: data }; @@ -102,7 +108,7 @@ function parseSighashTest(data) { const tx = TX.fromRaw(txHex, 'hex'); const script = Script.fromRaw(scriptHex, 'hex'); - const expected = util.revHex(hash); + const expected = util.fromRev(hash); let hex = type & 3; @@ -126,7 +132,7 @@ function parseSighashTest(data) { } function createInput(value, view) { - const hash = random.randomBytes(32).toString('hex'); + const hash = random.randomBytes(32); const input = { prevout: { @@ -169,7 +175,7 @@ function sigopContext(scriptSig, witness, scriptPubkey) { spend.version = 1; const input = new Input(); - input.prevout.hash = fund.hash('hex'); + input.prevout.hash = fund.hash(); input.prevout.index = 0; input.script = scriptSig; input.witness = witness; @@ -231,7 +237,7 @@ describe('TX', function() { assert.strictEqual(tx.outputs.length, 1980); assert(tx.hasWitness()); assert.notStrictEqual(tx.txid(), tx.wtxid()); - assert.strictEqual(tx.witnessHash('hex'), + assert.strictEqual(tx.witnessHash().toString('hex'), '088c919cd8408005f255c411f786928385688a9e8fdb2db4c9bc3578ce8c94cf'); assert.strictEqual(tx.getSize(), 62138); assert.strictEqual(tx.getVirtualSize(), 61813); @@ -335,7 +341,7 @@ describe('TX', function() { it(`should get sighash of ${hash} (${hex}) ${suffix}`, () => { const subscript = script.getSubscript(0).removeSeparators(); const hash = tx.signatureHash(index, subscript, 0, type, 0); - assert.strictEqual(hash.toString('hex'), expected); + assert.bufferEqual(hash, expected); }); } } @@ -691,7 +697,7 @@ describe('TX', function() { const output = Script.fromProgram(0, key.getKeyHash()); const ctx = sigopContext(input, witness, output); - ctx.spend.inputs[0].prevout.hash = encoding.NULL_HASH; + ctx.spend.inputs[0].prevout.hash = consensus.ZERO_HASH; ctx.spend.inputs[0].prevout.index = 0xffffffff; ctx.spend.refresh(); @@ -767,4 +773,353 @@ describe('TX', function() { assert.strictEqual(ctx.spend.getSigopsCost(ctx.view, flags), 2); }); + + it('should return addresses for standard inputs', () => { + // txid: 7ef7cde4e4a7829ea6feaf377c924b36d0958e2231a31ff268bd33a59ac9e178 + const [tx, view] = tx2.getTX(); + + const inputAddresses = [ + Address.fromBase58('1Wjrrc2DrtB2CXRiPa3c8528fDdNHnQ2K') + ]; + + const inputAddressesView = tx.getInputAddresses(view); + const inputAddressesNoView = tx.getInputAddresses(); + + assert.strictEqual(inputAddresses.length, inputAddressesView.length); + assert.strictEqual(inputAddresses.length, inputAddressesNoView.length); + + inputAddresses.forEach((inputAddr, i) => { + assert(inputAddr.equals(inputAddressesView[i])); + assert(inputAddr.equals(inputAddressesNoView[i])); + }); + }); + + it('should return addresses for standard outputs', () => { + // txid: 7f2dc9bcc0b1b0d19a4cb62d0f6474990c12a5b996d2fa2c4be54ca1beb5d339 + const [tx] = tx7.getTX(); + + // If you fetch only outputs they should be sorted + // by vouts, not merged. + const outputAddresses = [ + Address.fromBase58('1fLeMazoEy8FfgeFcppRxNYZs54jyLccw'), + Address.fromBase58('1EeREnzQujX7CLgmzDSaebS48jxjeyBHQM') + ]; + + const getOutputAddresses = tx.getOutputAddresses(); + + assert.strictEqual(outputAddresses.length, getOutputAddresses.length); + + outputAddresses.forEach((outputAddr, i) => { + assert(outputAddr.equals(getOutputAddresses[i])); + }); + }); + + it('should return addresses for standard inputs and outputs', () => { + // txid: 7ef7cde4e4a7829ea6feaf377c924b36d0958e2231a31ff268bd33a59ac9e178 + const [tx, view] = tx2.getTX(); + + const addresses = [ + // inputs + Address.fromBase58('1Wjrrc2DrtB2CXRiPa3c8528fDdNHnQ2K'), + // outputs + Address.fromBase58('1GcKLBv6iFSCkbhht2m44qnZTYK8xV8nNA'), + Address.fromBase58('1EpKnnMo1rSkktYw8vPLtXGBRNLraXWd73') + ]; + + const addressesView = tx.getAddresses(view); + const addressesNoView = tx.getAddresses(); + + assert.strictEqual(addresses.length, addressesView.length); + assert.strictEqual(addresses.length, addressesNoView.length); + + addresses.forEach((addr, i) => { + assert(addr.equals(addressesView[i])); + assert(addr.equals(addressesNoView[i])); + }); + }); + + it('should return merged addresses for same input/output address', () => { + // txid: 7f2dc9bcc0b1b0d19a4cb62d0f6474990c12a5b996d2fa2c4be54ca1beb5d339 + const [tx, view] = tx7.getTX(); + + const addresses = [ + // this is input and output + Address.fromBase58('1EeREnzQujX7CLgmzDSaebS48jxjeyBHQM'), + Address.fromBase58('1fLeMazoEy8FfgeFcppRxNYZs54jyLccw') + ]; + + const addressesView = tx.getAddresses(view); + const addressesNoView = tx.getAddresses(); + + assert.strictEqual(addresses.length, addressesView.length); + assert.strictEqual(addresses.length, addressesNoView.length); + + addresses.forEach((addr, i) => { + assert(addr.equals(addressesView[i])); + assert(addr.equals(addressesNoView[i])); + }); + }); + + it('should return addresses with witness data', () => { + const [tx, view] = tx5.getTX(); + + const addresses = [ + // inputs + Address.fromBech32('bc1qnjhhj5g8u46fvhnm34me52ahnx5vhhhuk6m7ng'), + Address.fromBech32('bc1q3ehzk5qa02sf05zyll0thth5t92kg6twah8hj3'), + Address.fromBase58('1C4irrkJiHhjKq62uPBw9huZnQsFSRHtjn'), + Address.fromBech32('bc1q4gzv2jkfnym3s8f69kj55l4yfh7aallphtzutp'), + Address.fromBech32('bc1q8838eem5cqqlxn34neay8sd7ru4nnm7yfv66xv'), + + // outputs + Address.fromBech32('bc1q4uxyx3qaanm5elq4w2kxytvkpufa33s08vldx7'), + Address.fromBech32('bc1q8m66kw3789mvpfpcxxh880zg6jjwpyntspcnmy') + ]; + + const addressesView = tx.getAddresses(view); + const addressesNoView = tx.getAddresses(); + + assert.strictEqual(addresses.length, addressesView.length); + assert.strictEqual(addresses.length, addressesNoView.length); + + addresses.forEach((addr, i) => { + assert(addr.equals(addressesView[i])); + assert(addr.equals(addressesNoView[i])); + }); + }); + + it('should return address hashes for standard inputs and outputs', () => { + // txid: 7ef7cde4e4a7829ea6feaf377c924b36d0958e2231a31ff268bd33a59ac9e178 + const [tx, view] = tx2.getTX(); + + const hashes = [ + // inputs + Address.fromBase58('1Wjrrc2DrtB2CXRiPa3c8528fDdNHnQ2K').getHash(), + // outputs + Address.fromBase58('1GcKLBv6iFSCkbhht2m44qnZTYK8xV8nNA').getHash(), + Address.fromBase58('1EpKnnMo1rSkktYw8vPLtXGBRNLraXWd73').getHash() + ]; + + const hashesBuf = tx.getHashes(view); + + assert.strictEqual(hashes.length, hashesBuf.length); + + hashes.forEach((hash, i) => { + assert.bufferEqual(hash, hashesBuf[i]); + }); + }); + + it('should return address hashes for standard inputs', () => { + // txid: 7ef7cde4e4a7829ea6feaf377c924b36d0958e2231a31ff268bd33a59ac9e178 + const [tx, view] = tx2.getTX(); + + const inputHashes = [ + Address.fromBase58('1Wjrrc2DrtB2CXRiPa3c8528fDdNHnQ2K').getHash() + ]; + + const hashesBuf = tx.getInputHashes(view); + + assert.strictEqual(inputHashes.length, hashesBuf.length); + + inputHashes.forEach((hash, i) => { + assert.bufferEqual(hash, hashesBuf[i]); + }); + }); + + it('should return address hashes for standard outputs', () => { + // txid: 7ef7cde4e4a7829ea6feaf377c924b36d0958e2231a31ff268bd33a59ac9e178 + const [tx] = tx2.getTX(); + + const outputHashes = [ + Address.fromBase58('1GcKLBv6iFSCkbhht2m44qnZTYK8xV8nNA').getHash(), + Address.fromBase58('1EpKnnMo1rSkktYw8vPLtXGBRNLraXWd73').getHash() + ]; + + const hashesBuf = tx.getOutputHashes(); + + assert.strictEqual(outputHashes.length, hashesBuf.length); + + outputHashes.forEach((hash, i) => { + assert.bufferEqual(hash, hashesBuf[i]); + }); + }); + + it('should return all prevouts', () => { + const [tx] = tx3.getTX(); + + const expectedPrevouts = [ + '2f196cf1e5bd426a04f07b882c893b5b5edebad67da6eb50f066c372ed736d5f', + 'ff8755f073f1170c0d519457ffc4acaa7cb2988148163b5dc457fae0fe42aa19' + ]; + + const prevouts = tx.getPrevout(); + + assert(expectedPrevouts.length, prevouts.length); + expectedPrevouts.forEach((prevout, i) => { + assert.strictEqual(prevout, prevouts[i].toString('hex')); + }); + }); + + it('should serialize without witness data', () => { + const [tx] = tx2.getTX(); + const [txWit] = tx5.getTX(); + + const bw1 = new BufferWriter(); + const bw2 = new BufferWriter(); + + tx.toNormalWriter(bw1); + txWit.toNormalWriter(bw2); + + const tx1normal = TX.fromRaw(bw1.render()); + const tx2normal = TX.fromRaw(bw2.render()); + + assert.strictEqual(tx1normal.hasWitness(), false); + assert.strictEqual(tx2normal.hasWitness(), false); + }); + + it('should check if tx is free', () => { + const value = 100000000; // 1 btc + const height = 100; + const [input, view] = createInput(value); + + // hack height into coinEntry + const entry = view.getEntry(input.prevout); + entry.height = height; + + const tx = new TX({ + version: 1, + inputs: [input], + outputs: [{ + script: [], + value: value + }], + locktime: 0 + }); + + // Priority should be more than FREE_THRESHOLD + // txsize: 60, value: 1 btc + // freeAfter: 144/250*txsize = 34.56 + const size = tx.getSize(); + const freeHeight = height + 35; + const freeAt34 = tx.isFree(view, freeHeight - 1); + const freeAt34size = tx.isFree(view, freeHeight - 1, tx, size); + const freeAt35 = tx.isFree(view, freeHeight); + const freeAt35size = tx.isFree(view, freeHeight, size); + + assert.strictEqual(freeAt34, false); + assert.strictEqual(freeAt34size, false); + assert.strictEqual(freeAt35, true); + assert.strictEqual(freeAt35size, true); + }); + + it('should return correct minFee and roundedFee', () => { + const value = 100000000; // 1 btc + + const [input] = createInput(value); + const tx = new TX({ + version: 1, + inputs: [input], + outputs: [{ + script: [], + value: value + }], + locktime: 0 + }); + + // 1000 satoshis per kb + const rate = 1000; + const size = tx.getSize(); // 60 bytes + + // doesn't round to KB + assert.strictEqual(tx.getMinFee(size, rate), 60); + assert.strictEqual(tx.getMinFee(size, rate * 10), 600); + assert.strictEqual(tx.getMinFee(size * 10, rate), 600); + + // rounds to KB + assert.strictEqual(tx.getRoundFee(size, rate), 1000); + // still under kb + assert.strictEqual(tx.getRoundFee(size * 10, rate), 1000); + assert.strictEqual(tx.getRoundFee(size, rate * 10), 10000); + + assert.strictEqual(tx.getRoundFee(1000, rate), 1000); + assert.strictEqual(tx.getRoundFee(1001, rate), 2000); + }); + + it('should return JSON for tx', () => { + const [tx, view] = tx2.getTX(); + const hash = '7ef7cde4e4a7829ea6feaf377c924b36d0958e22' + + '31a31ff268bd33a59ac9e178'; + const version = 0; + const locktime = 0; + const hex = tx2.getRaw().toString('hex'); + + // hack for ChainEntry + const entry = { + height: 1000, + hash: Buffer.from( + 'c82d447db6150d2308d9571c19bc3dc6efde97a8227d9e57bc77ec0900000000', + 'hex'), + time: 1365870306 + }; + const network = 'testnet'; + const index = 0; + + const jsonDefault = tx.getJSON(network); + const jsonView = tx.getJSON(network, view); + const jsonEntry = tx.getJSON(network, null, entry); + const jsonIndex = tx.getJSON(network, null, null, index); + const jsonAll = tx.getJSON(network, view, entry, index); + + for (const json of [jsonDefault, jsonView, jsonEntry, jsonIndex, jsonAll]) { + assert.strictEqual(json.hash, hash); + assert.strictEqual(json.witnessHash, hash); + assert.strictEqual(json.version, version); + assert.strictEqual(json.locktime, locktime); + assert.strictEqual(json.hex, hex); + } + + const fee = 10000; + const rate = 44247; + + for (const json of [jsonView, jsonAll]) { + assert.strictEqual(json.fee, fee); + assert.strictEqual(json.rate, rate); + } + + const date = '2013-04-13T16:25:06Z'; + for (const json of [jsonEntry, jsonAll]) { + assert.strictEqual(json.height, entry.height); + assert.strictEqual(json.block, util.revHex(entry.hash)); + assert.strictEqual(json.time, entry.time); + assert.strictEqual(json.date, date); + } + + for (const json of [jsonIndex, jsonAll]) { + assert.strictEqual(json.index, index); + } + }); + + it('should recover coins from JSON', () => { + const [tx, view] = tx2.getTX(); + + const mtx = MTX.fromTX(tx); + mtx.view = view; + + // get input value as example + const value1 = mtx.getInputValue(); + + const mtx2 = MTX.fromJSON(mtx.toJSON()); + const value2 = mtx2.getInputValue(); + + assert.strictEqual(value1, value2); + }); + + it('should inspect TX', () => { + const tx = new TX(); + const fmt = nodejsUtil.format(tx); + assert(typeof fmt === 'string'); + assert(fmt.includes('hash')); + assert(fmt.includes('version')); + assert(fmt.includes('locktime')); + }); }); diff --git a/test/txmeta-test.js b/test/txmeta-test.js new file mode 100644 index 000000000..3740597f6 --- /dev/null +++ b/test/txmeta-test.js @@ -0,0 +1,25 @@ +/* eslint-env mocha */ +/* eslint prefer-arrow-callback: "off" */ + +'use strict'; + +const assert = require('bsert'); +const Network = require('../lib/protocol/network'); +const TXMeta = require('../lib/primitives/txmeta'); + +const network = Network.get('regtest'); + +describe('TXMeta', function() { + it('should return JSON for txmeta', async () => { + // unconfirmed at height 100 + const txmeta1 = new TXMeta(); + const txJSON1 = txmeta1.getJSON(network, null, 100); + assert.strictEqual(txJSON1.confirmations, 0); + + // confirmed once at height 100 + const txmeta2 = TXMeta.fromOptions( {height: 100} ); + txmeta2.height = 100; + const txJSON2 = txmeta2.getJSON(network, null, 100); + assert.strictEqual(txJSON2.confirmations, 1); + }); +}); diff --git a/test/util/assert.js b/test/util/assert.js deleted file mode 100644 index c3a9e56b6..000000000 --- a/test/util/assert.js +++ /dev/null @@ -1,215 +0,0 @@ -'use strict'; - -const _assert = require('assert'); -const util = require('util'); - -const assert = function assert(value, message) { - if (!value) { - throw new assert.AssertionError({ - message, - actual: value, - expected: true, - operator: '==', - stackStartFunction: assert - }); - } -}; - -Object.setPrototypeOf(assert, _assert); - -assert.typeOf = function typeOf(value, expected, message) { - _isString(expected, '`expected` must be a string.', typeOf); - - const actual = _typeOf(value); - - if (actual !== expected) { - throw new assert.AssertionError({ - message, - actual, - expected, - operator: 'typeof ==', - stackStartFunction: typeOf - }); - } -}; - -assert.notTypeOf = function notTypeOf(value, expected, message) { - _isString(expected, '`expected` must be a string.', notTypeOf); - - const actual = _typeOf(value); - - if (actual === expected) { - throw new assert.AssertionError({ - message, - actual, - expected, - operator: 'typeof !=', - stackStartFunction: notTypeOf - }); - } -}; - -assert.instanceOf = function instanceOf(object, parent, message) { - _isFunction(parent, '`parent` must be a constructor.', instanceOf); - - if (!(object instanceof parent)) { - throw new assert.AssertionError({ - message, - actual: _getConstructorName(object), - expected: _getFunctionName(parent), - operator: 'instanceof', - stackStartFunction: instanceOf - }); - } -}; - -assert.notInstanceOf = function notInstanceOf(object, parent, message) { - _isFunction(parent, '`parent` must be a constructor.', notInstanceOf); - - if (object instanceof parent) { - throw new assert.AssertionError({ - message, - actual: _getConstructorName(object), - expected: _getFunctionName(parent), - operator: 'not instanceof', - stackStartFunction: notInstanceOf - }); - } -}; - -assert.bufferEqual = function bufferEqual(actual, expected, message) { - _isBuffer(actual, '`actual` must be a buffer.', bufferEqual); - _isBuffer(expected, '`expected` must be a buffer.', bufferEqual); - - if (actual !== expected && !actual.equals(expected)) { - throw new assert.AssertionError({ - message, - actual: actual.toString('hex'), - expected: expected.toString('hex'), - operator: '===', - stackStartFunction: bufferEqual - }); - } -}; - -assert.notBufferEqual = function notBufferEqual(actual, expected, message) { - _isBuffer(actual, '`actual` must be a buffer.', notBufferEqual); - _isBuffer(expected, '`expected` must be a buffer.', notBufferEqual); - - if (actual === expected || actual.equals(expected)) { - throw new assert.AssertionError({ - message, - actual: actual.toString('hex'), - expected: expected.toString('hex'), - operator: '!==', - stackStartFunction: notBufferEqual - }); - } -}; - -function _isString(value, message, stackStartFunction) { - if (typeof value !== 'string') { - throw new assert.AssertionError({ - message, - actual: _typeOf(value), - expected: 'string', - operator: 'typeof ==', - stackStartFunction - }); - } -} - -function _isFunction(value, message, stackStartFunction) { - if (typeof value !== 'function') { - throw new assert.AssertionError({ - message, - actual: _typeOf(value), - expected: 'function', - operator: 'typeof ==', - stackStartFunction - }); - } -} - -function _isBuffer(value, message, stackStartFunction) { - if (!Buffer.isBuffer(value)) { - throw new assert.AssertionError({ - message, - actual: _typeOf(value), - expected: 'buffer', - operator: 'typeof ==', - stackStartFunction - }); - } -} - -function _typeOf(value) { - const type = typeof value; - - switch (type) { - case 'object': - if (value === null) - return 'null'; - - if (Array.isArray(value)) - return 'array'; - - if (Buffer.isBuffer(value)) - return 'buffer'; - - if (ArrayBuffer.isView(value)) - return 'arraybuffer'; - - if (util.isError(value)) - return 'error'; - - if (util.isDate(value)) - return 'date'; - - if (util.isRegExp(value)) - return 'regexp'; - - break; - case 'number': - if (!isFinite(value)) - return 'nan'; - break; - } - - return type; -} - -function _getConstructorName(object) { - if (object === undefined) - return 'undefined'; - - if (object === null) - return 'null'; - - const proto = Object.getPrototypeOf(object); - - // Should never happen. - if (proto === undefined) - throw new Error('Bad prototype.'); - - // Inherited from `null`. - if (proto === null) - return 'Null'; - - // Someone overwrote their - // constructor property? - if (!proto.constructor) - return 'Object'; - - // Non-named constructor function. - if (!proto.constructor.name) - return 'Unknown'; - - return proto.constructor.name; -} - -function _getFunctionName(func) { - return func.name || 'Unknown'; -} - -module.exports = assert; diff --git a/test/util/common.js b/test/util/common.js index 149cb3429..8bdbab1a0 100644 --- a/test/util/common.js +++ b/test/util/common.js @@ -1,8 +1,11 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); +const {tmpdir} = require('os'); const path = require('path'); -const fs = require('../../lib/utils/fs'); +const fs = require('bfile'); +const bio = require('bufio'); +const {randomBytes} = require('bcrypto/lib/random'); const Block = require('../../lib/primitives/block'); const MerkleBlock = require('../../lib/primitives/merkleblock'); const Headers = require('../../lib/primitives/headers'); @@ -10,8 +13,6 @@ const {CompactBlock} = require('../../lib/net/bip152'); const TX = require('../../lib/primitives/tx'); const Output = require('../../lib/primitives/output'); const CoinView = require('../../lib/coins/coinview'); -const BufferReader = require('../../lib/utils/reader'); -const BufferWriter = require('../../lib/utils/writer'); const common = exports; @@ -86,8 +87,44 @@ common.writeTX = function writeTX(name, tx, view) { common.writeFile(`${name}-undo.raw`, undoRaw); }; +common.testdir = function(name) { + assert(/^[a-z]+$/.test(name), 'Invalid name'); + + const uniq = randomBytes(4).toString('hex'); + return path.join(tmpdir(), `bcoin-test-${name}-${uniq}`); +}; + +common.rimraf = async function(p) { + const allowed = /bcoin\-test\-[a-z]+\-[a-f0-9]{8}(\/[a-z]+)?$/; + if (!allowed.test(p)) + throw new Error(`Path not allowed: ${p}.`); + + return await fs.rimraf(p); +}; + +common.forValue = async function(obj, key, val, timeout = 30000) { + assert(typeof obj === 'object'); + assert(typeof key === 'string'); + + const ms = 10; + let interval = null; + let count = 0; + return new Promise((resolve, reject) => { + interval = setInterval(() => { + if (obj[key] === val) { + clearInterval(interval); + resolve(); + } else if (count * ms >= timeout) { + clearInterval(interval); + reject(new Error('Timeout waiting for value.')); + } + count += 1; + }, ms); + }); +}; + function parseUndo(data) { - const br = new BufferReader(data); + const br = bio.read(data); const items = []; while (br.left()) { @@ -99,7 +136,7 @@ function parseUndo(data) { } function serializeUndo(items) { - const bw = new BufferWriter(); + const bw = bio.write(); for (const item of items) { bw.writeI64(item.value); diff --git a/test/util/memwallet.js b/test/util/memwallet.js index a2364df56..712475e75 100644 --- a/test/util/memwallet.js +++ b/test/util/memwallet.js @@ -6,418 +6,428 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); +const {BufferMap, BufferSet} = require('buffer-map'); const Network = require('../../lib/protocol/network'); const MTX = require('../../lib/primitives/mtx'); const HD = require('../../lib/hd/hd'); -const Bloom = require('../../lib/utils/bloom'); +const {BloomFilter} = require('bfilter'); 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); - - 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 = Bloom.fromRate(1000000, 0.001, -1); - - if (options) - this.fromOptions(options); - - this.init(); -} +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 BufferSet(); + this.coins = new BufferMap(); + this.spent = new BufferMap(); + this.paths = new BufferMap(); + this.balance = 0; + this.txs = 0; + this.filter = BloomFilter.fromRate(1000000, 0.001, -1); + + if (options) + this.fromOptions(options); + + this.init(); + } -MemWallet.prototype.fromOptions = function fromOptions(options) { - if (options.network != null) { - assert(options.network); - this.network = Network.get(options.network); + 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; } - if (options.master != null) { - assert(options.master); - this.master = HD.PrivateKey.fromOptions(options.master, this.network); + 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(); } - if (options.key != null) { - assert(HD.isPrivate(options.key)); - this.key = options.key; + createReceive() { + const index = this.receiveDepth++; + const key = this.deriveReceive(index); + const hash = key.getHash(); + this.filter.add(hash); + this.paths.set(hash, new Path(hash, 0, index)); + this.receive = key; + return key; } - if (options.witness != null) { - assert(typeof options.witness === 'boolean'); - this.witness = options.witness; + createChange() { + const index = this.changeDepth++; + const key = this.deriveChange(index); + const hash = key.getHash(); + this.filter.add(hash); + this.paths.set(hash, new Path(hash, 1, index)); + this.change = key; + return key; } - if (options.account != null) { - assert(typeof options.account === 'number'); - this.account = options.account; + deriveReceive(index) { + return this.deriveKey(0, index); } - if (options.receiveDepth != null) { - assert(typeof options.receiveDepth === 'number'); - this.receiveDepth = options.receiveDepth; + deriveChange(index) { + return this.deriveKey(1, index); } - if (options.changeDepth != null) { - assert(typeof options.changeDepth === 'number'); - this.changeDepth = options.changeDepth; + 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; } - return this; -}; - -MemWallet.prototype.init = function init() { - let i; - - if (!this.master) - this.master = HD.PrivateKey.generate(); - - if (!this.key) - this.key = this.master.deriveAccount(44, 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; -}; + getKey(hash) { + const path = this.paths.get(hash); -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) { - let key = this.master.deriveAccount(44, 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 = 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; + if (!path) + return null; + + return this.derivePath(path); } -}; -MemWallet.prototype.addBlock = function addBlock(entry, txs) { - for (let i = 0; i < txs.length; i++) { - const tx = txs[i]; - this.addTX(tx, entry.height); + getPath(hash) { + return this.paths.get(hash); } -}; -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); + getCoin(key) { + return this.coins.get(key); } -}; -MemWallet.prototype.addTX = function addTX(tx, height) { - const hash = tx.hash('hex'); - let result = false; + getUndo(key) { + return this.spent.get(key); + } - if (height == null) - height = -1; + addCoin(coin) { + const op = new Outpoint(coin.hash, coin.index); + const key = op.toKey(); - if (this.map.has(hash)) - return true; + this.filter.add(op.toRaw()); - for (let i = 0; i < tx.inputs.length; i++) { - const input = tx.inputs[i]; - const op = input.prevout.toKey(); - const coin = this.getCoin(op); + this.spent.delete(key); + + this.coins.set(key, coin); + this.balance += coin.value; + } + + removeCoin(key) { + const coin = this.coins.get(key); if (!coin) - continue; + return; - result = true; + this.spent.set(key, coin); + this.balance -= coin.value; - this.removeCoin(op); + this.coins.delete(key); } - for (let i = 0; i < tx.outputs.length; i++) { - const output = tx.outputs[i]; - const addr = output.getHash('hex'); + getAddress() { + return this.receive.getAddress(); + } - if (!addr) - continue; + getReceive() { + return this.receive.getAddress(); + } - const path = this.getPath(addr); + getChange() { + return this.change.getAddress(); + } - if (!path) - continue; + getCoins() { + const coins = []; - result = true; + for (const coin of this.coins.values()) + coins.push(coin); - const coin = Coin.fromTX(tx, i, height); + return coins; + } - this.addCoin(coin); - this.syncKey(path); + 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; + } } - if (result) { - this.txs++; - this.map.add(hash); + addBlock(entry, txs) { + for (let i = 0; i < txs.length; i++) { + const tx = txs[i]; + this.addTX(tx, entry.height); + } } - return result; -}; + removeBlock(entry, txs) { + for (let i = txs.length - 1; i >= 0; i--) { + const tx = txs[i]; + this.removeTX(tx, entry.height); + } + } -MemWallet.prototype.removeTX = function removeTX(tx, height) { - const hash = tx.hash('hex'); - let result = false; + addTX(tx, height) { + const hash = tx.hash(); + let result = false; - if (!this.map.has(hash)) - return false; + if (height == null) + height = -1; - for (let i = 0; i < tx.outputs.length; i++) { - const op = Outpoint(hash, i).toKey(); - const coin = this.getCoin(op); + if (this.map.has(hash)) + return true; - if (!coin) - continue; + for (let i = 0; i < tx.inputs.length; i++) { + const input = tx.inputs[i]; + const op = input.prevout.toKey(); + const coin = this.getCoin(op); - result = true; + if (!coin) + continue; - this.removeCoin(op); - } + result = true; - for (let i = 0; i < tx.inputs.length; i++) { - const input = tx.inputs[i]; - const op = input.prevout.toKey(); - const coin = this.getUndo(op); + this.removeCoin(op); + } - if (!coin) - continue; + for (let i = 0; i < tx.outputs.length; i++) { + const output = tx.outputs[i]; + const addr = output.getHash(); + + if (!addr) + continue; + + const path = this.getPath(addr); + + if (!path) + continue; + + result = true; + + const coin = Coin.fromTX(tx, i, height); - result = true; + this.addCoin(coin); + this.syncKey(path); + } - this.addCoin(coin); + if (result) { + this.txs += 1; + this.map.add(hash); + } + + return result; } - if (result) - this.txs--; + removeTX(tx, height) { + const hash = tx.hash(); + let result = false; - this.map.delete(hash); + if (!this.map.has(hash)) + return false; - return result; -}; + for (let i = 0; i < tx.outputs.length; i++) { + const op = new Outpoint(hash, i).toKey(); + const coin = this.getCoin(op); -MemWallet.prototype.deriveInputs = function deriveInputs(mtx) { - const keys = []; + if (!coin) + continue; - for (let i = 0; i < mtx.inputs.length; i++) { - const input = mtx.inputs[i]; - const coin = mtx.view.getOutputFor(input); + result = true; - if (!coin) - continue; + this.removeCoin(op); + } - const addr = coin.getHash('hex'); + 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 (!addr) - continue; + if (!coin) + continue; - const path = this.getPath(addr); + result = true; - if (!path) - continue; + this.addCoin(coin); + } - const key = this.derivePath(path); + if (result) + this.txs -= 1; - keys.push(key); + this.map.delete(hash); + + return result; } - return keys; -}; + 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; -MemWallet.prototype.fund = function fund(mtx, options) { - const coins = this.getCoins(); + const addr = coin.getHash(); - if (!options) - options = {}; + if (!addr) + continue; - 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 - }); -}; + const path = this.getPath(addr); -MemWallet.prototype.template = function template(mtx) { - const keys = this.deriveInputs(mtx); - mtx.template(keys); -}; + if (!path) + continue; -MemWallet.prototype.sign = function sign(mtx) { - const keys = this.deriveInputs(mtx); - mtx.template(keys); - mtx.sign(keys); -}; + const key = this.derivePath(path); -MemWallet.prototype.create = async function create(options) { - const mtx = new MTX(options); + keys.push(key); + } - await this.fund(mtx, options); + 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); + } - assert(mtx.getFee() <= MTX.Selector.MAX_FEE, 'TX exceeds MAX_FEE.'); + async create(options) { + const mtx = new MTX(options); - mtx.sortMembers(); + await this.fund(mtx, options); - if (options.locktime != null) - mtx.setLocktime(options.locktime); + assert(mtx.getFee() <= MTX.Selector.MAX_FEE, 'TX exceeds MAX_FEE.'); - this.sign(mtx); + mtx.sortMembers(); - if (!mtx.isSigned()) - throw new Error('Cannot sign tx.'); + if (options.locktime != null) + mtx.setLocktime(options.locktime); - return mtx; -}; + this.sign(mtx); -MemWallet.prototype.send = async function send(options) { - const mtx = await this.create(options); - this.addTX(mtx.toTX()); - return mtx; -}; + if (!mtx.isSigned()) + throw new Error('Cannot sign tx.'); -function Path(hash, branch, index) { - this.hash = hash; - this.branch = branch; - this.index = index; + return mtx; + } + + async send(options) { + const mtx = await this.create(options); + this.addTX(mtx.toTX()); + return mtx; + } +} + +class Path { + constructor(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 f8b39cd81..953a7cb53 100644 --- a/test/util/node-context.js +++ b/test/util/node-context.js @@ -1,124 +1,123 @@ 'use strict'; -const assert = require('assert'); +const assert = require('bsert'); const FullNode = require('../../lib/node/fullnode'); const Network = require('../../lib/protocol/network'); -const co = require('../../lib/utils/co'); -const Logger = require('../../lib/node/logger'); - -function NodeContext(network, size) { - if (!(this instanceof NodeContext)) - return new NodeContext(network, size); - - 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); +const Logger = require('blgr'); + +class NodeContext { + constructor(network, size) { + this.network = Network.get(network); + this.size = size || 4; + this.nodes = []; + + this.init(); } -}; -NodeContext.prototype.open = function open() { - const jobs = []; + 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, + memory: true, + 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); + } + } - for (const node of this.nodes) - jobs.push(node.open()); + open() { + const jobs = []; - return Promise.all(jobs); -}; + for (const node of this.nodes) + jobs.push(node.open()); -NodeContext.prototype.close = function close() { - const jobs = []; + return Promise.all(jobs); + } - for (const node of this.nodes) - jobs.push(node.close()); + close() { + const jobs = []; - return Promise.all(jobs); -}; + for (const node of this.nodes) + jobs.push(node.close()); -NodeContext.prototype.connect = async function connect() { - for (const node of this.nodes) { - await node.connect(); - await co.timeout(1000); + return Promise.all(jobs); } -}; -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 co.timeout(1000); + async connect() { + for (const node of this.nodes) { + await node.connect(); + await new Promise(r => setTimeout(r, 1000)); + } } -}; -NodeContext.prototype.startSync = function startSync() { - for (const node of this.nodes) { - node.chain.synced = true; - node.chain.emit('full'); - node.startSync(); + 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)); + } } -}; -NodeContext.prototype.stopSync = function stopSync() { - for (const node of this.nodes) - node.stopSync(); -}; + startSync() { + for (const node of this.nodes) { + node.chain.synced = true; + node.chain.emit('full'); + node.startSync(); + } + } -NodeContext.prototype.generate = async function generate(index, blocks) { - const node = this.nodes[index]; + stopSync() { + for (const node of this.nodes) + node.stopSync(); + } + + async generate(index, blocks) { + const node = this.nodes[index]; - assert(node); + assert(node); - for (let i = 0; i < blocks; i++) { - const block = await node.miner.mineBlock(); - await node.chain.add(block); + for (let i = 0; i < blocks; i++) { + const block = await node.miner.mineBlock(); + await node.chain.add(block); + } } -}; -NodeContext.prototype.height = function height(index) { - const node = this.nodes[index]; + height(index) { + const node = this.nodes[index]; - assert(node); + assert(node); - return node.chain.height; -}; + return node.chain.height; + } -NodeContext.prototype.sync = async function sync() { - await co.timeout(3000); -}; + async sync() { + return new Promise(r => setTimeout(r, 3000)); + } +} module.exports = NodeContext; diff --git a/test/util/reorg.js b/test/util/reorg.js new file mode 100644 index 000000000..94b558c84 --- /dev/null +++ b/test/util/reorg.js @@ -0,0 +1,63 @@ +'use strict'; + +const assert = require('bsert'); +const Chain = require('../../lib/blockchain/chain'); +const CPUMiner = require('../../lib/mining/cpuminer'); + +/** + * Reorgs the chain to given height using miners. + * @param {Chain} chain chain + * @param {CPUMiner} cpu cpuminer + * @param {Number} height height + * @returns {Promise} null + */ +async function reorg(chain, cpu, height) { + assert(chain instanceof Chain); + assert(cpu instanceof CPUMiner); + assert(typeof height === 'number'); + + let tip1, tip2 = null; + for (let i = 0; i < height; i++) { + const job1 = await cpu.createJob(tip1); + const job2 = await cpu.createJob(tip2); + + const blk1 = await job1.mineAsync(); + const blk2 = await job2.mineAsync(); + + const hash1 = blk1.hash(); + const hash2 = blk2.hash(); + + assert(await chain.add(blk1)); + assert(await chain.add(blk2)); + + assert.bufferEqual(chain.tip.hash, hash1); + + tip1 = await chain.getEntry(hash1); + tip2 = await chain.getEntry(hash2); + + assert(tip1); + assert(tip2); + + assert(!await chain.isMainChain(tip2)); + } + + const entry = await chain.getEntry(tip2.hash); + assert(entry); + assert.strictEqual(chain.height, entry.height); + + const block = await cpu.mineBlock(entry); + assert(block); + + let forked = false; + chain.once('reorganize', () => { + forked = true; + }); + + assert(await chain.add(block)); + + assert(forked); + assert.bufferEqual(chain.tip.hash, block.hash()); + assert(chain.tip.chainwork.gt(tip1.chainwork)); +} + +module.exports = reorg; diff --git a/test/utils-test.js b/test/utils-test.js index 8f964c3ed..e01bb4c95 100644 --- a/test/utils-test.js +++ b/test/utils-test.js @@ -3,13 +3,12 @@ 'use strict'; -const assert = require('./util/assert'); -const {U64, I64} = require('../lib/utils/int64'); -const base58 = require('../lib/utils/base58'); -const encoding = require('../lib/utils/encoding'); +const Validator = require('bval'); +const {base58} = require('bstring'); +const {encoding} = require('bufio'); +const assert = require('bsert'); const Amount = require('../lib/btc/amount'); -const Validator = require('../lib/utils/validator'); -const util = require('../lib/utils/util'); +const fixed = require('../lib/utils/fixed'); const base58Tests = [ ['', ''], @@ -32,40 +31,6 @@ const base58Tests = [ ['00000000000000000000', '1111111111'] ]; -const validBech32Tests = [ - 'BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4', - 'tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7', - 'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw50' - + '8d6qejxtdg4y5r3zarvary0c5xw7k7grplx', - 'BC1SW50QA3JX3S', - 'bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj', - 'tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy' -]; - -const unsigned = [ - new U64('ffeeffee', 16), - new U64('001fffeeffeeffee', 16), - new U64('eeffeeff', 16), - new U64('001feeffeeffeeff', 16), - new U64(0), - new U64(1) -]; - -const signed = [ - new I64('ffeeffee', 16), - new I64('001fffeeffeeffee', 16), - new I64('eeffeeff', 16), - new I64('001feeffeeffeeff', 16), - new I64(0), - new I64(1), - new I64('ffeeffee', 16).ineg(), - new I64('001fffeeffeeffee', 16).ineg(), - new I64('eeffeeff', 16).ineg(), - new I64('001feeffeeffeeff', 16).ineg(), - new I64(0).ineg(), - new I64(1).ineg() -]; - describe('Utils', function() { it('should encode/decode base58', () => { const buf = Buffer.from('000000deadbeef', 'hex'); @@ -122,10 +87,10 @@ describe('Utils', function() { assert.strictEqual(parseFloat('0.15645647') * 1e8, 15645646.999999998); assert.strictEqual(15645647 / 1e8, 0.15645647); - assert.strictEqual(util.fromFixed('0.15645647', 8), 15645647); - assert.strictEqual(util.toFixed(15645647, 8), '0.15645647'); - assert.strictEqual(util.fromFloat(0.15645647, 8), 15645647); - assert.strictEqual(util.toFloat(15645647, 8), 0.15645647); + assert.strictEqual(fixed.decode('0.15645647', 8), 15645647); + assert.strictEqual(fixed.encode(15645647, 8), '0.15645647'); + assert.strictEqual(fixed.fromFloat(0.15645647, 8), 15645647); + assert.strictEqual(fixed.toFloat(15645647, 8), 0.15645647); }); it('should write/read new varints', () => { @@ -141,111 +106,56 @@ describe('Utils', function() { let b = Buffer.alloc(1, 0xff); encoding.writeVarint2(b, 0, 0); assert.strictEqual(encoding.readVarint2(b, 0).value, 0); - assert.deepEqual(b, [0]); + assert.strictEqual(b.toString('hex'), '00'); b = Buffer.alloc(1, 0xff); encoding.writeVarint2(b, 1, 0); assert.strictEqual(encoding.readVarint2(b, 0).value, 1); - assert.deepEqual(b, [1]); + assert.strictEqual(b.toString('hex'), '01'); b = Buffer.alloc(1, 0xff); encoding.writeVarint2(b, 127, 0); assert.strictEqual(encoding.readVarint2(b, 0).value, 127); - assert.deepEqual(b, [0x7f]); + assert.strictEqual(b.toString('hex'), '7f'); b = Buffer.alloc(2, 0xff); encoding.writeVarint2(b, 128, 0); assert.strictEqual(encoding.readVarint2(b, 0).value, 128); - assert.deepEqual(b, [0x80, 0x00]); + assert.strictEqual(b.toString('hex'), '8000'); b = Buffer.alloc(2, 0xff); encoding.writeVarint2(b, 255, 0); assert.strictEqual(encoding.readVarint2(b, 0).value, 255); - assert.deepEqual(b, [0x80, 0x7f]); + assert.strictEqual(b.toString('hex'), '807f'); b = Buffer.alloc(2, 0xff); encoding.writeVarint2(b, 16383, 0); assert.strictEqual(encoding.readVarint2(b, 0).value, 16383); - assert.deepEqual(b, [0xfe, 0x7f]); + assert.strictEqual(b.toString('hex'), 'fe7f'); b = Buffer.alloc(2, 0xff); encoding.writeVarint2(b, 16384, 0); assert.strictEqual(encoding.readVarint2(b, 0).value, 16384); - assert.deepEqual(b, [0xff, 0x00]); + assert.strictEqual(b.toString('hex'), 'ff00'); b = Buffer.alloc(3, 0xff); encoding.writeVarint2(b, 16511, 0); assert.strictEqual(encoding.readVarint2(b, 0).value, 16511); - assert.deepEqual(b.slice(0, 2), [0xff, 0x7f]); - // assert.deepEqual(b, [0x80, 0xff, 0x7f]); + assert.strictEqual(b.slice(0, 2).toString('hex'), 'ff7f'); + // assert.strictEqual(b.toString('hex'), '80ff7f'); b = Buffer.alloc(3, 0xff); encoding.writeVarint2(b, 65535, 0); assert.strictEqual(encoding.readVarint2(b, 0).value, 65535); - assert.deepEqual(b, [0x82, 0xfe, 0x7f]); - // assert.deepEqual(b, [0x82, 0xfd, 0x7f]); + assert.strictEqual(b.toString('hex'), '82fe7f'); + // assert.strictEqual(b.toString('hex'), '82fd7f'); b = Buffer.alloc(5, 0xff); encoding.writeVarint2(b, Math.pow(2, 32), 0); assert.strictEqual(encoding.readVarint2(b, 0).value, Math.pow(2, 32)); - assert.deepEqual(b, [0x8e, 0xfe, 0xfe, 0xff, 0x00]); + assert.strictEqual(b.toString('hex'), '8efefeff00'); }); - for (const num of unsigned) { - const bits = num.bitLength(); - - it(`should write+read a ${bits} bit unsigned int`, () => { - const buf1 = Buffer.allocUnsafe(8); - const buf2 = Buffer.allocUnsafe(8); - - encoding.writeU64N(buf1, num, 0); - encoding.writeU64(buf2, num.toNumber(), 0); - assert.bufferEqual(buf1, buf2); - - const n1 = encoding.readU64N(buf1, 0); - const n2 = encoding.readU64(buf2, 0); - - assert.strictEqual(n1.toNumber(), n2); - }); - } - - for (const num of signed) { - const bits = num.bitLength(); - const sign = num.isNeg() ? 'negative' : 'positive'; - - it(`should write+read a ${bits} bit ${sign} int`, () => { - const buf1 = Buffer.allocUnsafe(8); - const buf2 = Buffer.allocUnsafe(8); - - encoding.writeI64N(buf1, num, 0); - encoding.writeI64(buf2, num.toNumber(), 0); - assert.bufferEqual(buf1, buf2); - - const n1 = encoding.readI64N(buf1, 0); - const n2 = encoding.readI64(buf2, 0); - - assert.strictEqual(n1.toNumber(), n2); - }); - - it(`should write+read a ${bits} bit ${sign} int as unsigned`, () => { - const buf1 = Buffer.allocUnsafe(8); - const buf2 = Buffer.allocUnsafe(8); - - encoding.writeU64N(buf1, num.toU64(), 0); - encoding.writeU64(buf2, num.toNumber(), 0); - assert.bufferEqual(buf1, buf2); - - const n1 = encoding.readU64N(buf1, 0); - - if (num.isNeg()) { - assert.throws(() => encoding.readU64(buf2, 0)); - } else { - const n2 = encoding.readU64(buf2, 0); - assert.strictEqual(n1.toNumber(), n2); - } - }); - } - it('should validate integers 0 and 1 as booleans', () => { const validator = new Validator({ shouldBeTrue: 1, @@ -254,10 +164,4 @@ describe('Utils', function() { assert.strictEqual(validator.bool('shouldBeTrue'), true); assert.strictEqual(validator.bool('shouldBeFalse'), false); }); - - it('should validate bech32 addresses based only on string data', () => { - for (const bech32addr of validBech32Tests) { - assert.strictEqual(util.isBech32(bech32addr), true); - } - }); }); diff --git a/test/wallet-rpc-test.js b/test/wallet-rpc-test.js new file mode 100644 index 000000000..f6ee7fdbc --- /dev/null +++ b/test/wallet-rpc-test.js @@ -0,0 +1,480 @@ +/* eslint-env mocha */ + +'use strict'; + +const {NodeClient, WalletClient} = require('@oipwg/fclient'); +const assert = require('bsert'); +const consensus = require('../lib/protocol/consensus'); +const FullNode = require('../lib/node/fullnode'); +const Network = require('../lib/protocol/network'); +const Mnemonic = require('../lib/hd/mnemonic'); +const HDPrivateKey = require('../lib/hd/private'); +const Script = require('../lib/script/script'); +const Address = require('../lib/primitives/address'); +const network = Network.get('regtest'); +const mnemonics = require('./data/mnemonic-english.json'); +const {forValue} = require('./util/common'); +// Commonly used test mnemonic +const phrase = mnemonics[0][1]; + +const ports = { + p2p: 49331, + node: 49332, + wallet: 49333 +}; + +const node = new FullNode({ + network: network.type, + apiKey: 'bar', + walletAuth: true, + memory: true, + workers: true, + workersSize: 2, + plugins: [require('../lib/wallet/plugin')], + port: ports.p2p, + httpPort: ports.node, + env: { + 'BCOIN_WALLET_HTTP_PORT': ports.wallet.toString() + } +}); + +const {wdb} = node.require('walletdb'); + +const nclient = new NodeClient({ + port: ports.node, + apiKey: 'bar', + timeout: 15000 +}); + +const wclient = new WalletClient({ + port: ports.wallet, + apiKey: 'bar' +}); + +describe('Wallet RPC Methods', function() { + this.timeout(15000); + + // Define an account level hd extended public key to be + // used to derive addresses throughout the test suite + let xpub; + + let walletHot = null; + let walletMiner = null; + let addressHot = null; + let addressMiner = null; + let utxo = null; + + before(async () => { + await node.open(); + + // Derive the xpub using the well known + // mnemonic and network's coin type + const mnemonic = Mnemonic.fromPhrase(phrase); + const priv = HDPrivateKey.fromMnemonic(mnemonic); + const type = network.keyPrefix.coinType; + const key = priv.derive(44, true).derive(type, true).derive(0, true); + + xpub = key.toPublic(); + + // Assert that the expected test phrase was + // read from disk + assert.equal(phrase, [ + 'abandon', 'abandon', 'abandon', 'abandon', + 'abandon', 'abandon', 'abandon', 'abandon', + 'abandon', 'abandon', 'abandon', 'about' + ].join(' ')); + + // Create wallets. + { + const walletInfo = await wclient.createWallet('hot'); + walletHot = wclient.wallet('hot', walletInfo.token); + + const account = await walletHot.getAccount('default'); + addressHot = account.receiveAddress; + } + + { + const walletInfo = await wclient.createWallet('miner'); + walletMiner = wclient.wallet('miner', walletInfo.token); + + const account = await walletMiner.getAccount('default'); + addressMiner = account.receiveAddress; + } + + await nclient.execute('generatetoaddress', [102, addressMiner]); + await forValue(wdb, 'height', 102); + }); + + after(async () => { + await node.close(); + }); + + it('should rpc help', async () => { + assert(await wclient.execute('help', [])); + + await assert.rejects(async () => { + await wclient.execute('help', ['getbalance']); + }, { + name: 'Error', + message: /^getbalance/ + }); + }); + + it('should rpc selectwallet', async () => { + for (const wname of ['hot', 'miner']) { + const response = await wclient.execute('selectwallet', [wname]); + assert.strictEqual(response, null); + + const info = await wclient.execute('getwalletinfo'); + assert.strictEqual(info.walletid, wname); + } + }); + + it('should rpc getnewaddress from default account', async () => { + const acctAddr = await wclient.execute('getnewaddress', []); + assert(Address.fromString(acctAddr.toString())); + }); + + it('should rpc sendtoaddress', async () => { + await wclient.execute('selectwallet', ['miner']); + + const txid = await wclient.execute('sendtoaddress', [addressHot, 0.1234]); + assert.strictEqual(txid.length, 64); + }); + + it('should rpc getaccountaddress', async () => { + addressMiner = await wclient.execute('getaccountaddress', ['default']); + assert(Address.fromString(addressMiner.toString())); + }); + + it('should fail rpc getnewaddress from nonexistent account', async () => { + await assert.rejects(async () => { + await wclient.execute('getnewaddress', ['bad-account-name']); + }, { + name: 'Error', + message: 'Account not found.' + }); + }); + + it('should rpc sendmany', async () => { + const sendTo = {}; + sendTo[addressHot] = 1.0; + sendTo[addressMiner] = 0.1111; + const txid = await wclient.execute('sendmany', ['default', sendTo]); + assert.strictEqual(txid.length, 64); + }); + + it('should fail malformed rpc sendmany', async () => { + await assert.rejects(async () => { + await wclient.execute('sendmany', ['default', null]); + }, { + name: 'Error', + message: 'Invalid send-to address.' + }); + + const sendTo = {}; + sendTo[addressHot] = null; + await assert.rejects(async () => { + await wclient.execute('sendmany', ['default', sendTo]); + }, { + name: 'Error', + message: 'Invalid amount.' + }); + }); + + it('should rpc listreceivedbyaddress', async () => { + await wclient.execute('selectwallet', ['hot']); + + const listZeroConf = await wclient.execute('listreceivedbyaddress', + [0, false, false]); + assert.deepStrictEqual(listZeroConf, [{ + 'involvesWatchonly': false, + 'address': addressHot, + 'account': 'default', + 'amount': 1.1234, + 'confirmations': 0, + 'label': '' + }]); + + await nclient.execute('generatetoaddress', [1, addressMiner]); + await wdb.syncChain(); + + const listSomeConf = await wclient.execute('listreceivedbyaddress', + [1, false, false]); + assert.deepStrictEqual(listSomeConf, [{ + 'involvesWatchonly': false, + 'address': addressHot, + 'account': 'default', + 'amount': 1.1234, + 'confirmations': 1, + 'label': '' + }]); + + const listTooManyConf = await wclient.execute('listreceivedbyaddress', + [100, false, false]); + assert.deepStrictEqual(listTooManyConf, []); + }); + + it('should rpc listtransactions with no args', async () => { + const txs = await wclient.execute('listtransactions', []); + assert.strictEqual(txs.length, 2); + assert.strictEqual(txs[0].amount + txs[1].amount, 1.1234); + assert.strictEqual(txs[0].account, 'default'); + }); + + it('should rpc listtransactions from specified account', async () => { + const wallet = await wclient.wallet('hot'); + await wallet.createAccount('foo'); + + const txs = await wclient.execute('listtransactions', ['foo']); + assert.strictEqual(txs.length, 0); + }); + + it('should fail rpc listtransactions from nonexistent account', async () => { + assert.rejects(async () => { + await wclient.execute('listtransactions', ['nonexistent']); + }, { + name: 'Error', + message: 'Account not found.' + }); + }); + + it('should rpc listunspent', async () => { + utxo = await wclient.execute('listunspent', []); + assert.strictEqual(utxo.length, 2); + }); + + it('should rpc lockunspent and listlockunspent', async () => { + let result = await wclient.execute('listlockunspent', []); + assert.deepStrictEqual(result, []); + + // lock one utxo + const output = utxo[0]; + const outputsToLock = [{'txid': output.txid, 'vout': output.vout}]; + result = await wclient.execute('lockunspent', [false, outputsToLock]); + assert(result); + + result = await wclient.execute('listlockunspent', []); + assert.deepStrictEqual(result, outputsToLock); + + // unlock all + result = await wclient.execute('lockunspent', [true]); + assert(result); + + result = await wclient.execute('listlockunspent', []); + assert.deepStrictEqual(result, []); + }); + + it('should rpc listsinceblock', async () => { + const listNoBlock = await wclient.execute('listsinceblock', []); + assert.strictEqual(listNoBlock.transactions.length, 2); + + const txs = listNoBlock.transactions; + + // Sort transactions by blockheight + txs.sort((a, b) => a.blockheight - b.blockheight); + + // get lowest block hash. + const bhash = txs[0].blockhash; + const listOldBlock = await wclient.execute('listsinceblock', [bhash]); + assert.strictEqual(listOldBlock.transactions.length, 2); + + const nonexistentBlock = consensus.ZERO_HASH.toString('hex'); + await assert.rejects(async () => { + await wclient.execute('listsinceblock', [nonexistentBlock]); + }, { + name: 'Error', + message: 'Block not found.' + }); + }); + + describe('getaddressinfo', () => { + const watchOnlyWalletId = 'getaddressinfo-foo'; + const standardWalletId = 'getaddressinfo-bar'; + + // m/44'/1'/0'/0/{0,1} + const pubkeys = [ + Buffer.from('02a7451395735369f2ecdfc829c0f' + + '774e88ef1303dfe5b2f04dbaab30a535dfdd6', 'hex'), + Buffer.from('03589ae7c835ce76e23cf8feb32f1a' + + 'df4a7f2ba0ed2ad70801802b0bcd70e99c1c', 'hex') + ]; + + // set up the initial testing state + before(async () => { + { + // Create a watch only wallet using the path + // m/44'/1'/0' and assert that the wallet + // was properly created + const accountKey = xpub.xpubkey(network.type); + const response = await wclient.createWallet(watchOnlyWalletId, { + watchOnly: true, + accountKey: accountKey + }); + + assert.equal(response.id, watchOnlyWalletId); + + const wallet = wclient.wallet(watchOnlyWalletId); + const info = await wallet.getAccount('default'); + assert.equal(info.accountKey, accountKey); + assert.equal(info.watchOnly, true); + } + + { + // Create a wallet that manages the private keys itself + const response = await wclient.createWallet(standardWalletId); + assert.equal(response.id, standardWalletId); + + const info = await wclient.getAccount(standardWalletId, 'default'); + assert.equal(info.watchOnly, false); + }; + }); + + // The rpc interface requires the wallet to be selected first + it('should return iswatchonly correctly', async () => { + // m/44'/1'/0'/0/0 + const receive = 'mkpZhYtJu2r87Js3pDiWJDmPte2NRZ8bJV'; + + { + await wclient.execute('selectwallet', [standardWalletId]); + const response = await wclient.execute('getaddressinfo', [receive]); + assert.equal(response.iswatchonly, false); + } + { + await wclient.execute('selectwallet', [watchOnlyWalletId]); + const response = await wclient.execute('getaddressinfo', [receive]); + assert.equal(response.iswatchonly, true); + } + }); + + it('should return the correct address', async () => { + // m/44'/1'/0'/0/0 + const receive = 'mkpZhYtJu2r87Js3pDiWJDmPte2NRZ8bJV'; + + await wclient.execute('selectwallet', [watchOnlyWalletId]); + const response = await wclient.execute('getaddressinfo', [receive]); + assert.equal(response.address, receive); + }); + + it('should detect owned address', async () => { + // m/44'/1'/0'/0/0 + const receive = 'mkpZhYtJu2r87Js3pDiWJDmPte2NRZ8bJV'; + { + await wclient.execute('selectwallet', [watchOnlyWalletId]); + const response = await wclient.execute('getaddressinfo', [receive]); + assert.equal(response.ismine, true); + } + { + await wclient.execute('selectwallet', [standardWalletId]); + const response = await wclient.execute('getaddressinfo', [receive]); + assert.equal(response.ismine, false); + } + }); + + it('should detect a p2sh address', async () => { + const script = Script.fromMultisig(2, 2, pubkeys); + const address = Address.fromScript(script); + const addr = address.toString(network); + const response = await wclient.execute('getaddressinfo', [addr]); + + assert.equal(response.isscript, true); + assert.equal(response.iswitness, false); + assert.equal(response.witness_program, undefined); + }); + + it('should return the correct program for a p2wpkh address', async () => { + // m/44'/1'/0'/0/5 + const receive = 'bcrt1q53724q6cywuzsvq5e3nvdeuwrepu69jsc6ulmx'; + const addr = Address.fromString(receive); + + await wclient.execute('selectwallet', [watchOnlyWalletId]); + const str = addr.toString(network); + const response = await wclient.execute('getaddressinfo', [str]); + assert.equal(response.witness_program, addr.hash.toString('hex')); + }); + + it('should detect p2wsh program', async () => { + const script = Script.fromMultisig(2, 2, pubkeys); + const address = Address.fromWitnessScripthash(script.sha256()); + const addr = address.toString(network); + const response = await wclient.execute('getaddressinfo', [addr]); + + assert.equal(response.isscript, true); + assert.equal(response.iswitness, true); + assert.equal(response.witness_program, address.hash.toString('hex')); + }); + + it('should detect ismine up to the lookahead', async () => { + const info = await wclient.getAccount(watchOnlyWalletId, 'default'); + await wclient.execute('selectwallet', [watchOnlyWalletId]); + + // m/44'/1'/0' + const addresses = [ + 'mkpZhYtJu2r87Js3pDiWJDmPte2NRZ8bJV', // /0/0 + 'mzpbWabUQm1w8ijuJnAof5eiSTep27deVH', // /0/1 + 'mnTkxhNkgx7TsZrEdRcPti564yQTzynGJp', // /0/2 + 'mpW3iVi2Td1vqDK8Nfie29ddZXf9spmZkX', // /0/3 + 'n2BMo5arHDyAK2CM8c56eoEd18uEkKnRLC', // /0/4 + 'mvWgTTtQqZohUPnykucneWNXzM5PLj83an', // /0/5 + 'muTU2Av1EwnsyhieQhyPL7hgEf883LR4xg', // /0/6 + 'mwduZ8Ksa563v7rWdSPmqyKR4y2FeB5g8p', // /0/7 + 'miyBE85ro5zt9RseSzYVEbB3TfzkxgSm8C', // /0/8 + 'mnYwW7mU3jajB11vrpDZwZDrXwVfE5Jc31', // /0/9 + 'mx3YNRT8Vg8QwFq5Z5MAVDDVHp4ihHsffn' // /0/10 + ]; + + // Assert that the lookahead is configured as expected + // subtract one from addresses.length, it is 0 indexed + assert.equal(addresses.length - 1, info.lookahead); + + // Each address through the lookahead number should + // be recognized as an owned address + for (let i = 0; i <= info.lookahead; i++) { + const address = addresses[i]; + const response = await wclient.execute('getaddressinfo', [address]); + assert.equal(response.ismine, true); + } + + // m/44'/1'/0'/0/11 + // This address is outside of the lookahead range + const failed = 'myHL2QuECVYkx9Y94gyC6RSweLNnteETsB'; + + const response = await wclient.execute('getaddressinfo', [failed]); + assert.equal(response.ismine, false); + }); + + it('should detect change addresses', async () => { + // m/44'/1'/0'/1/0 + const address = 'mi8nhzZgGZQthq6DQHbru9crMDerUdTKva'; + const info = await wclient.execute('getaddressinfo', [address]); + + assert.equal(info.ischange, true); + }); + + it('should throw for the wrong network', async () => { + // m/44'/1'/0'/0/0 + const failed = '16JcQVoL61QsLCPS6ek8UJZ52eRfaFqLJt'; + + // Match the bitcoind response when sending the incorrect + // network. Expect an RPC error + const fn = async () => await wclient.execute('getaddressinfo', [failed]); + await assert.rejects(fn, { + name: 'Error', + message: 'Invalid address.' + }); + }); + + it('should fail for invalid address', async () => { + // m/44'/1'/0'/0/0 + let failed = '16JcQVoL61QsLCPS6ek8UJZ52eRfaFqLJt'; + // remove the first character + failed = failed.slice(1, failed.length); + + const fn = async () => await wclient.execute('getaddressinfo', [failed]); + await assert.rejects(fn, { + name: 'Error', + message: 'Invalid address.' + }); + }); + }); +}); diff --git a/test/wallet-test.js b/test/wallet-test.js index a59d56d4b..93c598af0 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -3,12 +3,11 @@ 'use strict'; -const assert = require('./util/assert'); +const assert = require('bsert'); const consensus = require('../lib/protocol/consensus'); const util = require('../lib/utils/util'); -const encoding = require('../lib/utils/encoding'); -const digest = require('../lib/crypto/digest'); -const random = require('../lib/crypto/random'); +const hash256 = require('bcrypto/lib/hash256'); +const random = require('bcrypto/lib/random'); const WalletDB = require('../lib/wallet/walletdb'); const WorkerPool = require('../lib/workers/workerpool'); const Address = require('../lib/primitives/address'); @@ -19,6 +18,8 @@ const Input = require('../lib/primitives/input'); const Outpoint = require('../lib/primitives/outpoint'); const Script = require('../lib/script/script'); const HD = require('../lib/hd'); +const Wallet = require('../lib/wallet/wallet'); +const nodejsUtil = require('util'); const KEY1 = 'xprv9s21ZrQH143K3Aj6xQBymM31Zb4BVc7wxqfUhMZrzewdDVCt' + 'qUP9iWfcHgJofs25xbaUpCps9GDXj83NiWvQCAkWQhVj5J4CorfnpKX94AZ'; @@ -26,15 +27,16 @@ const KEY1 = 'xprv9s21ZrQH143K3Aj6xQBymM31Zb4BVc7wxqfUhMZrzewdDVCt' const KEY2 = 'xprv9s21ZrQH143K3mqiSThzPtWAabQ22Pjp3uSNnZ53A5bQ4udp' + 'faKekc2m4AChLYH1XDzANhrSdxHYWUeTWjYJwFwWFyHkTMnMeAcW4JyRCZa'; +// abandon abandon... about key at m'/44'/0'/0' +const PUBKEY = 'xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhaw' + + 'A7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj'; + const workers = new WorkerPool({ - enabled: true + enabled: true, + size: 2 }); -const wdb = new WalletDB({ - db: 'memory', - verify: true, - workers -}); +const wdb = new WalletDB({ workers }); let currentWallet = null; let importedWallet = null; @@ -42,55 +44,61 @@ let importedKey = null; let doubleSpendWallet = null; let doubleSpendCoin = null; -let globalTime = util.now(); -let globalHeight = 1; +function fromU32(num) { + const data = Buffer.allocUnsafe(4); + data.writeUInt32LE(num, 0, true); + return data; +} -function nextBlock() { - const height = globalHeight++; - const time = globalTime++; +function curBlock(wdb) { + return fakeBlock(wdb.state.height); +}; - const prevHead = encoding.U32(height - 1); - const prevHash = digest.hash256(prevHead); +function nextBlock(wdb) { + return fakeBlock(wdb.state.height + 1); +} - const head = encoding.U32(height); - const hash = digest.hash256(head); +function fakeBlock(height) { + const prev = hash256.digest(fromU32((height - 1) >>> 0)); + const hash = hash256.digest(fromU32(height >>> 0)); + const root = hash256.digest(fromU32((height | 0x80000000) >>> 0)); return { - hash: hash.toString('hex'), - height: height, - prevBlock: prevHash.toString('hex'), - time: time, - merkleRoot: encoding.NULL_HASH, + hash: hash, + prevBlock: prev, + merkleRoot: root, + time: 500000000 + (height * (10 * 60)), + bits: 0, nonce: 0, - bits: 0 + height: height }; } function dummyInput() { - const hash = random.randomBytes(32).toString('hex'); + const hash = random.randomBytes(32); return Input.fromOutpoint(new Outpoint(hash, 0)); } async function testP2PKH(witness, nesting) { const flags = Script.flags.STANDARD_VERIFY_FLAGS; + const receiveAddress = nesting ? 'nestedAddress' : 'receiveAddress'; + const type = witness ? Address.types.WITNESS : Address.types.PUBKEYHASH; + const wallet = await wdb.create({ witness }); - const wallet = await wdb.create({ - witness - }); - - const addr = Address.fromString(wallet.getAddress('string')); + const waddr = await wallet.receiveAddress(); + const addr = Address.fromString(waddr.toString(wdb.network), wdb.network); - const type = witness ? Address.types.WITNESS : Address.types.PUBKEYHASH; assert.strictEqual(addr.type, type); + assert.strictEqual(addr.type, waddr.type); const src = new MTX(); src.addInput(dummyInput()); - src.addOutput(nesting ? wallet.getNested() : wallet.getAddress(), 5460 * 2); + src.addOutput(await wallet[receiveAddress](), 5460 * 2); src.addOutput(new Address(), 2 * 5460); const mtx = new MTX(); mtx.addTX(src, 0); - mtx.addOutput(wallet.getAddress(), 5460); + mtx.addOutput(await wallet.receiveAddress(), 5460); await wallet.sign(mtx); @@ -101,7 +109,7 @@ async function testP2PKH(witness, nesting) { async function testP2SH(witness, nesting) { const flags = Script.flags.STANDARD_VERIFY_FLAGS; - const receive = nesting ? 'nested' : 'receive'; + const receiveAddress = nesting ? 'nestedAddress' : 'receiveAddress'; const receiveDepth = nesting ? 'nestedDepth' : 'receiveDepth'; const vector = witness ? 'witness' : 'script'; @@ -118,17 +126,17 @@ async function testP2SH(witness, nesting) { const carol = await wdb.create(options); const recipient = await wdb.create(); - await alice.addSharedKey(bob.account.accountKey); - await alice.addSharedKey(carol.account.accountKey); + await alice.addSharedKey(0, await bob.accountKey(0)); + await alice.addSharedKey(0, await carol.accountKey(0)); - await bob.addSharedKey(alice.account.accountKey); - await bob.addSharedKey(carol.account.accountKey); + await bob.addSharedKey(0, await alice.accountKey(0)); + await bob.addSharedKey(0, await carol.accountKey(0)); - await carol.addSharedKey(alice.account.accountKey); - await carol.addSharedKey(bob.account.accountKey); + await carol.addSharedKey(0, await alice.accountKey(0)); + await carol.addSharedKey(0, await bob.accountKey(0)); // Our p2sh address - const addr1 = alice.account[receive].getAddress(); + const addr1 = await alice[receiveAddress](); if (witness) { const type = nesting ? Address.types.SCRIPTHASH : Address.types.WITNESS; @@ -137,17 +145,17 @@ async function testP2SH(witness, nesting) { assert.strictEqual(addr1.type, Address.types.SCRIPTHASH); } - assert(alice.account[receive].getAddress().equals(addr1)); - assert(bob.account[receive].getAddress().equals(addr1)); - assert(carol.account[receive].getAddress().equals(addr1)); + assert((await alice[receiveAddress]()).equals(addr1)); + assert((await bob[receiveAddress]()).equals(addr1)); + assert((await carol[receiveAddress]()).equals(addr1)); - const nestedAddr1 = alice.getNested(); + const nestedAddr1 = await alice.nestedAddress(); if (witness) { assert(nestedAddr1); - assert(alice.getNested().equals(nestedAddr1)); - assert(bob.getNested().equals(nestedAddr1)); - assert(carol.getNested().equals(nestedAddr1)); + assert((await alice.nestedAddress()).equals(nestedAddr1)); + assert((await bob.nestedAddress()).equals(nestedAddr1)); + assert((await carol.nestedAddress()).equals(nestedAddr1)); } { @@ -157,27 +165,25 @@ async function testP2SH(witness, nesting) { fund.addOutput(nesting ? nestedAddr1 : addr1, 5460 * 10); // Simulate a confirmation - const block = nextBlock(); - - assert.strictEqual(alice.account[receiveDepth], 1); + assert.strictEqual(await alice[receiveDepth](), 1); - await wdb.addBlock(block, [fund.toTX()]); + await wdb.addBlock(nextBlock(wdb), [fund.toTX()]); - assert.strictEqual(alice.account[receiveDepth], 2); - assert.strictEqual(alice.account.changeDepth, 1); + assert.strictEqual(await alice[receiveDepth](), 2); + assert.strictEqual(await alice.changeDepth(), 1); } - const addr2 = alice.account[receive].getAddress(); + const addr2 = await alice[receiveAddress](); assert(!addr2.equals(addr1)); - assert(alice.account[receive].getAddress().equals(addr2)); - assert(bob.account[receive].getAddress().equals(addr2)); - assert(carol.account[receive].getAddress().equals(addr2)); + assert((await alice[receiveAddress]()).equals(addr2)); + assert((await bob[receiveAddress]()).equals(addr2)); + assert((await carol[receiveAddress]()).equals(addr2)); // Create a tx requiring 2 signatures const send = new MTX(); - send.addOutput(recipient.getAddress(), 5460); + send.addOutput(await recipient.receiveAddress(), 5460); assert(!send.verify(flags)); @@ -195,35 +201,33 @@ async function testP2SH(witness, nesting) { const [tx, view] = send.commit(); assert(tx.verify(view, flags)); - assert.strictEqual(alice.account.changeDepth, 1); + assert.strictEqual(await alice.changeDepth(), 1); - const change = alice.account.change.getAddress(); + const change = await alice.changeAddress(); - assert(alice.account.change.getAddress().equals(change)); - assert(bob.account.change.getAddress().equals(change)); - assert(carol.account.change.getAddress().equals(change)); + assert((await alice.changeAddress()).equals(change)); + assert((await bob.changeAddress()).equals(change)); + assert((await carol.changeAddress()).equals(change)); // Simulate a confirmation { - const block = nextBlock(); + await wdb.addBlock(nextBlock(wdb), [tx]); - await wdb.addBlock(block, [tx]); + assert.strictEqual(await alice[receiveDepth](), 2); + assert.strictEqual(await alice.changeDepth(), 2); - assert.strictEqual(alice.account[receiveDepth], 2); - assert.strictEqual(alice.account.changeDepth, 2); - - assert(alice.account[receive].getAddress().equals(addr2)); - assert(!alice.account.change.getAddress().equals(change)); + assert((await alice[receiveAddress]()).equals(addr2)); + assert(!(await alice.changeAddress()).equals(change)); } - const change2 = alice.account.change.getAddress(); + const change2 = await alice.changeAddress(); - assert(alice.account.change.getAddress().equals(change2)); - assert(bob.account.change.getAddress().equals(change2)); - assert(carol.account.change.getAddress().equals(change2)); + assert((await alice.changeAddress()).equals(change2)); + assert((await bob.changeAddress()).equals(change2)); + assert((await carol.changeAddress()).equals(change2)); const input = tx.inputs[0]; - input[vector].setData(2, encoding.ZERO_SIG); + input[vector].setData(2, Buffer.alloc(73, 0x00)); input[vector].compile(); assert(!tx.verify(view, flags)); @@ -231,46 +235,48 @@ async function testP2SH(witness, nesting) { } describe('Wallet', function() { - this.timeout(5000); + this.timeout(process.browser ? 20000 : 5000); - it('should open walletdb', async () => { - consensus.COINBASE_MATURITY = 0; + before(async () => { await wdb.open(); + await workers.open(); + }); + + after(async () => { + await wdb.close(); + await workers.close(); + }); + + it('should open walletdb', () => { + consensus.COINBASE_MATURITY = 0; }); it('should generate new key and address', async () => { const wallet = await wdb.create(); - const addr1 = wallet.getAddress(); + const addr1 = await wallet.receiveAddress(); assert(addr1); - const str = addr1.toString(); - const addr2 = Address.fromString(str); + const str = addr1.toString(wdb.network); + const addr2 = Address.fromString(str, wdb.network); assert(addr2.equals(addr1)); }); it('should validate existing address', () => { - assert(Address.fromString('LdcyCZgmbwibypUeD6zAhdPEVM3mPj4zxH')); + assert(Address.fromString('LdcyCZgmbwibypUeD6zAhdPEVM3mPj4zxH', 'main')); }); it('should fail to validate invalid address', () => { assert.throws(() => { - Address.fromString('LdcyCZgmbwibypueD6zAhdPEVM3mPj4zxH'); + Address.fromString('LdcyCZgmbwibypueD6zAhdPEVM3mPj4zxH', 'main'); }); }); it('should create and get wallet', async () => { const wallet1 = await wdb.create(); - - await wallet1.destroy(); - const wallet2 = await wdb.get(wallet1.id); - - assert(wallet1 !== wallet2); - assert(wallet1.master !== wallet2.master); - assert(wallet1.master.key.equals(wallet2.master.key)); - assert(wallet1.account.accountKey.equals(wallet2.account.accountKey)); + assert(wallet1 === wallet2); }); it('should sign/verify p2pkh tx', async () => { @@ -293,12 +299,12 @@ describe('Wallet', function() { }); const xpriv = HD.PrivateKey.generate(); - const key = xpriv.deriveAccount(44, 0).toPublic(); + const key = xpriv.deriveAccount(44, 0, 0).toPublic(); - await wallet.addSharedKey(key); + await wallet.addSharedKey(0, key); const script = Script.fromMultisig(1, 2, [ - wallet.account.receive.getPublicKey(), + (await wallet.receiveKey()).publicKey, key.derivePath('m/0/0').publicKey ]); @@ -310,7 +316,7 @@ describe('Wallet', function() { const tx = new MTX(); tx.addTX(src, 0); - tx.addOutput(wallet.getAddress(), 5460); + tx.addOutput(await wallet.receiveAddress(), 5460); const maxSize = await tx.estimateSize(); @@ -320,7 +326,7 @@ describe('Wallet', function() { assert(tx.verify()); }); - it('should handle missed and invalid txs', async () => { + it('should handle missed txs', async () => { const alice = await wdb.create(); const bob = await wdb.create(); @@ -328,13 +334,13 @@ describe('Wallet', function() { // balance: 51000 const t1 = new MTX(); t1.addInput(dummyInput()); - t1.addOutput(alice.getAddress(), 50000); - t1.addOutput(alice.getAddress(), 1000); + t1.addOutput(await alice.receiveAddress(), 50000); + t1.addOutput(await alice.receiveAddress(), 1000); const t2 = new MTX(); t2.addTX(t1, 0); // 50000 - t2.addOutput(alice.getAddress(), 24000); - t2.addOutput(alice.getAddress(), 24000); + t2.addOutput(await alice.receiveAddress(), 24000); + t2.addOutput(await alice.receiveAddress(), 24000); // Save for later. doubleSpendWallet = alice; @@ -346,7 +352,7 @@ describe('Wallet', function() { const t3 = new MTX(); t3.addTX(t1, 1); // 1000 t3.addTX(t2, 0); // 24000 - t3.addOutput(alice.getAddress(), 23000); + t3.addOutput(await alice.receiveAddress(), 23000); // balance: 47000 await alice.sign(t3); @@ -354,80 +360,97 @@ describe('Wallet', function() { const t4 = new MTX(); t4.addTX(t2, 1); // 24000 t4.addTX(t3, 0); // 23000 - t4.addOutput(alice.getAddress(), 11000); - t4.addOutput(alice.getAddress(), 11000); + t4.addOutput(await alice.receiveAddress(), 11000); + t4.addOutput(await alice.receiveAddress(), 11000); // balance: 22000 await alice.sign(t4); const f1 = new MTX(); f1.addTX(t4, 1); // 11000 - f1.addOutput(bob.getAddress(), 10000); + f1.addOutput(await bob.receiveAddress(), 10000); // balance: 11000 await alice.sign(f1); - const fake = new MTX(); - fake.addTX(t1, 1); // 1000 (already redeemed) - fake.addOutput(alice.getAddress(), 500); - - // Script inputs but do not sign - await alice.template(fake); - // Fake signature - const input = fake.inputs[0]; - input.script.setData(0, encoding.ZERO_SIG); - input.script.compile(); - // balance: 11000 - - // Fake TX should temporarily change output. { - await wdb.addTX(fake.toTX()); await wdb.addTX(t4.toTX()); const balance = await alice.getBalance(); - assert.strictEqual(balance.unconfirmed, 22500); + assert.strictEqual(balance.unconfirmed, 22000); } { await wdb.addTX(t1.toTX()); const balance = await alice.getBalance(); - assert.strictEqual(balance.unconfirmed, 72500); + assert.strictEqual(balance.unconfirmed, 73000); } { await wdb.addTX(t2.toTX()); const balance = await alice.getBalance(); - assert.strictEqual(balance.unconfirmed, 46500); + assert.strictEqual(balance.unconfirmed, 71000); } { await wdb.addTX(t3.toTX()); const balance = await alice.getBalance(); - assert.strictEqual(balance.unconfirmed, 22000); + assert.strictEqual(balance.unconfirmed, 69000); } { await wdb.addTX(f1.toTX()); + const balance = await alice.getBalance(); + assert.strictEqual(balance.unconfirmed, 58000); + + const txs = await alice.getHistory(); + assert(txs.some((wtx) => { + return wtx.hash.equals(f1.hash()); + })); + } + + { + const balance = await bob.getBalance(); + assert.strictEqual(balance.unconfirmed, 10000); + + const txs = await bob.getHistory(); + assert(txs.some((wtx) => { + return wtx.tx.hash().equals(f1.hash()); + })); + } + + // Should recover from missed txs on block. + await wdb.addBlock(nextBlock(wdb), [ + t1.toTX(), + t2.toTX(), + t3.toTX(), + t4.toTX(), + f1.toTX() + ]); + + { const balance = await alice.getBalance(); assert.strictEqual(balance.unconfirmed, 11000); + assert.strictEqual(balance.confirmed, 11000); const txs = await alice.getHistory(); assert(txs.some((wtx) => { - return wtx.hash === f1.hash('hex'); + return wtx.hash.equals(f1.hash()); })); } { const balance = await bob.getBalance(); assert.strictEqual(balance.unconfirmed, 10000); + assert.strictEqual(balance.confirmed, 10000); const txs = await bob.getHistory(); assert(txs.some((wtx) => { - return wtx.tx.hash('hex') === f1.hash('hex'); + return wtx.tx.hash().equals(f1.hash()); })); } }); @@ -435,6 +458,9 @@ describe('Wallet', function() { it('should cleanup spenders after double-spend', async () => { const wallet = doubleSpendWallet; + // Reorg and unconfirm all previous txs. + await wdb.removeBlock(curBlock(wdb)); + { const txs = await wallet.getHistory(); assert.strictEqual(txs.length, 5); @@ -448,12 +474,13 @@ describe('Wallet', function() { { const balance = await wallet.getBalance(); assert.strictEqual(balance.unconfirmed, 11000); + assert.strictEqual(balance.confirmed, 0); } { const tx = new MTX(); tx.addCoin(doubleSpendCoin); - tx.addOutput(wallet.getAddress(), 5000); + tx.addOutput(await wallet.receiveAddress(), 5000); await wallet.sign(tx); @@ -474,30 +501,75 @@ describe('Wallet', function() { } }); - it('should handle missed txs without resolution', async () => { - const wdb = new WalletDB({ - name: 'wallet-test', - db: 'memory', - verify: false + it('should handle double-spend (not our input)', async () => { + const wallet = await wdb.create(); + + const t1 = new MTX(); + const input = dummyInput(); + t1.addInput(input); + t1.addOutput(await wallet.receiveAddress(), 50000); + await wdb.addTX(t1.toTX()); + assert.strictEqual((await wallet.getBalance()).unconfirmed, 50000); + + let conflict = false; + wallet.on('conflict', () => { + conflict = true; }); - await wdb.open(); + const t2 = new MTX(); + t2.addInput(input); + t2.addOutput(new Address(), 5000); + await wdb.addTX(t2.toTX()); + assert(conflict); + assert.strictEqual((await wallet.getBalance()).unconfirmed, 0); + }); + + it('should handle double-spend (multiple inputs)', async () => { + const wallet = await wdb.create(); + const address = await wallet.receiveAddress(); + + const hash = random.randomBytes(32); + const input0 = Input.fromOutpoint(new Outpoint(hash, 0)); + const input1 = Input.fromOutpoint(new Outpoint(hash, 1)); + + const txa = new MTX(); + txa.addInput(input0); + txa.addInput(input1); + txa.addOutput(address, 50000); + await wdb.addTX(txa.toTX()); + assert.strictEqual((await wallet.getBalance()).unconfirmed, 50000); + + let conflict = false; + wallet.on('conflict', () => { + conflict = true; + }); + + const txb = new MTX(); + txb.addInput(input0); + txb.addInput(input1); + txb.addOutput(address, 49000); + await wdb.addTX(txb.toTX()); + assert(conflict); + assert.strictEqual((await wallet.getBalance()).unconfirmed, 49000); + }); + + it('should handle more missed txs', async () => { const alice = await wdb.create(); const bob = await wdb.create(); // Coinbase const t1 = new MTX(); t1.addInput(dummyInput()); - t1.addOutput(alice.getAddress(), 50000); - t1.addOutput(alice.getAddress(), 1000); + t1.addOutput(await alice.receiveAddress(), 50000); + t1.addOutput(await alice.receiveAddress(), 1000); // balance: 51000 const t2 = new MTX(); t2.addTX(t1, 0); // 50000 - t2.addOutput(alice.getAddress(), 24000); - t2.addOutput(alice.getAddress(), 24000); + t2.addOutput(await alice.receiveAddress(), 24000); + t2.addOutput(await alice.receiveAddress(), 24000); // balance: 49000 await alice.sign(t2); @@ -505,7 +577,7 @@ describe('Wallet', function() { const t3 = new MTX(); t3.addTX(t1, 1); // 1000 t3.addTX(t2, 0); // 24000 - t3.addOutput(alice.getAddress(), 23000); + t3.addOutput(await alice.receiveAddress(), 23000); // balance: 47000 await alice.sign(t3); @@ -513,15 +585,15 @@ describe('Wallet', function() { const t4 = new MTX(); t4.addTX(t2, 1); // 24000 t4.addTX(t3, 0); // 23000 - t4.addOutput(alice.getAddress(), 11000); - t4.addOutput(alice.getAddress(), 11000); + t4.addOutput(await alice.receiveAddress(), 11000); + t4.addOutput(await alice.receiveAddress(), 11000); // balance: 22000 await alice.sign(t4); const f1 = new MTX(); f1.addTX(t4, 1); // 11000 - f1.addOutput(bob.getAddress(), 10000); + f1.addOutput(await bob.receiveAddress(), 10000); // balance: 11000 await alice.sign(f1); @@ -541,24 +613,24 @@ describe('Wallet', function() { { await wdb.addTX(t2.toTX()); const balance = await alice.getBalance(); - assert.strictEqual(balance.unconfirmed, 47000); + assert.strictEqual(balance.unconfirmed, 71000); } { await wdb.addTX(t3.toTX()); const balance = await alice.getBalance(); - assert.strictEqual(balance.unconfirmed, 22000); + assert.strictEqual(balance.unconfirmed, 69000); } { await wdb.addTX(f1.toTX()); const balance = await alice.getBalance(); - assert.strictEqual(balance.unconfirmed, 11000); + assert.strictEqual(balance.unconfirmed, 58000); const txs = await alice.getHistory(); assert(txs.some((wtx) => { - return wtx.tx.hash('hex') === f1.hash('hex'); + return wtx.tx.hash().equals(f1.hash()); })); } @@ -568,14 +640,18 @@ describe('Wallet', function() { const txs = await bob.getHistory(); assert(txs.some((wtx) => { - return wtx.tx.hash('hex') === f1.hash('hex'); + return wtx.tx.hash().equals(f1.hash()); })); } - await wdb.addTX(t2.toTX()); - await wdb.addTX(t3.toTX()); - await wdb.addTX(t4.toTX()); - await wdb.addTX(f1.toTX()); + // Should recover from missed txs on block. + await wdb.addBlock(nextBlock(wdb), [ + t1.toTX(), + t2.toTX(), + t3.toTX(), + t4.toTX(), + f1.toTX() + ]); { const balance = await alice.getBalance(); @@ -595,16 +671,16 @@ describe('Wallet', function() { // Coinbase const t1 = new MTX(); t1.addInput(dummyInput()); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); await wdb.addTX(t1.toTX()); // Create new transaction const m2 = new MTX(); - m2.addOutput(bob.getAddress(), 5460); + m2.addOutput(await bob.receiveAddress(), 5460); await alice.fund(m2, { rate: 10000, @@ -623,7 +699,7 @@ describe('Wallet', function() { // Create new transaction const t3 = new MTX(); - t3.addOutput(bob.getAddress(), 15000); + t3.addOutput(await bob.receiveAddress(), 15000); let err; try { @@ -650,17 +726,17 @@ describe('Wallet', function() { // Coinbase const t1 = new MTX(); - t1.addOutpoint(new Outpoint(encoding.NULL_HASH, 0)); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); + t1.addOutpoint(new Outpoint(consensus.ZERO_HASH, 0)); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); await wdb.addTX(t1.toTX()); // Create new transaction const m2 = new MTX(); - m2.addOutput(bob.getAddress(), 5460); + m2.addOutput(await bob.receiveAddress(), 5460); await alice.fund(m2, { rate: 10000 @@ -684,7 +760,7 @@ describe('Wallet', function() { assert.strictEqual(t2.getSize(), 521); assert.strictEqual(t2.getVirtualSize(), 521); - let balance; + let balance = null; bob.once('balance', (b) => { balance = b; }); @@ -693,7 +769,7 @@ describe('Wallet', function() { // Create new transaction const t3 = new MTX(); - t3.addOutput(bob.getAddress(), 15000); + t3.addOutput(await bob.receiveAddress(), 15000); let err; try { @@ -717,25 +793,25 @@ describe('Wallet', function() { // Coinbase const t1 = new MTX(); t1.addInput(dummyInput()); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); // Coinbase const t2 = new MTX(); t2.addInput(dummyInput()); - t2.addOutput(bob.getAddress(), 5460); - t2.addOutput(bob.getAddress(), 5460); - t2.addOutput(bob.getAddress(), 5460); - t2.addOutput(bob.getAddress(), 5460); + t2.addOutput(await bob.receiveAddress(), 5460); + t2.addOutput(await bob.receiveAddress(), 5460); + t2.addOutput(await bob.receiveAddress(), 5460); + t2.addOutput(await bob.receiveAddress(), 5460); await wdb.addTX(t1.toTX()); await wdb.addTX(t2.toTX()); // Create our tx with an output const tx = new MTX(); - tx.addOutput(carol.getAddress(), 5460); + tx.addOutput(await carol.receiveAddress(), 5460); const coins1 = await alice.getCoins(); const coins2 = await bob.getCoins(); @@ -776,6 +852,61 @@ describe('Wallet', function() { await testP2SH(true, true); }); + it('should create account', async () => { + const wallet = await wdb.create(); + const account = await wallet.createAccount({ + name: 'foo' + }); + + assert(account); + assert(account.initialized); + assert.strictEqual(account.name, 'foo'); + assert.strictEqual(account.accountIndex, 1); + assert.strictEqual(account.m, 1); + assert.strictEqual(account.n, 1); + }); + + it('should inspect Wallet', async () => { + const wallet = await wdb.create(); + + const fmt = nodejsUtil.format(wallet); + assert(typeof fmt === 'string'); + assert(fmt.includes('master')); + assert(fmt.includes('network')); + assert(fmt.includes('accountDepth')); + }); + + it('should inspect Account', async () => { + const wallet = await wdb.create(); + const account = await wallet.createAccount({ + name: 'foo' + }); + + const fmt = nodejsUtil.format(account); + assert(typeof fmt === 'string'); + assert(fmt.includes('name')); + assert(fmt.includes('foo')); + assert(fmt.includes('initialized')); + assert(fmt.includes('lookahead')); + }); + + it('should fail to create duplicate account', async () => { + const wallet = await wdb.create(); + const name = 'foo'; + + await wallet.createAccount({ name }); + + let err; + try { + await wallet.createAccount({ name }); + } catch (e) { + err = e; + } + + assert(err); + assert.strictEqual(err.message, 'Account already exists.'); + }); + it('should fill tx with account 1', async () => { const alice = await wdb.create(); const bob = await wdb.create(); @@ -795,16 +926,16 @@ describe('Wallet', function() { // Coinbase const t1 = new MTX(); t1.addInput(dummyInput()); - t1.addOutput(account.receive.getAddress(), 5460); - t1.addOutput(account.receive.getAddress(), 5460); - t1.addOutput(account.receive.getAddress(), 5460); - t1.addOutput(account.receive.getAddress(), 5460); + t1.addOutput(account.receiveAddress(), 5460); + t1.addOutput(account.receiveAddress(), 5460); + t1.addOutput(account.receiveAddress(), 5460); + t1.addOutput(account.receiveAddress(), 5460); await wdb.addTX(t1.toTX()); // Create new transaction const t2 = new MTX(); - t2.addOutput(bob.getAddress(), 5460); + t2.addOutput(await bob.receiveAddress(), 5460); await alice.fund(t2, { rate: 10000, @@ -821,7 +952,7 @@ describe('Wallet', function() { // Create new transaction const t3 = new MTX(); - t3.addOutput(bob.getAddress(), 15000); + t3.addOutput(await bob.receiveAddress(), 15000); let err; try { @@ -854,27 +985,23 @@ describe('Wallet', function() { const account = await wallet.getAccount('foo'); assert.strictEqual(account.name, 'foo'); assert.strictEqual(account.accountIndex, 1); - assert.strictEqual(wallet.account.accountIndex, 0); - assert(!account.receive.getAddress().equals( - wallet.account.receive.getAddress())); - - assert(wallet.getAddress().equals(wallet.account.receive.getAddress())); + assert(!account.receiveAddress().equals(await wallet.receiveAddress())); // Coinbase const t1 = new MTX(); t1.addInput(dummyInput()); - t1.addOutput(wallet.getAddress(), 5460); - t1.addOutput(wallet.getAddress(), 5460); - t1.addOutput(wallet.getAddress(), 5460); - t1.addOutput(account.receive.getAddress(), 5460); + t1.addOutput(await wallet.receiveAddress(), 5460); + t1.addOutput(await wallet.receiveAddress(), 5460); + t1.addOutput(await wallet.receiveAddress(), 5460); + t1.addOutput(account.receiveAddress(), 5460); await wdb.addTX(t1.toTX()); // Should fill from `foo` and fail const t2 = new MTX(); - t2.addOutput(wallet.getAddress(), 5460); + t2.addOutput(await wallet.receiveAddress(), 5460); let err; try { @@ -891,7 +1018,7 @@ describe('Wallet', function() { // Should fill from whole wallet and succeed const t3 = new MTX(); - t3.addOutput(wallet.getAddress(), 5460); + t3.addOutput(await wallet.receiveAddress(), 5460); await wallet.fund(t3, { rate: 10000, @@ -901,15 +1028,15 @@ describe('Wallet', function() { // Coinbase const t4 = new MTX(); t4.addInput(dummyInput()); - t4.addOutput(account.receive.getAddress(), 5460); - t4.addOutput(account.receive.getAddress(), 5460); - t4.addOutput(account.receive.getAddress(), 5460); + t4.addOutput(await wallet.receiveAddress('foo'), 5460); + t4.addOutput(await wallet.receiveAddress('foo'), 5460); + t4.addOutput(await wallet.receiveAddress('foo'), 5460); await wdb.addTX(t4.toTX()); // Should fill from `foo` and succeed const t5 = new MTX(); - t5.addOutput(wallet.getAddress(), 5460); + t5.addOutput(await wallet.receiveAddress(), 5460); await wallet.fund(t5, { rate: 10000, @@ -927,6 +1054,7 @@ describe('Wallet', function() { passphrase: 'foo' }); await wallet.destroy(); + wdb.unregister(wallet); } const wallet = await wdb.get('foobar'); @@ -954,16 +1082,16 @@ describe('Wallet', function() { // Coinbase const t1 = new MTX(); t1.addInput(dummyInput()); - t1.addOutput(wallet.getAddress(), 5460); - t1.addOutput(wallet.getAddress(), 5460); - t1.addOutput(wallet.getAddress(), 5460); - t1.addOutput(wallet.getAddress(), 5460); + t1.addOutput(await wallet.receiveAddress(), 5460); + t1.addOutput(await wallet.receiveAddress(), 5460); + t1.addOutput(await wallet.receiveAddress(), 5460); + t1.addOutput(await wallet.receiveAddress(), 5460); await wdb.addTX(t1.toTX()); // Create new transaction const t2 = new MTX(); - t2.addOutput(wallet.getAddress(), 5460); + t2.addOutput(await wallet.receiveAddress(), 5460); await wallet.fund(t2, { rate: 10000, @@ -993,16 +1121,16 @@ describe('Wallet', function() { // Coinbase const t1 = new MTX(); t1.addInput(dummyInput()); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); await wdb.addTX(t1.toTX()); // Create new transaction const t2 = new MTX(); - t2.addOutput(bob.getAddress(), 21840); + t2.addOutput(await bob.receiveAddress(), 21840); await alice.fund(t2, { rate: 10000, @@ -1026,10 +1154,10 @@ describe('Wallet', function() { // Coinbase const t1 = new MTX(); t1.addInput(dummyInput()); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); await wdb.addTX(t1.toTX()); @@ -1037,7 +1165,7 @@ describe('Wallet', function() { subtractFee: true, rate: 10000, round: true, - outputs: [{ address: bob.getAddress(), value: 21840 }] + outputs: [{ address: await bob.receiveAddress(), value: 21840 }] }; // Create new transaction @@ -1058,24 +1186,22 @@ describe('Wallet', function() { // Coinbase const t1 = new MTX(); t1.addInput(dummyInput()); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); - t1.addOutput(alice.getAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); + t1.addOutput(await alice.receiveAddress(), 5460); await wdb.addTX(t1.toTX()); // Coinbase const t2 = new MTX(); t2.addInput(dummyInput()); - t2.addOutput(alice.getAddress(), 5460); - t2.addOutput(alice.getAddress(), 5460); - t2.addOutput(alice.getAddress(), 5460); - t2.addOutput(alice.getAddress(), 5460); - - const block = nextBlock(); + t2.addOutput(await alice.receiveAddress(), 5460); + t2.addOutput(await alice.receiveAddress(), 5460); + t2.addOutput(await alice.receiveAddress(), 5460); + t2.addOutput(await alice.receiveAddress(), 5460); - await wdb.addBlock(block, [t2.toTX()]); + await wdb.addBlock(nextBlock(wdb), [t2.toTX()]); { const coins = await alice.getSmartCoins(); @@ -1083,7 +1209,7 @@ describe('Wallet', function() { for (let i = 0; i < coins.length; i++) { const coin = coins[i]; - assert.strictEqual(coin.height, block.height); + assert.strictEqual(coin.height, wdb.state.height); } } @@ -1092,7 +1218,7 @@ describe('Wallet', function() { subtractFee: true, rate: 1000, depth: 1, - outputs: [{ address: bob.getAddress(), value: 1461 }] + outputs: [{ address: await bob.receiveAddress(), value: 1461 }] }); const coins = await alice.getSmartCoins(); @@ -1110,7 +1236,7 @@ describe('Wallet', function() { assert(coin.value < 5460); found = true; } else { - assert.strictEqual(coin.height, block.height); + assert.strictEqual(coin.height, wdb.state.height); } total += coin.value; } @@ -1124,7 +1250,7 @@ describe('Wallet', function() { smart: true, rate: 10000, outputs: [{ - address: bob.getAddress(), + address: await bob.receiveAddress(), value: total }] }; @@ -1142,7 +1268,7 @@ describe('Wallet', function() { assert(coin.value < 5460); found = true; } else { - assert.strictEqual(coin.height, block.height); + assert.strictEqual(coin.height, wdb.state.height); } } @@ -1154,9 +1280,43 @@ describe('Wallet', function() { assert(t3.verify()); }); + for (const witness of [true, false]) { + it(`should create non-templated tx (witness=${witness})`, async () => { + const wallet = await wdb.create({ witness }); + + // Fund wallet + const t1 = new MTX(); + t1.addInput(dummyInput()); + t1.addOutput(await wallet.receiveAddress(), 500000); + + await wdb.addTX(t1.toTX()); + + const options = { + rate: 10000, + round: true, + outputs: [{ + address: await wallet.receiveAddress(), + value: 7000 + }], + template: false + }; + + const t2 = await wallet.createTX(options); + + assert(t2, 'Could not create tx.'); + + for (const input of t2.inputs) { + const {script, witness} = input; + + assert.strictEqual(script.length, 0, 'Input is templated.'); + assert.strictEqual(witness.length, 0, 'Input is templated.'); + } + }); + } + it('should get range of txs', async () => { const wallet = currentWallet; - const txs = await wallet.getRange({ + const txs = await wallet.getRange(null, { start: util.now() - 1000 }); assert.strictEqual(txs.length, 2); @@ -1202,9 +1362,9 @@ describe('Wallet', function() { await wallet.importKey('default', key, 'test'); - const wkey = await wallet.getKey(key.getHash('hex')); + const wkey = await wallet.getKey(key.getHash()); - assert.strictEqual(wkey.getHash('hex'), key.getHash('hex')); + assert.bufferEqual(wkey.getHash(), key.getHash()); // Coinbase const t1 = new MTX(); @@ -1217,15 +1377,15 @@ describe('Wallet', function() { await wdb.addTX(t1.toTX()); - const wtx = await wallet.getTX(t1.hash('hex')); + const wtx = await wallet.getTX(t1.hash()); assert(wtx); - assert.strictEqual(t1.hash('hex'), wtx.hash); + assert.bufferEqual(t1.hash(), wtx.hash); const options = { rate: 10000, round: true, outputs: [{ - address: wallet.getAddress(), + address: await wallet.receiveAddress(), value: 7000 }] }; @@ -1234,26 +1394,45 @@ describe('Wallet', function() { const t2 = await wallet.createTX(options); await wallet.sign(t2); assert(t2.verify()); - assert.strictEqual(t2.inputs[0].prevout.hash, wtx.hash); + assert.bufferEqual(t2.inputs[0].prevout.hash, wtx.hash); importedWallet = wallet; importedKey = key; }); + it('should require account key to create watch only wallet', async () => { + let err = null; + + try { + await wdb.create({ + watchOnly: true + }); + } catch (e) { + err = e; + } + + assert(err); + assert.strictEqual( + err.message, + 'Must add HD public keys to watch only wallet.' + ); + }); + it('should import pubkey', async () => { const key = KeyRing.generate(); const pub = new KeyRing(key.publicKey); const wallet = await wdb.create({ - watchOnly: true + watchOnly: true, + accountKey: PUBKEY }); await wallet.importKey('default', pub); - const path = await wallet.getPath(pub.getHash('hex')); - assert.strictEqual(path.hash, pub.getHash('hex')); + const path = await wallet.getPath(pub.getHash()); + assert.bufferEqual(path.hash, pub.getHash()); - const wkey = await wallet.getKey(pub.getHash('hex')); + const wkey = await wallet.getKey(pub.getHash()); assert(wkey); }); @@ -1261,16 +1440,17 @@ describe('Wallet', function() { const key = KeyRing.generate(); const wallet = await wdb.create({ - watchOnly: true + watchOnly: true, + accountKey: PUBKEY }); await wallet.importAddress('default', key.getAddress()); - const path = await wallet.getPath(key.getHash('hex')); + const path = await wallet.getPath(key.getHash()); assert(path); - assert.strictEqual(path.hash, key.getHash('hex')); + assert.bufferEqual(path.hash, key.getHash()); - const wkey = await wallet.getKey(key.getHash('hex')); + const wkey = await wallet.getKey(key.getHash()); assert(!wkey); }); @@ -1284,7 +1464,7 @@ describe('Wallet', function() { const details = await wallet.toDetails(txs); assert(details.some((tx) => { - return tx.toJSON().outputs[0].path.name === 'foo'; + return tx.toJSON(wdb.network).outputs[0].path.name === 'foo'; })); }); @@ -1299,7 +1479,8 @@ describe('Wallet', function() { const details = await wallet.toDetails(txs); - assert.strictEqual(details[0].toJSON().id, 'test'); + assert(details.length > 0); + assert.strictEqual(wallet.id, 'test'); }); it('should change passphrase with encrypted imports', async () => { @@ -1339,16 +1520,11 @@ describe('Wallet', function() { const key = await wallet.getKey(addr); assert(key); - assert.strictEqual(key.getHash('hex'), addr.getHash('hex')); + assert.bufferEqual(key.getHash(), addr.getHash()); }); it('should recover from a missed tx', async () => { - const wdb = new WalletDB({ - name: 'wallet-test', - db: 'memory', - verify: false - }); - + const wdb = new WalletDB({ workers }); await wdb.open(); const alice = await wdb.create({ @@ -1359,15 +1535,14 @@ describe('Wallet', function() { master: KEY1 }); - const addr = alice.getAddress(); + const addr = await alice.receiveAddress(); // Coinbase const t1 = new MTX(); t1.addInput(dummyInput()); t1.addOutput(addr, 50000); - await alice.add(t1.toTX()); - await bob.add(t1.toTX()); + await wdb.addTX(t1.toTX()); // Bob misses this tx! const t2 = new MTX(); @@ -1393,27 +1568,24 @@ describe('Wallet', function() { assert.strictEqual((await bob.getBalance()).unconfirmed, 50000); - await alice.add(t3.toTX()); - await bob.add(t3.toTX()); + await wdb.addTX(t3.toTX()); assert.strictEqual((await alice.getBalance()).unconfirmed, 30000); + // t1 gets confirmed. + await wdb.addBlock(nextBlock(wdb), [t1.toTX()]); + // Bob sees t2 on the chain. - await bob.add(t2.toTX()); + await wdb.addBlock(nextBlock(wdb), [t2.toTX()]); // Bob sees t3 on the chain. - await bob.add(t3.toTX()); + await wdb.addBlock(nextBlock(wdb), [t3.toTX()]); assert.strictEqual((await bob.getBalance()).unconfirmed, 30000); }); it('should recover from a missed tx and double spend', async () => { - const wdb = new WalletDB({ - name: 'wallet-test', - db: 'memory', - verify: false - }); - + const wdb = new WalletDB({ workers }); await wdb.open(); const alice = await wdb.create({ @@ -1424,15 +1596,14 @@ describe('Wallet', function() { master: KEY1 }); - const addr = alice.getAddress(); + const addr = await alice.receiveAddress(); // Coinbase const t1 = new MTX(); t1.addInput(dummyInput()); t1.addOutput(addr, 50000); - await alice.add(t1.toTX()); - await bob.add(t1.toTX()); + await wdb.addTX(t1.toTX()); // Bob misses this tx! const t2a = new MTX(); @@ -1468,21 +1639,121 @@ describe('Wallet', function() { assert.strictEqual((await bob.getBalance()).unconfirmed, 20000); - await alice.add(t3.toTX()); - await bob.add(t3.toTX()); + await wdb.addTX(t3.toTX()); assert.strictEqual((await alice.getBalance()).unconfirmed, 30000); + // t1 gets confirmed. + await wdb.addBlock(nextBlock(wdb), [t1.toTX()]); + // Bob sees t2a on the chain. - await bob.add(t2a.toTX()); + await wdb.addBlock(nextBlock(wdb), [t2a.toTX()]); // Bob sees t3 on the chain. - await bob.add(t3.toTX()); + await wdb.addBlock(nextBlock(wdb), [t3.toTX()]); assert.strictEqual((await bob.getBalance()).unconfirmed, 30000); }); - it('should cleanup', () => { + it('should remove a wallet', async () => { + await wdb.create({ + id: 'alice100' + }); + assert(await wdb.get('alice100')); + await wdb.remove('alice100'); + assert(!await wdb.get('alice100')); + }); + + const keyTypes = [ + { + name: 'receive', + method: 'createReceive', + branch: 0 + }, + { + name: 'change', + method: 'createChange', + branch: 1 + }, + { + name: 'nested', + method: 'createNested', + branch: 2 + } + ]; + + for (const type of keyTypes) { + it(`should create ${type.name} addresses`, async () => { + const account = 0; + const wallet = await wdb.create({ + witness: true + }); + const addresses = new Set(); + + for (let i = 0; i < 100; i++) { + const key = await wallet[type.method](account); + addresses.add(key.getAddress('string')); + assert.strictEqual(key.account, account); + assert.strictEqual(key.branch, type.branch); + assert.strictEqual(key.index, i + 1); + } + + assert.strictEqual(addresses.size, 100); + }); + + it(`should create ${type.name} addresses and get their keys`, async () => { + const account = 0; + const wallet = await wdb.create({ + witness: true + }); + + const addresses = new Set(); + + for (let i = 0; i < 100; i++) { + const key1 = await wallet[type.method](account); + const address = key1.getAddress(); + + assert(key1, `Could not get ${type.name}`); + addresses.add(address); + + assert.strictEqual(key1.account, account); + assert.strictEqual(key1.branch, type.branch); + assert.strictEqual(key1.index, i + 1); + + const key2 = await wallet.getKey(address); + assert(key2, `Could not get key for ${address.toString()}` + + `, Key: xpub/${type.branch}/${i+1}`); + + assert.strictEqual(key2.name, key1.name); + assert.strictEqual(key2.account, key1.account); + assert.strictEqual(key2.branch, key1.branch); + assert.strictEqual(key2.witness, key1.witness); + assert.strictEqual(key2.nested, key1.nested); + assert.bufferEqual(key2.publicKey, key1.publicKey); + assert.strictEqual(key2.getType(), key1.getType()); + } + + assert.strictEqual(addresses.size, 100); + }); + } + + it('should throw error with missing outputs', async () => { + const wallet = new Wallet({}); + + let err = null; + + try { + await wallet.send({outputs: []}); + } catch (e) { + err = e; + } + + assert(err); + assert.equal(err.message, 'At least one output required.'); + }); + + it('should cleanup', async () => { consensus.COINBASE_MATURITY = 100; + // await wdb.close(); }); }); diff --git a/vendor/unorm.js b/vendor/unorm.js deleted file mode 100644 index 79a7c9f7f..000000000 --- a/vendor/unorm.js +++ /dev/null @@ -1,429 +0,0 @@ -/*! -https://github.com/walling/unorm -The software dual licensed under the MIT and GPL licenses. MIT license: - - Copyright (c) 2008-2013 Matsuza , Bjarke Walling - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to - deal in the Software without restriction, including without limitation the - rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 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. - -GPL notice (please read the [full GPL license] online): - - Copyright (C) 2008-2013 Matsuza , Bjarke Walling - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 2 - of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - -[full GPL license]: http://www.gnu.org/licenses/gpl-2.0-standalone.html -*/ - -(function (root) { - "use strict"; - -/***** unorm.js *****/ - -/* - * UnicodeNormalizer 1.0.0 - * Copyright (c) 2008 Matsuza - * Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. - * $Date: 2008-06-05 16:44:17 +0200 (Thu, 05 Jun 2008) $ - * $Rev: 13309 $ - */ - - var DEFAULT_FEATURE = [null, 0, {}]; - var CACHE_THRESHOLD = 10; - var SBase = 0xAC00, LBase = 0x1100, VBase = 0x1161, TBase = 0x11A7, LCount = 19, VCount = 21, TCount = 28; - var NCount = VCount * TCount; // 588 - var SCount = LCount * NCount; // 11172 - - var UChar = function(cp, feature){ - this.codepoint = cp; - this.feature = feature; - }; - - // Strategies - var cache = {}; - var cacheCounter = []; - for (var i = 0; i <= 0xFF; ++i){ - cacheCounter[i] = 0; - } - - function fromCache(next, cp, needFeature){ - var ret = cache[cp]; - if(!ret){ - ret = next(cp, needFeature); - if(!!ret.feature && ++cacheCounter[(cp >> 8) & 0xFF] > CACHE_THRESHOLD){ - cache[cp] = ret; - } - } - return ret; - } - - function fromData(next, cp, needFeature){ - var hash = cp & 0xFF00; - var dunit = UChar.udata[hash] || {}; - var f = dunit[cp]; - return f ? new UChar(cp, f) : new UChar(cp, DEFAULT_FEATURE); - } - function fromCpOnly(next, cp, needFeature){ - return !!needFeature ? next(cp, needFeature) : new UChar(cp, null); - } - function fromRuleBasedJamo(next, cp, needFeature){ - var j; - if(cp < LBase || (LBase + LCount <= cp && cp < SBase) || (SBase + SCount < cp)){ - return next(cp, needFeature); - } - if(LBase <= cp && cp < LBase + LCount){ - var c = {}; - var base = (cp - LBase) * VCount; - for (j = 0; j < VCount; ++j){ - c[VBase + j] = SBase + TCount * (j + base); - } - return new UChar(cp, [,,c]); - } - - var SIndex = cp - SBase; - var TIndex = SIndex % TCount; - var feature = []; - if(TIndex !== 0){ - feature[0] = [SBase + SIndex - TIndex, TBase + TIndex]; - } else { - feature[0] = [LBase + Math.floor(SIndex / NCount), VBase + Math.floor((SIndex % NCount) / TCount)]; - feature[2] = {}; - for (j = 1; j < TCount; ++j){ - feature[2][TBase + j] = cp + j; - } - } - return new UChar(cp, feature); - } - function fromCpFilter(next, cp, needFeature){ - return cp < 60 || 13311 < cp && cp < 42607 ? new UChar(cp, DEFAULT_FEATURE) : next(cp, needFeature); - } - - var strategies = [fromCpFilter, fromCache, fromCpOnly, fromRuleBasedJamo, fromData]; - - UChar.fromCharCode = strategies.reduceRight(function (next, strategy) { - return function (cp, needFeature) { - return strategy(next, cp, needFeature); - }; - }, null); - - UChar.isHighSurrogate = function(cp){ - return cp >= 0xD800 && cp <= 0xDBFF; - }; - UChar.isLowSurrogate = function(cp){ - return cp >= 0xDC00 && cp <= 0xDFFF; - }; - - UChar.prototype.prepFeature = function(){ - if(!this.feature){ - this.feature = UChar.fromCharCode(this.codepoint, true).feature; - } - }; - - UChar.prototype.toString = function(){ - if(this.codepoint < 0x10000){ - return String.fromCharCode(this.codepoint); - } else { - var x = this.codepoint - 0x10000; - return String.fromCharCode(Math.floor(x / 0x400) + 0xD800, x % 0x400 + 0xDC00); - } - }; - - UChar.prototype.getDecomp = function(){ - this.prepFeature(); - return this.feature[0] || null; - }; - - UChar.prototype.isCompatibility = function(){ - this.prepFeature(); - return !!this.feature[1] && (this.feature[1] & (1 << 8)); - }; - UChar.prototype.isExclude = function(){ - this.prepFeature(); - return !!this.feature[1] && (this.feature[1] & (1 << 9)); - }; - UChar.prototype.getCanonicalClass = function(){ - this.prepFeature(); - return !!this.feature[1] ? (this.feature[1] & 0xff) : 0; - }; - UChar.prototype.getComposite = function(following){ - this.prepFeature(); - if(!this.feature[2]){ - return null; - } - var cp = this.feature[2][following.codepoint]; - return cp ? UChar.fromCharCode(cp) : null; - }; - - var UCharIterator = function(str){ - this.str = str; - this.cursor = 0; - }; - UCharIterator.prototype.next = function(){ - if(!!this.str && this.cursor < this.str.length){ - var cp = this.str.charCodeAt(this.cursor++); - var d; - if(UChar.isHighSurrogate(cp) && this.cursor < this.str.length && UChar.isLowSurrogate((d = this.str.charCodeAt(this.cursor)))){ - cp = (cp - 0xD800) * 0x400 + (d -0xDC00) + 0x10000; - ++this.cursor; - } - return UChar.fromCharCode(cp); - } else { - this.str = null; - return null; - } - }; - - var RecursDecompIterator = function(it, cano){ - this.it = it; - this.canonical = cano; - this.resBuf = []; - }; - - RecursDecompIterator.prototype.next = function(){ - function recursiveDecomp(cano, uchar){ - var decomp = uchar.getDecomp(); - if(!!decomp && !(cano && uchar.isCompatibility())){ - var ret = []; - for(var i = 0; i < decomp.length; ++i){ - var a = recursiveDecomp(cano, UChar.fromCharCode(decomp[i])); - ret = ret.concat(a); - } - return ret; - } else { - return [uchar]; - } - } - if(this.resBuf.length === 0){ - var uchar = this.it.next(); - if(!uchar){ - return null; - } - this.resBuf = recursiveDecomp(this.canonical, uchar); - } - return this.resBuf.shift(); - }; - - var DecompIterator = function(it){ - this.it = it; - this.resBuf = []; - }; - - DecompIterator.prototype.next = function(){ - var cc; - if(this.resBuf.length === 0){ - do{ - var uchar = this.it.next(); - if(!uchar){ - break; - } - cc = uchar.getCanonicalClass(); - var inspt = this.resBuf.length; - if(cc !== 0){ - for(; inspt > 0; --inspt){ - var uchar2 = this.resBuf[inspt - 1]; - var cc2 = uchar2.getCanonicalClass(); - if(cc2 <= cc){ - break; - } - } - } - this.resBuf.splice(inspt, 0, uchar); - } while(cc !== 0); - } - return this.resBuf.shift(); - }; - - var CompIterator = function(it){ - this.it = it; - this.procBuf = []; - this.resBuf = []; - this.lastClass = null; - }; - - CompIterator.prototype.next = function(){ - while(this.resBuf.length === 0){ - var uchar = this.it.next(); - if(!uchar){ - this.resBuf = this.procBuf; - this.procBuf = []; - break; - } - if(this.procBuf.length === 0){ - this.lastClass = uchar.getCanonicalClass(); - this.procBuf.push(uchar); - } else { - var starter = this.procBuf[0]; - var composite = starter.getComposite(uchar); - var cc = uchar.getCanonicalClass(); - if(!!composite && (this.lastClass < cc || this.lastClass === 0)){ - this.procBuf[0] = composite; - } else { - if(cc === 0){ - this.resBuf = this.procBuf; - this.procBuf = []; - } - this.lastClass = cc; - this.procBuf.push(uchar); - } - } - } - return this.resBuf.shift(); - }; - - var createIterator = function(mode, str){ - switch(mode){ - case "NFD": - return new DecompIterator(new RecursDecompIterator(new UCharIterator(str), true)); - case "NFKD": - return new DecompIterator(new RecursDecompIterator(new UCharIterator(str), false)); - case "NFC": - return new CompIterator(new DecompIterator(new RecursDecompIterator(new UCharIterator(str), true))); - case "NFKC": - return new CompIterator(new DecompIterator(new RecursDecompIterator(new UCharIterator(str), false))); - } - throw mode + " is invalid"; - }; - var normalize = function(mode, str){ - var it = createIterator(mode, str); - var ret = ""; - var uchar; - while(!!(uchar = it.next())){ - ret += uchar.toString(); - } - return ret; - }; - - /* API functions */ - function nfd(str){ - return normalize("NFD", str); - } - - function nfkd(str){ - return normalize("NFKD", str); - } - - function nfc(str){ - return normalize("NFC", str); - } - - function nfkc(str){ - return normalize("NFKC", str); - } - -/* Unicode data */ -UChar.udata={ -0:{60:[,,{824:8814}],61:[,,{824:8800}],62:[,,{824:8815}],65:[,,{768:192,769:193,770:194,771:195,772:256,774:258,775:550,776:196,777:7842,778:197,780:461,783:512,785:514,803:7840,805:7680,808:260}],66:[,,{775:7682,803:7684,817:7686}],67:[,,{769:262,770:264,775:266,780:268,807:199}],68:[,,{775:7690,780:270,803:7692,807:7696,813:7698,817:7694}],69:[,,{768:200,769:201,770:202,771:7868,772:274,774:276,775:278,776:203,777:7866,780:282,783:516,785:518,803:7864,807:552,808:280,813:7704,816:7706}],70:[,,{775:7710}],71:[,,{769:500,770:284,772:7712,774:286,775:288,780:486,807:290}],72:[,,{770:292,775:7714,776:7718,780:542,803:7716,807:7720,814:7722}],73:[,,{768:204,769:205,770:206,771:296,772:298,774:300,775:304,776:207,777:7880,780:463,783:520,785:522,803:7882,808:302,816:7724}],74:[,,{770:308}],75:[,,{769:7728,780:488,803:7730,807:310,817:7732}],76:[,,{769:313,780:317,803:7734,807:315,813:7740,817:7738}],77:[,,{769:7742,775:7744,803:7746}],78:[,,{768:504,769:323,771:209,775:7748,780:327,803:7750,807:325,813:7754,817:7752}],79:[,,{768:210,769:211,770:212,771:213,772:332,774:334,775:558,776:214,777:7886,779:336,780:465,783:524,785:526,795:416,803:7884,808:490}],80:[,,{769:7764,775:7766}],82:[,,{769:340,775:7768,780:344,783:528,785:530,803:7770,807:342,817:7774}],83:[,,{769:346,770:348,775:7776,780:352,803:7778,806:536,807:350}],84:[,,{775:7786,780:356,803:7788,806:538,807:354,813:7792,817:7790}],85:[,,{768:217,769:218,770:219,771:360,772:362,774:364,776:220,777:7910,778:366,779:368,780:467,783:532,785:534,795:431,803:7908,804:7794,808:370,813:7798,816:7796}],86:[,,{771:7804,803:7806}],87:[,,{768:7808,769:7810,770:372,775:7814,776:7812,803:7816}],88:[,,{775:7818,776:7820}],89:[,,{768:7922,769:221,770:374,771:7928,772:562,775:7822,776:376,777:7926,803:7924}],90:[,,{769:377,770:7824,775:379,780:381,803:7826,817:7828}],97:[,,{768:224,769:225,770:226,771:227,772:257,774:259,775:551,776:228,777:7843,778:229,780:462,783:513,785:515,803:7841,805:7681,808:261}],98:[,,{775:7683,803:7685,817:7687}],99:[,,{769:263,770:265,775:267,780:269,807:231}],100:[,,{775:7691,780:271,803:7693,807:7697,813:7699,817:7695}],101:[,,{768:232,769:233,770:234,771:7869,772:275,774:277,775:279,776:235,777:7867,780:283,783:517,785:519,803:7865,807:553,808:281,813:7705,816:7707}],102:[,,{775:7711}],103:[,,{769:501,770:285,772:7713,774:287,775:289,780:487,807:291}],104:[,,{770:293,775:7715,776:7719,780:543,803:7717,807:7721,814:7723,817:7830}],105:[,,{768:236,769:237,770:238,771:297,772:299,774:301,776:239,777:7881,780:464,783:521,785:523,803:7883,808:303,816:7725}],106:[,,{770:309,780:496}],107:[,,{769:7729,780:489,803:7731,807:311,817:7733}],108:[,,{769:314,780:318,803:7735,807:316,813:7741,817:7739}],109:[,,{769:7743,775:7745,803:7747}],110:[,,{768:505,769:324,771:241,775:7749,780:328,803:7751,807:326,813:7755,817:7753}],111:[,,{768:242,769:243,770:244,771:245,772:333,774:335,775:559,776:246,777:7887,779:337,780:466,783:525,785:527,795:417,803:7885,808:491}],112:[,,{769:7765,775:7767}],114:[,,{769:341,775:7769,780:345,783:529,785:531,803:7771,807:343,817:7775}],115:[,,{769:347,770:349,775:7777,780:353,803:7779,806:537,807:351}],116:[,,{775:7787,776:7831,780:357,803:7789,806:539,807:355,813:7793,817:7791}],117:[,,{768:249,769:250,770:251,771:361,772:363,774:365,776:252,777:7911,778:367,779:369,780:468,783:533,785:535,795:432,803:7909,804:7795,808:371,813:7799,816:7797}],118:[,,{771:7805,803:7807}],119:[,,{768:7809,769:7811,770:373,775:7815,776:7813,778:7832,803:7817}],120:[,,{775:7819,776:7821}],121:[,,{768:7923,769:253,770:375,771:7929,772:563,775:7823,776:255,777:7927,778:7833,803:7925}],122:[,,{769:378,770:7825,775:380,780:382,803:7827,817:7829}],160:[[32],256],168:[[32,776],256,{768:8173,769:901,834:8129}],170:[[97],256],175:[[32,772],256],178:[[50],256],179:[[51],256],180:[[32,769],256],181:[[956],256],184:[[32,807],256],185:[[49],256],186:[[111],256],188:[[49,8260,52],256],189:[[49,8260,50],256],190:[[51,8260,52],256],192:[[65,768]],193:[[65,769]],194:[[65,770],,{768:7846,769:7844,771:7850,777:7848}],195:[[65,771]],196:[[65,776],,{772:478}],197:[[65,778],,{769:506}],198:[,,{769:508,772:482}],199:[[67,807],,{769:7688}],200:[[69,768]],201:[[69,769]],202:[[69,770],,{768:7872,769:7870,771:7876,777:7874}],203:[[69,776]],204:[[73,768]],205:[[73,769]],206:[[73,770]],207:[[73,776],,{769:7726}],209:[[78,771]],210:[[79,768]],211:[[79,769]],212:[[79,770],,{768:7890,769:7888,771:7894,777:7892}],213:[[79,771],,{769:7756,772:556,776:7758}],214:[[79,776],,{772:554}],216:[,,{769:510}],217:[[85,768]],218:[[85,769]],219:[[85,770]],220:[[85,776],,{768:475,769:471,772:469,780:473}],221:[[89,769]],224:[[97,768]],225:[[97,769]],226:[[97,770],,{768:7847,769:7845,771:7851,777:7849}],227:[[97,771]],228:[[97,776],,{772:479}],229:[[97,778],,{769:507}],230:[,,{769:509,772:483}],231:[[99,807],,{769:7689}],232:[[101,768]],233:[[101,769]],234:[[101,770],,{768:7873,769:7871,771:7877,777:7875}],235:[[101,776]],236:[[105,768]],237:[[105,769]],238:[[105,770]],239:[[105,776],,{769:7727}],241:[[110,771]],242:[[111,768]],243:[[111,769]],244:[[111,770],,{768:7891,769:7889,771:7895,777:7893}],245:[[111,771],,{769:7757,772:557,776:7759}],246:[[111,776],,{772:555}],248:[,,{769:511}],249:[[117,768]],250:[[117,769]],251:[[117,770]],252:[[117,776],,{768:476,769:472,772:470,780:474}],253:[[121,769]],255:[[121,776]]}, -256:{256:[[65,772]],257:[[97,772]],258:[[65,774],,{768:7856,769:7854,771:7860,777:7858}],259:[[97,774],,{768:7857,769:7855,771:7861,777:7859}],260:[[65,808]],261:[[97,808]],262:[[67,769]],263:[[99,769]],264:[[67,770]],265:[[99,770]],266:[[67,775]],267:[[99,775]],268:[[67,780]],269:[[99,780]],270:[[68,780]],271:[[100,780]],274:[[69,772],,{768:7700,769:7702}],275:[[101,772],,{768:7701,769:7703}],276:[[69,774]],277:[[101,774]],278:[[69,775]],279:[[101,775]],280:[[69,808]],281:[[101,808]],282:[[69,780]],283:[[101,780]],284:[[71,770]],285:[[103,770]],286:[[71,774]],287:[[103,774]],288:[[71,775]],289:[[103,775]],290:[[71,807]],291:[[103,807]],292:[[72,770]],293:[[104,770]],296:[[73,771]],297:[[105,771]],298:[[73,772]],299:[[105,772]],300:[[73,774]],301:[[105,774]],302:[[73,808]],303:[[105,808]],304:[[73,775]],306:[[73,74],256],307:[[105,106],256],308:[[74,770]],309:[[106,770]],310:[[75,807]],311:[[107,807]],313:[[76,769]],314:[[108,769]],315:[[76,807]],316:[[108,807]],317:[[76,780]],318:[[108,780]],319:[[76,183],256],320:[[108,183],256],323:[[78,769]],324:[[110,769]],325:[[78,807]],326:[[110,807]],327:[[78,780]],328:[[110,780]],329:[[700,110],256],332:[[79,772],,{768:7760,769:7762}],333:[[111,772],,{768:7761,769:7763}],334:[[79,774]],335:[[111,774]],336:[[79,779]],337:[[111,779]],340:[[82,769]],341:[[114,769]],342:[[82,807]],343:[[114,807]],344:[[82,780]],345:[[114,780]],346:[[83,769],,{775:7780}],347:[[115,769],,{775:7781}],348:[[83,770]],349:[[115,770]],350:[[83,807]],351:[[115,807]],352:[[83,780],,{775:7782}],353:[[115,780],,{775:7783}],354:[[84,807]],355:[[116,807]],356:[[84,780]],357:[[116,780]],360:[[85,771],,{769:7800}],361:[[117,771],,{769:7801}],362:[[85,772],,{776:7802}],363:[[117,772],,{776:7803}],364:[[85,774]],365:[[117,774]],366:[[85,778]],367:[[117,778]],368:[[85,779]],369:[[117,779]],370:[[85,808]],371:[[117,808]],372:[[87,770]],373:[[119,770]],374:[[89,770]],375:[[121,770]],376:[[89,776]],377:[[90,769]],378:[[122,769]],379:[[90,775]],380:[[122,775]],381:[[90,780]],382:[[122,780]],383:[[115],256,{775:7835}],416:[[79,795],,{768:7900,769:7898,771:7904,777:7902,803:7906}],417:[[111,795],,{768:7901,769:7899,771:7905,777:7903,803:7907}],431:[[85,795],,{768:7914,769:7912,771:7918,777:7916,803:7920}],432:[[117,795],,{768:7915,769:7913,771:7919,777:7917,803:7921}],439:[,,{780:494}],452:[[68,381],256],453:[[68,382],256],454:[[100,382],256],455:[[76,74],256],456:[[76,106],256],457:[[108,106],256],458:[[78,74],256],459:[[78,106],256],460:[[110,106],256],461:[[65,780]],462:[[97,780]],463:[[73,780]],464:[[105,780]],465:[[79,780]],466:[[111,780]],467:[[85,780]],468:[[117,780]],469:[[220,772]],470:[[252,772]],471:[[220,769]],472:[[252,769]],473:[[220,780]],474:[[252,780]],475:[[220,768]],476:[[252,768]],478:[[196,772]],479:[[228,772]],480:[[550,772]],481:[[551,772]],482:[[198,772]],483:[[230,772]],486:[[71,780]],487:[[103,780]],488:[[75,780]],489:[[107,780]],490:[[79,808],,{772:492}],491:[[111,808],,{772:493}],492:[[490,772]],493:[[491,772]],494:[[439,780]],495:[[658,780]],496:[[106,780]],497:[[68,90],256],498:[[68,122],256],499:[[100,122],256],500:[[71,769]],501:[[103,769]],504:[[78,768]],505:[[110,768]],506:[[197,769]],507:[[229,769]],508:[[198,769]],509:[[230,769]],510:[[216,769]],511:[[248,769]],66045:[,220]}, -512:{512:[[65,783]],513:[[97,783]],514:[[65,785]],515:[[97,785]],516:[[69,783]],517:[[101,783]],518:[[69,785]],519:[[101,785]],520:[[73,783]],521:[[105,783]],522:[[73,785]],523:[[105,785]],524:[[79,783]],525:[[111,783]],526:[[79,785]],527:[[111,785]],528:[[82,783]],529:[[114,783]],530:[[82,785]],531:[[114,785]],532:[[85,783]],533:[[117,783]],534:[[85,785]],535:[[117,785]],536:[[83,806]],537:[[115,806]],538:[[84,806]],539:[[116,806]],542:[[72,780]],543:[[104,780]],550:[[65,775],,{772:480}],551:[[97,775],,{772:481}],552:[[69,807],,{774:7708}],553:[[101,807],,{774:7709}],554:[[214,772]],555:[[246,772]],556:[[213,772]],557:[[245,772]],558:[[79,775],,{772:560}],559:[[111,775],,{772:561}],560:[[558,772]],561:[[559,772]],562:[[89,772]],563:[[121,772]],658:[,,{780:495}],688:[[104],256],689:[[614],256],690:[[106],256],691:[[114],256],692:[[633],256],693:[[635],256],694:[[641],256],695:[[119],256],696:[[121],256],728:[[32,774],256],729:[[32,775],256],730:[[32,778],256],731:[[32,808],256],732:[[32,771],256],733:[[32,779],256],736:[[611],256],737:[[108],256],738:[[115],256],739:[[120],256],740:[[661],256],66272:[,220]}, -768:{768:[,230],769:[,230],770:[,230],771:[,230],772:[,230],773:[,230],774:[,230],775:[,230],776:[,230,{769:836}],777:[,230],778:[,230],779:[,230],780:[,230],781:[,230],782:[,230],783:[,230],784:[,230],785:[,230],786:[,230],787:[,230],788:[,230],789:[,232],790:[,220],791:[,220],792:[,220],793:[,220],794:[,232],795:[,216],796:[,220],797:[,220],798:[,220],799:[,220],800:[,220],801:[,202],802:[,202],803:[,220],804:[,220],805:[,220],806:[,220],807:[,202],808:[,202],809:[,220],810:[,220],811:[,220],812:[,220],813:[,220],814:[,220],815:[,220],816:[,220],817:[,220],818:[,220],819:[,220],820:[,1],821:[,1],822:[,1],823:[,1],824:[,1],825:[,220],826:[,220],827:[,220],828:[,220],829:[,230],830:[,230],831:[,230],832:[[768],230],833:[[769],230],834:[,230],835:[[787],230],836:[[776,769],230],837:[,240],838:[,230],839:[,220],840:[,220],841:[,220],842:[,230],843:[,230],844:[,230],845:[,220],846:[,220],848:[,230],849:[,230],850:[,230],851:[,220],852:[,220],853:[,220],854:[,220],855:[,230],856:[,232],857:[,220],858:[,220],859:[,230],860:[,233],861:[,234],862:[,234],863:[,233],864:[,234],865:[,234],866:[,233],867:[,230],868:[,230],869:[,230],870:[,230],871:[,230],872:[,230],873:[,230],874:[,230],875:[,230],876:[,230],877:[,230],878:[,230],879:[,230],884:[[697]],890:[[32,837],256],894:[[59]],900:[[32,769],256],901:[[168,769]],902:[[913,769]],903:[[183]],904:[[917,769]],905:[[919,769]],906:[[921,769]],908:[[927,769]],910:[[933,769]],911:[[937,769]],912:[[970,769]],913:[,,{768:8122,769:902,772:8121,774:8120,787:7944,788:7945,837:8124}],917:[,,{768:8136,769:904,787:7960,788:7961}],919:[,,{768:8138,769:905,787:7976,788:7977,837:8140}],921:[,,{768:8154,769:906,772:8153,774:8152,776:938,787:7992,788:7993}],927:[,,{768:8184,769:908,787:8008,788:8009}],929:[,,{788:8172}],933:[,,{768:8170,769:910,772:8169,774:8168,776:939,788:8025}],937:[,,{768:8186,769:911,787:8040,788:8041,837:8188}],938:[[921,776]],939:[[933,776]],940:[[945,769],,{837:8116}],941:[[949,769]],942:[[951,769],,{837:8132}],943:[[953,769]],944:[[971,769]],945:[,,{768:8048,769:940,772:8113,774:8112,787:7936,788:7937,834:8118,837:8115}],949:[,,{768:8050,769:941,787:7952,788:7953}],951:[,,{768:8052,769:942,787:7968,788:7969,834:8134,837:8131}],953:[,,{768:8054,769:943,772:8145,774:8144,776:970,787:7984,788:7985,834:8150}],959:[,,{768:8056,769:972,787:8000,788:8001}],961:[,,{787:8164,788:8165}],965:[,,{768:8058,769:973,772:8161,774:8160,776:971,787:8016,788:8017,834:8166}],969:[,,{768:8060,769:974,787:8032,788:8033,834:8182,837:8179}],970:[[953,776],,{768:8146,769:912,834:8151}],971:[[965,776],,{768:8162,769:944,834:8167}],972:[[959,769]],973:[[965,769]],974:[[969,769],,{837:8180}],976:[[946],256],977:[[952],256],978:[[933],256,{769:979,776:980}],979:[[978,769]],980:[[978,776]],981:[[966],256],982:[[960],256],1008:[[954],256],1009:[[961],256],1010:[[962],256],1012:[[920],256],1013:[[949],256],1017:[[931],256],66422:[,230],66423:[,230],66424:[,230],66425:[,230],66426:[,230]}, -1024:{1024:[[1045,768]],1025:[[1045,776]],1027:[[1043,769]],1030:[,,{776:1031}],1031:[[1030,776]],1036:[[1050,769]],1037:[[1048,768]],1038:[[1059,774]],1040:[,,{774:1232,776:1234}],1043:[,,{769:1027}],1045:[,,{768:1024,774:1238,776:1025}],1046:[,,{774:1217,776:1244}],1047:[,,{776:1246}],1048:[,,{768:1037,772:1250,774:1049,776:1252}],1049:[[1048,774]],1050:[,,{769:1036}],1054:[,,{776:1254}],1059:[,,{772:1262,774:1038,776:1264,779:1266}],1063:[,,{776:1268}],1067:[,,{776:1272}],1069:[,,{776:1260}],1072:[,,{774:1233,776:1235}],1075:[,,{769:1107}],1077:[,,{768:1104,774:1239,776:1105}],1078:[,,{774:1218,776:1245}],1079:[,,{776:1247}],1080:[,,{768:1117,772:1251,774:1081,776:1253}],1081:[[1080,774]],1082:[,,{769:1116}],1086:[,,{776:1255}],1091:[,,{772:1263,774:1118,776:1265,779:1267}],1095:[,,{776:1269}],1099:[,,{776:1273}],1101:[,,{776:1261}],1104:[[1077,768]],1105:[[1077,776]],1107:[[1075,769]],1110:[,,{776:1111}],1111:[[1110,776]],1116:[[1082,769]],1117:[[1080,768]],1118:[[1091,774]],1140:[,,{783:1142}],1141:[,,{783:1143}],1142:[[1140,783]],1143:[[1141,783]],1155:[,230],1156:[,230],1157:[,230],1158:[,230],1159:[,230],1217:[[1046,774]],1218:[[1078,774]],1232:[[1040,774]],1233:[[1072,774]],1234:[[1040,776]],1235:[[1072,776]],1238:[[1045,774]],1239:[[1077,774]],1240:[,,{776:1242}],1241:[,,{776:1243}],1242:[[1240,776]],1243:[[1241,776]],1244:[[1046,776]],1245:[[1078,776]],1246:[[1047,776]],1247:[[1079,776]],1250:[[1048,772]],1251:[[1080,772]],1252:[[1048,776]],1253:[[1080,776]],1254:[[1054,776]],1255:[[1086,776]],1256:[,,{776:1258}],1257:[,,{776:1259}],1258:[[1256,776]],1259:[[1257,776]],1260:[[1069,776]],1261:[[1101,776]],1262:[[1059,772]],1263:[[1091,772]],1264:[[1059,776]],1265:[[1091,776]],1266:[[1059,779]],1267:[[1091,779]],1268:[[1063,776]],1269:[[1095,776]],1272:[[1067,776]],1273:[[1099,776]]}, -1280:{1415:[[1381,1410],256],1425:[,220],1426:[,230],1427:[,230],1428:[,230],1429:[,230],1430:[,220],1431:[,230],1432:[,230],1433:[,230],1434:[,222],1435:[,220],1436:[,230],1437:[,230],1438:[,230],1439:[,230],1440:[,230],1441:[,230],1442:[,220],1443:[,220],1444:[,220],1445:[,220],1446:[,220],1447:[,220],1448:[,230],1449:[,230],1450:[,220],1451:[,230],1452:[,230],1453:[,222],1454:[,228],1455:[,230],1456:[,10],1457:[,11],1458:[,12],1459:[,13],1460:[,14],1461:[,15],1462:[,16],1463:[,17],1464:[,18],1465:[,19],1466:[,19],1467:[,20],1468:[,21],1469:[,22],1471:[,23],1473:[,24],1474:[,25],1476:[,230],1477:[,220],1479:[,18]}, -1536:{1552:[,230],1553:[,230],1554:[,230],1555:[,230],1556:[,230],1557:[,230],1558:[,230],1559:[,230],1560:[,30],1561:[,31],1562:[,32],1570:[[1575,1619]],1571:[[1575,1620]],1572:[[1608,1620]],1573:[[1575,1621]],1574:[[1610,1620]],1575:[,,{1619:1570,1620:1571,1621:1573}],1608:[,,{1620:1572}],1610:[,,{1620:1574}],1611:[,27],1612:[,28],1613:[,29],1614:[,30],1615:[,31],1616:[,32],1617:[,33],1618:[,34],1619:[,230],1620:[,230],1621:[,220],1622:[,220],1623:[,230],1624:[,230],1625:[,230],1626:[,230],1627:[,230],1628:[,220],1629:[,230],1630:[,230],1631:[,220],1648:[,35],1653:[[1575,1652],256],1654:[[1608,1652],256],1655:[[1735,1652],256],1656:[[1610,1652],256],1728:[[1749,1620]],1729:[,,{1620:1730}],1730:[[1729,1620]],1746:[,,{1620:1747}],1747:[[1746,1620]],1749:[,,{1620:1728}],1750:[,230],1751:[,230],1752:[,230],1753:[,230],1754:[,230],1755:[,230],1756:[,230],1759:[,230],1760:[,230],1761:[,230],1762:[,230],1763:[,220],1764:[,230],1767:[,230],1768:[,230],1770:[,220],1771:[,230],1772:[,230],1773:[,220]}, -1792:{1809:[,36],1840:[,230],1841:[,220],1842:[,230],1843:[,230],1844:[,220],1845:[,230],1846:[,230],1847:[,220],1848:[,220],1849:[,220],1850:[,230],1851:[,220],1852:[,220],1853:[,230],1854:[,220],1855:[,230],1856:[,230],1857:[,230],1858:[,220],1859:[,230],1860:[,220],1861:[,230],1862:[,220],1863:[,230],1864:[,220],1865:[,230],1866:[,230],2027:[,230],2028:[,230],2029:[,230],2030:[,230],2031:[,230],2032:[,230],2033:[,230],2034:[,220],2035:[,230]}, -2048:{2070:[,230],2071:[,230],2072:[,230],2073:[,230],2075:[,230],2076:[,230],2077:[,230],2078:[,230],2079:[,230],2080:[,230],2081:[,230],2082:[,230],2083:[,230],2085:[,230],2086:[,230],2087:[,230],2089:[,230],2090:[,230],2091:[,230],2092:[,230],2093:[,230],2137:[,220],2138:[,220],2139:[,220],2276:[,230],2277:[,230],2278:[,220],2279:[,230],2280:[,230],2281:[,220],2282:[,230],2283:[,230],2284:[,230],2285:[,220],2286:[,220],2287:[,220],2288:[,27],2289:[,28],2290:[,29],2291:[,230],2292:[,230],2293:[,230],2294:[,220],2295:[,230],2296:[,230],2297:[,220],2298:[,220],2299:[,230],2300:[,230],2301:[,230],2302:[,230],2303:[,230]}, -2304:{2344:[,,{2364:2345}],2345:[[2344,2364]],2352:[,,{2364:2353}],2353:[[2352,2364]],2355:[,,{2364:2356}],2356:[[2355,2364]],2364:[,7],2381:[,9],2385:[,230],2386:[,220],2387:[,230],2388:[,230],2392:[[2325,2364],512],2393:[[2326,2364],512],2394:[[2327,2364],512],2395:[[2332,2364],512],2396:[[2337,2364],512],2397:[[2338,2364],512],2398:[[2347,2364],512],2399:[[2351,2364],512],2492:[,7],2503:[,,{2494:2507,2519:2508}],2507:[[2503,2494]],2508:[[2503,2519]],2509:[,9],2524:[[2465,2492],512],2525:[[2466,2492],512],2527:[[2479,2492],512]}, -2560:{2611:[[2610,2620],512],2614:[[2616,2620],512],2620:[,7],2637:[,9],2649:[[2582,2620],512],2650:[[2583,2620],512],2651:[[2588,2620],512],2654:[[2603,2620],512],2748:[,7],2765:[,9],68109:[,220],68111:[,230],68152:[,230],68153:[,1],68154:[,220],68159:[,9],68325:[,230],68326:[,220]}, -2816:{2876:[,7],2887:[,,{2878:2891,2902:2888,2903:2892}],2888:[[2887,2902]],2891:[[2887,2878]],2892:[[2887,2903]],2893:[,9],2908:[[2849,2876],512],2909:[[2850,2876],512],2962:[,,{3031:2964}],2964:[[2962,3031]],3014:[,,{3006:3018,3031:3020}],3015:[,,{3006:3019}],3018:[[3014,3006]],3019:[[3015,3006]],3020:[[3014,3031]],3021:[,9]}, -3072:{3142:[,,{3158:3144}],3144:[[3142,3158]],3149:[,9],3157:[,84],3158:[,91],3260:[,7],3263:[,,{3285:3264}],3264:[[3263,3285]],3270:[,,{3266:3274,3285:3271,3286:3272}],3271:[[3270,3285]],3272:[[3270,3286]],3274:[[3270,3266],,{3285:3275}],3275:[[3274,3285]],3277:[,9]}, -3328:{3398:[,,{3390:3402,3415:3404}],3399:[,,{3390:3403}],3402:[[3398,3390]],3403:[[3399,3390]],3404:[[3398,3415]],3405:[,9],3530:[,9],3545:[,,{3530:3546,3535:3548,3551:3550}],3546:[[3545,3530]],3548:[[3545,3535],,{3530:3549}],3549:[[3548,3530]],3550:[[3545,3551]]}, -3584:{3635:[[3661,3634],256],3640:[,103],3641:[,103],3642:[,9],3656:[,107],3657:[,107],3658:[,107],3659:[,107],3763:[[3789,3762],256],3768:[,118],3769:[,118],3784:[,122],3785:[,122],3786:[,122],3787:[,122],3804:[[3755,3737],256],3805:[[3755,3745],256]}, -3840:{3852:[[3851],256],3864:[,220],3865:[,220],3893:[,220],3895:[,220],3897:[,216],3907:[[3906,4023],512],3917:[[3916,4023],512],3922:[[3921,4023],512],3927:[[3926,4023],512],3932:[[3931,4023],512],3945:[[3904,4021],512],3953:[,129],3954:[,130],3955:[[3953,3954],512],3956:[,132],3957:[[3953,3956],512],3958:[[4018,3968],512],3959:[[4018,3969],256],3960:[[4019,3968],512],3961:[[4019,3969],256],3962:[,130],3963:[,130],3964:[,130],3965:[,130],3968:[,130],3969:[[3953,3968],512],3970:[,230],3971:[,230],3972:[,9],3974:[,230],3975:[,230],3987:[[3986,4023],512],3997:[[3996,4023],512],4002:[[4001,4023],512],4007:[[4006,4023],512],4012:[[4011,4023],512],4025:[[3984,4021],512],4038:[,220]}, -4096:{4133:[,,{4142:4134}],4134:[[4133,4142]],4151:[,7],4153:[,9],4154:[,9],4237:[,220],4348:[[4316],256],69702:[,9],69759:[,9],69785:[,,{69818:69786}],69786:[[69785,69818]],69787:[,,{69818:69788}],69788:[[69787,69818]],69797:[,,{69818:69803}],69803:[[69797,69818]],69817:[,9],69818:[,7]}, -4352:{69888:[,230],69889:[,230],69890:[,230],69934:[[69937,69927]],69935:[[69938,69927]],69937:[,,{69927:69934}],69938:[,,{69927:69935}],69939:[,9],69940:[,9],70003:[,7],70080:[,9]}, -4608:{70197:[,9],70198:[,7],70377:[,7],70378:[,9]}, -4864:{4957:[,230],4958:[,230],4959:[,230],70460:[,7],70471:[,,{70462:70475,70487:70476}],70475:[[70471,70462]],70476:[[70471,70487]],70477:[,9],70502:[,230],70503:[,230],70504:[,230],70505:[,230],70506:[,230],70507:[,230],70508:[,230],70512:[,230],70513:[,230],70514:[,230],70515:[,230],70516:[,230]}, -5120:{70841:[,,{70832:70844,70842:70843,70845:70846}],70843:[[70841,70842]],70844:[[70841,70832]],70846:[[70841,70845]],70850:[,9],70851:[,7]}, -5376:{71096:[,,{71087:71098}],71097:[,,{71087:71099}],71098:[[71096,71087]],71099:[[71097,71087]],71103:[,9],71104:[,7]}, -5632:{71231:[,9],71350:[,9],71351:[,7]}, -5888:{5908:[,9],5940:[,9],6098:[,9],6109:[,230]}, -6144:{6313:[,228]}, -6400:{6457:[,222],6458:[,230],6459:[,220]}, -6656:{6679:[,230],6680:[,220],6752:[,9],6773:[,230],6774:[,230],6775:[,230],6776:[,230],6777:[,230],6778:[,230],6779:[,230],6780:[,230],6783:[,220],6832:[,230],6833:[,230],6834:[,230],6835:[,230],6836:[,230],6837:[,220],6838:[,220],6839:[,220],6840:[,220],6841:[,220],6842:[,220],6843:[,230],6844:[,230],6845:[,220]}, -6912:{6917:[,,{6965:6918}],6918:[[6917,6965]],6919:[,,{6965:6920}],6920:[[6919,6965]],6921:[,,{6965:6922}],6922:[[6921,6965]],6923:[,,{6965:6924}],6924:[[6923,6965]],6925:[,,{6965:6926}],6926:[[6925,6965]],6929:[,,{6965:6930}],6930:[[6929,6965]],6964:[,7],6970:[,,{6965:6971}],6971:[[6970,6965]],6972:[,,{6965:6973}],6973:[[6972,6965]],6974:[,,{6965:6976}],6975:[,,{6965:6977}],6976:[[6974,6965]],6977:[[6975,6965]],6978:[,,{6965:6979}],6979:[[6978,6965]],6980:[,9],7019:[,230],7020:[,220],7021:[,230],7022:[,230],7023:[,230],7024:[,230],7025:[,230],7026:[,230],7027:[,230],7082:[,9],7083:[,9],7142:[,7],7154:[,9],7155:[,9]}, -7168:{7223:[,7],7376:[,230],7377:[,230],7378:[,230],7380:[,1],7381:[,220],7382:[,220],7383:[,220],7384:[,220],7385:[,220],7386:[,230],7387:[,230],7388:[,220],7389:[,220],7390:[,220],7391:[,220],7392:[,230],7394:[,1],7395:[,1],7396:[,1],7397:[,1],7398:[,1],7399:[,1],7400:[,1],7405:[,220],7412:[,230],7416:[,230],7417:[,230]}, -7424:{7468:[[65],256],7469:[[198],256],7470:[[66],256],7472:[[68],256],7473:[[69],256],7474:[[398],256],7475:[[71],256],7476:[[72],256],7477:[[73],256],7478:[[74],256],7479:[[75],256],7480:[[76],256],7481:[[77],256],7482:[[78],256],7484:[[79],256],7485:[[546],256],7486:[[80],256],7487:[[82],256],7488:[[84],256],7489:[[85],256],7490:[[87],256],7491:[[97],256],7492:[[592],256],7493:[[593],256],7494:[[7426],256],7495:[[98],256],7496:[[100],256],7497:[[101],256],7498:[[601],256],7499:[[603],256],7500:[[604],256],7501:[[103],256],7503:[[107],256],7504:[[109],256],7505:[[331],256],7506:[[111],256],7507:[[596],256],7508:[[7446],256],7509:[[7447],256],7510:[[112],256],7511:[[116],256],7512:[[117],256],7513:[[7453],256],7514:[[623],256],7515:[[118],256],7516:[[7461],256],7517:[[946],256],7518:[[947],256],7519:[[948],256],7520:[[966],256],7521:[[967],256],7522:[[105],256],7523:[[114],256],7524:[[117],256],7525:[[118],256],7526:[[946],256],7527:[[947],256],7528:[[961],256],7529:[[966],256],7530:[[967],256],7544:[[1085],256],7579:[[594],256],7580:[[99],256],7581:[[597],256],7582:[[240],256],7583:[[604],256],7584:[[102],256],7585:[[607],256],7586:[[609],256],7587:[[613],256],7588:[[616],256],7589:[[617],256],7590:[[618],256],7591:[[7547],256],7592:[[669],256],7593:[[621],256],7594:[[7557],256],7595:[[671],256],7596:[[625],256],7597:[[624],256],7598:[[626],256],7599:[[627],256],7600:[[628],256],7601:[[629],256],7602:[[632],256],7603:[[642],256],7604:[[643],256],7605:[[427],256],7606:[[649],256],7607:[[650],256],7608:[[7452],256],7609:[[651],256],7610:[[652],256],7611:[[122],256],7612:[[656],256],7613:[[657],256],7614:[[658],256],7615:[[952],256],7616:[,230],7617:[,230],7618:[,220],7619:[,230],7620:[,230],7621:[,230],7622:[,230],7623:[,230],7624:[,230],7625:[,230],7626:[,220],7627:[,230],7628:[,230],7629:[,234],7630:[,214],7631:[,220],7632:[,202],7633:[,230],7634:[,230],7635:[,230],7636:[,230],7637:[,230],7638:[,230],7639:[,230],7640:[,230],7641:[,230],7642:[,230],7643:[,230],7644:[,230],7645:[,230],7646:[,230],7647:[,230],7648:[,230],7649:[,230],7650:[,230],7651:[,230],7652:[,230],7653:[,230],7654:[,230],7655:[,230],7656:[,230],7657:[,230],7658:[,230],7659:[,230],7660:[,230],7661:[,230],7662:[,230],7663:[,230],7664:[,230],7665:[,230],7666:[,230],7667:[,230],7668:[,230],7669:[,230],7676:[,233],7677:[,220],7678:[,230],7679:[,220]}, -7680:{7680:[[65,805]],7681:[[97,805]],7682:[[66,775]],7683:[[98,775]],7684:[[66,803]],7685:[[98,803]],7686:[[66,817]],7687:[[98,817]],7688:[[199,769]],7689:[[231,769]],7690:[[68,775]],7691:[[100,775]],7692:[[68,803]],7693:[[100,803]],7694:[[68,817]],7695:[[100,817]],7696:[[68,807]],7697:[[100,807]],7698:[[68,813]],7699:[[100,813]],7700:[[274,768]],7701:[[275,768]],7702:[[274,769]],7703:[[275,769]],7704:[[69,813]],7705:[[101,813]],7706:[[69,816]],7707:[[101,816]],7708:[[552,774]],7709:[[553,774]],7710:[[70,775]],7711:[[102,775]],7712:[[71,772]],7713:[[103,772]],7714:[[72,775]],7715:[[104,775]],7716:[[72,803]],7717:[[104,803]],7718:[[72,776]],7719:[[104,776]],7720:[[72,807]],7721:[[104,807]],7722:[[72,814]],7723:[[104,814]],7724:[[73,816]],7725:[[105,816]],7726:[[207,769]],7727:[[239,769]],7728:[[75,769]],7729:[[107,769]],7730:[[75,803]],7731:[[107,803]],7732:[[75,817]],7733:[[107,817]],7734:[[76,803],,{772:7736}],7735:[[108,803],,{772:7737}],7736:[[7734,772]],7737:[[7735,772]],7738:[[76,817]],7739:[[108,817]],7740:[[76,813]],7741:[[108,813]],7742:[[77,769]],7743:[[109,769]],7744:[[77,775]],7745:[[109,775]],7746:[[77,803]],7747:[[109,803]],7748:[[78,775]],7749:[[110,775]],7750:[[78,803]],7751:[[110,803]],7752:[[78,817]],7753:[[110,817]],7754:[[78,813]],7755:[[110,813]],7756:[[213,769]],7757:[[245,769]],7758:[[213,776]],7759:[[245,776]],7760:[[332,768]],7761:[[333,768]],7762:[[332,769]],7763:[[333,769]],7764:[[80,769]],7765:[[112,769]],7766:[[80,775]],7767:[[112,775]],7768:[[82,775]],7769:[[114,775]],7770:[[82,803],,{772:7772}],7771:[[114,803],,{772:7773}],7772:[[7770,772]],7773:[[7771,772]],7774:[[82,817]],7775:[[114,817]],7776:[[83,775]],7777:[[115,775]],7778:[[83,803],,{775:7784}],7779:[[115,803],,{775:7785}],7780:[[346,775]],7781:[[347,775]],7782:[[352,775]],7783:[[353,775]],7784:[[7778,775]],7785:[[7779,775]],7786:[[84,775]],7787:[[116,775]],7788:[[84,803]],7789:[[116,803]],7790:[[84,817]],7791:[[116,817]],7792:[[84,813]],7793:[[116,813]],7794:[[85,804]],7795:[[117,804]],7796:[[85,816]],7797:[[117,816]],7798:[[85,813]],7799:[[117,813]],7800:[[360,769]],7801:[[361,769]],7802:[[362,776]],7803:[[363,776]],7804:[[86,771]],7805:[[118,771]],7806:[[86,803]],7807:[[118,803]],7808:[[87,768]],7809:[[119,768]],7810:[[87,769]],7811:[[119,769]],7812:[[87,776]],7813:[[119,776]],7814:[[87,775]],7815:[[119,775]],7816:[[87,803]],7817:[[119,803]],7818:[[88,775]],7819:[[120,775]],7820:[[88,776]],7821:[[120,776]],7822:[[89,775]],7823:[[121,775]],7824:[[90,770]],7825:[[122,770]],7826:[[90,803]],7827:[[122,803]],7828:[[90,817]],7829:[[122,817]],7830:[[104,817]],7831:[[116,776]],7832:[[119,778]],7833:[[121,778]],7834:[[97,702],256],7835:[[383,775]],7840:[[65,803],,{770:7852,774:7862}],7841:[[97,803],,{770:7853,774:7863}],7842:[[65,777]],7843:[[97,777]],7844:[[194,769]],7845:[[226,769]],7846:[[194,768]],7847:[[226,768]],7848:[[194,777]],7849:[[226,777]],7850:[[194,771]],7851:[[226,771]],7852:[[7840,770]],7853:[[7841,770]],7854:[[258,769]],7855:[[259,769]],7856:[[258,768]],7857:[[259,768]],7858:[[258,777]],7859:[[259,777]],7860:[[258,771]],7861:[[259,771]],7862:[[7840,774]],7863:[[7841,774]],7864:[[69,803],,{770:7878}],7865:[[101,803],,{770:7879}],7866:[[69,777]],7867:[[101,777]],7868:[[69,771]],7869:[[101,771]],7870:[[202,769]],7871:[[234,769]],7872:[[202,768]],7873:[[234,768]],7874:[[202,777]],7875:[[234,777]],7876:[[202,771]],7877:[[234,771]],7878:[[7864,770]],7879:[[7865,770]],7880:[[73,777]],7881:[[105,777]],7882:[[73,803]],7883:[[105,803]],7884:[[79,803],,{770:7896}],7885:[[111,803],,{770:7897}],7886:[[79,777]],7887:[[111,777]],7888:[[212,769]],7889:[[244,769]],7890:[[212,768]],7891:[[244,768]],7892:[[212,777]],7893:[[244,777]],7894:[[212,771]],7895:[[244,771]],7896:[[7884,770]],7897:[[7885,770]],7898:[[416,769]],7899:[[417,769]],7900:[[416,768]],7901:[[417,768]],7902:[[416,777]],7903:[[417,777]],7904:[[416,771]],7905:[[417,771]],7906:[[416,803]],7907:[[417,803]],7908:[[85,803]],7909:[[117,803]],7910:[[85,777]],7911:[[117,777]],7912:[[431,769]],7913:[[432,769]],7914:[[431,768]],7915:[[432,768]],7916:[[431,777]],7917:[[432,777]],7918:[[431,771]],7919:[[432,771]],7920:[[431,803]],7921:[[432,803]],7922:[[89,768]],7923:[[121,768]],7924:[[89,803]],7925:[[121,803]],7926:[[89,777]],7927:[[121,777]],7928:[[89,771]],7929:[[121,771]]}, -7936:{7936:[[945,787],,{768:7938,769:7940,834:7942,837:8064}],7937:[[945,788],,{768:7939,769:7941,834:7943,837:8065}],7938:[[7936,768],,{837:8066}],7939:[[7937,768],,{837:8067}],7940:[[7936,769],,{837:8068}],7941:[[7937,769],,{837:8069}],7942:[[7936,834],,{837:8070}],7943:[[7937,834],,{837:8071}],7944:[[913,787],,{768:7946,769:7948,834:7950,837:8072}],7945:[[913,788],,{768:7947,769:7949,834:7951,837:8073}],7946:[[7944,768],,{837:8074}],7947:[[7945,768],,{837:8075}],7948:[[7944,769],,{837:8076}],7949:[[7945,769],,{837:8077}],7950:[[7944,834],,{837:8078}],7951:[[7945,834],,{837:8079}],7952:[[949,787],,{768:7954,769:7956}],7953:[[949,788],,{768:7955,769:7957}],7954:[[7952,768]],7955:[[7953,768]],7956:[[7952,769]],7957:[[7953,769]],7960:[[917,787],,{768:7962,769:7964}],7961:[[917,788],,{768:7963,769:7965}],7962:[[7960,768]],7963:[[7961,768]],7964:[[7960,769]],7965:[[7961,769]],7968:[[951,787],,{768:7970,769:7972,834:7974,837:8080}],7969:[[951,788],,{768:7971,769:7973,834:7975,837:8081}],7970:[[7968,768],,{837:8082}],7971:[[7969,768],,{837:8083}],7972:[[7968,769],,{837:8084}],7973:[[7969,769],,{837:8085}],7974:[[7968,834],,{837:8086}],7975:[[7969,834],,{837:8087}],7976:[[919,787],,{768:7978,769:7980,834:7982,837:8088}],7977:[[919,788],,{768:7979,769:7981,834:7983,837:8089}],7978:[[7976,768],,{837:8090}],7979:[[7977,768],,{837:8091}],7980:[[7976,769],,{837:8092}],7981:[[7977,769],,{837:8093}],7982:[[7976,834],,{837:8094}],7983:[[7977,834],,{837:8095}],7984:[[953,787],,{768:7986,769:7988,834:7990}],7985:[[953,788],,{768:7987,769:7989,834:7991}],7986:[[7984,768]],7987:[[7985,768]],7988:[[7984,769]],7989:[[7985,769]],7990:[[7984,834]],7991:[[7985,834]],7992:[[921,787],,{768:7994,769:7996,834:7998}],7993:[[921,788],,{768:7995,769:7997,834:7999}],7994:[[7992,768]],7995:[[7993,768]],7996:[[7992,769]],7997:[[7993,769]],7998:[[7992,834]],7999:[[7993,834]],8000:[[959,787],,{768:8002,769:8004}],8001:[[959,788],,{768:8003,769:8005}],8002:[[8000,768]],8003:[[8001,768]],8004:[[8000,769]],8005:[[8001,769]],8008:[[927,787],,{768:8010,769:8012}],8009:[[927,788],,{768:8011,769:8013}],8010:[[8008,768]],8011:[[8009,768]],8012:[[8008,769]],8013:[[8009,769]],8016:[[965,787],,{768:8018,769:8020,834:8022}],8017:[[965,788],,{768:8019,769:8021,834:8023}],8018:[[8016,768]],8019:[[8017,768]],8020:[[8016,769]],8021:[[8017,769]],8022:[[8016,834]],8023:[[8017,834]],8025:[[933,788],,{768:8027,769:8029,834:8031}],8027:[[8025,768]],8029:[[8025,769]],8031:[[8025,834]],8032:[[969,787],,{768:8034,769:8036,834:8038,837:8096}],8033:[[969,788],,{768:8035,769:8037,834:8039,837:8097}],8034:[[8032,768],,{837:8098}],8035:[[8033,768],,{837:8099}],8036:[[8032,769],,{837:8100}],8037:[[8033,769],,{837:8101}],8038:[[8032,834],,{837:8102}],8039:[[8033,834],,{837:8103}],8040:[[937,787],,{768:8042,769:8044,834:8046,837:8104}],8041:[[937,788],,{768:8043,769:8045,834:8047,837:8105}],8042:[[8040,768],,{837:8106}],8043:[[8041,768],,{837:8107}],8044:[[8040,769],,{837:8108}],8045:[[8041,769],,{837:8109}],8046:[[8040,834],,{837:8110}],8047:[[8041,834],,{837:8111}],8048:[[945,768],,{837:8114}],8049:[[940]],8050:[[949,768]],8051:[[941]],8052:[[951,768],,{837:8130}],8053:[[942]],8054:[[953,768]],8055:[[943]],8056:[[959,768]],8057:[[972]],8058:[[965,768]],8059:[[973]],8060:[[969,768],,{837:8178}],8061:[[974]],8064:[[7936,837]],8065:[[7937,837]],8066:[[7938,837]],8067:[[7939,837]],8068:[[7940,837]],8069:[[7941,837]],8070:[[7942,837]],8071:[[7943,837]],8072:[[7944,837]],8073:[[7945,837]],8074:[[7946,837]],8075:[[7947,837]],8076:[[7948,837]],8077:[[7949,837]],8078:[[7950,837]],8079:[[7951,837]],8080:[[7968,837]],8081:[[7969,837]],8082:[[7970,837]],8083:[[7971,837]],8084:[[7972,837]],8085:[[7973,837]],8086:[[7974,837]],8087:[[7975,837]],8088:[[7976,837]],8089:[[7977,837]],8090:[[7978,837]],8091:[[7979,837]],8092:[[7980,837]],8093:[[7981,837]],8094:[[7982,837]],8095:[[7983,837]],8096:[[8032,837]],8097:[[8033,837]],8098:[[8034,837]],8099:[[8035,837]],8100:[[8036,837]],8101:[[8037,837]],8102:[[8038,837]],8103:[[8039,837]],8104:[[8040,837]],8105:[[8041,837]],8106:[[8042,837]],8107:[[8043,837]],8108:[[8044,837]],8109:[[8045,837]],8110:[[8046,837]],8111:[[8047,837]],8112:[[945,774]],8113:[[945,772]],8114:[[8048,837]],8115:[[945,837]],8116:[[940,837]],8118:[[945,834],,{837:8119}],8119:[[8118,837]],8120:[[913,774]],8121:[[913,772]],8122:[[913,768]],8123:[[902]],8124:[[913,837]],8125:[[32,787],256],8126:[[953]],8127:[[32,787],256,{768:8141,769:8142,834:8143}],8128:[[32,834],256],8129:[[168,834]],8130:[[8052,837]],8131:[[951,837]],8132:[[942,837]],8134:[[951,834],,{837:8135}],8135:[[8134,837]],8136:[[917,768]],8137:[[904]],8138:[[919,768]],8139:[[905]],8140:[[919,837]],8141:[[8127,768]],8142:[[8127,769]],8143:[[8127,834]],8144:[[953,774]],8145:[[953,772]],8146:[[970,768]],8147:[[912]],8150:[[953,834]],8151:[[970,834]],8152:[[921,774]],8153:[[921,772]],8154:[[921,768]],8155:[[906]],8157:[[8190,768]],8158:[[8190,769]],8159:[[8190,834]],8160:[[965,774]],8161:[[965,772]],8162:[[971,768]],8163:[[944]],8164:[[961,787]],8165:[[961,788]],8166:[[965,834]],8167:[[971,834]],8168:[[933,774]],8169:[[933,772]],8170:[[933,768]],8171:[[910]],8172:[[929,788]],8173:[[168,768]],8174:[[901]],8175:[[96]],8178:[[8060,837]],8179:[[969,837]],8180:[[974,837]],8182:[[969,834],,{837:8183}],8183:[[8182,837]],8184:[[927,768]],8185:[[908]],8186:[[937,768]],8187:[[911]],8188:[[937,837]],8189:[[180]],8190:[[32,788],256,{768:8157,769:8158,834:8159}]}, -8192:{8192:[[8194]],8193:[[8195]],8194:[[32],256],8195:[[32],256],8196:[[32],256],8197:[[32],256],8198:[[32],256],8199:[[32],256],8200:[[32],256],8201:[[32],256],8202:[[32],256],8209:[[8208],256],8215:[[32,819],256],8228:[[46],256],8229:[[46,46],256],8230:[[46,46,46],256],8239:[[32],256],8243:[[8242,8242],256],8244:[[8242,8242,8242],256],8246:[[8245,8245],256],8247:[[8245,8245,8245],256],8252:[[33,33],256],8254:[[32,773],256],8263:[[63,63],256],8264:[[63,33],256],8265:[[33,63],256],8279:[[8242,8242,8242,8242],256],8287:[[32],256],8304:[[48],256],8305:[[105],256],8308:[[52],256],8309:[[53],256],8310:[[54],256],8311:[[55],256],8312:[[56],256],8313:[[57],256],8314:[[43],256],8315:[[8722],256],8316:[[61],256],8317:[[40],256],8318:[[41],256],8319:[[110],256],8320:[[48],256],8321:[[49],256],8322:[[50],256],8323:[[51],256],8324:[[52],256],8325:[[53],256],8326:[[54],256],8327:[[55],256],8328:[[56],256],8329:[[57],256],8330:[[43],256],8331:[[8722],256],8332:[[61],256],8333:[[40],256],8334:[[41],256],8336:[[97],256],8337:[[101],256],8338:[[111],256],8339:[[120],256],8340:[[601],256],8341:[[104],256],8342:[[107],256],8343:[[108],256],8344:[[109],256],8345:[[110],256],8346:[[112],256],8347:[[115],256],8348:[[116],256],8360:[[82,115],256],8400:[,230],8401:[,230],8402:[,1],8403:[,1],8404:[,230],8405:[,230],8406:[,230],8407:[,230],8408:[,1],8409:[,1],8410:[,1],8411:[,230],8412:[,230],8417:[,230],8421:[,1],8422:[,1],8423:[,230],8424:[,220],8425:[,230],8426:[,1],8427:[,1],8428:[,220],8429:[,220],8430:[,220],8431:[,220],8432:[,230]}, -8448:{8448:[[97,47,99],256],8449:[[97,47,115],256],8450:[[67],256],8451:[[176,67],256],8453:[[99,47,111],256],8454:[[99,47,117],256],8455:[[400],256],8457:[[176,70],256],8458:[[103],256],8459:[[72],256],8460:[[72],256],8461:[[72],256],8462:[[104],256],8463:[[295],256],8464:[[73],256],8465:[[73],256],8466:[[76],256],8467:[[108],256],8469:[[78],256],8470:[[78,111],256],8473:[[80],256],8474:[[81],256],8475:[[82],256],8476:[[82],256],8477:[[82],256],8480:[[83,77],256],8481:[[84,69,76],256],8482:[[84,77],256],8484:[[90],256],8486:[[937]],8488:[[90],256],8490:[[75]],8491:[[197]],8492:[[66],256],8493:[[67],256],8495:[[101],256],8496:[[69],256],8497:[[70],256],8499:[[77],256],8500:[[111],256],8501:[[1488],256],8502:[[1489],256],8503:[[1490],256],8504:[[1491],256],8505:[[105],256],8507:[[70,65,88],256],8508:[[960],256],8509:[[947],256],8510:[[915],256],8511:[[928],256],8512:[[8721],256],8517:[[68],256],8518:[[100],256],8519:[[101],256],8520:[[105],256],8521:[[106],256],8528:[[49,8260,55],256],8529:[[49,8260,57],256],8530:[[49,8260,49,48],256],8531:[[49,8260,51],256],8532:[[50,8260,51],256],8533:[[49,8260,53],256],8534:[[50,8260,53],256],8535:[[51,8260,53],256],8536:[[52,8260,53],256],8537:[[49,8260,54],256],8538:[[53,8260,54],256],8539:[[49,8260,56],256],8540:[[51,8260,56],256],8541:[[53,8260,56],256],8542:[[55,8260,56],256],8543:[[49,8260],256],8544:[[73],256],8545:[[73,73],256],8546:[[73,73,73],256],8547:[[73,86],256],8548:[[86],256],8549:[[86,73],256],8550:[[86,73,73],256],8551:[[86,73,73,73],256],8552:[[73,88],256],8553:[[88],256],8554:[[88,73],256],8555:[[88,73,73],256],8556:[[76],256],8557:[[67],256],8558:[[68],256],8559:[[77],256],8560:[[105],256],8561:[[105,105],256],8562:[[105,105,105],256],8563:[[105,118],256],8564:[[118],256],8565:[[118,105],256],8566:[[118,105,105],256],8567:[[118,105,105,105],256],8568:[[105,120],256],8569:[[120],256],8570:[[120,105],256],8571:[[120,105,105],256],8572:[[108],256],8573:[[99],256],8574:[[100],256],8575:[[109],256],8585:[[48,8260,51],256],8592:[,,{824:8602}],8594:[,,{824:8603}],8596:[,,{824:8622}],8602:[[8592,824]],8603:[[8594,824]],8622:[[8596,824]],8653:[[8656,824]],8654:[[8660,824]],8655:[[8658,824]],8656:[,,{824:8653}],8658:[,,{824:8655}],8660:[,,{824:8654}]}, -8704:{8707:[,,{824:8708}],8708:[[8707,824]],8712:[,,{824:8713}],8713:[[8712,824]],8715:[,,{824:8716}],8716:[[8715,824]],8739:[,,{824:8740}],8740:[[8739,824]],8741:[,,{824:8742}],8742:[[8741,824]],8748:[[8747,8747],256],8749:[[8747,8747,8747],256],8751:[[8750,8750],256],8752:[[8750,8750,8750],256],8764:[,,{824:8769}],8769:[[8764,824]],8771:[,,{824:8772}],8772:[[8771,824]],8773:[,,{824:8775}],8775:[[8773,824]],8776:[,,{824:8777}],8777:[[8776,824]],8781:[,,{824:8813}],8800:[[61,824]],8801:[,,{824:8802}],8802:[[8801,824]],8804:[,,{824:8816}],8805:[,,{824:8817}],8813:[[8781,824]],8814:[[60,824]],8815:[[62,824]],8816:[[8804,824]],8817:[[8805,824]],8818:[,,{824:8820}],8819:[,,{824:8821}],8820:[[8818,824]],8821:[[8819,824]],8822:[,,{824:8824}],8823:[,,{824:8825}],8824:[[8822,824]],8825:[[8823,824]],8826:[,,{824:8832}],8827:[,,{824:8833}],8828:[,,{824:8928}],8829:[,,{824:8929}],8832:[[8826,824]],8833:[[8827,824]],8834:[,,{824:8836}],8835:[,,{824:8837}],8836:[[8834,824]],8837:[[8835,824]],8838:[,,{824:8840}],8839:[,,{824:8841}],8840:[[8838,824]],8841:[[8839,824]],8849:[,,{824:8930}],8850:[,,{824:8931}],8866:[,,{824:8876}],8872:[,,{824:8877}],8873:[,,{824:8878}],8875:[,,{824:8879}],8876:[[8866,824]],8877:[[8872,824]],8878:[[8873,824]],8879:[[8875,824]],8882:[,,{824:8938}],8883:[,,{824:8939}],8884:[,,{824:8940}],8885:[,,{824:8941}],8928:[[8828,824]],8929:[[8829,824]],8930:[[8849,824]],8931:[[8850,824]],8938:[[8882,824]],8939:[[8883,824]],8940:[[8884,824]],8941:[[8885,824]]}, -8960:{9001:[[12296]],9002:[[12297]]}, -9216:{9312:[[49],256],9313:[[50],256],9314:[[51],256],9315:[[52],256],9316:[[53],256],9317:[[54],256],9318:[[55],256],9319:[[56],256],9320:[[57],256],9321:[[49,48],256],9322:[[49,49],256],9323:[[49,50],256],9324:[[49,51],256],9325:[[49,52],256],9326:[[49,53],256],9327:[[49,54],256],9328:[[49,55],256],9329:[[49,56],256],9330:[[49,57],256],9331:[[50,48],256],9332:[[40,49,41],256],9333:[[40,50,41],256],9334:[[40,51,41],256],9335:[[40,52,41],256],9336:[[40,53,41],256],9337:[[40,54,41],256],9338:[[40,55,41],256],9339:[[40,56,41],256],9340:[[40,57,41],256],9341:[[40,49,48,41],256],9342:[[40,49,49,41],256],9343:[[40,49,50,41],256],9344:[[40,49,51,41],256],9345:[[40,49,52,41],256],9346:[[40,49,53,41],256],9347:[[40,49,54,41],256],9348:[[40,49,55,41],256],9349:[[40,49,56,41],256],9350:[[40,49,57,41],256],9351:[[40,50,48,41],256],9352:[[49,46],256],9353:[[50,46],256],9354:[[51,46],256],9355:[[52,46],256],9356:[[53,46],256],9357:[[54,46],256],9358:[[55,46],256],9359:[[56,46],256],9360:[[57,46],256],9361:[[49,48,46],256],9362:[[49,49,46],256],9363:[[49,50,46],256],9364:[[49,51,46],256],9365:[[49,52,46],256],9366:[[49,53,46],256],9367:[[49,54,46],256],9368:[[49,55,46],256],9369:[[49,56,46],256],9370:[[49,57,46],256],9371:[[50,48,46],256],9372:[[40,97,41],256],9373:[[40,98,41],256],9374:[[40,99,41],256],9375:[[40,100,41],256],9376:[[40,101,41],256],9377:[[40,102,41],256],9378:[[40,103,41],256],9379:[[40,104,41],256],9380:[[40,105,41],256],9381:[[40,106,41],256],9382:[[40,107,41],256],9383:[[40,108,41],256],9384:[[40,109,41],256],9385:[[40,110,41],256],9386:[[40,111,41],256],9387:[[40,112,41],256],9388:[[40,113,41],256],9389:[[40,114,41],256],9390:[[40,115,41],256],9391:[[40,116,41],256],9392:[[40,117,41],256],9393:[[40,118,41],256],9394:[[40,119,41],256],9395:[[40,120,41],256],9396:[[40,121,41],256],9397:[[40,122,41],256],9398:[[65],256],9399:[[66],256],9400:[[67],256],9401:[[68],256],9402:[[69],256],9403:[[70],256],9404:[[71],256],9405:[[72],256],9406:[[73],256],9407:[[74],256],9408:[[75],256],9409:[[76],256],9410:[[77],256],9411:[[78],256],9412:[[79],256],9413:[[80],256],9414:[[81],256],9415:[[82],256],9416:[[83],256],9417:[[84],256],9418:[[85],256],9419:[[86],256],9420:[[87],256],9421:[[88],256],9422:[[89],256],9423:[[90],256],9424:[[97],256],9425:[[98],256],9426:[[99],256],9427:[[100],256],9428:[[101],256],9429:[[102],256],9430:[[103],256],9431:[[104],256],9432:[[105],256],9433:[[106],256],9434:[[107],256],9435:[[108],256],9436:[[109],256],9437:[[110],256],9438:[[111],256],9439:[[112],256],9440:[[113],256],9441:[[114],256],9442:[[115],256],9443:[[116],256],9444:[[117],256],9445:[[118],256],9446:[[119],256],9447:[[120],256],9448:[[121],256],9449:[[122],256],9450:[[48],256]}, -10752:{10764:[[8747,8747,8747,8747],256],10868:[[58,58,61],256],10869:[[61,61],256],10870:[[61,61,61],256],10972:[[10973,824],512]}, -11264:{11388:[[106],256],11389:[[86],256],11503:[,230],11504:[,230],11505:[,230]}, -11520:{11631:[[11617],256],11647:[,9],11744:[,230],11745:[,230],11746:[,230],11747:[,230],11748:[,230],11749:[,230],11750:[,230],11751:[,230],11752:[,230],11753:[,230],11754:[,230],11755:[,230],11756:[,230],11757:[,230],11758:[,230],11759:[,230],11760:[,230],11761:[,230],11762:[,230],11763:[,230],11764:[,230],11765:[,230],11766:[,230],11767:[,230],11768:[,230],11769:[,230],11770:[,230],11771:[,230],11772:[,230],11773:[,230],11774:[,230],11775:[,230]}, -11776:{11935:[[27597],256],12019:[[40863],256]}, -12032:{12032:[[19968],256],12033:[[20008],256],12034:[[20022],256],12035:[[20031],256],12036:[[20057],256],12037:[[20101],256],12038:[[20108],256],12039:[[20128],256],12040:[[20154],256],12041:[[20799],256],12042:[[20837],256],12043:[[20843],256],12044:[[20866],256],12045:[[20886],256],12046:[[20907],256],12047:[[20960],256],12048:[[20981],256],12049:[[20992],256],12050:[[21147],256],12051:[[21241],256],12052:[[21269],256],12053:[[21274],256],12054:[[21304],256],12055:[[21313],256],12056:[[21340],256],12057:[[21353],256],12058:[[21378],256],12059:[[21430],256],12060:[[21448],256],12061:[[21475],256],12062:[[22231],256],12063:[[22303],256],12064:[[22763],256],12065:[[22786],256],12066:[[22794],256],12067:[[22805],256],12068:[[22823],256],12069:[[22899],256],12070:[[23376],256],12071:[[23424],256],12072:[[23544],256],12073:[[23567],256],12074:[[23586],256],12075:[[23608],256],12076:[[23662],256],12077:[[23665],256],12078:[[24027],256],12079:[[24037],256],12080:[[24049],256],12081:[[24062],256],12082:[[24178],256],12083:[[24186],256],12084:[[24191],256],12085:[[24308],256],12086:[[24318],256],12087:[[24331],256],12088:[[24339],256],12089:[[24400],256],12090:[[24417],256],12091:[[24435],256],12092:[[24515],256],12093:[[25096],256],12094:[[25142],256],12095:[[25163],256],12096:[[25903],256],12097:[[25908],256],12098:[[25991],256],12099:[[26007],256],12100:[[26020],256],12101:[[26041],256],12102:[[26080],256],12103:[[26085],256],12104:[[26352],256],12105:[[26376],256],12106:[[26408],256],12107:[[27424],256],12108:[[27490],256],12109:[[27513],256],12110:[[27571],256],12111:[[27595],256],12112:[[27604],256],12113:[[27611],256],12114:[[27663],256],12115:[[27668],256],12116:[[27700],256],12117:[[28779],256],12118:[[29226],256],12119:[[29238],256],12120:[[29243],256],12121:[[29247],256],12122:[[29255],256],12123:[[29273],256],12124:[[29275],256],12125:[[29356],256],12126:[[29572],256],12127:[[29577],256],12128:[[29916],256],12129:[[29926],256],12130:[[29976],256],12131:[[29983],256],12132:[[29992],256],12133:[[30000],256],12134:[[30091],256],12135:[[30098],256],12136:[[30326],256],12137:[[30333],256],12138:[[30382],256],12139:[[30399],256],12140:[[30446],256],12141:[[30683],256],12142:[[30690],256],12143:[[30707],256],12144:[[31034],256],12145:[[31160],256],12146:[[31166],256],12147:[[31348],256],12148:[[31435],256],12149:[[31481],256],12150:[[31859],256],12151:[[31992],256],12152:[[32566],256],12153:[[32593],256],12154:[[32650],256],12155:[[32701],256],12156:[[32769],256],12157:[[32780],256],12158:[[32786],256],12159:[[32819],256],12160:[[32895],256],12161:[[32905],256],12162:[[33251],256],12163:[[33258],256],12164:[[33267],256],12165:[[33276],256],12166:[[33292],256],12167:[[33307],256],12168:[[33311],256],12169:[[33390],256],12170:[[33394],256],12171:[[33400],256],12172:[[34381],256],12173:[[34411],256],12174:[[34880],256],12175:[[34892],256],12176:[[34915],256],12177:[[35198],256],12178:[[35211],256],12179:[[35282],256],12180:[[35328],256],12181:[[35895],256],12182:[[35910],256],12183:[[35925],256],12184:[[35960],256],12185:[[35997],256],12186:[[36196],256],12187:[[36208],256],12188:[[36275],256],12189:[[36523],256],12190:[[36554],256],12191:[[36763],256],12192:[[36784],256],12193:[[36789],256],12194:[[37009],256],12195:[[37193],256],12196:[[37318],256],12197:[[37324],256],12198:[[37329],256],12199:[[38263],256],12200:[[38272],256],12201:[[38428],256],12202:[[38582],256],12203:[[38585],256],12204:[[38632],256],12205:[[38737],256],12206:[[38750],256],12207:[[38754],256],12208:[[38761],256],12209:[[38859],256],12210:[[38893],256],12211:[[38899],256],12212:[[38913],256],12213:[[39080],256],12214:[[39131],256],12215:[[39135],256],12216:[[39318],256],12217:[[39321],256],12218:[[39340],256],12219:[[39592],256],12220:[[39640],256],12221:[[39647],256],12222:[[39717],256],12223:[[39727],256],12224:[[39730],256],12225:[[39740],256],12226:[[39770],256],12227:[[40165],256],12228:[[40565],256],12229:[[40575],256],12230:[[40613],256],12231:[[40635],256],12232:[[40643],256],12233:[[40653],256],12234:[[40657],256],12235:[[40697],256],12236:[[40701],256],12237:[[40718],256],12238:[[40723],256],12239:[[40736],256],12240:[[40763],256],12241:[[40778],256],12242:[[40786],256],12243:[[40845],256],12244:[[40860],256],12245:[[40864],256]}, -12288:{12288:[[32],256],12330:[,218],12331:[,228],12332:[,232],12333:[,222],12334:[,224],12335:[,224],12342:[[12306],256],12344:[[21313],256],12345:[[21316],256],12346:[[21317],256],12358:[,,{12441:12436}],12363:[,,{12441:12364}],12364:[[12363,12441]],12365:[,,{12441:12366}],12366:[[12365,12441]],12367:[,,{12441:12368}],12368:[[12367,12441]],12369:[,,{12441:12370}],12370:[[12369,12441]],12371:[,,{12441:12372}],12372:[[12371,12441]],12373:[,,{12441:12374}],12374:[[12373,12441]],12375:[,,{12441:12376}],12376:[[12375,12441]],12377:[,,{12441:12378}],12378:[[12377,12441]],12379:[,,{12441:12380}],12380:[[12379,12441]],12381:[,,{12441:12382}],12382:[[12381,12441]],12383:[,,{12441:12384}],12384:[[12383,12441]],12385:[,,{12441:12386}],12386:[[12385,12441]],12388:[,,{12441:12389}],12389:[[12388,12441]],12390:[,,{12441:12391}],12391:[[12390,12441]],12392:[,,{12441:12393}],12393:[[12392,12441]],12399:[,,{12441:12400,12442:12401}],12400:[[12399,12441]],12401:[[12399,12442]],12402:[,,{12441:12403,12442:12404}],12403:[[12402,12441]],12404:[[12402,12442]],12405:[,,{12441:12406,12442:12407}],12406:[[12405,12441]],12407:[[12405,12442]],12408:[,,{12441:12409,12442:12410}],12409:[[12408,12441]],12410:[[12408,12442]],12411:[,,{12441:12412,12442:12413}],12412:[[12411,12441]],12413:[[12411,12442]],12436:[[12358,12441]],12441:[,8],12442:[,8],12443:[[32,12441],256],12444:[[32,12442],256],12445:[,,{12441:12446}],12446:[[12445,12441]],12447:[[12424,12426],256],12454:[,,{12441:12532}],12459:[,,{12441:12460}],12460:[[12459,12441]],12461:[,,{12441:12462}],12462:[[12461,12441]],12463:[,,{12441:12464}],12464:[[12463,12441]],12465:[,,{12441:12466}],12466:[[12465,12441]],12467:[,,{12441:12468}],12468:[[12467,12441]],12469:[,,{12441:12470}],12470:[[12469,12441]],12471:[,,{12441:12472}],12472:[[12471,12441]],12473:[,,{12441:12474}],12474:[[12473,12441]],12475:[,,{12441:12476}],12476:[[12475,12441]],12477:[,,{12441:12478}],12478:[[12477,12441]],12479:[,,{12441:12480}],12480:[[12479,12441]],12481:[,,{12441:12482}],12482:[[12481,12441]],12484:[,,{12441:12485}],12485:[[12484,12441]],12486:[,,{12441:12487}],12487:[[12486,12441]],12488:[,,{12441:12489}],12489:[[12488,12441]],12495:[,,{12441:12496,12442:12497}],12496:[[12495,12441]],12497:[[12495,12442]],12498:[,,{12441:12499,12442:12500}],12499:[[12498,12441]],12500:[[12498,12442]],12501:[,,{12441:12502,12442:12503}],12502:[[12501,12441]],12503:[[12501,12442]],12504:[,,{12441:12505,12442:12506}],12505:[[12504,12441]],12506:[[12504,12442]],12507:[,,{12441:12508,12442:12509}],12508:[[12507,12441]],12509:[[12507,12442]],12527:[,,{12441:12535}],12528:[,,{12441:12536}],12529:[,,{12441:12537}],12530:[,,{12441:12538}],12532:[[12454,12441]],12535:[[12527,12441]],12536:[[12528,12441]],12537:[[12529,12441]],12538:[[12530,12441]],12541:[,,{12441:12542}],12542:[[12541,12441]],12543:[[12467,12488],256]}, -12544:{12593:[[4352],256],12594:[[4353],256],12595:[[4522],256],12596:[[4354],256],12597:[[4524],256],12598:[[4525],256],12599:[[4355],256],12600:[[4356],256],12601:[[4357],256],12602:[[4528],256],12603:[[4529],256],12604:[[4530],256],12605:[[4531],256],12606:[[4532],256],12607:[[4533],256],12608:[[4378],256],12609:[[4358],256],12610:[[4359],256],12611:[[4360],256],12612:[[4385],256],12613:[[4361],256],12614:[[4362],256],12615:[[4363],256],12616:[[4364],256],12617:[[4365],256],12618:[[4366],256],12619:[[4367],256],12620:[[4368],256],12621:[[4369],256],12622:[[4370],256],12623:[[4449],256],12624:[[4450],256],12625:[[4451],256],12626:[[4452],256],12627:[[4453],256],12628:[[4454],256],12629:[[4455],256],12630:[[4456],256],12631:[[4457],256],12632:[[4458],256],12633:[[4459],256],12634:[[4460],256],12635:[[4461],256],12636:[[4462],256],12637:[[4463],256],12638:[[4464],256],12639:[[4465],256],12640:[[4466],256],12641:[[4467],256],12642:[[4468],256],12643:[[4469],256],12644:[[4448],256],12645:[[4372],256],12646:[[4373],256],12647:[[4551],256],12648:[[4552],256],12649:[[4556],256],12650:[[4558],256],12651:[[4563],256],12652:[[4567],256],12653:[[4569],256],12654:[[4380],256],12655:[[4573],256],12656:[[4575],256],12657:[[4381],256],12658:[[4382],256],12659:[[4384],256],12660:[[4386],256],12661:[[4387],256],12662:[[4391],256],12663:[[4393],256],12664:[[4395],256],12665:[[4396],256],12666:[[4397],256],12667:[[4398],256],12668:[[4399],256],12669:[[4402],256],12670:[[4406],256],12671:[[4416],256],12672:[[4423],256],12673:[[4428],256],12674:[[4593],256],12675:[[4594],256],12676:[[4439],256],12677:[[4440],256],12678:[[4441],256],12679:[[4484],256],12680:[[4485],256],12681:[[4488],256],12682:[[4497],256],12683:[[4498],256],12684:[[4500],256],12685:[[4510],256],12686:[[4513],256],12690:[[19968],256],12691:[[20108],256],12692:[[19977],256],12693:[[22235],256],12694:[[19978],256],12695:[[20013],256],12696:[[19979],256],12697:[[30002],256],12698:[[20057],256],12699:[[19993],256],12700:[[19969],256],12701:[[22825],256],12702:[[22320],256],12703:[[20154],256]}, -12800:{12800:[[40,4352,41],256],12801:[[40,4354,41],256],12802:[[40,4355,41],256],12803:[[40,4357,41],256],12804:[[40,4358,41],256],12805:[[40,4359,41],256],12806:[[40,4361,41],256],12807:[[40,4363,41],256],12808:[[40,4364,41],256],12809:[[40,4366,41],256],12810:[[40,4367,41],256],12811:[[40,4368,41],256],12812:[[40,4369,41],256],12813:[[40,4370,41],256],12814:[[40,4352,4449,41],256],12815:[[40,4354,4449,41],256],12816:[[40,4355,4449,41],256],12817:[[40,4357,4449,41],256],12818:[[40,4358,4449,41],256],12819:[[40,4359,4449,41],256],12820:[[40,4361,4449,41],256],12821:[[40,4363,4449,41],256],12822:[[40,4364,4449,41],256],12823:[[40,4366,4449,41],256],12824:[[40,4367,4449,41],256],12825:[[40,4368,4449,41],256],12826:[[40,4369,4449,41],256],12827:[[40,4370,4449,41],256],12828:[[40,4364,4462,41],256],12829:[[40,4363,4457,4364,4453,4523,41],256],12830:[[40,4363,4457,4370,4462,41],256],12832:[[40,19968,41],256],12833:[[40,20108,41],256],12834:[[40,19977,41],256],12835:[[40,22235,41],256],12836:[[40,20116,41],256],12837:[[40,20845,41],256],12838:[[40,19971,41],256],12839:[[40,20843,41],256],12840:[[40,20061,41],256],12841:[[40,21313,41],256],12842:[[40,26376,41],256],12843:[[40,28779,41],256],12844:[[40,27700,41],256],12845:[[40,26408,41],256],12846:[[40,37329,41],256],12847:[[40,22303,41],256],12848:[[40,26085,41],256],12849:[[40,26666,41],256],12850:[[40,26377,41],256],12851:[[40,31038,41],256],12852:[[40,21517,41],256],12853:[[40,29305,41],256],12854:[[40,36001,41],256],12855:[[40,31069,41],256],12856:[[40,21172,41],256],12857:[[40,20195,41],256],12858:[[40,21628,41],256],12859:[[40,23398,41],256],12860:[[40,30435,41],256],12861:[[40,20225,41],256],12862:[[40,36039,41],256],12863:[[40,21332,41],256],12864:[[40,31085,41],256],12865:[[40,20241,41],256],12866:[[40,33258,41],256],12867:[[40,33267,41],256],12868:[[21839],256],12869:[[24188],256],12870:[[25991],256],12871:[[31631],256],12880:[[80,84,69],256],12881:[[50,49],256],12882:[[50,50],256],12883:[[50,51],256],12884:[[50,52],256],12885:[[50,53],256],12886:[[50,54],256],12887:[[50,55],256],12888:[[50,56],256],12889:[[50,57],256],12890:[[51,48],256],12891:[[51,49],256],12892:[[51,50],256],12893:[[51,51],256],12894:[[51,52],256],12895:[[51,53],256],12896:[[4352],256],12897:[[4354],256],12898:[[4355],256],12899:[[4357],256],12900:[[4358],256],12901:[[4359],256],12902:[[4361],256],12903:[[4363],256],12904:[[4364],256],12905:[[4366],256],12906:[[4367],256],12907:[[4368],256],12908:[[4369],256],12909:[[4370],256],12910:[[4352,4449],256],12911:[[4354,4449],256],12912:[[4355,4449],256],12913:[[4357,4449],256],12914:[[4358,4449],256],12915:[[4359,4449],256],12916:[[4361,4449],256],12917:[[4363,4449],256],12918:[[4364,4449],256],12919:[[4366,4449],256],12920:[[4367,4449],256],12921:[[4368,4449],256],12922:[[4369,4449],256],12923:[[4370,4449],256],12924:[[4366,4449,4535,4352,4457],256],12925:[[4364,4462,4363,4468],256],12926:[[4363,4462],256],12928:[[19968],256],12929:[[20108],256],12930:[[19977],256],12931:[[22235],256],12932:[[20116],256],12933:[[20845],256],12934:[[19971],256],12935:[[20843],256],12936:[[20061],256],12937:[[21313],256],12938:[[26376],256],12939:[[28779],256],12940:[[27700],256],12941:[[26408],256],12942:[[37329],256],12943:[[22303],256],12944:[[26085],256],12945:[[26666],256],12946:[[26377],256],12947:[[31038],256],12948:[[21517],256],12949:[[29305],256],12950:[[36001],256],12951:[[31069],256],12952:[[21172],256],12953:[[31192],256],12954:[[30007],256],12955:[[22899],256],12956:[[36969],256],12957:[[20778],256],12958:[[21360],256],12959:[[27880],256],12960:[[38917],256],12961:[[20241],256],12962:[[20889],256],12963:[[27491],256],12964:[[19978],256],12965:[[20013],256],12966:[[19979],256],12967:[[24038],256],12968:[[21491],256],12969:[[21307],256],12970:[[23447],256],12971:[[23398],256],12972:[[30435],256],12973:[[20225],256],12974:[[36039],256],12975:[[21332],256],12976:[[22812],256],12977:[[51,54],256],12978:[[51,55],256],12979:[[51,56],256],12980:[[51,57],256],12981:[[52,48],256],12982:[[52,49],256],12983:[[52,50],256],12984:[[52,51],256],12985:[[52,52],256],12986:[[52,53],256],12987:[[52,54],256],12988:[[52,55],256],12989:[[52,56],256],12990:[[52,57],256],12991:[[53,48],256],12992:[[49,26376],256],12993:[[50,26376],256],12994:[[51,26376],256],12995:[[52,26376],256],12996:[[53,26376],256],12997:[[54,26376],256],12998:[[55,26376],256],12999:[[56,26376],256],13000:[[57,26376],256],13001:[[49,48,26376],256],13002:[[49,49,26376],256],13003:[[49,50,26376],256],13004:[[72,103],256],13005:[[101,114,103],256],13006:[[101,86],256],13007:[[76,84,68],256],13008:[[12450],256],13009:[[12452],256],13010:[[12454],256],13011:[[12456],256],13012:[[12458],256],13013:[[12459],256],13014:[[12461],256],13015:[[12463],256],13016:[[12465],256],13017:[[12467],256],13018:[[12469],256],13019:[[12471],256],13020:[[12473],256],13021:[[12475],256],13022:[[12477],256],13023:[[12479],256],13024:[[12481],256],13025:[[12484],256],13026:[[12486],256],13027:[[12488],256],13028:[[12490],256],13029:[[12491],256],13030:[[12492],256],13031:[[12493],256],13032:[[12494],256],13033:[[12495],256],13034:[[12498],256],13035:[[12501],256],13036:[[12504],256],13037:[[12507],256],13038:[[12510],256],13039:[[12511],256],13040:[[12512],256],13041:[[12513],256],13042:[[12514],256],13043:[[12516],256],13044:[[12518],256],13045:[[12520],256],13046:[[12521],256],13047:[[12522],256],13048:[[12523],256],13049:[[12524],256],13050:[[12525],256],13051:[[12527],256],13052:[[12528],256],13053:[[12529],256],13054:[[12530],256]}, -13056:{13056:[[12450,12497,12540,12488],256],13057:[[12450,12523,12501,12449],256],13058:[[12450,12531,12506,12450],256],13059:[[12450,12540,12523],256],13060:[[12452,12491,12531,12464],256],13061:[[12452,12531,12481],256],13062:[[12454,12457,12531],256],13063:[[12456,12473,12463,12540,12489],256],13064:[[12456,12540,12459,12540],256],13065:[[12458,12531,12473],256],13066:[[12458,12540,12512],256],13067:[[12459,12452,12522],256],13068:[[12459,12521,12483,12488],256],13069:[[12459,12525,12522,12540],256],13070:[[12460,12525,12531],256],13071:[[12460,12531,12510],256],13072:[[12462,12460],256],13073:[[12462,12491,12540],256],13074:[[12461,12517,12522,12540],256],13075:[[12462,12523,12480,12540],256],13076:[[12461,12525],256],13077:[[12461,12525,12464,12521,12512],256],13078:[[12461,12525,12513,12540,12488,12523],256],13079:[[12461,12525,12527,12483,12488],256],13080:[[12464,12521,12512],256],13081:[[12464,12521,12512,12488,12531],256],13082:[[12463,12523,12476,12452,12525],256],13083:[[12463,12525,12540,12493],256],13084:[[12465,12540,12473],256],13085:[[12467,12523,12490],256],13086:[[12467,12540,12509],256],13087:[[12469,12452,12463,12523],256],13088:[[12469,12531,12481,12540,12512],256],13089:[[12471,12522,12531,12464],256],13090:[[12475,12531,12481],256],13091:[[12475,12531,12488],256],13092:[[12480,12540,12473],256],13093:[[12487,12471],256],13094:[[12489,12523],256],13095:[[12488,12531],256],13096:[[12490,12494],256],13097:[[12494,12483,12488],256],13098:[[12495,12452,12484],256],13099:[[12497,12540,12475,12531,12488],256],13100:[[12497,12540,12484],256],13101:[[12496,12540,12524,12523],256],13102:[[12500,12450,12473,12488,12523],256],13103:[[12500,12463,12523],256],13104:[[12500,12467],256],13105:[[12499,12523],256],13106:[[12501,12449,12521,12483,12489],256],13107:[[12501,12451,12540,12488],256],13108:[[12502,12483,12471,12455,12523],256],13109:[[12501,12521,12531],256],13110:[[12504,12463,12479,12540,12523],256],13111:[[12506,12477],256],13112:[[12506,12491,12498],256],13113:[[12504,12523,12484],256],13114:[[12506,12531,12473],256],13115:[[12506,12540,12472],256],13116:[[12505,12540,12479],256],13117:[[12509,12452,12531,12488],256],13118:[[12508,12523,12488],256],13119:[[12507,12531],256],13120:[[12509,12531,12489],256],13121:[[12507,12540,12523],256],13122:[[12507,12540,12531],256],13123:[[12510,12452,12463,12525],256],13124:[[12510,12452,12523],256],13125:[[12510,12483,12495],256],13126:[[12510,12523,12463],256],13127:[[12510,12531,12471,12519,12531],256],13128:[[12511,12463,12525,12531],256],13129:[[12511,12522],256],13130:[[12511,12522,12496,12540,12523],256],13131:[[12513,12460],256],13132:[[12513,12460,12488,12531],256],13133:[[12513,12540,12488,12523],256],13134:[[12516,12540,12489],256],13135:[[12516,12540,12523],256],13136:[[12518,12450,12531],256],13137:[[12522,12483,12488,12523],256],13138:[[12522,12521],256],13139:[[12523,12500,12540],256],13140:[[12523,12540,12502,12523],256],13141:[[12524,12512],256],13142:[[12524,12531,12488,12466,12531],256],13143:[[12527,12483,12488],256],13144:[[48,28857],256],13145:[[49,28857],256],13146:[[50,28857],256],13147:[[51,28857],256],13148:[[52,28857],256],13149:[[53,28857],256],13150:[[54,28857],256],13151:[[55,28857],256],13152:[[56,28857],256],13153:[[57,28857],256],13154:[[49,48,28857],256],13155:[[49,49,28857],256],13156:[[49,50,28857],256],13157:[[49,51,28857],256],13158:[[49,52,28857],256],13159:[[49,53,28857],256],13160:[[49,54,28857],256],13161:[[49,55,28857],256],13162:[[49,56,28857],256],13163:[[49,57,28857],256],13164:[[50,48,28857],256],13165:[[50,49,28857],256],13166:[[50,50,28857],256],13167:[[50,51,28857],256],13168:[[50,52,28857],256],13169:[[104,80,97],256],13170:[[100,97],256],13171:[[65,85],256],13172:[[98,97,114],256],13173:[[111,86],256],13174:[[112,99],256],13175:[[100,109],256],13176:[[100,109,178],256],13177:[[100,109,179],256],13178:[[73,85],256],13179:[[24179,25104],256],13180:[[26157,21644],256],13181:[[22823,27491],256],13182:[[26126,27835],256],13183:[[26666,24335,20250,31038],256],13184:[[112,65],256],13185:[[110,65],256],13186:[[956,65],256],13187:[[109,65],256],13188:[[107,65],256],13189:[[75,66],256],13190:[[77,66],256],13191:[[71,66],256],13192:[[99,97,108],256],13193:[[107,99,97,108],256],13194:[[112,70],256],13195:[[110,70],256],13196:[[956,70],256],13197:[[956,103],256],13198:[[109,103],256],13199:[[107,103],256],13200:[[72,122],256],13201:[[107,72,122],256],13202:[[77,72,122],256],13203:[[71,72,122],256],13204:[[84,72,122],256],13205:[[956,8467],256],13206:[[109,8467],256],13207:[[100,8467],256],13208:[[107,8467],256],13209:[[102,109],256],13210:[[110,109],256],13211:[[956,109],256],13212:[[109,109],256],13213:[[99,109],256],13214:[[107,109],256],13215:[[109,109,178],256],13216:[[99,109,178],256],13217:[[109,178],256],13218:[[107,109,178],256],13219:[[109,109,179],256],13220:[[99,109,179],256],13221:[[109,179],256],13222:[[107,109,179],256],13223:[[109,8725,115],256],13224:[[109,8725,115,178],256],13225:[[80,97],256],13226:[[107,80,97],256],13227:[[77,80,97],256],13228:[[71,80,97],256],13229:[[114,97,100],256],13230:[[114,97,100,8725,115],256],13231:[[114,97,100,8725,115,178],256],13232:[[112,115],256],13233:[[110,115],256],13234:[[956,115],256],13235:[[109,115],256],13236:[[112,86],256],13237:[[110,86],256],13238:[[956,86],256],13239:[[109,86],256],13240:[[107,86],256],13241:[[77,86],256],13242:[[112,87],256],13243:[[110,87],256],13244:[[956,87],256],13245:[[109,87],256],13246:[[107,87],256],13247:[[77,87],256],13248:[[107,937],256],13249:[[77,937],256],13250:[[97,46,109,46],256],13251:[[66,113],256],13252:[[99,99],256],13253:[[99,100],256],13254:[[67,8725,107,103],256],13255:[[67,111,46],256],13256:[[100,66],256],13257:[[71,121],256],13258:[[104,97],256],13259:[[72,80],256],13260:[[105,110],256],13261:[[75,75],256],13262:[[75,77],256],13263:[[107,116],256],13264:[[108,109],256],13265:[[108,110],256],13266:[[108,111,103],256],13267:[[108,120],256],13268:[[109,98],256],13269:[[109,105,108],256],13270:[[109,111,108],256],13271:[[80,72],256],13272:[[112,46,109,46],256],13273:[[80,80,77],256],13274:[[80,82],256],13275:[[115,114],256],13276:[[83,118],256],13277:[[87,98],256],13278:[[86,8725,109],256],13279:[[65,8725,109],256],13280:[[49,26085],256],13281:[[50,26085],256],13282:[[51,26085],256],13283:[[52,26085],256],13284:[[53,26085],256],13285:[[54,26085],256],13286:[[55,26085],256],13287:[[56,26085],256],13288:[[57,26085],256],13289:[[49,48,26085],256],13290:[[49,49,26085],256],13291:[[49,50,26085],256],13292:[[49,51,26085],256],13293:[[49,52,26085],256],13294:[[49,53,26085],256],13295:[[49,54,26085],256],13296:[[49,55,26085],256],13297:[[49,56,26085],256],13298:[[49,57,26085],256],13299:[[50,48,26085],256],13300:[[50,49,26085],256],13301:[[50,50,26085],256],13302:[[50,51,26085],256],13303:[[50,52,26085],256],13304:[[50,53,26085],256],13305:[[50,54,26085],256],13306:[[50,55,26085],256],13307:[[50,56,26085],256],13308:[[50,57,26085],256],13309:[[51,48,26085],256],13310:[[51,49,26085],256],13311:[[103,97,108],256]}, -27136:{92912:[,1],92913:[,1],92914:[,1],92915:[,1],92916:[,1]}, -27392:{92976:[,230],92977:[,230],92978:[,230],92979:[,230],92980:[,230],92981:[,230],92982:[,230]}, -42496:{42607:[,230],42612:[,230],42613:[,230],42614:[,230],42615:[,230],42616:[,230],42617:[,230],42618:[,230],42619:[,230],42620:[,230],42621:[,230],42652:[[1098],256],42653:[[1100],256],42655:[,230],42736:[,230],42737:[,230]}, -42752:{42864:[[42863],256],43000:[[294],256],43001:[[339],256]}, -43008:{43014:[,9],43204:[,9],43232:[,230],43233:[,230],43234:[,230],43235:[,230],43236:[,230],43237:[,230],43238:[,230],43239:[,230],43240:[,230],43241:[,230],43242:[,230],43243:[,230],43244:[,230],43245:[,230],43246:[,230],43247:[,230],43248:[,230],43249:[,230]}, -43264:{43307:[,220],43308:[,220],43309:[,220],43347:[,9],43443:[,7],43456:[,9]}, -43520:{43696:[,230],43698:[,230],43699:[,230],43700:[,220],43703:[,230],43704:[,230],43710:[,230],43711:[,230],43713:[,230],43766:[,9]}, -43776:{43868:[[42791],256],43869:[[43831],256],43870:[[619],256],43871:[[43858],256],44013:[,9]}, -48128:{113822:[,1]}, -53504:{119134:[[119127,119141],512],119135:[[119128,119141],512],119136:[[119135,119150],512],119137:[[119135,119151],512],119138:[[119135,119152],512],119139:[[119135,119153],512],119140:[[119135,119154],512],119141:[,216],119142:[,216],119143:[,1],119144:[,1],119145:[,1],119149:[,226],119150:[,216],119151:[,216],119152:[,216],119153:[,216],119154:[,216],119163:[,220],119164:[,220],119165:[,220],119166:[,220],119167:[,220],119168:[,220],119169:[,220],119170:[,220],119173:[,230],119174:[,230],119175:[,230],119176:[,230],119177:[,230],119178:[,220],119179:[,220],119210:[,230],119211:[,230],119212:[,230],119213:[,230],119227:[[119225,119141],512],119228:[[119226,119141],512],119229:[[119227,119150],512],119230:[[119228,119150],512],119231:[[119227,119151],512],119232:[[119228,119151],512]}, -53760:{119362:[,230],119363:[,230],119364:[,230]}, -54272:{119808:[[65],256],119809:[[66],256],119810:[[67],256],119811:[[68],256],119812:[[69],256],119813:[[70],256],119814:[[71],256],119815:[[72],256],119816:[[73],256],119817:[[74],256],119818:[[75],256],119819:[[76],256],119820:[[77],256],119821:[[78],256],119822:[[79],256],119823:[[80],256],119824:[[81],256],119825:[[82],256],119826:[[83],256],119827:[[84],256],119828:[[85],256],119829:[[86],256],119830:[[87],256],119831:[[88],256],119832:[[89],256],119833:[[90],256],119834:[[97],256],119835:[[98],256],119836:[[99],256],119837:[[100],256],119838:[[101],256],119839:[[102],256],119840:[[103],256],119841:[[104],256],119842:[[105],256],119843:[[106],256],119844:[[107],256],119845:[[108],256],119846:[[109],256],119847:[[110],256],119848:[[111],256],119849:[[112],256],119850:[[113],256],119851:[[114],256],119852:[[115],256],119853:[[116],256],119854:[[117],256],119855:[[118],256],119856:[[119],256],119857:[[120],256],119858:[[121],256],119859:[[122],256],119860:[[65],256],119861:[[66],256],119862:[[67],256],119863:[[68],256],119864:[[69],256],119865:[[70],256],119866:[[71],256],119867:[[72],256],119868:[[73],256],119869:[[74],256],119870:[[75],256],119871:[[76],256],119872:[[77],256],119873:[[78],256],119874:[[79],256],119875:[[80],256],119876:[[81],256],119877:[[82],256],119878:[[83],256],119879:[[84],256],119880:[[85],256],119881:[[86],256],119882:[[87],256],119883:[[88],256],119884:[[89],256],119885:[[90],256],119886:[[97],256],119887:[[98],256],119888:[[99],256],119889:[[100],256],119890:[[101],256],119891:[[102],256],119892:[[103],256],119894:[[105],256],119895:[[106],256],119896:[[107],256],119897:[[108],256],119898:[[109],256],119899:[[110],256],119900:[[111],256],119901:[[112],256],119902:[[113],256],119903:[[114],256],119904:[[115],256],119905:[[116],256],119906:[[117],256],119907:[[118],256],119908:[[119],256],119909:[[120],256],119910:[[121],256],119911:[[122],256],119912:[[65],256],119913:[[66],256],119914:[[67],256],119915:[[68],256],119916:[[69],256],119917:[[70],256],119918:[[71],256],119919:[[72],256],119920:[[73],256],119921:[[74],256],119922:[[75],256],119923:[[76],256],119924:[[77],256],119925:[[78],256],119926:[[79],256],119927:[[80],256],119928:[[81],256],119929:[[82],256],119930:[[83],256],119931:[[84],256],119932:[[85],256],119933:[[86],256],119934:[[87],256],119935:[[88],256],119936:[[89],256],119937:[[90],256],119938:[[97],256],119939:[[98],256],119940:[[99],256],119941:[[100],256],119942:[[101],256],119943:[[102],256],119944:[[103],256],119945:[[104],256],119946:[[105],256],119947:[[106],256],119948:[[107],256],119949:[[108],256],119950:[[109],256],119951:[[110],256],119952:[[111],256],119953:[[112],256],119954:[[113],256],119955:[[114],256],119956:[[115],256],119957:[[116],256],119958:[[117],256],119959:[[118],256],119960:[[119],256],119961:[[120],256],119962:[[121],256],119963:[[122],256],119964:[[65],256],119966:[[67],256],119967:[[68],256],119970:[[71],256],119973:[[74],256],119974:[[75],256],119977:[[78],256],119978:[[79],256],119979:[[80],256],119980:[[81],256],119982:[[83],256],119983:[[84],256],119984:[[85],256],119985:[[86],256],119986:[[87],256],119987:[[88],256],119988:[[89],256],119989:[[90],256],119990:[[97],256],119991:[[98],256],119992:[[99],256],119993:[[100],256],119995:[[102],256],119997:[[104],256],119998:[[105],256],119999:[[106],256],120000:[[107],256],120001:[[108],256],120002:[[109],256],120003:[[110],256],120005:[[112],256],120006:[[113],256],120007:[[114],256],120008:[[115],256],120009:[[116],256],120010:[[117],256],120011:[[118],256],120012:[[119],256],120013:[[120],256],120014:[[121],256],120015:[[122],256],120016:[[65],256],120017:[[66],256],120018:[[67],256],120019:[[68],256],120020:[[69],256],120021:[[70],256],120022:[[71],256],120023:[[72],256],120024:[[73],256],120025:[[74],256],120026:[[75],256],120027:[[76],256],120028:[[77],256],120029:[[78],256],120030:[[79],256],120031:[[80],256],120032:[[81],256],120033:[[82],256],120034:[[83],256],120035:[[84],256],120036:[[85],256],120037:[[86],256],120038:[[87],256],120039:[[88],256],120040:[[89],256],120041:[[90],256],120042:[[97],256],120043:[[98],256],120044:[[99],256],120045:[[100],256],120046:[[101],256],120047:[[102],256],120048:[[103],256],120049:[[104],256],120050:[[105],256],120051:[[106],256],120052:[[107],256],120053:[[108],256],120054:[[109],256],120055:[[110],256],120056:[[111],256],120057:[[112],256],120058:[[113],256],120059:[[114],256],120060:[[115],256],120061:[[116],256],120062:[[117],256],120063:[[118],256]}, -54528:{120064:[[119],256],120065:[[120],256],120066:[[121],256],120067:[[122],256],120068:[[65],256],120069:[[66],256],120071:[[68],256],120072:[[69],256],120073:[[70],256],120074:[[71],256],120077:[[74],256],120078:[[75],256],120079:[[76],256],120080:[[77],256],120081:[[78],256],120082:[[79],256],120083:[[80],256],120084:[[81],256],120086:[[83],256],120087:[[84],256],120088:[[85],256],120089:[[86],256],120090:[[87],256],120091:[[88],256],120092:[[89],256],120094:[[97],256],120095:[[98],256],120096:[[99],256],120097:[[100],256],120098:[[101],256],120099:[[102],256],120100:[[103],256],120101:[[104],256],120102:[[105],256],120103:[[106],256],120104:[[107],256],120105:[[108],256],120106:[[109],256],120107:[[110],256],120108:[[111],256],120109:[[112],256],120110:[[113],256],120111:[[114],256],120112:[[115],256],120113:[[116],256],120114:[[117],256],120115:[[118],256],120116:[[119],256],120117:[[120],256],120118:[[121],256],120119:[[122],256],120120:[[65],256],120121:[[66],256],120123:[[68],256],120124:[[69],256],120125:[[70],256],120126:[[71],256],120128:[[73],256],120129:[[74],256],120130:[[75],256],120131:[[76],256],120132:[[77],256],120134:[[79],256],120138:[[83],256],120139:[[84],256],120140:[[85],256],120141:[[86],256],120142:[[87],256],120143:[[88],256],120144:[[89],256],120146:[[97],256],120147:[[98],256],120148:[[99],256],120149:[[100],256],120150:[[101],256],120151:[[102],256],120152:[[103],256],120153:[[104],256],120154:[[105],256],120155:[[106],256],120156:[[107],256],120157:[[108],256],120158:[[109],256],120159:[[110],256],120160:[[111],256],120161:[[112],256],120162:[[113],256],120163:[[114],256],120164:[[115],256],120165:[[116],256],120166:[[117],256],120167:[[118],256],120168:[[119],256],120169:[[120],256],120170:[[121],256],120171:[[122],256],120172:[[65],256],120173:[[66],256],120174:[[67],256],120175:[[68],256],120176:[[69],256],120177:[[70],256],120178:[[71],256],120179:[[72],256],120180:[[73],256],120181:[[74],256],120182:[[75],256],120183:[[76],256],120184:[[77],256],120185:[[78],256],120186:[[79],256],120187:[[80],256],120188:[[81],256],120189:[[82],256],120190:[[83],256],120191:[[84],256],120192:[[85],256],120193:[[86],256],120194:[[87],256],120195:[[88],256],120196:[[89],256],120197:[[90],256],120198:[[97],256],120199:[[98],256],120200:[[99],256],120201:[[100],256],120202:[[101],256],120203:[[102],256],120204:[[103],256],120205:[[104],256],120206:[[105],256],120207:[[106],256],120208:[[107],256],120209:[[108],256],120210:[[109],256],120211:[[110],256],120212:[[111],256],120213:[[112],256],120214:[[113],256],120215:[[114],256],120216:[[115],256],120217:[[116],256],120218:[[117],256],120219:[[118],256],120220:[[119],256],120221:[[120],256],120222:[[121],256],120223:[[122],256],120224:[[65],256],120225:[[66],256],120226:[[67],256],120227:[[68],256],120228:[[69],256],120229:[[70],256],120230:[[71],256],120231:[[72],256],120232:[[73],256],120233:[[74],256],120234:[[75],256],120235:[[76],256],120236:[[77],256],120237:[[78],256],120238:[[79],256],120239:[[80],256],120240:[[81],256],120241:[[82],256],120242:[[83],256],120243:[[84],256],120244:[[85],256],120245:[[86],256],120246:[[87],256],120247:[[88],256],120248:[[89],256],120249:[[90],256],120250:[[97],256],120251:[[98],256],120252:[[99],256],120253:[[100],256],120254:[[101],256],120255:[[102],256],120256:[[103],256],120257:[[104],256],120258:[[105],256],120259:[[106],256],120260:[[107],256],120261:[[108],256],120262:[[109],256],120263:[[110],256],120264:[[111],256],120265:[[112],256],120266:[[113],256],120267:[[114],256],120268:[[115],256],120269:[[116],256],120270:[[117],256],120271:[[118],256],120272:[[119],256],120273:[[120],256],120274:[[121],256],120275:[[122],256],120276:[[65],256],120277:[[66],256],120278:[[67],256],120279:[[68],256],120280:[[69],256],120281:[[70],256],120282:[[71],256],120283:[[72],256],120284:[[73],256],120285:[[74],256],120286:[[75],256],120287:[[76],256],120288:[[77],256],120289:[[78],256],120290:[[79],256],120291:[[80],256],120292:[[81],256],120293:[[82],256],120294:[[83],256],120295:[[84],256],120296:[[85],256],120297:[[86],256],120298:[[87],256],120299:[[88],256],120300:[[89],256],120301:[[90],256],120302:[[97],256],120303:[[98],256],120304:[[99],256],120305:[[100],256],120306:[[101],256],120307:[[102],256],120308:[[103],256],120309:[[104],256],120310:[[105],256],120311:[[106],256],120312:[[107],256],120313:[[108],256],120314:[[109],256],120315:[[110],256],120316:[[111],256],120317:[[112],256],120318:[[113],256],120319:[[114],256]}, -54784:{120320:[[115],256],120321:[[116],256],120322:[[117],256],120323:[[118],256],120324:[[119],256],120325:[[120],256],120326:[[121],256],120327:[[122],256],120328:[[65],256],120329:[[66],256],120330:[[67],256],120331:[[68],256],120332:[[69],256],120333:[[70],256],120334:[[71],256],120335:[[72],256],120336:[[73],256],120337:[[74],256],120338:[[75],256],120339:[[76],256],120340:[[77],256],120341:[[78],256],120342:[[79],256],120343:[[80],256],120344:[[81],256],120345:[[82],256],120346:[[83],256],120347:[[84],256],120348:[[85],256],120349:[[86],256],120350:[[87],256],120351:[[88],256],120352:[[89],256],120353:[[90],256],120354:[[97],256],120355:[[98],256],120356:[[99],256],120357:[[100],256],120358:[[101],256],120359:[[102],256],120360:[[103],256],120361:[[104],256],120362:[[105],256],120363:[[106],256],120364:[[107],256],120365:[[108],256],120366:[[109],256],120367:[[110],256],120368:[[111],256],120369:[[112],256],120370:[[113],256],120371:[[114],256],120372:[[115],256],120373:[[116],256],120374:[[117],256],120375:[[118],256],120376:[[119],256],120377:[[120],256],120378:[[121],256],120379:[[122],256],120380:[[65],256],120381:[[66],256],120382:[[67],256],120383:[[68],256],120384:[[69],256],120385:[[70],256],120386:[[71],256],120387:[[72],256],120388:[[73],256],120389:[[74],256],120390:[[75],256],120391:[[76],256],120392:[[77],256],120393:[[78],256],120394:[[79],256],120395:[[80],256],120396:[[81],256],120397:[[82],256],120398:[[83],256],120399:[[84],256],120400:[[85],256],120401:[[86],256],120402:[[87],256],120403:[[88],256],120404:[[89],256],120405:[[90],256],120406:[[97],256],120407:[[98],256],120408:[[99],256],120409:[[100],256],120410:[[101],256],120411:[[102],256],120412:[[103],256],120413:[[104],256],120414:[[105],256],120415:[[106],256],120416:[[107],256],120417:[[108],256],120418:[[109],256],120419:[[110],256],120420:[[111],256],120421:[[112],256],120422:[[113],256],120423:[[114],256],120424:[[115],256],120425:[[116],256],120426:[[117],256],120427:[[118],256],120428:[[119],256],120429:[[120],256],120430:[[121],256],120431:[[122],256],120432:[[65],256],120433:[[66],256],120434:[[67],256],120435:[[68],256],120436:[[69],256],120437:[[70],256],120438:[[71],256],120439:[[72],256],120440:[[73],256],120441:[[74],256],120442:[[75],256],120443:[[76],256],120444:[[77],256],120445:[[78],256],120446:[[79],256],120447:[[80],256],120448:[[81],256],120449:[[82],256],120450:[[83],256],120451:[[84],256],120452:[[85],256],120453:[[86],256],120454:[[87],256],120455:[[88],256],120456:[[89],256],120457:[[90],256],120458:[[97],256],120459:[[98],256],120460:[[99],256],120461:[[100],256],120462:[[101],256],120463:[[102],256],120464:[[103],256],120465:[[104],256],120466:[[105],256],120467:[[106],256],120468:[[107],256],120469:[[108],256],120470:[[109],256],120471:[[110],256],120472:[[111],256],120473:[[112],256],120474:[[113],256],120475:[[114],256],120476:[[115],256],120477:[[116],256],120478:[[117],256],120479:[[118],256],120480:[[119],256],120481:[[120],256],120482:[[121],256],120483:[[122],256],120484:[[305],256],120485:[[567],256],120488:[[913],256],120489:[[914],256],120490:[[915],256],120491:[[916],256],120492:[[917],256],120493:[[918],256],120494:[[919],256],120495:[[920],256],120496:[[921],256],120497:[[922],256],120498:[[923],256],120499:[[924],256],120500:[[925],256],120501:[[926],256],120502:[[927],256],120503:[[928],256],120504:[[929],256],120505:[[1012],256],120506:[[931],256],120507:[[932],256],120508:[[933],256],120509:[[934],256],120510:[[935],256],120511:[[936],256],120512:[[937],256],120513:[[8711],256],120514:[[945],256],120515:[[946],256],120516:[[947],256],120517:[[948],256],120518:[[949],256],120519:[[950],256],120520:[[951],256],120521:[[952],256],120522:[[953],256],120523:[[954],256],120524:[[955],256],120525:[[956],256],120526:[[957],256],120527:[[958],256],120528:[[959],256],120529:[[960],256],120530:[[961],256],120531:[[962],256],120532:[[963],256],120533:[[964],256],120534:[[965],256],120535:[[966],256],120536:[[967],256],120537:[[968],256],120538:[[969],256],120539:[[8706],256],120540:[[1013],256],120541:[[977],256],120542:[[1008],256],120543:[[981],256],120544:[[1009],256],120545:[[982],256],120546:[[913],256],120547:[[914],256],120548:[[915],256],120549:[[916],256],120550:[[917],256],120551:[[918],256],120552:[[919],256],120553:[[920],256],120554:[[921],256],120555:[[922],256],120556:[[923],256],120557:[[924],256],120558:[[925],256],120559:[[926],256],120560:[[927],256],120561:[[928],256],120562:[[929],256],120563:[[1012],256],120564:[[931],256],120565:[[932],256],120566:[[933],256],120567:[[934],256],120568:[[935],256],120569:[[936],256],120570:[[937],256],120571:[[8711],256],120572:[[945],256],120573:[[946],256],120574:[[947],256],120575:[[948],256]}, -55040:{120576:[[949],256],120577:[[950],256],120578:[[951],256],120579:[[952],256],120580:[[953],256],120581:[[954],256],120582:[[955],256],120583:[[956],256],120584:[[957],256],120585:[[958],256],120586:[[959],256],120587:[[960],256],120588:[[961],256],120589:[[962],256],120590:[[963],256],120591:[[964],256],120592:[[965],256],120593:[[966],256],120594:[[967],256],120595:[[968],256],120596:[[969],256],120597:[[8706],256],120598:[[1013],256],120599:[[977],256],120600:[[1008],256],120601:[[981],256],120602:[[1009],256],120603:[[982],256],120604:[[913],256],120605:[[914],256],120606:[[915],256],120607:[[916],256],120608:[[917],256],120609:[[918],256],120610:[[919],256],120611:[[920],256],120612:[[921],256],120613:[[922],256],120614:[[923],256],120615:[[924],256],120616:[[925],256],120617:[[926],256],120618:[[927],256],120619:[[928],256],120620:[[929],256],120621:[[1012],256],120622:[[931],256],120623:[[932],256],120624:[[933],256],120625:[[934],256],120626:[[935],256],120627:[[936],256],120628:[[937],256],120629:[[8711],256],120630:[[945],256],120631:[[946],256],120632:[[947],256],120633:[[948],256],120634:[[949],256],120635:[[950],256],120636:[[951],256],120637:[[952],256],120638:[[953],256],120639:[[954],256],120640:[[955],256],120641:[[956],256],120642:[[957],256],120643:[[958],256],120644:[[959],256],120645:[[960],256],120646:[[961],256],120647:[[962],256],120648:[[963],256],120649:[[964],256],120650:[[965],256],120651:[[966],256],120652:[[967],256],120653:[[968],256],120654:[[969],256],120655:[[8706],256],120656:[[1013],256],120657:[[977],256],120658:[[1008],256],120659:[[981],256],120660:[[1009],256],120661:[[982],256],120662:[[913],256],120663:[[914],256],120664:[[915],256],120665:[[916],256],120666:[[917],256],120667:[[918],256],120668:[[919],256],120669:[[920],256],120670:[[921],256],120671:[[922],256],120672:[[923],256],120673:[[924],256],120674:[[925],256],120675:[[926],256],120676:[[927],256],120677:[[928],256],120678:[[929],256],120679:[[1012],256],120680:[[931],256],120681:[[932],256],120682:[[933],256],120683:[[934],256],120684:[[935],256],120685:[[936],256],120686:[[937],256],120687:[[8711],256],120688:[[945],256],120689:[[946],256],120690:[[947],256],120691:[[948],256],120692:[[949],256],120693:[[950],256],120694:[[951],256],120695:[[952],256],120696:[[953],256],120697:[[954],256],120698:[[955],256],120699:[[956],256],120700:[[957],256],120701:[[958],256],120702:[[959],256],120703:[[960],256],120704:[[961],256],120705:[[962],256],120706:[[963],256],120707:[[964],256],120708:[[965],256],120709:[[966],256],120710:[[967],256],120711:[[968],256],120712:[[969],256],120713:[[8706],256],120714:[[1013],256],120715:[[977],256],120716:[[1008],256],120717:[[981],256],120718:[[1009],256],120719:[[982],256],120720:[[913],256],120721:[[914],256],120722:[[915],256],120723:[[916],256],120724:[[917],256],120725:[[918],256],120726:[[919],256],120727:[[920],256],120728:[[921],256],120729:[[922],256],120730:[[923],256],120731:[[924],256],120732:[[925],256],120733:[[926],256],120734:[[927],256],120735:[[928],256],120736:[[929],256],120737:[[1012],256],120738:[[931],256],120739:[[932],256],120740:[[933],256],120741:[[934],256],120742:[[935],256],120743:[[936],256],120744:[[937],256],120745:[[8711],256],120746:[[945],256],120747:[[946],256],120748:[[947],256],120749:[[948],256],120750:[[949],256],120751:[[950],256],120752:[[951],256],120753:[[952],256],120754:[[953],256],120755:[[954],256],120756:[[955],256],120757:[[956],256],120758:[[957],256],120759:[[958],256],120760:[[959],256],120761:[[960],256],120762:[[961],256],120763:[[962],256],120764:[[963],256],120765:[[964],256],120766:[[965],256],120767:[[966],256],120768:[[967],256],120769:[[968],256],120770:[[969],256],120771:[[8706],256],120772:[[1013],256],120773:[[977],256],120774:[[1008],256],120775:[[981],256],120776:[[1009],256],120777:[[982],256],120778:[[988],256],120779:[[989],256],120782:[[48],256],120783:[[49],256],120784:[[50],256],120785:[[51],256],120786:[[52],256],120787:[[53],256],120788:[[54],256],120789:[[55],256],120790:[[56],256],120791:[[57],256],120792:[[48],256],120793:[[49],256],120794:[[50],256],120795:[[51],256],120796:[[52],256],120797:[[53],256],120798:[[54],256],120799:[[55],256],120800:[[56],256],120801:[[57],256],120802:[[48],256],120803:[[49],256],120804:[[50],256],120805:[[51],256],120806:[[52],256],120807:[[53],256],120808:[[54],256],120809:[[55],256],120810:[[56],256],120811:[[57],256],120812:[[48],256],120813:[[49],256],120814:[[50],256],120815:[[51],256],120816:[[52],256],120817:[[53],256],120818:[[54],256],120819:[[55],256],120820:[[56],256],120821:[[57],256],120822:[[48],256],120823:[[49],256],120824:[[50],256],120825:[[51],256],120826:[[52],256],120827:[[53],256],120828:[[54],256],120829:[[55],256],120830:[[56],256],120831:[[57],256]}, -59392:{125136:[,220],125137:[,220],125138:[,220],125139:[,220],125140:[,220],125141:[,220],125142:[,220]}, -60928:{126464:[[1575],256],126465:[[1576],256],126466:[[1580],256],126467:[[1583],256],126469:[[1608],256],126470:[[1586],256],126471:[[1581],256],126472:[[1591],256],126473:[[1610],256],126474:[[1603],256],126475:[[1604],256],126476:[[1605],256],126477:[[1606],256],126478:[[1587],256],126479:[[1593],256],126480:[[1601],256],126481:[[1589],256],126482:[[1602],256],126483:[[1585],256],126484:[[1588],256],126485:[[1578],256],126486:[[1579],256],126487:[[1582],256],126488:[[1584],256],126489:[[1590],256],126490:[[1592],256],126491:[[1594],256],126492:[[1646],256],126493:[[1722],256],126494:[[1697],256],126495:[[1647],256],126497:[[1576],256],126498:[[1580],256],126500:[[1607],256],126503:[[1581],256],126505:[[1610],256],126506:[[1603],256],126507:[[1604],256],126508:[[1605],256],126509:[[1606],256],126510:[[1587],256],126511:[[1593],256],126512:[[1601],256],126513:[[1589],256],126514:[[1602],256],126516:[[1588],256],126517:[[1578],256],126518:[[1579],256],126519:[[1582],256],126521:[[1590],256],126523:[[1594],256],126530:[[1580],256],126535:[[1581],256],126537:[[1610],256],126539:[[1604],256],126541:[[1606],256],126542:[[1587],256],126543:[[1593],256],126545:[[1589],256],126546:[[1602],256],126548:[[1588],256],126551:[[1582],256],126553:[[1590],256],126555:[[1594],256],126557:[[1722],256],126559:[[1647],256],126561:[[1576],256],126562:[[1580],256],126564:[[1607],256],126567:[[1581],256],126568:[[1591],256],126569:[[1610],256],126570:[[1603],256],126572:[[1605],256],126573:[[1606],256],126574:[[1587],256],126575:[[1593],256],126576:[[1601],256],126577:[[1589],256],126578:[[1602],256],126580:[[1588],256],126581:[[1578],256],126582:[[1579],256],126583:[[1582],256],126585:[[1590],256],126586:[[1592],256],126587:[[1594],256],126588:[[1646],256],126590:[[1697],256],126592:[[1575],256],126593:[[1576],256],126594:[[1580],256],126595:[[1583],256],126596:[[1607],256],126597:[[1608],256],126598:[[1586],256],126599:[[1581],256],126600:[[1591],256],126601:[[1610],256],126603:[[1604],256],126604:[[1605],256],126605:[[1606],256],126606:[[1587],256],126607:[[1593],256],126608:[[1601],256],126609:[[1589],256],126610:[[1602],256],126611:[[1585],256],126612:[[1588],256],126613:[[1578],256],126614:[[1579],256],126615:[[1582],256],126616:[[1584],256],126617:[[1590],256],126618:[[1592],256],126619:[[1594],256],126625:[[1576],256],126626:[[1580],256],126627:[[1583],256],126629:[[1608],256],126630:[[1586],256],126631:[[1581],256],126632:[[1591],256],126633:[[1610],256],126635:[[1604],256],126636:[[1605],256],126637:[[1606],256],126638:[[1587],256],126639:[[1593],256],126640:[[1601],256],126641:[[1589],256],126642:[[1602],256],126643:[[1585],256],126644:[[1588],256],126645:[[1578],256],126646:[[1579],256],126647:[[1582],256],126648:[[1584],256],126649:[[1590],256],126650:[[1592],256],126651:[[1594],256]}, -61696:{127232:[[48,46],256],127233:[[48,44],256],127234:[[49,44],256],127235:[[50,44],256],127236:[[51,44],256],127237:[[52,44],256],127238:[[53,44],256],127239:[[54,44],256],127240:[[55,44],256],127241:[[56,44],256],127242:[[57,44],256],127248:[[40,65,41],256],127249:[[40,66,41],256],127250:[[40,67,41],256],127251:[[40,68,41],256],127252:[[40,69,41],256],127253:[[40,70,41],256],127254:[[40,71,41],256],127255:[[40,72,41],256],127256:[[40,73,41],256],127257:[[40,74,41],256],127258:[[40,75,41],256],127259:[[40,76,41],256],127260:[[40,77,41],256],127261:[[40,78,41],256],127262:[[40,79,41],256],127263:[[40,80,41],256],127264:[[40,81,41],256],127265:[[40,82,41],256],127266:[[40,83,41],256],127267:[[40,84,41],256],127268:[[40,85,41],256],127269:[[40,86,41],256],127270:[[40,87,41],256],127271:[[40,88,41],256],127272:[[40,89,41],256],127273:[[40,90,41],256],127274:[[12308,83,12309],256],127275:[[67],256],127276:[[82],256],127277:[[67,68],256],127278:[[87,90],256],127280:[[65],256],127281:[[66],256],127282:[[67],256],127283:[[68],256],127284:[[69],256],127285:[[70],256],127286:[[71],256],127287:[[72],256],127288:[[73],256],127289:[[74],256],127290:[[75],256],127291:[[76],256],127292:[[77],256],127293:[[78],256],127294:[[79],256],127295:[[80],256],127296:[[81],256],127297:[[82],256],127298:[[83],256],127299:[[84],256],127300:[[85],256],127301:[[86],256],127302:[[87],256],127303:[[88],256],127304:[[89],256],127305:[[90],256],127306:[[72,86],256],127307:[[77,86],256],127308:[[83,68],256],127309:[[83,83],256],127310:[[80,80,86],256],127311:[[87,67],256],127338:[[77,67],256],127339:[[77,68],256],127376:[[68,74],256]}, -61952:{127488:[[12411,12363],256],127489:[[12467,12467],256],127490:[[12469],256],127504:[[25163],256],127505:[[23383],256],127506:[[21452],256],127507:[[12487],256],127508:[[20108],256],127509:[[22810],256],127510:[[35299],256],127511:[[22825],256],127512:[[20132],256],127513:[[26144],256],127514:[[28961],256],127515:[[26009],256],127516:[[21069],256],127517:[[24460],256],127518:[[20877],256],127519:[[26032],256],127520:[[21021],256],127521:[[32066],256],127522:[[29983],256],127523:[[36009],256],127524:[[22768],256],127525:[[21561],256],127526:[[28436],256],127527:[[25237],256],127528:[[25429],256],127529:[[19968],256],127530:[[19977],256],127531:[[36938],256],127532:[[24038],256],127533:[[20013],256],127534:[[21491],256],127535:[[25351],256],127536:[[36208],256],127537:[[25171],256],127538:[[31105],256],127539:[[31354],256],127540:[[21512],256],127541:[[28288],256],127542:[[26377],256],127543:[[26376],256],127544:[[30003],256],127545:[[21106],256],127546:[[21942],256],127552:[[12308,26412,12309],256],127553:[[12308,19977,12309],256],127554:[[12308,20108,12309],256],127555:[[12308,23433,12309],256],127556:[[12308,28857,12309],256],127557:[[12308,25171,12309],256],127558:[[12308,30423,12309],256],127559:[[12308,21213,12309],256],127560:[[12308,25943,12309],256],127568:[[24471],256],127569:[[21487],256]}, -63488:{194560:[[20029]],194561:[[20024]],194562:[[20033]],194563:[[131362]],194564:[[20320]],194565:[[20398]],194566:[[20411]],194567:[[20482]],194568:[[20602]],194569:[[20633]],194570:[[20711]],194571:[[20687]],194572:[[13470]],194573:[[132666]],194574:[[20813]],194575:[[20820]],194576:[[20836]],194577:[[20855]],194578:[[132380]],194579:[[13497]],194580:[[20839]],194581:[[20877]],194582:[[132427]],194583:[[20887]],194584:[[20900]],194585:[[20172]],194586:[[20908]],194587:[[20917]],194588:[[168415]],194589:[[20981]],194590:[[20995]],194591:[[13535]],194592:[[21051]],194593:[[21062]],194594:[[21106]],194595:[[21111]],194596:[[13589]],194597:[[21191]],194598:[[21193]],194599:[[21220]],194600:[[21242]],194601:[[21253]],194602:[[21254]],194603:[[21271]],194604:[[21321]],194605:[[21329]],194606:[[21338]],194607:[[21363]],194608:[[21373]],194609:[[21375]],194610:[[21375]],194611:[[21375]],194612:[[133676]],194613:[[28784]],194614:[[21450]],194615:[[21471]],194616:[[133987]],194617:[[21483]],194618:[[21489]],194619:[[21510]],194620:[[21662]],194621:[[21560]],194622:[[21576]],194623:[[21608]],194624:[[21666]],194625:[[21750]],194626:[[21776]],194627:[[21843]],194628:[[21859]],194629:[[21892]],194630:[[21892]],194631:[[21913]],194632:[[21931]],194633:[[21939]],194634:[[21954]],194635:[[22294]],194636:[[22022]],194637:[[22295]],194638:[[22097]],194639:[[22132]],194640:[[20999]],194641:[[22766]],194642:[[22478]],194643:[[22516]],194644:[[22541]],194645:[[22411]],194646:[[22578]],194647:[[22577]],194648:[[22700]],194649:[[136420]],194650:[[22770]],194651:[[22775]],194652:[[22790]],194653:[[22810]],194654:[[22818]],194655:[[22882]],194656:[[136872]],194657:[[136938]],194658:[[23020]],194659:[[23067]],194660:[[23079]],194661:[[23000]],194662:[[23142]],194663:[[14062]],194664:[[14076]],194665:[[23304]],194666:[[23358]],194667:[[23358]],194668:[[137672]],194669:[[23491]],194670:[[23512]],194671:[[23527]],194672:[[23539]],194673:[[138008]],194674:[[23551]],194675:[[23558]],194676:[[24403]],194677:[[23586]],194678:[[14209]],194679:[[23648]],194680:[[23662]],194681:[[23744]],194682:[[23693]],194683:[[138724]],194684:[[23875]],194685:[[138726]],194686:[[23918]],194687:[[23915]],194688:[[23932]],194689:[[24033]],194690:[[24034]],194691:[[14383]],194692:[[24061]],194693:[[24104]],194694:[[24125]],194695:[[24169]],194696:[[14434]],194697:[[139651]],194698:[[14460]],194699:[[24240]],194700:[[24243]],194701:[[24246]],194702:[[24266]],194703:[[172946]],194704:[[24318]],194705:[[140081]],194706:[[140081]],194707:[[33281]],194708:[[24354]],194709:[[24354]],194710:[[14535]],194711:[[144056]],194712:[[156122]],194713:[[24418]],194714:[[24427]],194715:[[14563]],194716:[[24474]],194717:[[24525]],194718:[[24535]],194719:[[24569]],194720:[[24705]],194721:[[14650]],194722:[[14620]],194723:[[24724]],194724:[[141012]],194725:[[24775]],194726:[[24904]],194727:[[24908]],194728:[[24910]],194729:[[24908]],194730:[[24954]],194731:[[24974]],194732:[[25010]],194733:[[24996]],194734:[[25007]],194735:[[25054]],194736:[[25074]],194737:[[25078]],194738:[[25104]],194739:[[25115]],194740:[[25181]],194741:[[25265]],194742:[[25300]],194743:[[25424]],194744:[[142092]],194745:[[25405]],194746:[[25340]],194747:[[25448]],194748:[[25475]],194749:[[25572]],194750:[[142321]],194751:[[25634]],194752:[[25541]],194753:[[25513]],194754:[[14894]],194755:[[25705]],194756:[[25726]],194757:[[25757]],194758:[[25719]],194759:[[14956]],194760:[[25935]],194761:[[25964]],194762:[[143370]],194763:[[26083]],194764:[[26360]],194765:[[26185]],194766:[[15129]],194767:[[26257]],194768:[[15112]],194769:[[15076]],194770:[[20882]],194771:[[20885]],194772:[[26368]],194773:[[26268]],194774:[[32941]],194775:[[17369]],194776:[[26391]],194777:[[26395]],194778:[[26401]],194779:[[26462]],194780:[[26451]],194781:[[144323]],194782:[[15177]],194783:[[26618]],194784:[[26501]],194785:[[26706]],194786:[[26757]],194787:[[144493]],194788:[[26766]],194789:[[26655]],194790:[[26900]],194791:[[15261]],194792:[[26946]],194793:[[27043]],194794:[[27114]],194795:[[27304]],194796:[[145059]],194797:[[27355]],194798:[[15384]],194799:[[27425]],194800:[[145575]],194801:[[27476]],194802:[[15438]],194803:[[27506]],194804:[[27551]],194805:[[27578]],194806:[[27579]],194807:[[146061]],194808:[[138507]],194809:[[146170]],194810:[[27726]],194811:[[146620]],194812:[[27839]],194813:[[27853]],194814:[[27751]],194815:[[27926]]}, -63744:{63744:[[35912]],63745:[[26356]],63746:[[36554]],63747:[[36040]],63748:[[28369]],63749:[[20018]],63750:[[21477]],63751:[[40860]],63752:[[40860]],63753:[[22865]],63754:[[37329]],63755:[[21895]],63756:[[22856]],63757:[[25078]],63758:[[30313]],63759:[[32645]],63760:[[34367]],63761:[[34746]],63762:[[35064]],63763:[[37007]],63764:[[27138]],63765:[[27931]],63766:[[28889]],63767:[[29662]],63768:[[33853]],63769:[[37226]],63770:[[39409]],63771:[[20098]],63772:[[21365]],63773:[[27396]],63774:[[29211]],63775:[[34349]],63776:[[40478]],63777:[[23888]],63778:[[28651]],63779:[[34253]],63780:[[35172]],63781:[[25289]],63782:[[33240]],63783:[[34847]],63784:[[24266]],63785:[[26391]],63786:[[28010]],63787:[[29436]],63788:[[37070]],63789:[[20358]],63790:[[20919]],63791:[[21214]],63792:[[25796]],63793:[[27347]],63794:[[29200]],63795:[[30439]],63796:[[32769]],63797:[[34310]],63798:[[34396]],63799:[[36335]],63800:[[38706]],63801:[[39791]],63802:[[40442]],63803:[[30860]],63804:[[31103]],63805:[[32160]],63806:[[33737]],63807:[[37636]],63808:[[40575]],63809:[[35542]],63810:[[22751]],63811:[[24324]],63812:[[31840]],63813:[[32894]],63814:[[29282]],63815:[[30922]],63816:[[36034]],63817:[[38647]],63818:[[22744]],63819:[[23650]],63820:[[27155]],63821:[[28122]],63822:[[28431]],63823:[[32047]],63824:[[32311]],63825:[[38475]],63826:[[21202]],63827:[[32907]],63828:[[20956]],63829:[[20940]],63830:[[31260]],63831:[[32190]],63832:[[33777]],63833:[[38517]],63834:[[35712]],63835:[[25295]],63836:[[27138]],63837:[[35582]],63838:[[20025]],63839:[[23527]],63840:[[24594]],63841:[[29575]],63842:[[30064]],63843:[[21271]],63844:[[30971]],63845:[[20415]],63846:[[24489]],63847:[[19981]],63848:[[27852]],63849:[[25976]],63850:[[32034]],63851:[[21443]],63852:[[22622]],63853:[[30465]],63854:[[33865]],63855:[[35498]],63856:[[27578]],63857:[[36784]],63858:[[27784]],63859:[[25342]],63860:[[33509]],63861:[[25504]],63862:[[30053]],63863:[[20142]],63864:[[20841]],63865:[[20937]],63866:[[26753]],63867:[[31975]],63868:[[33391]],63869:[[35538]],63870:[[37327]],63871:[[21237]],63872:[[21570]],63873:[[22899]],63874:[[24300]],63875:[[26053]],63876:[[28670]],63877:[[31018]],63878:[[38317]],63879:[[39530]],63880:[[40599]],63881:[[40654]],63882:[[21147]],63883:[[26310]],63884:[[27511]],63885:[[36706]],63886:[[24180]],63887:[[24976]],63888:[[25088]],63889:[[25754]],63890:[[28451]],63891:[[29001]],63892:[[29833]],63893:[[31178]],63894:[[32244]],63895:[[32879]],63896:[[36646]],63897:[[34030]],63898:[[36899]],63899:[[37706]],63900:[[21015]],63901:[[21155]],63902:[[21693]],63903:[[28872]],63904:[[35010]],63905:[[35498]],63906:[[24265]],63907:[[24565]],63908:[[25467]],63909:[[27566]],63910:[[31806]],63911:[[29557]],63912:[[20196]],63913:[[22265]],63914:[[23527]],63915:[[23994]],63916:[[24604]],63917:[[29618]],63918:[[29801]],63919:[[32666]],63920:[[32838]],63921:[[37428]],63922:[[38646]],63923:[[38728]],63924:[[38936]],63925:[[20363]],63926:[[31150]],63927:[[37300]],63928:[[38584]],63929:[[24801]],63930:[[20102]],63931:[[20698]],63932:[[23534]],63933:[[23615]],63934:[[26009]],63935:[[27138]],63936:[[29134]],63937:[[30274]],63938:[[34044]],63939:[[36988]],63940:[[40845]],63941:[[26248]],63942:[[38446]],63943:[[21129]],63944:[[26491]],63945:[[26611]],63946:[[27969]],63947:[[28316]],63948:[[29705]],63949:[[30041]],63950:[[30827]],63951:[[32016]],63952:[[39006]],63953:[[20845]],63954:[[25134]],63955:[[38520]],63956:[[20523]],63957:[[23833]],63958:[[28138]],63959:[[36650]],63960:[[24459]],63961:[[24900]],63962:[[26647]],63963:[[29575]],63964:[[38534]],63965:[[21033]],63966:[[21519]],63967:[[23653]],63968:[[26131]],63969:[[26446]],63970:[[26792]],63971:[[27877]],63972:[[29702]],63973:[[30178]],63974:[[32633]],63975:[[35023]],63976:[[35041]],63977:[[37324]],63978:[[38626]],63979:[[21311]],63980:[[28346]],63981:[[21533]],63982:[[29136]],63983:[[29848]],63984:[[34298]],63985:[[38563]],63986:[[40023]],63987:[[40607]],63988:[[26519]],63989:[[28107]],63990:[[33256]],63991:[[31435]],63992:[[31520]],63993:[[31890]],63994:[[29376]],63995:[[28825]],63996:[[35672]],63997:[[20160]],63998:[[33590]],63999:[[21050]],194816:[[27966]],194817:[[28023]],194818:[[27969]],194819:[[28009]],194820:[[28024]],194821:[[28037]],194822:[[146718]],194823:[[27956]],194824:[[28207]],194825:[[28270]],194826:[[15667]],194827:[[28363]],194828:[[28359]],194829:[[147153]],194830:[[28153]],194831:[[28526]],194832:[[147294]],194833:[[147342]],194834:[[28614]],194835:[[28729]],194836:[[28702]],194837:[[28699]],194838:[[15766]],194839:[[28746]],194840:[[28797]],194841:[[28791]],194842:[[28845]],194843:[[132389]],194844:[[28997]],194845:[[148067]],194846:[[29084]],194847:[[148395]],194848:[[29224]],194849:[[29237]],194850:[[29264]],194851:[[149000]],194852:[[29312]],194853:[[29333]],194854:[[149301]],194855:[[149524]],194856:[[29562]],194857:[[29579]],194858:[[16044]],194859:[[29605]],194860:[[16056]],194861:[[16056]],194862:[[29767]],194863:[[29788]],194864:[[29809]],194865:[[29829]],194866:[[29898]],194867:[[16155]],194868:[[29988]],194869:[[150582]],194870:[[30014]],194871:[[150674]],194872:[[30064]],194873:[[139679]],194874:[[30224]],194875:[[151457]],194876:[[151480]],194877:[[151620]],194878:[[16380]],194879:[[16392]],194880:[[30452]],194881:[[151795]],194882:[[151794]],194883:[[151833]],194884:[[151859]],194885:[[30494]],194886:[[30495]],194887:[[30495]],194888:[[30538]],194889:[[16441]],194890:[[30603]],194891:[[16454]],194892:[[16534]],194893:[[152605]],194894:[[30798]],194895:[[30860]],194896:[[30924]],194897:[[16611]],194898:[[153126]],194899:[[31062]],194900:[[153242]],194901:[[153285]],194902:[[31119]],194903:[[31211]],194904:[[16687]],194905:[[31296]],194906:[[31306]],194907:[[31311]],194908:[[153980]],194909:[[154279]],194910:[[154279]],194911:[[31470]],194912:[[16898]],194913:[[154539]],194914:[[31686]],194915:[[31689]],194916:[[16935]],194917:[[154752]],194918:[[31954]],194919:[[17056]],194920:[[31976]],194921:[[31971]],194922:[[32000]],194923:[[155526]],194924:[[32099]],194925:[[17153]],194926:[[32199]],194927:[[32258]],194928:[[32325]],194929:[[17204]],194930:[[156200]],194931:[[156231]],194932:[[17241]],194933:[[156377]],194934:[[32634]],194935:[[156478]],194936:[[32661]],194937:[[32762]],194938:[[32773]],194939:[[156890]],194940:[[156963]],194941:[[32864]],194942:[[157096]],194943:[[32880]],194944:[[144223]],194945:[[17365]],194946:[[32946]],194947:[[33027]],194948:[[17419]],194949:[[33086]],194950:[[23221]],194951:[[157607]],194952:[[157621]],194953:[[144275]],194954:[[144284]],194955:[[33281]],194956:[[33284]],194957:[[36766]],194958:[[17515]],194959:[[33425]],194960:[[33419]],194961:[[33437]],194962:[[21171]],194963:[[33457]],194964:[[33459]],194965:[[33469]],194966:[[33510]],194967:[[158524]],194968:[[33509]],194969:[[33565]],194970:[[33635]],194971:[[33709]],194972:[[33571]],194973:[[33725]],194974:[[33767]],194975:[[33879]],194976:[[33619]],194977:[[33738]],194978:[[33740]],194979:[[33756]],194980:[[158774]],194981:[[159083]],194982:[[158933]],194983:[[17707]],194984:[[34033]],194985:[[34035]],194986:[[34070]],194987:[[160714]],194988:[[34148]],194989:[[159532]],194990:[[17757]],194991:[[17761]],194992:[[159665]],194993:[[159954]],194994:[[17771]],194995:[[34384]],194996:[[34396]],194997:[[34407]],194998:[[34409]],194999:[[34473]],195000:[[34440]],195001:[[34574]],195002:[[34530]],195003:[[34681]],195004:[[34600]],195005:[[34667]],195006:[[34694]],195007:[[17879]],195008:[[34785]],195009:[[34817]],195010:[[17913]],195011:[[34912]],195012:[[34915]],195013:[[161383]],195014:[[35031]],195015:[[35038]],195016:[[17973]],195017:[[35066]],195018:[[13499]],195019:[[161966]],195020:[[162150]],195021:[[18110]],195022:[[18119]],195023:[[35488]],195024:[[35565]],195025:[[35722]],195026:[[35925]],195027:[[162984]],195028:[[36011]],195029:[[36033]],195030:[[36123]],195031:[[36215]],195032:[[163631]],195033:[[133124]],195034:[[36299]],195035:[[36284]],195036:[[36336]],195037:[[133342]],195038:[[36564]],195039:[[36664]],195040:[[165330]],195041:[[165357]],195042:[[37012]],195043:[[37105]],195044:[[37137]],195045:[[165678]],195046:[[37147]],195047:[[37432]],195048:[[37591]],195049:[[37592]],195050:[[37500]],195051:[[37881]],195052:[[37909]],195053:[[166906]],195054:[[38283]],195055:[[18837]],195056:[[38327]],195057:[[167287]],195058:[[18918]],195059:[[38595]],195060:[[23986]],195061:[[38691]],195062:[[168261]],195063:[[168474]],195064:[[19054]],195065:[[19062]],195066:[[38880]],195067:[[168970]],195068:[[19122]],195069:[[169110]],195070:[[38923]],195071:[[38923]]}, -64000:{64000:[[20999]],64001:[[24230]],64002:[[25299]],64003:[[31958]],64004:[[23429]],64005:[[27934]],64006:[[26292]],64007:[[36667]],64008:[[34892]],64009:[[38477]],64010:[[35211]],64011:[[24275]],64012:[[20800]],64013:[[21952]],64016:[[22618]],64018:[[26228]],64021:[[20958]],64022:[[29482]],64023:[[30410]],64024:[[31036]],64025:[[31070]],64026:[[31077]],64027:[[31119]],64028:[[38742]],64029:[[31934]],64030:[[32701]],64032:[[34322]],64034:[[35576]],64037:[[36920]],64038:[[37117]],64042:[[39151]],64043:[[39164]],64044:[[39208]],64045:[[40372]],64046:[[37086]],64047:[[38583]],64048:[[20398]],64049:[[20711]],64050:[[20813]],64051:[[21193]],64052:[[21220]],64053:[[21329]],64054:[[21917]],64055:[[22022]],64056:[[22120]],64057:[[22592]],64058:[[22696]],64059:[[23652]],64060:[[23662]],64061:[[24724]],64062:[[24936]],64063:[[24974]],64064:[[25074]],64065:[[25935]],64066:[[26082]],64067:[[26257]],64068:[[26757]],64069:[[28023]],64070:[[28186]],64071:[[28450]],64072:[[29038]],64073:[[29227]],64074:[[29730]],64075:[[30865]],64076:[[31038]],64077:[[31049]],64078:[[31048]],64079:[[31056]],64080:[[31062]],64081:[[31069]],64082:[[31117]],64083:[[31118]],64084:[[31296]],64085:[[31361]],64086:[[31680]],64087:[[32244]],64088:[[32265]],64089:[[32321]],64090:[[32626]],64091:[[32773]],64092:[[33261]],64093:[[33401]],64094:[[33401]],64095:[[33879]],64096:[[35088]],64097:[[35222]],64098:[[35585]],64099:[[35641]],64100:[[36051]],64101:[[36104]],64102:[[36790]],64103:[[36920]],64104:[[38627]],64105:[[38911]],64106:[[38971]],64107:[[24693]],64108:[[148206]],64109:[[33304]],64112:[[20006]],64113:[[20917]],64114:[[20840]],64115:[[20352]],64116:[[20805]],64117:[[20864]],64118:[[21191]],64119:[[21242]],64120:[[21917]],64121:[[21845]],64122:[[21913]],64123:[[21986]],64124:[[22618]],64125:[[22707]],64126:[[22852]],64127:[[22868]],64128:[[23138]],64129:[[23336]],64130:[[24274]],64131:[[24281]],64132:[[24425]],64133:[[24493]],64134:[[24792]],64135:[[24910]],64136:[[24840]],64137:[[24974]],64138:[[24928]],64139:[[25074]],64140:[[25140]],64141:[[25540]],64142:[[25628]],64143:[[25682]],64144:[[25942]],64145:[[26228]],64146:[[26391]],64147:[[26395]],64148:[[26454]],64149:[[27513]],64150:[[27578]],64151:[[27969]],64152:[[28379]],64153:[[28363]],64154:[[28450]],64155:[[28702]],64156:[[29038]],64157:[[30631]],64158:[[29237]],64159:[[29359]],64160:[[29482]],64161:[[29809]],64162:[[29958]],64163:[[30011]],64164:[[30237]],64165:[[30239]],64166:[[30410]],64167:[[30427]],64168:[[30452]],64169:[[30538]],64170:[[30528]],64171:[[30924]],64172:[[31409]],64173:[[31680]],64174:[[31867]],64175:[[32091]],64176:[[32244]],64177:[[32574]],64178:[[32773]],64179:[[33618]],64180:[[33775]],64181:[[34681]],64182:[[35137]],64183:[[35206]],64184:[[35222]],64185:[[35519]],64186:[[35576]],64187:[[35531]],64188:[[35585]],64189:[[35582]],64190:[[35565]],64191:[[35641]],64192:[[35722]],64193:[[36104]],64194:[[36664]],64195:[[36978]],64196:[[37273]],64197:[[37494]],64198:[[38524]],64199:[[38627]],64200:[[38742]],64201:[[38875]],64202:[[38911]],64203:[[38923]],64204:[[38971]],64205:[[39698]],64206:[[40860]],64207:[[141386]],64208:[[141380]],64209:[[144341]],64210:[[15261]],64211:[[16408]],64212:[[16441]],64213:[[152137]],64214:[[154832]],64215:[[163539]],64216:[[40771]],64217:[[40846]],195072:[[38953]],195073:[[169398]],195074:[[39138]],195075:[[19251]],195076:[[39209]],195077:[[39335]],195078:[[39362]],195079:[[39422]],195080:[[19406]],195081:[[170800]],195082:[[39698]],195083:[[40000]],195084:[[40189]],195085:[[19662]],195086:[[19693]],195087:[[40295]],195088:[[172238]],195089:[[19704]],195090:[[172293]],195091:[[172558]],195092:[[172689]],195093:[[40635]],195094:[[19798]],195095:[[40697]],195096:[[40702]],195097:[[40709]],195098:[[40719]],195099:[[40726]],195100:[[40763]],195101:[[173568]]}, -64256:{64256:[[102,102],256],64257:[[102,105],256],64258:[[102,108],256],64259:[[102,102,105],256],64260:[[102,102,108],256],64261:[[383,116],256],64262:[[115,116],256],64275:[[1396,1398],256],64276:[[1396,1381],256],64277:[[1396,1387],256],64278:[[1406,1398],256],64279:[[1396,1389],256],64285:[[1497,1460],512],64286:[,26],64287:[[1522,1463],512],64288:[[1506],256],64289:[[1488],256],64290:[[1491],256],64291:[[1492],256],64292:[[1499],256],64293:[[1500],256],64294:[[1501],256],64295:[[1512],256],64296:[[1514],256],64297:[[43],256],64298:[[1513,1473],512],64299:[[1513,1474],512],64300:[[64329,1473],512],64301:[[64329,1474],512],64302:[[1488,1463],512],64303:[[1488,1464],512],64304:[[1488,1468],512],64305:[[1489,1468],512],64306:[[1490,1468],512],64307:[[1491,1468],512],64308:[[1492,1468],512],64309:[[1493,1468],512],64310:[[1494,1468],512],64312:[[1496,1468],512],64313:[[1497,1468],512],64314:[[1498,1468],512],64315:[[1499,1468],512],64316:[[1500,1468],512],64318:[[1502,1468],512],64320:[[1504,1468],512],64321:[[1505,1468],512],64323:[[1507,1468],512],64324:[[1508,1468],512],64326:[[1510,1468],512],64327:[[1511,1468],512],64328:[[1512,1468],512],64329:[[1513,1468],512],64330:[[1514,1468],512],64331:[[1493,1465],512],64332:[[1489,1471],512],64333:[[1499,1471],512],64334:[[1508,1471],512],64335:[[1488,1500],256],64336:[[1649],256],64337:[[1649],256],64338:[[1659],256],64339:[[1659],256],64340:[[1659],256],64341:[[1659],256],64342:[[1662],256],64343:[[1662],256],64344:[[1662],256],64345:[[1662],256],64346:[[1664],256],64347:[[1664],256],64348:[[1664],256],64349:[[1664],256],64350:[[1658],256],64351:[[1658],256],64352:[[1658],256],64353:[[1658],256],64354:[[1663],256],64355:[[1663],256],64356:[[1663],256],64357:[[1663],256],64358:[[1657],256],64359:[[1657],256],64360:[[1657],256],64361:[[1657],256],64362:[[1700],256],64363:[[1700],256],64364:[[1700],256],64365:[[1700],256],64366:[[1702],256],64367:[[1702],256],64368:[[1702],256],64369:[[1702],256],64370:[[1668],256],64371:[[1668],256],64372:[[1668],256],64373:[[1668],256],64374:[[1667],256],64375:[[1667],256],64376:[[1667],256],64377:[[1667],256],64378:[[1670],256],64379:[[1670],256],64380:[[1670],256],64381:[[1670],256],64382:[[1671],256],64383:[[1671],256],64384:[[1671],256],64385:[[1671],256],64386:[[1677],256],64387:[[1677],256],64388:[[1676],256],64389:[[1676],256],64390:[[1678],256],64391:[[1678],256],64392:[[1672],256],64393:[[1672],256],64394:[[1688],256],64395:[[1688],256],64396:[[1681],256],64397:[[1681],256],64398:[[1705],256],64399:[[1705],256],64400:[[1705],256],64401:[[1705],256],64402:[[1711],256],64403:[[1711],256],64404:[[1711],256],64405:[[1711],256],64406:[[1715],256],64407:[[1715],256],64408:[[1715],256],64409:[[1715],256],64410:[[1713],256],64411:[[1713],256],64412:[[1713],256],64413:[[1713],256],64414:[[1722],256],64415:[[1722],256],64416:[[1723],256],64417:[[1723],256],64418:[[1723],256],64419:[[1723],256],64420:[[1728],256],64421:[[1728],256],64422:[[1729],256],64423:[[1729],256],64424:[[1729],256],64425:[[1729],256],64426:[[1726],256],64427:[[1726],256],64428:[[1726],256],64429:[[1726],256],64430:[[1746],256],64431:[[1746],256],64432:[[1747],256],64433:[[1747],256],64467:[[1709],256],64468:[[1709],256],64469:[[1709],256],64470:[[1709],256],64471:[[1735],256],64472:[[1735],256],64473:[[1734],256],64474:[[1734],256],64475:[[1736],256],64476:[[1736],256],64477:[[1655],256],64478:[[1739],256],64479:[[1739],256],64480:[[1733],256],64481:[[1733],256],64482:[[1737],256],64483:[[1737],256],64484:[[1744],256],64485:[[1744],256],64486:[[1744],256],64487:[[1744],256],64488:[[1609],256],64489:[[1609],256],64490:[[1574,1575],256],64491:[[1574,1575],256],64492:[[1574,1749],256],64493:[[1574,1749],256],64494:[[1574,1608],256],64495:[[1574,1608],256],64496:[[1574,1735],256],64497:[[1574,1735],256],64498:[[1574,1734],256],64499:[[1574,1734],256],64500:[[1574,1736],256],64501:[[1574,1736],256],64502:[[1574,1744],256],64503:[[1574,1744],256],64504:[[1574,1744],256],64505:[[1574,1609],256],64506:[[1574,1609],256],64507:[[1574,1609],256],64508:[[1740],256],64509:[[1740],256],64510:[[1740],256],64511:[[1740],256]}, -64512:{64512:[[1574,1580],256],64513:[[1574,1581],256],64514:[[1574,1605],256],64515:[[1574,1609],256],64516:[[1574,1610],256],64517:[[1576,1580],256],64518:[[1576,1581],256],64519:[[1576,1582],256],64520:[[1576,1605],256],64521:[[1576,1609],256],64522:[[1576,1610],256],64523:[[1578,1580],256],64524:[[1578,1581],256],64525:[[1578,1582],256],64526:[[1578,1605],256],64527:[[1578,1609],256],64528:[[1578,1610],256],64529:[[1579,1580],256],64530:[[1579,1605],256],64531:[[1579,1609],256],64532:[[1579,1610],256],64533:[[1580,1581],256],64534:[[1580,1605],256],64535:[[1581,1580],256],64536:[[1581,1605],256],64537:[[1582,1580],256],64538:[[1582,1581],256],64539:[[1582,1605],256],64540:[[1587,1580],256],64541:[[1587,1581],256],64542:[[1587,1582],256],64543:[[1587,1605],256],64544:[[1589,1581],256],64545:[[1589,1605],256],64546:[[1590,1580],256],64547:[[1590,1581],256],64548:[[1590,1582],256],64549:[[1590,1605],256],64550:[[1591,1581],256],64551:[[1591,1605],256],64552:[[1592,1605],256],64553:[[1593,1580],256],64554:[[1593,1605],256],64555:[[1594,1580],256],64556:[[1594,1605],256],64557:[[1601,1580],256],64558:[[1601,1581],256],64559:[[1601,1582],256],64560:[[1601,1605],256],64561:[[1601,1609],256],64562:[[1601,1610],256],64563:[[1602,1581],256],64564:[[1602,1605],256],64565:[[1602,1609],256],64566:[[1602,1610],256],64567:[[1603,1575],256],64568:[[1603,1580],256],64569:[[1603,1581],256],64570:[[1603,1582],256],64571:[[1603,1604],256],64572:[[1603,1605],256],64573:[[1603,1609],256],64574:[[1603,1610],256],64575:[[1604,1580],256],64576:[[1604,1581],256],64577:[[1604,1582],256],64578:[[1604,1605],256],64579:[[1604,1609],256],64580:[[1604,1610],256],64581:[[1605,1580],256],64582:[[1605,1581],256],64583:[[1605,1582],256],64584:[[1605,1605],256],64585:[[1605,1609],256],64586:[[1605,1610],256],64587:[[1606,1580],256],64588:[[1606,1581],256],64589:[[1606,1582],256],64590:[[1606,1605],256],64591:[[1606,1609],256],64592:[[1606,1610],256],64593:[[1607,1580],256],64594:[[1607,1605],256],64595:[[1607,1609],256],64596:[[1607,1610],256],64597:[[1610,1580],256],64598:[[1610,1581],256],64599:[[1610,1582],256],64600:[[1610,1605],256],64601:[[1610,1609],256],64602:[[1610,1610],256],64603:[[1584,1648],256],64604:[[1585,1648],256],64605:[[1609,1648],256],64606:[[32,1612,1617],256],64607:[[32,1613,1617],256],64608:[[32,1614,1617],256],64609:[[32,1615,1617],256],64610:[[32,1616,1617],256],64611:[[32,1617,1648],256],64612:[[1574,1585],256],64613:[[1574,1586],256],64614:[[1574,1605],256],64615:[[1574,1606],256],64616:[[1574,1609],256],64617:[[1574,1610],256],64618:[[1576,1585],256],64619:[[1576,1586],256],64620:[[1576,1605],256],64621:[[1576,1606],256],64622:[[1576,1609],256],64623:[[1576,1610],256],64624:[[1578,1585],256],64625:[[1578,1586],256],64626:[[1578,1605],256],64627:[[1578,1606],256],64628:[[1578,1609],256],64629:[[1578,1610],256],64630:[[1579,1585],256],64631:[[1579,1586],256],64632:[[1579,1605],256],64633:[[1579,1606],256],64634:[[1579,1609],256],64635:[[1579,1610],256],64636:[[1601,1609],256],64637:[[1601,1610],256],64638:[[1602,1609],256],64639:[[1602,1610],256],64640:[[1603,1575],256],64641:[[1603,1604],256],64642:[[1603,1605],256],64643:[[1603,1609],256],64644:[[1603,1610],256],64645:[[1604,1605],256],64646:[[1604,1609],256],64647:[[1604,1610],256],64648:[[1605,1575],256],64649:[[1605,1605],256],64650:[[1606,1585],256],64651:[[1606,1586],256],64652:[[1606,1605],256],64653:[[1606,1606],256],64654:[[1606,1609],256],64655:[[1606,1610],256],64656:[[1609,1648],256],64657:[[1610,1585],256],64658:[[1610,1586],256],64659:[[1610,1605],256],64660:[[1610,1606],256],64661:[[1610,1609],256],64662:[[1610,1610],256],64663:[[1574,1580],256],64664:[[1574,1581],256],64665:[[1574,1582],256],64666:[[1574,1605],256],64667:[[1574,1607],256],64668:[[1576,1580],256],64669:[[1576,1581],256],64670:[[1576,1582],256],64671:[[1576,1605],256],64672:[[1576,1607],256],64673:[[1578,1580],256],64674:[[1578,1581],256],64675:[[1578,1582],256],64676:[[1578,1605],256],64677:[[1578,1607],256],64678:[[1579,1605],256],64679:[[1580,1581],256],64680:[[1580,1605],256],64681:[[1581,1580],256],64682:[[1581,1605],256],64683:[[1582,1580],256],64684:[[1582,1605],256],64685:[[1587,1580],256],64686:[[1587,1581],256],64687:[[1587,1582],256],64688:[[1587,1605],256],64689:[[1589,1581],256],64690:[[1589,1582],256],64691:[[1589,1605],256],64692:[[1590,1580],256],64693:[[1590,1581],256],64694:[[1590,1582],256],64695:[[1590,1605],256],64696:[[1591,1581],256],64697:[[1592,1605],256],64698:[[1593,1580],256],64699:[[1593,1605],256],64700:[[1594,1580],256],64701:[[1594,1605],256],64702:[[1601,1580],256],64703:[[1601,1581],256],64704:[[1601,1582],256],64705:[[1601,1605],256],64706:[[1602,1581],256],64707:[[1602,1605],256],64708:[[1603,1580],256],64709:[[1603,1581],256],64710:[[1603,1582],256],64711:[[1603,1604],256],64712:[[1603,1605],256],64713:[[1604,1580],256],64714:[[1604,1581],256],64715:[[1604,1582],256],64716:[[1604,1605],256],64717:[[1604,1607],256],64718:[[1605,1580],256],64719:[[1605,1581],256],64720:[[1605,1582],256],64721:[[1605,1605],256],64722:[[1606,1580],256],64723:[[1606,1581],256],64724:[[1606,1582],256],64725:[[1606,1605],256],64726:[[1606,1607],256],64727:[[1607,1580],256],64728:[[1607,1605],256],64729:[[1607,1648],256],64730:[[1610,1580],256],64731:[[1610,1581],256],64732:[[1610,1582],256],64733:[[1610,1605],256],64734:[[1610,1607],256],64735:[[1574,1605],256],64736:[[1574,1607],256],64737:[[1576,1605],256],64738:[[1576,1607],256],64739:[[1578,1605],256],64740:[[1578,1607],256],64741:[[1579,1605],256],64742:[[1579,1607],256],64743:[[1587,1605],256],64744:[[1587,1607],256],64745:[[1588,1605],256],64746:[[1588,1607],256],64747:[[1603,1604],256],64748:[[1603,1605],256],64749:[[1604,1605],256],64750:[[1606,1605],256],64751:[[1606,1607],256],64752:[[1610,1605],256],64753:[[1610,1607],256],64754:[[1600,1614,1617],256],64755:[[1600,1615,1617],256],64756:[[1600,1616,1617],256],64757:[[1591,1609],256],64758:[[1591,1610],256],64759:[[1593,1609],256],64760:[[1593,1610],256],64761:[[1594,1609],256],64762:[[1594,1610],256],64763:[[1587,1609],256],64764:[[1587,1610],256],64765:[[1588,1609],256],64766:[[1588,1610],256],64767:[[1581,1609],256]}, -64768:{64768:[[1581,1610],256],64769:[[1580,1609],256],64770:[[1580,1610],256],64771:[[1582,1609],256],64772:[[1582,1610],256],64773:[[1589,1609],256],64774:[[1589,1610],256],64775:[[1590,1609],256],64776:[[1590,1610],256],64777:[[1588,1580],256],64778:[[1588,1581],256],64779:[[1588,1582],256],64780:[[1588,1605],256],64781:[[1588,1585],256],64782:[[1587,1585],256],64783:[[1589,1585],256],64784:[[1590,1585],256],64785:[[1591,1609],256],64786:[[1591,1610],256],64787:[[1593,1609],256],64788:[[1593,1610],256],64789:[[1594,1609],256],64790:[[1594,1610],256],64791:[[1587,1609],256],64792:[[1587,1610],256],64793:[[1588,1609],256],64794:[[1588,1610],256],64795:[[1581,1609],256],64796:[[1581,1610],256],64797:[[1580,1609],256],64798:[[1580,1610],256],64799:[[1582,1609],256],64800:[[1582,1610],256],64801:[[1589,1609],256],64802:[[1589,1610],256],64803:[[1590,1609],256],64804:[[1590,1610],256],64805:[[1588,1580],256],64806:[[1588,1581],256],64807:[[1588,1582],256],64808:[[1588,1605],256],64809:[[1588,1585],256],64810:[[1587,1585],256],64811:[[1589,1585],256],64812:[[1590,1585],256],64813:[[1588,1580],256],64814:[[1588,1581],256],64815:[[1588,1582],256],64816:[[1588,1605],256],64817:[[1587,1607],256],64818:[[1588,1607],256],64819:[[1591,1605],256],64820:[[1587,1580],256],64821:[[1587,1581],256],64822:[[1587,1582],256],64823:[[1588,1580],256],64824:[[1588,1581],256],64825:[[1588,1582],256],64826:[[1591,1605],256],64827:[[1592,1605],256],64828:[[1575,1611],256],64829:[[1575,1611],256],64848:[[1578,1580,1605],256],64849:[[1578,1581,1580],256],64850:[[1578,1581,1580],256],64851:[[1578,1581,1605],256],64852:[[1578,1582,1605],256],64853:[[1578,1605,1580],256],64854:[[1578,1605,1581],256],64855:[[1578,1605,1582],256],64856:[[1580,1605,1581],256],64857:[[1580,1605,1581],256],64858:[[1581,1605,1610],256],64859:[[1581,1605,1609],256],64860:[[1587,1581,1580],256],64861:[[1587,1580,1581],256],64862:[[1587,1580,1609],256],64863:[[1587,1605,1581],256],64864:[[1587,1605,1581],256],64865:[[1587,1605,1580],256],64866:[[1587,1605,1605],256],64867:[[1587,1605,1605],256],64868:[[1589,1581,1581],256],64869:[[1589,1581,1581],256],64870:[[1589,1605,1605],256],64871:[[1588,1581,1605],256],64872:[[1588,1581,1605],256],64873:[[1588,1580,1610],256],64874:[[1588,1605,1582],256],64875:[[1588,1605,1582],256],64876:[[1588,1605,1605],256],64877:[[1588,1605,1605],256],64878:[[1590,1581,1609],256],64879:[[1590,1582,1605],256],64880:[[1590,1582,1605],256],64881:[[1591,1605,1581],256],64882:[[1591,1605,1581],256],64883:[[1591,1605,1605],256],64884:[[1591,1605,1610],256],64885:[[1593,1580,1605],256],64886:[[1593,1605,1605],256],64887:[[1593,1605,1605],256],64888:[[1593,1605,1609],256],64889:[[1594,1605,1605],256],64890:[[1594,1605,1610],256],64891:[[1594,1605,1609],256],64892:[[1601,1582,1605],256],64893:[[1601,1582,1605],256],64894:[[1602,1605,1581],256],64895:[[1602,1605,1605],256],64896:[[1604,1581,1605],256],64897:[[1604,1581,1610],256],64898:[[1604,1581,1609],256],64899:[[1604,1580,1580],256],64900:[[1604,1580,1580],256],64901:[[1604,1582,1605],256],64902:[[1604,1582,1605],256],64903:[[1604,1605,1581],256],64904:[[1604,1605,1581],256],64905:[[1605,1581,1580],256],64906:[[1605,1581,1605],256],64907:[[1605,1581,1610],256],64908:[[1605,1580,1581],256],64909:[[1605,1580,1605],256],64910:[[1605,1582,1580],256],64911:[[1605,1582,1605],256],64914:[[1605,1580,1582],256],64915:[[1607,1605,1580],256],64916:[[1607,1605,1605],256],64917:[[1606,1581,1605],256],64918:[[1606,1581,1609],256],64919:[[1606,1580,1605],256],64920:[[1606,1580,1605],256],64921:[[1606,1580,1609],256],64922:[[1606,1605,1610],256],64923:[[1606,1605,1609],256],64924:[[1610,1605,1605],256],64925:[[1610,1605,1605],256],64926:[[1576,1582,1610],256],64927:[[1578,1580,1610],256],64928:[[1578,1580,1609],256],64929:[[1578,1582,1610],256],64930:[[1578,1582,1609],256],64931:[[1578,1605,1610],256],64932:[[1578,1605,1609],256],64933:[[1580,1605,1610],256],64934:[[1580,1581,1609],256],64935:[[1580,1605,1609],256],64936:[[1587,1582,1609],256],64937:[[1589,1581,1610],256],64938:[[1588,1581,1610],256],64939:[[1590,1581,1610],256],64940:[[1604,1580,1610],256],64941:[[1604,1605,1610],256],64942:[[1610,1581,1610],256],64943:[[1610,1580,1610],256],64944:[[1610,1605,1610],256],64945:[[1605,1605,1610],256],64946:[[1602,1605,1610],256],64947:[[1606,1581,1610],256],64948:[[1602,1605,1581],256],64949:[[1604,1581,1605],256],64950:[[1593,1605,1610],256],64951:[[1603,1605,1610],256],64952:[[1606,1580,1581],256],64953:[[1605,1582,1610],256],64954:[[1604,1580,1605],256],64955:[[1603,1605,1605],256],64956:[[1604,1580,1605],256],64957:[[1606,1580,1581],256],64958:[[1580,1581,1610],256],64959:[[1581,1580,1610],256],64960:[[1605,1580,1610],256],64961:[[1601,1605,1610],256],64962:[[1576,1581,1610],256],64963:[[1603,1605,1605],256],64964:[[1593,1580,1605],256],64965:[[1589,1605,1605],256],64966:[[1587,1582,1610],256],64967:[[1606,1580,1610],256],65008:[[1589,1604,1746],256],65009:[[1602,1604,1746],256],65010:[[1575,1604,1604,1607],256],65011:[[1575,1603,1576,1585],256],65012:[[1605,1581,1605,1583],256],65013:[[1589,1604,1593,1605],256],65014:[[1585,1587,1608,1604],256],65015:[[1593,1604,1610,1607],256],65016:[[1608,1587,1604,1605],256],65017:[[1589,1604,1609],256],65018:[[1589,1604,1609,32,1575,1604,1604,1607,32,1593,1604,1610,1607,32,1608,1587,1604,1605],256],65019:[[1580,1604,32,1580,1604,1575,1604,1607],256],65020:[[1585,1740,1575,1604],256]}, -65024:{65040:[[44],256],65041:[[12289],256],65042:[[12290],256],65043:[[58],256],65044:[[59],256],65045:[[33],256],65046:[[63],256],65047:[[12310],256],65048:[[12311],256],65049:[[8230],256],65056:[,230],65057:[,230],65058:[,230],65059:[,230],65060:[,230],65061:[,230],65062:[,230],65063:[,220],65064:[,220],65065:[,220],65066:[,220],65067:[,220],65068:[,220],65069:[,220],65072:[[8229],256],65073:[[8212],256],65074:[[8211],256],65075:[[95],256],65076:[[95],256],65077:[[40],256],65078:[[41],256],65079:[[123],256],65080:[[125],256],65081:[[12308],256],65082:[[12309],256],65083:[[12304],256],65084:[[12305],256],65085:[[12298],256],65086:[[12299],256],65087:[[12296],256],65088:[[12297],256],65089:[[12300],256],65090:[[12301],256],65091:[[12302],256],65092:[[12303],256],65095:[[91],256],65096:[[93],256],65097:[[8254],256],65098:[[8254],256],65099:[[8254],256],65100:[[8254],256],65101:[[95],256],65102:[[95],256],65103:[[95],256],65104:[[44],256],65105:[[12289],256],65106:[[46],256],65108:[[59],256],65109:[[58],256],65110:[[63],256],65111:[[33],256],65112:[[8212],256],65113:[[40],256],65114:[[41],256],65115:[[123],256],65116:[[125],256],65117:[[12308],256],65118:[[12309],256],65119:[[35],256],65120:[[38],256],65121:[[42],256],65122:[[43],256],65123:[[45],256],65124:[[60],256],65125:[[62],256],65126:[[61],256],65128:[[92],256],65129:[[36],256],65130:[[37],256],65131:[[64],256],65136:[[32,1611],256],65137:[[1600,1611],256],65138:[[32,1612],256],65140:[[32,1613],256],65142:[[32,1614],256],65143:[[1600,1614],256],65144:[[32,1615],256],65145:[[1600,1615],256],65146:[[32,1616],256],65147:[[1600,1616],256],65148:[[32,1617],256],65149:[[1600,1617],256],65150:[[32,1618],256],65151:[[1600,1618],256],65152:[[1569],256],65153:[[1570],256],65154:[[1570],256],65155:[[1571],256],65156:[[1571],256],65157:[[1572],256],65158:[[1572],256],65159:[[1573],256],65160:[[1573],256],65161:[[1574],256],65162:[[1574],256],65163:[[1574],256],65164:[[1574],256],65165:[[1575],256],65166:[[1575],256],65167:[[1576],256],65168:[[1576],256],65169:[[1576],256],65170:[[1576],256],65171:[[1577],256],65172:[[1577],256],65173:[[1578],256],65174:[[1578],256],65175:[[1578],256],65176:[[1578],256],65177:[[1579],256],65178:[[1579],256],65179:[[1579],256],65180:[[1579],256],65181:[[1580],256],65182:[[1580],256],65183:[[1580],256],65184:[[1580],256],65185:[[1581],256],65186:[[1581],256],65187:[[1581],256],65188:[[1581],256],65189:[[1582],256],65190:[[1582],256],65191:[[1582],256],65192:[[1582],256],65193:[[1583],256],65194:[[1583],256],65195:[[1584],256],65196:[[1584],256],65197:[[1585],256],65198:[[1585],256],65199:[[1586],256],65200:[[1586],256],65201:[[1587],256],65202:[[1587],256],65203:[[1587],256],65204:[[1587],256],65205:[[1588],256],65206:[[1588],256],65207:[[1588],256],65208:[[1588],256],65209:[[1589],256],65210:[[1589],256],65211:[[1589],256],65212:[[1589],256],65213:[[1590],256],65214:[[1590],256],65215:[[1590],256],65216:[[1590],256],65217:[[1591],256],65218:[[1591],256],65219:[[1591],256],65220:[[1591],256],65221:[[1592],256],65222:[[1592],256],65223:[[1592],256],65224:[[1592],256],65225:[[1593],256],65226:[[1593],256],65227:[[1593],256],65228:[[1593],256],65229:[[1594],256],65230:[[1594],256],65231:[[1594],256],65232:[[1594],256],65233:[[1601],256],65234:[[1601],256],65235:[[1601],256],65236:[[1601],256],65237:[[1602],256],65238:[[1602],256],65239:[[1602],256],65240:[[1602],256],65241:[[1603],256],65242:[[1603],256],65243:[[1603],256],65244:[[1603],256],65245:[[1604],256],65246:[[1604],256],65247:[[1604],256],65248:[[1604],256],65249:[[1605],256],65250:[[1605],256],65251:[[1605],256],65252:[[1605],256],65253:[[1606],256],65254:[[1606],256],65255:[[1606],256],65256:[[1606],256],65257:[[1607],256],65258:[[1607],256],65259:[[1607],256],65260:[[1607],256],65261:[[1608],256],65262:[[1608],256],65263:[[1609],256],65264:[[1609],256],65265:[[1610],256],65266:[[1610],256],65267:[[1610],256],65268:[[1610],256],65269:[[1604,1570],256],65270:[[1604,1570],256],65271:[[1604,1571],256],65272:[[1604,1571],256],65273:[[1604,1573],256],65274:[[1604,1573],256],65275:[[1604,1575],256],65276:[[1604,1575],256]}, -65280:{65281:[[33],256],65282:[[34],256],65283:[[35],256],65284:[[36],256],65285:[[37],256],65286:[[38],256],65287:[[39],256],65288:[[40],256],65289:[[41],256],65290:[[42],256],65291:[[43],256],65292:[[44],256],65293:[[45],256],65294:[[46],256],65295:[[47],256],65296:[[48],256],65297:[[49],256],65298:[[50],256],65299:[[51],256],65300:[[52],256],65301:[[53],256],65302:[[54],256],65303:[[55],256],65304:[[56],256],65305:[[57],256],65306:[[58],256],65307:[[59],256],65308:[[60],256],65309:[[61],256],65310:[[62],256],65311:[[63],256],65312:[[64],256],65313:[[65],256],65314:[[66],256],65315:[[67],256],65316:[[68],256],65317:[[69],256],65318:[[70],256],65319:[[71],256],65320:[[72],256],65321:[[73],256],65322:[[74],256],65323:[[75],256],65324:[[76],256],65325:[[77],256],65326:[[78],256],65327:[[79],256],65328:[[80],256],65329:[[81],256],65330:[[82],256],65331:[[83],256],65332:[[84],256],65333:[[85],256],65334:[[86],256],65335:[[87],256],65336:[[88],256],65337:[[89],256],65338:[[90],256],65339:[[91],256],65340:[[92],256],65341:[[93],256],65342:[[94],256],65343:[[95],256],65344:[[96],256],65345:[[97],256],65346:[[98],256],65347:[[99],256],65348:[[100],256],65349:[[101],256],65350:[[102],256],65351:[[103],256],65352:[[104],256],65353:[[105],256],65354:[[106],256],65355:[[107],256],65356:[[108],256],65357:[[109],256],65358:[[110],256],65359:[[111],256],65360:[[112],256],65361:[[113],256],65362:[[114],256],65363:[[115],256],65364:[[116],256],65365:[[117],256],65366:[[118],256],65367:[[119],256],65368:[[120],256],65369:[[121],256],65370:[[122],256],65371:[[123],256],65372:[[124],256],65373:[[125],256],65374:[[126],256],65375:[[10629],256],65376:[[10630],256],65377:[[12290],256],65378:[[12300],256],65379:[[12301],256],65380:[[12289],256],65381:[[12539],256],65382:[[12530],256],65383:[[12449],256],65384:[[12451],256],65385:[[12453],256],65386:[[12455],256],65387:[[12457],256],65388:[[12515],256],65389:[[12517],256],65390:[[12519],256],65391:[[12483],256],65392:[[12540],256],65393:[[12450],256],65394:[[12452],256],65395:[[12454],256],65396:[[12456],256],65397:[[12458],256],65398:[[12459],256],65399:[[12461],256],65400:[[12463],256],65401:[[12465],256],65402:[[12467],256],65403:[[12469],256],65404:[[12471],256],65405:[[12473],256],65406:[[12475],256],65407:[[12477],256],65408:[[12479],256],65409:[[12481],256],65410:[[12484],256],65411:[[12486],256],65412:[[12488],256],65413:[[12490],256],65414:[[12491],256],65415:[[12492],256],65416:[[12493],256],65417:[[12494],256],65418:[[12495],256],65419:[[12498],256],65420:[[12501],256],65421:[[12504],256],65422:[[12507],256],65423:[[12510],256],65424:[[12511],256],65425:[[12512],256],65426:[[12513],256],65427:[[12514],256],65428:[[12516],256],65429:[[12518],256],65430:[[12520],256],65431:[[12521],256],65432:[[12522],256],65433:[[12523],256],65434:[[12524],256],65435:[[12525],256],65436:[[12527],256],65437:[[12531],256],65438:[[12441],256],65439:[[12442],256],65440:[[12644],256],65441:[[12593],256],65442:[[12594],256],65443:[[12595],256],65444:[[12596],256],65445:[[12597],256],65446:[[12598],256],65447:[[12599],256],65448:[[12600],256],65449:[[12601],256],65450:[[12602],256],65451:[[12603],256],65452:[[12604],256],65453:[[12605],256],65454:[[12606],256],65455:[[12607],256],65456:[[12608],256],65457:[[12609],256],65458:[[12610],256],65459:[[12611],256],65460:[[12612],256],65461:[[12613],256],65462:[[12614],256],65463:[[12615],256],65464:[[12616],256],65465:[[12617],256],65466:[[12618],256],65467:[[12619],256],65468:[[12620],256],65469:[[12621],256],65470:[[12622],256],65474:[[12623],256],65475:[[12624],256],65476:[[12625],256],65477:[[12626],256],65478:[[12627],256],65479:[[12628],256],65482:[[12629],256],65483:[[12630],256],65484:[[12631],256],65485:[[12632],256],65486:[[12633],256],65487:[[12634],256],65490:[[12635],256],65491:[[12636],256],65492:[[12637],256],65493:[[12638],256],65494:[[12639],256],65495:[[12640],256],65498:[[12641],256],65499:[[12642],256],65500:[[12643],256],65504:[[162],256],65505:[[163],256],65506:[[172],256],65507:[[175],256],65508:[[166],256],65509:[[165],256],65510:[[8361],256],65512:[[9474],256],65513:[[8592],256],65514:[[8593],256],65515:[[8594],256],65516:[[8595],256],65517:[[9632],256],65518:[[9675],256]} - -}; - - /***** Module to export */ - var unorm = { - nfc: nfc, - nfd: nfd, - nfkc: nfkc, - nfkd: nfkd - }; - - module.exports = unorm; -}(this)); diff --git a/webpack.browser.js b/webpack.browser.js deleted file mode 100644 index 7579f8704..000000000 --- a/webpack.browser.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -const webpack = require('webpack'); -const path = require('path'); -const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); -const str = JSON.stringify; -const env = process.env; - -module.exports = { - target: 'web', - entry: { - 'bcoin': './lib/bcoin-browser', - 'bcoin-worker': './lib/workers/worker' - }, - output: { - path: path.join(__dirname, 'browser'), - filename: '[name].js' - }, - resolve: { - modules: ['node_modules'], - extensions: ['-browser.js', '.js', '.json'] - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env.BCOIN_NETWORK': - str(env.BCOIN_NETWORK || 'main'), - 'process.env.BCOIN_WORKER_FILE': - str(env.BCOIN_WORKER_FILE || '/bcoin-worker.js') - }), - new UglifyJsPlugin({ - compress: { - warnings: true - } - }) - ] -}; diff --git a/webpack.compat.js b/webpack.compat.js deleted file mode 100644 index 26a352675..000000000 --- a/webpack.compat.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -const webpack = require('webpack'); -const path = require('path'); -const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); -const str = JSON.stringify; -const env = process.env; - -module.exports = { - target: 'web', - entry: { - 'bcoin': './lib/bcoin-browser', - 'bcoin-worker': './lib/workers/worker' - }, - output: { - path: path.join(__dirname, 'browser'), - filename: '[name].js' - }, - resolve: { - modules: ['node_modules'], - extensions: ['-browser.js', '.js', '.json'] - }, - module: { - rules: [{ - test: /\.js$/, - exclude: /node_modules\/(?!bcoin|elliptic|bn\.js|n64)/, - loader: 'babel-loader' - }] - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env.BCOIN_NETWORK': - str(env.BCOIN_NETWORK || 'main'), - 'process.env.BCOIN_WORKER_FILE': - str(env.BCOIN_WORKER_FILE || '/bcoin-worker.js') - }), - new UglifyJsPlugin({ - compress: { - warnings: false - } - }) - ] -}; diff --git a/webpack.node.js b/webpack.node.js deleted file mode 100644 index 110ce7231..000000000 --- a/webpack.node.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict'; - -const webpack = require('webpack'); -const path = require('path'); -const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); -const str = JSON.stringify; -const env = process.env; - -module.exports = { - target: 'node', - entry: { - 'bcoin': './lib/bcoin-browser', - 'bcoin-worker': './lib/workers/worker' - }, - output: { - path: __dirname, - filename: '[name].js', - libraryTarget: 'commonjs2' - }, - resolve: { - modules: ['node_modules'], - extensions: ['.node', '.js', '.json'], - alias: { - 'bindings': path.resolve(__dirname, 'webpack', 'bindings.js') - } - }, - node: { - __dirname: false, - __filename: false - }, - module: { - rules: [{ - test: /\.node$/, - loader: 'node-loader' - }] - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env.BCOIN_WORKER_FILE': - str(env.BCOIN_WORKER_FILE || 'bcoin-worker.js') - }), - new webpack.IgnorePlugin(/^utf-8-validate|bufferutil$/), - new UglifyJsPlugin({ - compress: { - warnings: true - } - }) - ] -}; diff --git a/webpack/bindings.js b/webpack/bindings.js deleted file mode 100644 index b8ed4ff15..000000000 --- a/webpack/bindings.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -module.exports = function bindings(name) { - switch (name) { - case 'leveldown': - return require('leveldown/build/Release/leveldown.node'); - case 'bcoin-native': - return require('bcoin-native/build/Release/bcoin-native.node'); - case 'secp256k1': - return require('secp256k1/build/Release/secp256k1.node'); - } - throw new Error(`Cannot find module "${name}".`); -};