diff --git a/.gitignore b/.gitignore index 67d3750..42b42f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,30 +1,33 @@ .idea +package-lock.json +test-build +dist # Created by https://www.gitignore.io/api/osx,node ### OSX ### -*.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon -# Thumbnails -._* -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon +# Thumbnails +._* +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk ### Node ### diff --git a/.travis.yml b/.travis.yml index fdd5946..8c67ccd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: node_js node_js: - - "7" - "8" -env: - - CXX=g++-4.8 + - "10" + - "11" + - "12" addons: firefox: latest apt: @@ -14,6 +14,7 @@ addons: before_install: - sh -e /etc/init.d/xvfb start env: + - CXX=g++-4.8 global: - DISPLAY=:99.0 matrix: @@ -28,9 +29,19 @@ matrix: node_js: "8" env: CXX=g++-4.8 TEST_SUITE=lint - os: linux - node_js: "7" + node_js: "8" env: CXX=g++-4.8 TEST_SUITE=test:node - os: linux node_js: "8" env: CXX=g++-4.8 TEST_SUITE=test:node + - os: linux + node_js: "10" + env: CXX=g++-4.8 TEST_SUITE=test:node + - os: linux + node_js: "11" + env: CXX=g++-4.8 TEST_SUITE=test:node + - os: linux + node_js: "12" + env: CXX=g++-4.8 TEST_SUITE=test:node + script: npm run $TEST_SUITE diff --git a/CHANGELOG.md b/CHANGELOG.md index 7456e19..83d90c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,21 @@ # Changelog + All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -(modification: no type change headlines) and this project adheres to +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +(modification: no type change headlines) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - ## [2.2.0] - 2019-02-06 -**Petersburg** (aka `constantinopleFix`) as well as **Goerli** -support/readiness by updating to a supporting `ethereumjs-common` version -[v1.1.0](https://github.com/ethereumjs/ethereumjs-common/releases/tag/v1.1.0), + +**Petersburg** (aka `constantinopleFix`) as well as **Goerli** +support/readiness by updating to a supporting `ethereumjs-common` version +[v1.1.0](https://github.com/ethereumjs/ethereumjs-common/releases/tag/v1.1.0), PR [#64](https://github.com/ethereumjs/ethereumjs-block/pull/64) **Other Changes:** -- Fixed package size issue by excluding tests and docs from being included in + +- Fixed package size issue by excluding tests and docs from being included in the package, PR [#66](https://github.com/ethereumjs/ethereumjs-block/pull/66) - Error message fixes in `index.js`, PR [#62](https://github.com/ethereumjs/ethereumjs-block/pull/62) @@ -25,58 +27,66 @@ PR [#64](https://github.com/ethereumjs/ethereumjs-block/pull/64) [2.2.0]: https://github.com/ethereumjs/ethereumjs-vm/compare/v2.1.0...v2.2.0 ## [2.1.0] - 2018-10-19 + - **Constantinople** support, added difficulty bomb delay (EIP-1234), PR [#54](https://github.com/ethereumjs/ethereumjs-block/pull/54) - Updated test data, added Constantinople tests, PR [#56](https://github.com/ethereumjs/ethereumjs-block/pull/56), [#57](https://github.com/ethereumjs/ethereumjs-block/pull/57) -- Added ``timestamp`` field to ``setGenesisParams()``, PR [#52](https://github.com/ethereumjs/ethereumjs-block/pull/52) +- Added `timestamp` field to `setGenesisParams()`, PR [#52](https://github.com/ethereumjs/ethereumjs-block/pull/52) [2.1.0]: https://github.com/ethereumjs/ethereumjs-vm/compare/v2.0.1...v2.1.0 ## [2.0.1] - 2018-08-08 -- Fixes ``BlockHeader.prototype.validate()`` bug, see PR [#49](https://github.com/ethereumjs/ethereumjs-block/pull/49) + +- Fixes `BlockHeader.prototype.validate()` bug, see PR [#49](https://github.com/ethereumjs/ethereumjs-block/pull/49) [2.0.1]: https://github.com/ethereumjs/ethereumjs-vm/compare/v2.0.0...v2.0.1 ## [2.0.0] - 2018-06-25 -This release introduces both support for different ``chains`` (``mainnet``, ``ropsten``, ...) -and ``hardforks`` up to the latest applied HF (``byzantium``). Parameters and genesis values + +This release introduces both support for different `chains` (`mainnet`, `ropsten`, ...) +and `hardforks` up to the latest applied HF (`byzantium`). Parameters and genesis values are provided by the new [ethereumjs-common](https://github.com/ethereumjs/ethereumjs-common) library which also defines the set of supported chains and forks. Changes in detail: -- New initialization parameters ``opts.chain`` (default: ``mainnet``) and ``opts.hardfork`` - (default: ``null``, block number-based behaviour), PR [#44](https://github.com/ethereumjs/ethereumjs-block/pull/44) -- Alternatively a ``Common`` class object can be provided directly with the ``opts.common`` parameter, + +- New initialization parameters `opts.chain` (default: `mainnet`) and `opts.hardfork` + (default: `null`, block number-based behaviour), PR [#44](https://github.com/ethereumjs/ethereumjs-block/pull/44) +- Alternatively a `Common` class object can be provided directly with the `opts.common` parameter, see [API](https://github.com/ethereumjs/ethereumjs-block/blob/master/docs/index.md) docs -- Correct block validation for all know hardforks, PR +- Correct block validation for all know hardforks, PR [#47](https://github.com/ethereumjs/ethereumjs-block/pull/47), if no hardfork is set validation logic - is determined by block number in combination with the ``chain`` set -- Genesis block initialization depending on the ``chain`` set (see ``ethereumjs-common`` for supported chains) + is determined by block number in combination with the `chain` set +- Genesis block initialization depending on the `chain` set (see `ethereumjs-common` for supported chains) - Extensive test additions to cover the newly introduced capabilities and changes -- Fix default value for ``nonce`` (empty buffer -> ````), PR [#42](https://github.com/ethereumjs/ethereumjs-block/pull/42) +- Fix default value for `nonce` (empty buffer -> ``), PR [#42](https://github.com/ethereumjs/ethereumjs-block/pull/42) [2.0.0]: https://github.com/ethereumjs/ethereumjs-vm/compare/v1.7.1...v2.0.0 ## [1.7.1] - 2018-02-15 -- Fix ``browserify`` issue blocking updates for packages depending on ``ethereumjs-block`` + +- Fix `browserify` issue blocking updates for packages depending on `ethereumjs-block` library, PR [#40](https://github.com/ethereumjs/ethereumjs-block/pull/40) -- Updated ``ethereumjs/common`` dependency, PR [#38](https://github.com/ethereumjs/ethereumjs-block/pull/38) +- Updated `ethereumjs/common` dependency, PR [#38](https://github.com/ethereumjs/ethereumjs-block/pull/38) [1.7.1]: https://github.com/ethereumjs/ethereumjs-vm/compare/v1.7.0...v1.7.1 ## [1.7.0] - 2017-10-11 -- ``Metro-Byzantium`` compatible + +- `Metro-Byzantium` compatible - New difficulty formula (EIP 100) - Difficulty bomb delay (EIP 649) -- Removed ``isHomestead``, ``isHomesteadReprice`` from API methods +- Removed `isHomestead`, `isHomesteadReprice` from API methods [1.7.0]: https://github.com/ethereumjs/ethereumjs-vm/compare/v1.6.0...v1.7.0 ## [1.6.0] - 2017-07-12 + - Breakout header-from-rpc as separate module [1.6.0]: https://github.com/ethereumjs/ethereumjs-block/compare/v1.5.1...v1.6.0 ## [1.5.1] - 2017-06-04 + - Dev dependency updates - BN for gas limit @@ -88,5 +98,3 @@ Changes in detail: - [1.4.0](https://github.com/ethereumjs/ethereumjs-block/compare/v1.3.1...v1.4.0) - 2016-12-15 - [1.3.1](https://github.com/ethereumjs/ethereumjs-block/compare/v1.3.0...v1.3.1) - 2016-10-14 - [1.3.0](https://github.com/ethereumjs/ethereumjs-block/compare/v1.2.2...v1.3.0) - 2017-10-11 - - diff --git a/README.md b/README.md index f345aca..80756a0 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,33 @@ -# SYNOPSIS +# SYNOPSIS + [![NPM Package](https://img.shields.io/npm/v/ethereumjs-block.svg?style=flat-square)](https://www.npmjs.org/package/ethereumjs-block) [![Build Status](https://img.shields.io/travis/ethereumjs/ethereumjs-block.svg?branch=master&style=flat-square)](https://travis-ci.org/ethereumjs/ethereumjs-block) [![Coverage Status](https://img.shields.io/coveralls/ethereumjs/ethereumjs-block.svg?style=flat-square)](https://coveralls.io/r/ethereumjs/ethereumjs-block) -[![Gitter](https://img.shields.io/gitter/room/ethereum/ethereumjs-lib.svg?style=flat-square)]() or #ethereumjs on freenode - -[![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) +[![Gitter](https://img.shields.io/gitter/room/ethereum/ethereumjs-lib.svg?style=flat-square)]() or #ethereumjs on freenode +[![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) -Implements schema and functions related to Ethereum's block. +Implements schema and functions related to Ethereum's block. # INSTALL + `npm install ethereumjs-block` -# BROWSER +# BROWSER + This module work with `browserify`. # API + [./docs](./docs/index.md) # TESTING -Tests in the ``tests`` directory are partly outdated and testing is primarily done by running the ``BlockchainTests`` from within the [ethereumjs-vm](https://github.com/ethereumjs/ethereumjs-vm) repository. + +Tests in the `tests` directory are partly outdated and testing is primarily done by running the `BlockchainTests` from within the [ethereumjs-vm](https://github.com/ethereumjs/ethereumjs-vm) repository. Relevant test folders: -- ``bcTotalDifficultyTest`` + +- `bcTotalDifficultyTest` - TODO # EthereumJS @@ -32,4 +37,5 @@ See our organizational [documentation](https://ethereumjs.readthedocs.io) for an If you want to join for work or do improvements on the libraries have a look at our [contribution guidelines](https://ethereumjs.readthedocs.io/en/latest/contributing.html). # LICENSE -[MPL-2.0](https://tldrlegal.com/license/mozilla-public-license-2.0-(mpl-2)) + +[MPL-2.0]() diff --git a/docs/fromRpc.md b/docs/fromRpc.md index 52b1167..8c58320 100644 --- a/docs/fromRpc.md +++ b/docs/fromRpc.md @@ -6,6 +6,6 @@ Creates a new block object from Ethereum JSON RPC. **Parameters** -- `blockParams` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Ethereum JSON RPC of block (eth_getBlockByNumber) -- `Optional` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)>** list of Ethereum JSON RPC of uncles (eth_getUncleByBlockHashAndIndex) -- `uncles` +- `blockParams` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Ethereum JSON RPC of block (eth_getBlockByNumber) +- `Optional` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)>** list of Ethereum JSON RPC of uncles (eth_getUncleByBlockHashAndIndex) +- `uncles` diff --git a/docs/index.md b/docs/index.md index 1a38b58..ff40076 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,17 +6,17 @@ Creates a new block object **Parameters** -- `data` **([Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) \| [Buffer](https://nodejs.org/api/buffer.html) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object))** -- `opts` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** Options - - `opts.chain` **([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number))** The chain for the block [default: 'mainnet'] - - `opts.hardfork` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** Hardfork for the block [default: null, block number-based behaviour] - - `opts.common` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Alternatively pass a Common instance (ethereumjs-common) instead of setting chain/hardfork directly +- `data` **([Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) \| [Buffer](https://nodejs.org/api/buffer.html) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object))** +- `opts` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** Options + - `opts.chain` **([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number))** The chain for the block [default: 'mainnet'] + - `opts.hardfork` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** Hardfork for the block [default: null, block number-based behaviour] + - `opts.common` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Alternatively pass a Common instance (ethereumjs-common) instead of setting chain/hardfork directly **Properties** -- `header` **Header** the block's header -- `uncleList` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<Header>** an array of uncle headers -- `raw` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Buffer](https://nodejs.org/api/buffer.html)>** an array of buffers containing the raw blocks. +- `header` **Header** the block's header +- `uncleList` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<Header>** an array of uncle headers +- `raw` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Buffer](https://nodejs.org/api/buffer.html)>** an array of buffers containing the raw blocks. ## hash @@ -38,7 +38,7 @@ Produces a serialization of the block. **Parameters** -- `rlpEncode` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** whether to rlp encode the block or not +- `rlpEncode` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** whether to rlp encode the block or not ## genTxTrie @@ -47,13 +47,13 @@ be validated with `validateTransactionTrie` **Parameters** -- `cb` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** the callback +- `cb` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** the callback ## validateTransactionTrie Validates the transaction trie -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** ## validateTransactions @@ -61,9 +61,9 @@ Validates the transactions **Parameters** -- `stringError` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** whether to return a string with a dscription of why the validation failed or return a Bloolean (optional, default `false`) +- `stringError` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** whether to return a string with a dscription of why the validation failed or return a Bloolean (optional, default `false`) -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** ## validate @@ -71,14 +71,14 @@ Validates the entire block. Returns a string to the callback if block is invalid **Parameters** -- `blockChain` **BlockChain** the blockchain that this block wants to be part of -- `cb` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** the callback which is given a `String` if the block is not valid +- `blockChain` **BlockChain** the blockchain that this block wants to be part of +- `cb` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** the callback which is given a `String` if the block is not valid ## validateUncleHash Validates the uncle's hash -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** ## validateUncles @@ -86,9 +86,9 @@ Validates the uncles that are in the block if any. Returns a string to the callb **Parameters** -- `blockChaina` **Blockchain** an instance of the Blockchain -- `cb` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** the callback -- `blockChain` +- `blockChaina` **Blockchain** an instance of the Blockchain +- `cb` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** the callback +- `blockChain` ## toJSON @@ -96,9 +96,9 @@ Converts the block toJSON **Parameters** -- `labeled` **Bool** whether to create an labeled object or an array +- `labeled` **Bool** whether to create an labeled object or an array -Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** +Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** # BlockHeader @@ -106,28 +106,28 @@ An object that repersents the block header **Parameters** -- `data` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** raw data, deserialized -- `opts` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** Options - - `opts.chain` **([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number))** The chain for the block header [default: 'mainnet'] - - `opts.hardfork` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** Hardfork for the block header [default: null, block number-based behaviour] - - `opts.common` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Alternatively pass a Common instance instead of setting chain/hardfork directly +- `data` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** raw data, deserialized +- `opts` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** Options + - `opts.chain` **([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number))** The chain for the block header [default: 'mainnet'] + - `opts.hardfork` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** Hardfork for the block header [default: null, block number-based behaviour] + - `opts.common` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Alternatively pass a Common instance instead of setting chain/hardfork directly **Properties** -- `parentHash` **[Buffer](https://nodejs.org/api/buffer.html)** the blocks' parent's hash -- `uncleHash` **[Buffer](https://nodejs.org/api/buffer.html)** sha3(rlp_encode(uncle_list)) -- `coinbase` **[Buffer](https://nodejs.org/api/buffer.html)** the miner address -- `stateRoot` **[Buffer](https://nodejs.org/api/buffer.html)** The root of a Merkle Patricia tree -- `transactionTrie` **[Buffer](https://nodejs.org/api/buffer.html)** the root of a Trie containing the transactions -- `receiptTrie` **[Buffer](https://nodejs.org/api/buffer.html)** the root of a Trie containing the transaction Reciept -- `bloom` **[Buffer](https://nodejs.org/api/buffer.html)** -- `difficulty` **[Buffer](https://nodejs.org/api/buffer.html)** -- `number` **[Buffer](https://nodejs.org/api/buffer.html)** the block's height -- `gasLimit` **[Buffer](https://nodejs.org/api/buffer.html)** -- `gasUsed` **[Buffer](https://nodejs.org/api/buffer.html)** -- `timestamp` **[Buffer](https://nodejs.org/api/buffer.html)** -- `extraData` **[Buffer](https://nodejs.org/api/buffer.html)** -- `raw` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Buffer](https://nodejs.org/api/buffer.html)>** an array of buffers containing the raw blocks. +- `parentHash` **[Buffer](https://nodejs.org/api/buffer.html)** the blocks' parent's hash +- `uncleHash` **[Buffer](https://nodejs.org/api/buffer.html)** sha3(rlp_encode(uncle_list)) +- `coinbase` **[Buffer](https://nodejs.org/api/buffer.html)** the miner address +- `stateRoot` **[Buffer](https://nodejs.org/api/buffer.html)** The root of a Merkle Patricia tree +- `transactionTrie` **[Buffer](https://nodejs.org/api/buffer.html)** the root of a Trie containing the transactions +- `receiptTrie` **[Buffer](https://nodejs.org/api/buffer.html)** the root of a Trie containing the transaction Reciept +- `bloom` **[Buffer](https://nodejs.org/api/buffer.html)** +- `difficulty` **[Buffer](https://nodejs.org/api/buffer.html)** +- `number` **[Buffer](https://nodejs.org/api/buffer.html)** the block's height +- `gasLimit` **[Buffer](https://nodejs.org/api/buffer.html)** +- `gasUsed` **[Buffer](https://nodejs.org/api/buffer.html)** +- `timestamp` **[Buffer](https://nodejs.org/api/buffer.html)** +- `extraData` **[Buffer](https://nodejs.org/api/buffer.html)** +- `raw` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Buffer](https://nodejs.org/api/buffer.html)>** an array of buffers containing the raw blocks. ## canonicalDifficulty @@ -135,9 +135,9 @@ Returns the canoncical difficulty of the block **Parameters** -- `parentBlock` **[Block](#block)** the parent `Block` of the this header +- `parentBlock` **[Block](#block)** the parent `Block` of the this header -Returns **BN** +Returns **BN** ## validateDifficulty @@ -145,9 +145,9 @@ checks that the block's `difficuly` matches the canonical difficulty **Parameters** -- `parentBlock` **[Block](#block)** this block's parent +- `parentBlock` **[Block](#block)** this block's parent -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** ## validateGasLimit @@ -155,9 +155,9 @@ Validates the gasLimit **Parameters** -- `parentBlock` **[Block](#block)** this block's parent +- `parentBlock` **[Block](#block)** this block's parent -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** ## validate @@ -165,22 +165,22 @@ Validates the entire block header **Parameters** -- `blockChain` **Blockchain** the blockchain that this block is validating against -- `height` **Bignum?** if this is an uncle header, this is the height of the block that is including it -- `cb` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** the callback function. The callback is given an `error` if the block is invalid -- `blockchain` +- `blockChain` **Blockchain** the blockchain that this block is validating against +- `height` **Bignum?** if this is an uncle header, this is the height of the block that is including it +- `cb` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** the callback function. The callback is given an `error` if the block is invalid +- `blockchain` ## hash Returns the sha3 hash of the blockheader -Returns **[Buffer](https://nodejs.org/api/buffer.html)** +Returns **[Buffer](https://nodejs.org/api/buffer.html)** ## isGenesis checks if the blockheader is a genesis header -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** ## setGenesisParams diff --git a/karma.conf.js b/karma.conf.js index 9445846..45c591e 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,45 +1,18 @@ -process.env.ethTest = 'BasicTests' - module.exports = function(config) { config.set({ browserNoActivityTimeout: 60000, frameworks: ['browserify', 'detectBrowsers', 'tap'], - files: ['./tests/difficulty.js'], + files: ['./test-build/test/index.js'], preprocessors: { - 'tests/*.js': ['browserify', 'env'], + './test-build/**/*.js': ['browserify'] }, singleRun: true, - plugins: [ - 'karma-browserify', - 'karma-chrome-launcher', - 'karma-env-preprocessor', - 'karma-tap', - 'karma-firefox-launcher', - 'karma-detect-browsers', - ], - browserify: { - transform: [ - [ - 'babelify', - { - presets: ['env'], - }, - ], - ], - }, detectBrowsers: { enabled: true, usePhantomJS: false, - postDetection: function(availableBrowser) { - if (process.env.TRAVIS) { - return ['Firefox'] - } - - var browsers = ['Chrome', 'Firefox'] - return browsers.filter(function(browser) { - return availableBrowser.indexOf(browser) !== -1 - }) + postDetection: function(availableBrowsers) { + return ['Firefox'] }, - }, + } }) } diff --git a/package.json b/package.json index 0c33e05..4815b21 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,29 @@ { "name": "ethereumjs-block", - "version": "2.2.0", + "version": "3.0.0", "description": "Provides Block serialization and help functions", - "main": "index.js", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ - "*.js" + "dist" ], "scripts": { - "coverage": "istanbul cover ./tests/index.js", - "coveralls": "npm run coverage && coveralls < coverage/lcov.info", - "lint": "standard", - "test": "npm run test:node && npm run test:browser", - "test:browser": "karma start karma.conf.js", - "test:node": "node tests/index.js", - "build:docs": "documentation build ./index.js ./header.js --format md --shallow > ./docs/index.md && documentation build ./from-rpc.js --format md --shallow > ./docs/fromRpc.md" + "build": "ethereumjs-config-build", + "prepublishOnly": "npm run test && npm run build", + "coverage": "ethereumjs-config-coverage", + "coveralls": "ethereumjs-config-coveralls", + "docs:build": "typedoc --out docs --mode file --readme none --theme markdown --mdEngine github --excludeNotExported src", + "format": "ethereumjs-config-format", + "format:fix": "ethereumjs-config-format-fix", + "tslint": "ethereumjs-config-tslint", + "tslint:fix": "ethereumjs-config-tslint-fix", + "tsc": "ethereumjs-config-tsc", + "lint": "ethereumjs-config-lint", + "lint:fix": "ethereumjs-config-lint-fix", + "test": "npm run test:node", + "test:node": "ts-node node_modules/tape/bin/tape ./test/index.ts", + "test:browser:build": "tsc && cp ./test/*.json test-build/test/", + "test:browser": "npm run test:browser:build && karma start karma.conf.js" }, "husky": { "hooks": { @@ -35,35 +45,36 @@ }, "homepage": "https://github.com/ethereumjs/ethereumjs-block#readme", "dependencies": { - "async": "^2.0.1", - "ethereumjs-common": "^1.1.0", - "ethereumjs-tx": "^1.2.2", - "ethereumjs-util": "^5.0.0", + "@types/bn.js": "^4.11.5", + "ethereumjs-common": "^1.3.0", + "ethereumjs-tx": "^2.1.0", + "ethereumjs-util": "^6.1.0", "merkle-patricia-tree": "^2.1.2" }, "devDependencies": { + "@ethereumjs/config-nyc": "^1.1.1", "@ethereumjs/config-prettier": "^1.1.1", "@ethereumjs/config-tsc": "^1.1.1", "@ethereumjs/config-tslint": "^1.1.1", - "babel-preset-env": "^1.6.1", - "babelify": "^8.0.0", - "browserify": "^15.0.0", - "coveralls": "^2.11.6", - "documentation": "4.0.0-beta16", + "@types/node": "^11.13.4", + "@types/tape": "^4.2.33", + "browserify": "^16.2.3", + "coveralls": "^2.11.4", "husky": "^2.1.0", - "istanbul": "^0.4.2", - "karma": "^2.0.0", - "karma-browserify": "^5.1.0", - "karma-chrome-launcher": "^2.2.0", - "karma-detect-browsers": "^2.2.5", - "karma-env-preprocessor": "^0.1.1", + "istanbul": "^0.4.1", + "karma": "^4.1.0", + "karma-browserify": "^6.0.0", + "karma-detect-browsers": "^2.3.3", "karma-firefox-launcher": "^1.1.0", - "karma-tap": "^4.0.0", - "prettier": "^1.18.2", - "standard": "^8.4.0", - "tape": "^4.2.0", - "ts-node": "^8.3.0", - "tslint": "^5.18.0", - "typescript": "^3.5.3" + "karma-tap": "^4.1.4", + "nyc": "^14.0.0", + "prettier": "^1.17.0", + "tape": "^4.0.3", + "ts-node": "^8.0.3", + "tslint": "^5.15.0", + "typedoc": "^0.14.2", + "typedoc-plugin-markdown": "^1.2.0", + "typescript": "^3.4.3", + "typestrict": "^1.0.2" } } diff --git a/src/block.ts b/src/block.ts new file mode 100644 index 0000000..767a447 --- /dev/null +++ b/src/block.ts @@ -0,0 +1,303 @@ +import Common from 'ethereumjs-common' +import * as ethUtil from 'ethereumjs-util' +import { BN, rlp } from 'ethereumjs-util' +import { Transaction } from 'ethereumjs-tx' + +import { BlockHeader } from './header' +import { Blockchain, BlockData, NetworkOptions, NoReturnValueCallback } from './types' +import { callbackify } from './callbackify' + +const Trie = require('merkle-patricia-tree') + +/** + * Creates a new block object + * @constructor the raw serialized or the deserialized block. + * @param {Array|Buffer|Object} data + * @param {Array} opts Options + * @param {String|Number} opts.chain The chain for the block [default: 'mainnet'] + * @param {String} opts.hardfork Hardfork for the block [default: null, block number-based behaviour] + * @param {Object} opts.common Alternatively pass a Common instance (ethereumjs-common) instead of setting chain/hardfork directly + * @prop {Header} header the block's header + * @prop {Array.
} uncleList an array of uncle headers + * @prop {Array.} raw an array of buffers containing the raw blocks. + */ +export class Block { + public readonly header: BlockHeader + public readonly transactions: Transaction[] = [] + public readonly uncleHeaders: BlockHeader[] = [] + public readonly txTrie = new Trie() + + private readonly _common: Common + + constructor( + data: Buffer | [Buffer[], Buffer[], Buffer[]] | BlockData = {}, + opts: NetworkOptions = {}, + ) { + if (opts.common) { + if (opts.chain !== undefined || opts.hardfork !== undefined) { + throw new Error( + 'Instantiation with both opts.common and opts.chain / opts.hardfork parameter not allowed!', + ) + } + + this._common = opts.common + } else { + const chain = opts.chain ? opts.chain : 'mainnet' + const hardfork = opts.hardfork ? opts.hardfork : null + this._common = new Common(chain, hardfork) + } + + let rawTransactions + let rawUncleHeaders + + if (Buffer.isBuffer(data)) { + // We do this to silence a TS error. We know that after this statement, data is + // a [Buffer[], Buffer[], Buffer[]] + const dataAsAny = rlp.decode(data) as any + data = dataAsAny as [Buffer[], Buffer[], Buffer[]] + } + + if (Array.isArray(data)) { + // TODO: Pass the common object + this.header = new BlockHeader(data[0], opts) + rawTransactions = data[1] + rawUncleHeaders = data[2] + } else { + // TODO: Pass the common object + this.header = new BlockHeader(data.header, opts) + rawTransactions = data.transactions || [] + rawUncleHeaders = data.uncleHeaders || [] + } + + // parse uncle headers + for (let i = 0; i < rawUncleHeaders.length; i++) { + this.uncleHeaders.push(new BlockHeader(rawUncleHeaders[i], opts)) + } + + // parse transactions + for (let i = 0; i < rawTransactions.length; i++) { + const tx = new Transaction(rawTransactions[i], opts) + this.transactions.push(tx) + } + } + + get raw() { + return this.serialize(false) + } + + /** + * Produces a hash the RLP of the block + * @method hash + */ + hash() { + return this.header.hash() + } + + /** + * Determines if a given block is the genesis block + * @method isGenisis + * @return Boolean + */ + isGenesis() { + return this.header.isGenesis() + } + + /** + * turns the block into the canonical genesis block + * @method setGenesisParams + */ + setGenesisParams() { + this.header.setGenesisParams() + } + + /** + * Produces a serialization of the block. + */ + serialize(): Buffer + serialize(rlpEncode: true): Buffer + serialize(rlpEncode: false): [Buffer[], Buffer[], Buffer[]] + serialize(rlpEncode = true) { + const raw = [ + this.header.raw, + this.transactions.map(tx => tx.raw), + this.uncleHeaders.map(uh => uh.raw), + ] + + return rlpEncode ? rlp.encode(raw) : raw + } + + /** + * Generate transaction trie. The tx trie must be generated before the transaction trie can + * be validated with `validateTransactionTrie` + */ + genTxTrie(cb: NoReturnValueCallback) { + callbackify(this._getTxTrie.bind(this))(cb) + } + + private async _getTxTrie() { + for (let i = 0; i < this.transactions.length; i++) { + const tx = this.transactions[i] + await this._putTxInTrie(i, tx) + } + } + + private async _putTxInTrie(txIndex: number, tx: Transaction) { + await new Promise((resolve, reject) => { + this.txTrie.put(rlp.encode(txIndex), tx.serialize(), (err: any) => { + if (err) { + reject(err) + } else { + resolve() + } + }) + }) + } + + /** + * Validates the transaction trie + * @method validateTransactionTrie + * @return {Boolean} + */ + validateTransactionsTrie(): boolean { + const txT = this.header.transactionsTrie.toString('hex') + if (this.transactions.length) { + return txT === this.txTrie.root.toString('hex') + } else { + return txT === ethUtil.KECCAK256_RLP.toString('hex') + } + } + + /** + * Validates the transactions + */ + validateTransactions(): boolean + validateTransactions(stringError: false): boolean + validateTransactions(stringError: true): string + validateTransactions(stringError = false) { + const errors: string[] = [] + + this.transactions.forEach(function(tx, i) { + const error = tx.validate(true) + if (error) { + errors.push(error + ' at tx ' + i) + } + }) + + if (!stringError) { + return errors.length === 0 + } + + return errors.join(' ') + } + + /** + * Validates the entire block. Returns a string to the callback if block is invalid + * @method validate + * @param {BlockChain} blockChain the blockchain that this block wants to be part of + * @param {Function} cb the callback which is given a `String` if the block is not valid + */ + validate(blockChain: Blockchain, cb: NoReturnValueCallback) { + callbackify(this._validate.bind(this, blockChain))(cb) + } + + private async _validate(blockChain: Blockchain) { + await Promise.all([ + this._validateUncles(blockChain), + this._getTxTrie(), + this._validateHeader(this.header, blockChain), + ]) + + if (!this.validateTransactionsTrie()) { + throw new Error('invalid transaction trie') + } + + const txErrors = this.validateTransactions(true) + if (txErrors !== '') { + throw new Error(txErrors) + } + + if (!this.validateUnclesHash()) { + throw new Error('invalid uncle hash') + } + } + + /** + * Validates the uncle's hash + */ + validateUnclesHash(): boolean { + const raw = rlp.encode(this.uncleHeaders.map(uh => uh.raw)) + + return ethUtil.keccak256(raw).toString('hex') === this.header.uncleHash.toString('hex') + } + + /** + * Validates the uncles that are in the block if any. Returns a string to the callback if uncles are invalid + * @method validateUncles + * @param {Blockchain} blockChain an instance of the Blockchain + * @param {Function} cb the callback + */ + validateUncles(blockChain: Blockchain, cb: NoReturnValueCallback) { + callbackify(this._validateUncles.bind(this, blockChain))(cb) + } + + private async _validateUncles(blockchain: Blockchain) { + if (this.isGenesis()) { + return + } + + if (this.uncleHeaders.length > 2) { + throw new Error('too many uncle headers') + } + + const uncleHashes = this.uncleHeaders.map(header => header.hash().toString('hex')) + + if (!(new Set(uncleHashes).size === uncleHashes.length)) { + throw new Error('duplicate uncles') + } + + return Promise.all( + this.uncleHeaders.map(async uh => { + const height = new BN(this.header.number) + return this._validateHeader(uh, blockchain, height) + }), + ) + } + + /** + * Returns the block in JSON format + * @see {@link https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#defineproperties|ethereumjs-util} + */ + toJSON(labeled: boolean = false) { + if (labeled) { + return { + header: this.header.toJSON(true), + transactions: this.transactions.map(tx => tx.toJSON(true)), + uncleHeaders: this.uncleHeaders.forEach(uh => uh.toJSON(true)), + } + } else { + return ethUtil.baToJSON(this.raw) + } + } + + private _validateHeader(header: BlockHeader, blockchain: Blockchain, height?: BN) { + return new Promise((resolve, reject) => { + if (height !== undefined) { + header.validate(blockchain, height, err => { + if (err) { + reject(err) + } + + resolve() + }) + } else { + header.validate(blockchain, err => { + if (err) { + reject(err) + } + + resolve() + }) + } + }) + } +} diff --git a/src/callbackify.ts b/src/callbackify.ts new file mode 100644 index 0000000..5d3fbc9 --- /dev/null +++ b/src/callbackify.ts @@ -0,0 +1,77 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + +function callbackifyOnRejected(reason: any, cb: any) { + // `!reason` guard inspired by bluebird (Ref: https://goo.gl/t5IS6M). + // Because `null` is a special error value in callbacks which means "no error + // occurred", we error-wrap so the callback consumer can distinguish between + // "the promise rejected with null" or "the promise fulfilled with undefined". + if (!reason) { + const newReason = new Error('Promise was rejected with a falsy value') as any + newReason.reason = reason + reason = newReason + } + return cb(reason) +} + +export function callbackify(original: any): any { + if (typeof original !== 'function') { + throw new TypeError('The "original" argument must be of type Function') + } + + // We DO NOT return the promise as it gives the user a false sense that + // the promise is actually somehow related to the callback's execution + // and that the callback throwing will reject the promise. + function callbackified(this: any) { + const args = [] + for (let i = 0; i < arguments.length; i++) { + args.push(arguments[i]) + } + + const maybeCb = args.pop() + if (typeof maybeCb !== 'function') { + throw new TypeError('The last argument must be of type Function') + } + + //tslint:disable-next-line no-invalid-this + const self = this + const cb = function() { + return maybeCb.apply(self, arguments) + } + + // In true node style we process the callback on `nextTick` with all the + // implications (stack, `uncaughtException`, `async_hooks`) + //tslint:disable-next-line no-invalid-this + original.apply(this, args).then( + function(ret: any) { + process.nextTick(cb.bind(null, null, ret)) + }, + function(rej: any) { + process.nextTick(callbackifyOnRejected.bind(null, rej, cb)) + }, + ) + } + + Object.setPrototypeOf(callbackified, Object.getPrototypeOf(original)) + Object.defineProperties(callbackified, Object.getOwnPropertyDescriptors(original)) + + return callbackified +} diff --git a/src/from-rpc.ts b/src/from-rpc.ts index 201cf70..f73cd03 100644 --- a/src/from-rpc.ts +++ b/src/from-rpc.ts @@ -1,56 +1,56 @@ -'use strict' -const Transaction = require('ethereumjs-tx') -const ethUtil = require('ethereumjs-util') -const Block = require('./') -const blockHeaderFromRpc = require('./header-from-rpc') - -module.exports = blockFromRpc - -/** - * Creates a new block object from Ethereum JSON RPC. - * @param {Object} blockParams - Ethereum JSON RPC of block (eth_getBlockByNumber) - * @param {Array.} Optional list of Ethereum JSON RPC of uncles (eth_getUncleByBlockHashAndIndex) - */ -function blockFromRpc(blockParams, uncles) { - uncles = uncles || [] - const block = new Block({ - transactions: [], - uncleHeaders: [], - }) - block.header = blockHeaderFromRpc(blockParams) - - block.transactions = (blockParams.transactions || []).map(function(_txParams) { - const txParams = normalizeTxParams(_txParams) - // override from address - const fromAddress = ethUtil.toBuffer(txParams.from) - delete txParams.from - const tx = new Transaction(txParams) - tx._from = fromAddress - tx.getSenderAddress = function() { - return fromAddress - } - // override hash - const txHash = ethUtil.toBuffer(txParams.hash) - tx.hash = function() { - return txHash - } - return tx - }) - block.uncleHeaders = uncles.map(function(uncleParams) { - return blockHeaderFromRpc(uncleParams) - }) - - return block -} - -function normalizeTxParams(_txParams) { - const txParams = Object.assign({}, _txParams) - // hot fix for https://github.com/ethereumjs/ethereumjs-util/issues/40 - txParams.gasLimit = txParams.gasLimit === undefined ? txParams.gas : txParams.gasLimit - txParams.data = txParams.data === undefined ? txParams.input : txParams.data - // strict byte length checking - txParams.to = txParams.to ? ethUtil.setLengthLeft(ethUtil.toBuffer(txParams.to), 20) : null - // v as raw signature value {0,1} - txParams.v = txParams.v < 27 ? txParams.v + 27 : txParams.v - return txParams -} +// import { Transaction } from 'ethereumjs-tx' +// import * as ethUtil from 'ethereumjs-util' +// import { Block } from './index' +// +// const blockHeaderFromRpc = require('./header-from-rpc') +// +// module.exports = blockFromRpc +// +// /** +// * Creates a new block object from Ethereum JSON RPC. +// * @param {Object} blockParams - Ethereum JSON RPC of block (eth_getBlockByNumber) +// * @param {Array.} Optional list of Ethereum JSON RPC of uncles (eth_getUncleByBlockHashAndIndex) +// */ +// function blockFromRpc(blockParams, uncles) { +// uncles = uncles || [] +// const block = new Block({ +// transactions: [], +// uncleHeaders: [], +// }) +// block.header = blockHeaderFromRpc(blockParams) +// +// block.transactions = (blockParams.transactions || []).map(function(_txParams) { +// const txParams = normalizeTxParams(_txParams) +// // override from address +// const fromAddress = ethUtil.toBuffer(txParams.from) +// delete txParams.from +// const tx = new Transaction(txParams) +// tx._from = fromAddress +// tx.getSenderAddress = function() { +// return fromAddress +// } +// // override hash +// const txHash = ethUtil.toBuffer(txParams.hash) +// tx.hash = function() { +// return txHash +// } +// return tx +// }) +// block.uncleHeaders = uncles.map(function(uncleParams) { +// return blockHeaderFromRpc(uncleParams) +// }) +// +// return block +// } +// +// function normalizeTxParams(_txParams) { +// const txParams = Object.assign({}, _txParams) +// // hot fix for https://github.com/ethereumjs/ethereumjs-util/issues/40 +// txParams.gasLimit = txParams.gasLimit === undefined ? txParams.gas : txParams.gasLimit +// txParams.data = txParams.data === undefined ? txParams.input : txParams.data +// // strict byte length checking +// txParams.to = txParams.to ? ethUtil.setLengthLeft(ethUtil.toBuffer(txParams.to), 20) : null +// // v as raw signature value {0,1} +// txParams.v = txParams.v < 27 ? txParams.v + 27 : txParams.v +// return txParams +// } diff --git a/src/header-from-rpc.ts b/src/header-from-rpc.ts index 48437bd..4367e17 100644 --- a/src/header-from-rpc.ts +++ b/src/header-from-rpc.ts @@ -1,36 +1,36 @@ -'use strict' -const BlockHeader = require('./header') -const ethUtil = require('ethereumjs-util') - -module.exports = blockHeaderFromRpc - -/** - * Creates a new block header object from Ethereum JSON RPC. - * @param {Object} blockParams - Ethereum JSON RPC of block (eth_getBlockByNumber) - */ -function blockHeaderFromRpc(blockParams) { - const blockHeader = new BlockHeader({ - parentHash: blockParams.parentHash, - uncleHash: blockParams.sha3Uncles, - coinbase: blockParams.miner, - stateRoot: blockParams.stateRoot, - transactionsTrie: blockParams.transactionsRoot, - receiptTrie: blockParams.receiptRoot || blockParams.receiptsRoot || ethUtil.SHA3_NULL, - bloom: blockParams.logsBloom, - difficulty: blockParams.difficulty, - number: blockParams.number, - gasLimit: blockParams.gasLimit, - gasUsed: blockParams.gasUsed, - timestamp: blockParams.timestamp, - extraData: blockParams.extraData, - mixHash: blockParams.mixHash, - nonce: blockParams.nonce, - }) - - // override hash incase something was missing - blockHeader.hash = function() { - return ethUtil.toBuffer(blockParams.hash) - } - - return blockHeader -} +// 'use strict' +// const BlockHeader = require('./header') +// const ethUtil = require('ethereumjs-util') +// +// module.exports = blockHeaderFromRpc +// +// /** +// * Creates a new block header object from Ethereum JSON RPC. +// * @param {Object} blockParams - Ethereum JSON RPC of block (eth_getBlockByNumber) +// */ +// function blockHeaderFromRpc(blockParams) { +// const blockHeader = new BlockHeader({ +// parentHash: blockParams.parentHash, +// uncleHash: blockParams.sha3Uncles, +// coinbase: blockParams.miner, +// stateRoot: blockParams.stateRoot, +// transactionsTrie: blockParams.transactionsRoot, +// receiptTrie: blockParams.receiptRoot || blockParams.receiptsRoot || ethUtil.SHA3_NULL, +// bloom: blockParams.logsBloom, +// difficulty: blockParams.difficulty, +// number: blockParams.number, +// gasLimit: blockParams.gasLimit, +// gasUsed: blockParams.gasUsed, +// timestamp: blockParams.timestamp, +// extraData: blockParams.extraData, +// mixHash: blockParams.mixHash, +// nonce: blockParams.nonce, +// }) +// +// // override hash incase something was missing +// blockHeader.hash = function() { +// return ethUtil.toBuffer(blockParams.hash) +// } +// +// return blockHeader +// } diff --git a/src/header.ts b/src/header.ts index f700ca0..2c0a12b 100644 --- a/src/header.ts +++ b/src/header.ts @@ -1,6 +1,17 @@ -const Common = require('ethereumjs-common').default -const utils = require('ethereumjs-util') -const BN = utils.BN +import Common from 'ethereumjs-common' +import * as utils from 'ethereumjs-util' +import { BN } from 'ethereumjs-util' +import { + Blockchain, + BlockHeaderData, + BufferLike, + NetworkOptions, + NoReturnValueCallback, + PrefixedHexString, +} from './types' +import { Buffer } from 'buffer' +import { Block } from './block' + /** * An object that repersents the block header * @constructor @@ -24,303 +35,361 @@ const BN = utils.BN * @prop {Buffer} extraData * @prop {Array.} raw an array of buffers containing the raw blocks. */ -var BlockHeader = (module.exports = function(data, opts) { - opts = opts || {} +export class BlockHeader { + public raw!: Buffer[] + public parentHash!: Buffer + public uncleHash!: Buffer + public coinbase!: Buffer + public stateRoot!: Buffer + public transactionsTrie!: Buffer + public receiptTrie!: Buffer + public bloom!: Buffer + public difficulty!: Buffer + public number!: Buffer + public gasLimit!: Buffer + public gasUsed!: Buffer + public timestamp!: Buffer + public extraData!: Buffer + public mixHash!: Buffer + public nonce!: Buffer - if (opts.common) { - if (opts.chain) { - throw new Error('Instantiation with both opts.common and opts.chain parameter not allowed!') - } - this._common = opts.common - } else { - let chain = opts.chain ? opts.chain : 'mainnet' - let hardfork = opts.hardfork ? opts.hardfork : null - this._common = new Common(chain, hardfork) - } + private readonly _common: Common - var fields = [ - { - name: 'parentHash', - length: 32, - default: utils.zeros(32), - }, - { - name: 'uncleHash', - default: utils.SHA3_RLP_ARRAY, - }, - { - name: 'coinbase', - length: 20, - default: utils.zeros(20), - }, - { - name: 'stateRoot', - length: 32, - default: utils.zeros(32), - }, - { - name: 'transactionsTrie', - length: 32, - default: utils.SHA3_RLP, - }, - { - name: 'receiptTrie', - length: 32, - default: utils.SHA3_RLP, - }, - { - name: 'bloom', - default: utils.zeros(256), - }, - { - name: 'difficulty', - default: Buffer.from([]), - }, - { - name: 'number', - // TODO: params.homeSteadForkNumber.v left for legacy reasons, replace on future release - default: utils.intToBuffer(1150000), - }, - { - name: 'gasLimit', - default: Buffer.from('ffffffffffffff', 'hex'), - }, - { - name: 'gasUsed', - empty: true, - default: Buffer.from([]), - }, - { - name: 'timestamp', - default: Buffer.from([]), - }, - { - name: 'extraData', - allowZero: true, - empty: true, - default: Buffer.from([]), - }, - { - name: 'mixHash', - default: utils.zeros(32), - // length: 32 - }, - { - name: 'nonce', - default: utils.zeros(8), // sha3(42) - }, - ] - utils.defineProperties(this, fields, data) -}) + constructor( + data: Buffer | PrefixedHexString | BufferLike[] | BlockHeaderData = {}, + opts: NetworkOptions = {}, + ) { + if (opts.common !== undefined) { + if (opts.chain !== undefined || opts.hardfork !== undefined) { + throw new Error( + 'Instantiation with both opts.common and opts.chain / opts.hardfork parameter not allowed!', + ) + } -/** - * Returns the canoncical difficulty of the block - * @method canonicalDifficulty - * @param {Block} parentBlock the parent `Block` of the this header - * @return {BN} - */ -BlockHeader.prototype.canonicalDifficulty = function(parentBlock) { - const hardfork = - this._common.hardfork() || this._common.activeHardfork(utils.bufferToInt(this.number)) - const blockTs = new BN(this.timestamp) - const parentTs = new BN(parentBlock.header.timestamp) - const parentDif = new BN(parentBlock.header.difficulty) - const minimumDifficulty = new BN(this._common.param('pow', 'minimumDifficulty', hardfork)) - var offset = parentDif.div(new BN(this._common.param('pow', 'difficultyBoundDivisor', hardfork))) - var num = new BN(this.number) - var a - var cutoff - var dif - - if (this._common.hardforkGteHardfork(hardfork, 'byzantium')) { - // max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99) (EIP100) - var uncleAddend = parentBlock.header.uncleHash.equals(utils.SHA3_RLP_ARRAY) ? 1 : 2 - a = blockTs - .sub(parentTs) - .idivn(9) - .ineg() - .iaddn(uncleAddend) - cutoff = new BN(-99) - // MAX(cutoff, a) - if (cutoff.cmp(a) === 1) { - a = cutoff + this._common = opts.common + } else { + const chain = opts.chain ? opts.chain : 'mainnet' + const hardfork = opts.hardfork ? opts.hardfork : null + this._common = new Common(chain, hardfork) } - dif = parentDif.add(offset.mul(a)) + + const fields = [ + { + name: 'parentHash', + length: 32, + default: utils.zeros(32), + }, + { + name: 'uncleHash', + default: utils.KECCAK256_RLP_ARRAY, + }, + { + name: 'coinbase', + length: 20, + default: utils.zeros(20), + }, + { + name: 'stateRoot', + length: 32, + default: utils.zeros(32), + }, + { + name: 'transactionsTrie', + length: 32, + default: utils.KECCAK256_RLP, + }, + { + name: 'receiptTrie', + length: 32, + default: utils.KECCAK256_RLP, + }, + { + name: 'bloom', + default: utils.zeros(256), + }, + { + name: 'difficulty', + default: Buffer.from([]), + }, + { + name: 'number', + // TODO: params.homeSteadForkNumber.v left for legacy reasons, replace on future release + default: utils.toBuffer(1150000), + }, + { + name: 'gasLimit', + default: Buffer.from('ffffffffffffff', 'hex'), + }, + { + name: 'gasUsed', + empty: true, + default: Buffer.from([]), + }, + { + name: 'timestamp', + default: Buffer.from([]), + }, + { + name: 'extraData', + allowZero: true, + empty: true, + default: Buffer.from([]), + }, + { + name: 'mixHash', + default: utils.zeros(32), + // length: 32 + }, + { + name: 'nonce', + default: utils.zeros(8), // sha3(42) + }, + ] + utils.defineProperties(this, fields, data) } - if (this._common.hardforkGteHardfork(hardfork, 'constantinople')) { - // Constantinople difficulty bomb delay (EIP1234) - num.isubn(5000000) - if (num.ltn(0)) { - num = new BN(0) + /** + * Returns the canoncical difficulty of the block + * @method canonicalDifficulty + * @param {Block} parentBlock the parent `Block` of the this header + * @return {BN} + */ + canonicalDifficulty(parentBlock: Block): BN { + const hardfork = this._getHardfork() + const blockTs = new BN(this.timestamp) + const parentTs = new BN(parentBlock.header.timestamp) + const parentDif = new BN(parentBlock.header.difficulty) + const minimumDifficulty = new BN(this._common.param('pow', 'minimumDifficulty', hardfork)) + const offset = parentDif.div( + new BN(this._common.param('pow', 'difficultyBoundDivisor', hardfork)), + ) + let num = new BN(this.number) + + // We use a ! here as TS can follow this hardforks-dependent logic, but it always gets assigned + let dif!: BN + + if (this._common.hardforkGteHardfork(hardfork, 'byzantium')) { + // max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99) (EIP100) + const uncleAddend = parentBlock.header.uncleHash.equals(utils.KECCAK256_RLP_ARRAY) ? 1 : 2 + let a = blockTs + .sub(parentTs) + .idivn(9) + .ineg() + .iaddn(uncleAddend) + const cutoff = new BN(-99) + // MAX(cutoff, a) + if (cutoff.cmp(a) === 1) { + a = cutoff + } + dif = parentDif.add(offset.mul(a)) } - } else if (this._common.hardforkGteHardfork(hardfork, 'byzantium')) { - // Byzantium difficulty bomb delay (EIP649) - num.isubn(3000000) - if (num.ltn(0)) { - num = new BN(0) + + if (this._common.hardforkGteHardfork(hardfork, 'constantinople')) { + // Constantinople difficulty bomb delay (EIP1234) + num.isubn(5000000) + if (num.ltn(0)) { + num = new BN(0) + } + } else if (this._common.hardforkGteHardfork(hardfork, 'byzantium')) { + // Byzantium difficulty bomb delay (EIP649) + num.isubn(3000000) + if (num.ltn(0)) { + num = new BN(0) + } + } else if (this._common.hardforkGteHardfork(hardfork, 'homestead')) { + // 1 - (block_timestamp - parent_timestamp) // 10 + let a = blockTs + .sub(parentTs) + .idivn(10) + .ineg() + .iaddn(1) + const cutoff = new BN(-99) + // MAX(cutoff, a) + if (cutoff.cmp(a) === 1) { + a = cutoff + } + dif = parentDif.add(offset.mul(a)) + } else { + // pre-homestead + if (parentTs.addn(this._common.param('pow', 'durationLimit', hardfork)).cmp(blockTs) === 1) { + dif = offset.add(parentDif) + } else { + dif = parentDif.sub(offset) + } } - } else if (this._common.hardforkGteHardfork(hardfork, 'homestead')) { - // 1 - (block_timestamp - parent_timestamp) // 10 - a = blockTs - .sub(parentTs) - .idivn(10) - .ineg() - .iaddn(1) - cutoff = new BN(-99) - // MAX(cutoff, a) - if (cutoff.cmp(a) === 1) { - a = cutoff + + const exp = num.idivn(100000).isubn(2) + if (!exp.isNeg()) { + dif.iadd(new BN(2).pow(exp)) } - dif = parentDif.add(offset.mul(a)) - } else { - // pre-homestead - if (parentTs.addn(this._common.param('pow', 'durationLimit', hardfork)).cmp(blockTs) === 1) { - dif = offset.add(parentDif) - } else { - dif = parentDif.sub(offset) + + if (dif.cmp(minimumDifficulty) === -1) { + dif = minimumDifficulty } - } - var exp = num.idivn(100000).isubn(2) - if (!exp.isNeg()) { - dif.iadd(new BN(2).pow(exp)) + return dif } - if (dif.cmp(minimumDifficulty) === -1) { - dif = minimumDifficulty + /** + * checks that the block's `difficuly` matches the canonical difficulty + * @method validateDifficulty + * @param {Block} parentBlock this block's parent + * @return {Boolean} + */ + validateDifficulty(parentBlock: Block): boolean { + const dif = this.canonicalDifficulty(parentBlock) + return dif.cmp(new BN(this.difficulty)) === 0 } - return dif -} + /** + * Validates the gasLimit + * @method validateGasLimit + * @param {Block} parentBlock this block's parent + * @returns {Boolean} + */ + validateGasLimit(parentBlock: Block): boolean { + const pGasLimit = new BN(parentBlock.header.gasLimit) + const gasLimit = new BN(this.gasLimit) + const hardfork = this._getHardfork() -/** - * checks that the block's `difficuly` matches the canonical difficulty - * @method validateDifficulty - * @param {Block} parentBlock this block's parent - * @return {Boolean} - */ -BlockHeader.prototype.validateDifficulty = function(parentBlock) { - const dif = this.canonicalDifficulty(parentBlock) - return dif.cmp(new BN(this.difficulty)) === 0 -} + const a = pGasLimit.div( + new BN(this._common.param('gasConfig', 'gasLimitBoundDivisor', hardfork)), + ) + const maxGasLimit = pGasLimit.add(a) + const minGasLimit = pGasLimit.sub(a) -/** - * Validates the gasLimit - * @method validateGasLimit - * @param {Block} parentBlock this block's parent - * @returns {Boolean} - */ -BlockHeader.prototype.validateGasLimit = function(parentBlock) { - const pGasLimit = new BN(parentBlock.header.gasLimit) - const gasLimit = new BN(this.gasLimit) - const hardfork = this._common.hardfork() - ? this._common.hardfork() - : this._common.activeHardfork(this.number) - const a = pGasLimit.div(new BN(this._common.param('gasConfig', 'gasLimitBoundDivisor', hardfork))) - const maxGasLimit = pGasLimit.add(a) - const minGasLimit = pGasLimit.sub(a) - - return ( - gasLimit.lt(maxGasLimit) && - gasLimit.gt(minGasLimit) && - gasLimit.gte(this._common.param('gasConfig', 'minGasLimit', hardfork)) - ) -} - -/** - * Validates the entire block header - * @method validate - * @param {Blockchain} blockChain the blockchain that this block is validating against - * @param {Bignum} [height] if this is an uncle header, this is the height of the block that is including it - * @param {Function} cb the callback function. The callback is given an `error` if the block is invalid - */ -BlockHeader.prototype.validate = function(blockchain, height, cb) { - var self = this - if (arguments.length === 2) { - cb = height - height = false + return ( + gasLimit.lt(maxGasLimit) && + gasLimit.gt(minGasLimit) && + gasLimit.gte(this._common.param('gasConfig', 'minGasLimit', hardfork)) + ) } - if (this.isGenesis()) { - return cb() - } + /** + * Validates the entire block header + * @method validate + * @param {Blockchain} blockChain the blockchain that this block is validating against + * @param {Bignum} [height] if this is an uncle header, this is the height of the block that is including it + * @param {Function} cb the callback function. The callback is given an `error` if the block is invalid + */ + validate(blockchain: Blockchain, cb: NoReturnValueCallback): void + validate(blockchain: Blockchain, height: BN, cb: NoReturnValueCallback): void + validate( + blockchain: Blockchain, + heightOrCb: BN | NoReturnValueCallback, + cb?: NoReturnValueCallback, + ): void { + if (heightOrCb instanceof Function) { + cb = heightOrCb + } - // find the blocks parent - blockchain.getBlock(self.parentHash, function(err, parentBlock) { - if (err) { - return cb('could not find parent block') + if (cb === undefined) { + throw new Error('No callback provided') } - self.parentBlock = parentBlock + const callback = cb - var number = new BN(self.number) - if (number.cmp(new BN(parentBlock.header.number).iaddn(1)) !== 0) { - return cb('invalid number') + if (this.isGenesis()) { + return callback(null) } - if (height) { - var dif = height.sub(new BN(parentBlock.header.number)) - if (!(dif.cmpn(8) === -1 && dif.cmpn(1) === 1)) { - return cb('uncle block has a parent that is too old or to young') + // find the blocks parent + blockchain.getBlock(this.parentHash, (err, parentBlock) => { + if (err || parentBlock === undefined) { + return callback(new Error('could not find parent block')) } - } - if (!self.validateDifficulty(parentBlock)) { - return cb('invalid Difficulty') - } + const number = new BN(this.number) + if (number.cmp(new BN(parentBlock.header.number).iaddn(1)) !== 0) { + return callback(new Error('invalid number')) + } - if (!self.validateGasLimit(parentBlock)) { - return cb('invalid gas limit') - } + if (BN.isBN(heightOrCb)) { + const dif = heightOrCb.sub(new BN(parentBlock.header.number)) + if (!(dif.cmpn(8) === -1 && dif.cmpn(1) === 1)) { + return callback(new Error('uncle block has a parent that is too old or to young')) + } + } - if (utils.bufferToInt(parentBlock.header.number) + 1 !== utils.bufferToInt(self.number)) { - return cb('invalid heigth') - } + if (!this.validateDifficulty(parentBlock)) { + return callback(new Error('invalid Difficulty')) + } - if (utils.bufferToInt(self.timestamp) <= utils.bufferToInt(parentBlock.header.timestamp)) { - return cb('invalid timestamp') - } + if (!this.validateGasLimit(parentBlock)) { + return callback(new Error('invalid gas limit')) + } - const hardfork = self._common.hardfork() - ? self._common.hardfork() - : self._common.activeHardfork(height) - if (self.extraData.length > self._common.param('vm', 'maxExtraDataSize', hardfork)) { - return cb('invalid amount of extra data') - } + if (utils.bufferToInt(parentBlock.header.number) + 1 !== utils.bufferToInt(this.number)) { + return callback(new Error('invalid heigth')) + } - cb() - }) -} + if (utils.bufferToInt(this.timestamp) <= utils.bufferToInt(parentBlock.header.timestamp)) { + return callback(new Error('invalid timestamp')) + } -/** - * Returns the sha3 hash of the blockheader - * @method hash - * @return {Buffer} - */ -BlockHeader.prototype.hash = function() { - return utils.rlphash(this.raw) -} + const hardfork = this._getHardfork() + if (this.extraData.length > this._common.param('vm', 'maxExtraDataSize', hardfork)) { + return callback(new Error('invalid amount of extra data')) + } -/** - * checks if the blockheader is a genesis header - * @method isGenesis - * @return {Boolean} - */ -BlockHeader.prototype.isGenesis = function() { - return this.number.toString('hex') === '' -} + callback(null) + }) + } -/** - * turns the header into the canonical genesis block header - * @method setGenesisParams - */ -BlockHeader.prototype.setGenesisParams = function() { - this.timestamp = this._common.genesis().timestamp - this.gasLimit = this._common.genesis().gasLimit - this.difficulty = this._common.genesis().difficulty - this.extraData = this._common.genesis().extraData - this.nonce = this._common.genesis().nonce - this.stateRoot = this._common.genesis().stateRoot - this.number = Buffer.from([]) + /** + * Returns the sha3 hash of the blockheader + * @method hash + * @return {Buffer} + */ + hash(): Buffer { + return utils.rlphash(this.raw) + } + + /** + * checks if the blockheader is a genesis header + * @method isGenesis + * @return {Boolean} + */ + isGenesis(): boolean { + return this.number.length === 0 + } + + /** + * turns the header into the canonical genesis block header + * @method setGenesisParams + */ + setGenesisParams(): void { + this.timestamp = this._common.genesis().timestamp + this.gasLimit = this._common.genesis().gasLimit + this.difficulty = this._common.genesis().difficulty + this.extraData = this._common.genesis().extraData + this.nonce = this._common.genesis().nonce + this.stateRoot = this._common.genesis().stateRoot + this.number = Buffer.from([]) + } + + /** + * Returns the rlp encoding of the block header + */ + serialize(): Buffer { + // Note: This never gets executed, defineProperties overwrites it. + return Buffer.from([]) + } + + /** + * Returns the block header in JSON format + * @see {@link https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#defineproperties|ethereumjs-util} + */ + toJSON(_labels: boolean = false): { [key: string]: string } | string[] { + // Note: This never gets executed, defineProperties overwrites it. + return {} + } + + private _getHardfork(): string { + const commonHardFork = this._common.hardfork() + + return commonHardFork !== null + ? commonHardFork + : this._common.activeHardfork(utils.bufferToInt(this.number)) + } } diff --git a/src/index.ts b/src/index.ts index fd9ef21..6273a83 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,335 +1,3 @@ -const Common = require('ethereumjs-common').default -const ethUtil = require('ethereumjs-util') -const Tx = require('ethereumjs-tx') -const Trie = require('merkle-patricia-tree') -const BN = ethUtil.BN -const rlp = ethUtil.rlp -const async = require('async') -const BlockHeader = require('./header') - -/** - * Creates a new block object - * @constructor the raw serialized or the deserialized block. - * @param {Array|Buffer|Object} data - * @param {Array} opts Options - * @param {String|Number} opts.chain The chain for the block [default: 'mainnet'] - * @param {String} opts.hardfork Hardfork for the block [default: null, block number-based behaviour] - * @param {Object} opts.common Alternatively pass a Common instance (ethereumjs-common) instead of setting chain/hardfork directly - * @prop {Header} header the block's header - * @prop {Array.
} uncleList an array of uncle headers - * @prop {Array.} raw an array of buffers containing the raw blocks. - */ -var Block = (module.exports = function(data, opts) { - opts = opts || {} - - if (opts.common) { - if (opts.chain) { - throw new Error('Instantiation with both opts.common and opts.chain parameter not allowed!') - } - this._common = opts.common - } else { - let chain = opts.chain ? opts.chain : 'mainnet' - let hardfork = opts.hardfork ? opts.hardfork : null - this._common = new Common(chain, hardfork) - } - - this.transactions = [] - this.uncleHeaders = [] - this._inBlockChain = false - this.txTrie = new Trie() - - Object.defineProperty(this, 'raw', { - get: function() { - return this.serialize(false) - }, - }) - - var rawTransactions, rawUncleHeaders - - // defaults - if (!data) { - data = [[], [], []] - } - - if (Buffer.isBuffer(data)) { - data = rlp.decode(data) - } - - if (Array.isArray(data)) { - this.header = new BlockHeader(data[0], opts) - rawTransactions = data[1] - rawUncleHeaders = data[2] - } else { - this.header = new BlockHeader(data.header, opts) - rawTransactions = data.transactions || [] - rawUncleHeaders = data.uncleHeaders || [] - } - - // parse uncle headers - for (var i = 0; i < rawUncleHeaders.length; i++) { - this.uncleHeaders.push(new BlockHeader(rawUncleHeaders[i], opts)) - } - - // parse transactions - for (i = 0; i < rawTransactions.length; i++) { - var tx = new Tx(rawTransactions[i]) - tx._homestead = true - this.transactions.push(tx) - } -}) - -Block.Header = BlockHeader - -/** - * Produces a hash the RLP of the block - * @method hash - */ -Block.prototype.hash = function() { - return this.header.hash() -} - -/** - * Determines if a given block is the genesis block - * @method isGenisis - * @return Boolean - */ -Block.prototype.isGenesis = function() { - return this.header.isGenesis() -} - -/** - * turns the block into the canonical genesis block - * @method setGenesisParams - */ -Block.prototype.setGenesisParams = function() { - this.header.setGenesisParams() -} - -/** - * Produces a serialization of the block. - * @method serialize - * @param {Boolean} rlpEncode whether to rlp encode the block or not - */ -Block.prototype.serialize = function(rlpEncode) { - var raw = [this.header.raw, [], []] - - // rlpEnode defaults to true - if (typeof rlpEncode === 'undefined') { - rlpEncode = true - } - - this.transactions.forEach(function(tx) { - raw[1].push(tx.raw) - }) - - this.uncleHeaders.forEach(function(uncle) { - raw[2].push(uncle.raw) - }) - - return rlpEncode ? rlp.encode(raw) : raw -} - -/** - * Generate transaction trie. The tx trie must be generated before the transaction trie can - * be validated with `validateTransactionTrie` - * @method genTxTrie - * @param {Function} cb the callback - */ -Block.prototype.genTxTrie = function(cb) { - var i = 0 - var self = this - - async.eachSeries( - this.transactions, - function(tx, done) { - self.txTrie.put(rlp.encode(i), tx.serialize(), done) - i++ - }, - cb, - ) -} - -/** - * Validates the transaction trie - * @method validateTransactionTrie - * @return {Boolean} - */ -Block.prototype.validateTransactionsTrie = function() { - var txT = this.header.transactionsTrie.toString('hex') - if (this.transactions.length) { - return txT === this.txTrie.root.toString('hex') - } else { - return txT === ethUtil.SHA3_RLP.toString('hex') - } -} - -/** - * Validates the transactions - * @method validateTransactions - * @param {Boolean} [stringError=false] whether to return a string with a dscription of why the validation failed or return a Bloolean - * @return {Boolean} - */ -Block.prototype.validateTransactions = function(stringError) { - var errors = [] - - this.transactions.forEach(function(tx, i) { - var error = tx.validate(true) - if (error) { - errors.push(error + ' at tx ' + i) - } - }) - - if (stringError === undefined || stringError === false) { - return errors.length === 0 - } else { - return arrayToString(errors) - } -} - -/** - * Validates the entire block. Returns a string to the callback if block is invalid - * @method validate - * @param {BlockChain} blockChain the blockchain that this block wants to be part of - * @param {Function} cb the callback which is given a `String` if the block is not valid - */ -Block.prototype.validate = function(blockChain, cb) { - var self = this - var errors = [] - - async.parallel( - [ - // validate uncles - self.validateUncles.bind(self, blockChain), - // validate block - self.header.validate.bind(self.header, blockChain), - // generate the transaction trie - self.genTxTrie.bind(self), - ], - function(err) { - if (err) { - errors.push(err) - } - - if (!self.validateTransactionsTrie()) { - errors.push('invalid transaction trie') - } - - var txErrors = self.validateTransactions(true) - if (txErrors !== '') { - errors.push(txErrors) - } - - if (!self.validateUnclesHash()) { - errors.push('invalid uncle hash') - } - - cb(arrayToString(errors)) - }, - ) -} - -/** - * Validates the uncle's hash - * @method validateUncleHash - * @return {Boolean} - */ -Block.prototype.validateUnclesHash = function() { - var raw = [] - this.uncleHeaders.forEach(function(uncle) { - raw.push(uncle.raw) - }) - - raw = rlp.encode(raw) - return ethUtil.sha3(raw).toString('hex') === this.header.uncleHash.toString('hex') -} - -/** - * Validates the uncles that are in the block if any. Returns a string to the callback if uncles are invalid - * @method validateUncles - * @param {Blockchain} blockChaina an instance of the Blockchain - * @param {Function} cb the callback - */ -Block.prototype.validateUncles = function(blockChain, cb) { - if (this.isGenesis()) { - return cb() - } - - var self = this - - if (self.uncleHeaders.length > 2) { - return cb('too many uncle headers') - } - - var uncleHashes = self.uncleHeaders.map(function(header) { - return header.hash().toString('hex') - }) - - if (!(new Set(uncleHashes).size === uncleHashes.length)) { - return cb('duplicate uncles') - } - - async.each( - self.uncleHeaders, - function(uncle, cb2) { - var height = new BN(self.header.number) - async.parallel( - [ - uncle.validate.bind(uncle, blockChain, height), - // check to make sure the uncle is not already in the blockchain - function(cb3) { - blockChain.getDetails(uncle.hash(), function(err, blockInfo) { - // TODO: remove uncles from BC - if (blockInfo && blockInfo.isUncle) { - cb3(err || 'uncle already included') - } else { - cb3() - } - }) - }, - ], - cb2, - ) - }, - cb, - ) -} - -/** - * Converts the block toJSON - * @method toJSON - * @param {Bool} labeled whether to create an labeled object or an array - * @return {Object} - */ -Block.prototype.toJSON = function(labeled) { - if (labeled) { - var obj = { - header: this.header.toJSON(true), - transactions: [], - uncleHeaders: [], - } - - this.transactions.forEach(function(tx) { - obj.transactions.push(tx.toJSON(labeled)) - }) - - this.uncleHeaders.forEach(function(uh) { - obj.uncleHeaders.push(uh.toJSON()) - }) - return obj - } else { - return ethUtil.baToJSON(this.raw) - } -} - -function arrayToString(array) { - try { - return array.reduce(function(str, err) { - if (str) { - str += ' ' - } - return str + err - }) - } catch (e) { - return '' - } -} +export { Block } from './block' +export { BlockHeader } from './header' +export * from './types' diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..6dea7a5 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,77 @@ +import Common from 'ethereumjs-common' +import { TxData } from 'ethereumjs-tx' +import { Block } from './block' + +/** + * An object to set which network blocks and their headers belong to. This could be specified using + * a Common object, or `chain` and `hardfork`. Defaults to mainnet. + */ +export interface NetworkOptions { + /** + * A Common object defining the chain and the hardfork a block/block header belongs to. + */ + common?: Common + + /** + * The chain of the block/block header, default: 'mainnet' + */ + chain?: number | string + + /** + * The hardfork of the block/block header, default: 'petersburg' + */ + hardfork?: string +} + +/** + * Any object that can be transformed into a `Buffer` + */ +export interface TransformableToBuffer { + toBuffer(): Buffer +} + +/** + * A hex string prefixed with `0x`. + */ +export type PrefixedHexString = string + +/** + * A Buffer, hex string prefixed with `0x`, Number, or an object with a toBuffer method such as BN. + */ +export type BufferLike = Buffer | TransformableToBuffer | PrefixedHexString | number + +/** + * A block header's data. + */ +export interface BlockHeaderData { + parentHash?: BufferLike + uncleHash?: BufferLike + coinbase?: BufferLike + stateRoot?: BufferLike + transactionsTrie?: BufferLike + receiptTrie?: BufferLike + bloom?: BufferLike + difficulty?: BufferLike + number?: BufferLike + gasLimit?: BufferLike + gasUsed?: BufferLike + timestamp?: BufferLike + extraData?: BufferLike + mixHash?: BufferLike + nonce?: BufferLike +} + +/** + * A block's data. + */ +export interface BlockData { + header?: Buffer | PrefixedHexString | BufferLike[] | BlockHeaderData + transactions?: Array + uncleHeaders?: Array +} + +export interface Blockchain { + getBlock(hash: Buffer, callback: (err: Error | null, block?: Block) => void): void +} + +export type NoReturnValueCallback = (err: Error | null) => void diff --git a/test/block.js b/test/block.ts similarity index 71% rename from test/block.js rename to test/block.ts index 5e1062c..413f75b 100644 --- a/test/block.js +++ b/test/block.ts @@ -1,13 +1,14 @@ -const tape = require('tape') -const Common = require('ethereumjs-common').default -const rlp = require('ethereumjs-util').rlp -const Block = require('../index.js') +import Common from 'ethereumjs-common' +import tape = require('tape') +import { rlp } from 'ethereumjs-util' + +import { Block } from '../src/block' tape('[Block]: block functions', function(t) { t.test('should test block initialization', function(st) { - const block1 = new Block(null, { chain: 'ropsten' }) + const block1 = new Block(undefined, { chain: 'ropsten' }) const common = new Common('ropsten') - const block2 = new Block(null, { common: common }) + const block2 = new Block(undefined, { common: common }) block1.setGenesisParams() block2.setGenesisParams() st.strictEqual( @@ -18,7 +19,7 @@ tape('[Block]: block functions', function(t) { st.throws( function() { - new Block(null, { chain: 'ropsten', common: common }) + new Block(undefined, { chain: 'ropsten', common: common }) }, /not allowed!$/, 'should throw on initialization with chain and common parameter', @@ -28,7 +29,7 @@ tape('[Block]: block functions', function(t) { const testData = require('./testdata.json') - function testTransactionValidation(st, block) { + function testTransactionValidation(st: tape.Test, block: Block) { st.equal(block.validateTransactions(), true) block.genTxTrie(function() { @@ -38,26 +39,26 @@ tape('[Block]: block functions', function(t) { } t.test('should test transaction validation', function(st) { - var block = new Block(rlp.decode(testData.blocks[0].rlp)) + const block = new Block(rlp.decode(testData.blocks[0].rlp)) st.plan(2) testTransactionValidation(st, block) }) t.test('should test transaction validation with empty transaction list', function(st) { - var block = new Block() + const block = new Block() st.plan(2) testTransactionValidation(st, block) }) const testData2 = require('./testdata2.json') t.test('should test uncles hash validation', function(st) { - var block = new Block(rlp.decode(testData2.blocks[2].rlp)) + const block = new Block(rlp.decode(testData2.blocks[2].rlp)) st.equal(block.validateUnclesHash(), true) st.end() }) t.test('should test isGenesis (mainnet default)', function(st) { - var block = new Block() + const block = new Block() st.notEqual(block.isGenesis(), true) block.header.number = Buffer.from([]) st.equal(block.isGenesis(), true) @@ -65,7 +66,7 @@ tape('[Block]: block functions', function(t) { }) t.test('should test isGenesis (ropsten)', function(st) { - var block = new Block(null, { chain: 'ropsten' }) + const block = new Block(undefined, { chain: 'ropsten' }) st.notEqual(block.isGenesis(), true) block.header.number = Buffer.from([]) st.equal(block.isGenesis(), true) @@ -74,9 +75,9 @@ tape('[Block]: block functions', function(t) { const testDataGenesis = require('./genesishashestest.json').test t.test('should test genesis hashes (mainnet default)', function(st) { - var genesisBlock = new Block() + const genesisBlock = new Block() genesisBlock.setGenesisParams() - var rlp = genesisBlock.serialize() + const rlp = genesisBlock.serialize() st.strictEqual(rlp.toString('hex'), testDataGenesis.genesis_rlp_hex, 'rlp hex match') st.strictEqual( genesisBlock.hash().toString('hex'), @@ -87,8 +88,8 @@ tape('[Block]: block functions', function(t) { }) t.test('should test genesis hashes (ropsten)', function(st) { - var common = new Common('ropsten') - var genesisBlock = new Block(null, { common: common }) + const common = new Common('ropsten') + const genesisBlock = new Block(undefined, { common: common }) genesisBlock.setGenesisParams() st.strictEqual( genesisBlock.hash().toString('hex'), @@ -99,8 +100,8 @@ tape('[Block]: block functions', function(t) { }) t.test('should test genesis hashes (rinkeby)', function(st) { - var common = new Common('rinkeby') - var genesisBlock = new Block(null, { common: common }) + const common = new Common('rinkeby') + const genesisBlock = new Block(undefined, { common: common }) genesisBlock.setGenesisParams() st.strictEqual( genesisBlock.hash().toString('hex'), @@ -111,9 +112,9 @@ tape('[Block]: block functions', function(t) { }) t.test('should test genesis parameters (ropsten)', function(st) { - var genesisBlock = new Block(null, { chain: 'ropsten' }) + const genesisBlock = new Block(undefined, { chain: 'ropsten' }) genesisBlock.setGenesisParams() - let ropstenStateRoot = '217b0bbcfb72e2d57e28f33cb361b9983513177755dc3f33ce3e7022ed62b77b' + const ropstenStateRoot = '217b0bbcfb72e2d57e28f33cb361b9983513177755dc3f33ce3e7022ed62b77b' st.strictEqual( genesisBlock.header.stateRoot.toString('hex'), ropstenStateRoot, @@ -123,7 +124,7 @@ tape('[Block]: block functions', function(t) { }) t.test('should test toJSON', function(st) { - var block = new Block(rlp.decode(testData2.blocks[2].rlp)) + const block = new Block(rlp.decode(testData2.blocks[2].rlp)) st.equal(typeof block.toJSON(), 'object') st.equal(typeof block.toJSON(true), 'object') st.end() diff --git a/test/difficulty.js b/test/difficulty.ts similarity index 65% rename from test/difficulty.js rename to test/difficulty.ts index feedc2b..2321a1d 100644 --- a/test/difficulty.js +++ b/test/difficulty.ts @@ -1,41 +1,45 @@ -const utils = require('ethereumjs-util') -const tape = require('tape') -const Block = require('../') -const BN = utils.BN +import * as utils from 'ethereumjs-util' +import { BN } from 'ethereumjs-util' +import { Block } from '../src/block' +import tape = require('tape') -function normalize(data) { - Object.keys(data).map(function(i) { +function isHexPrefixed(str: string) { + return str.toLowerCase().startsWith('0x') +} + +function normalize(data: any) { + Object.keys(data).forEach(function(i) { if (i !== 'homestead' && typeof data[i] === 'string') { - data[i] = utils.isHexPrefixed(data[i]) ? new BN(utils.toBuffer(data[i])) : new BN(data[i]) + data[i] = isHexPrefixed(data[i]) ? new BN(utils.toBuffer(data[i])) : new BN(data[i]) } }) } tape('[Header]: difficulty tests', t => { - function runDifficultyTests(test, parentBlock, block, msg) { + function runDifficultyTests(test: any, parentBlock: Block, block: Block, msg: string) { normalize(test) - var dif = block.header.canonicalDifficulty(parentBlock) + const dif = block.header.canonicalDifficulty(parentBlock) t.equal(dif.toString(), test.currentDifficulty.toString(), `test canonicalDifficulty (${msg})`) t.assert(block.header.validateDifficulty(parentBlock), `test validateDifficulty (${msg})`) } - const hardforkTestData = { + const hardforkTestData: any = { chainstart: require('./difficultyFrontier.json').tests, homestead: require('./difficultyHomestead.json').tests, byzantium: require('./difficultyByzantium.json').tests, constantinople: require('./difficultyConstantinople.json').tests, } - for (let hardfork in hardforkTestData) { + for (const hardfork in hardforkTestData) { const testData = hardforkTestData[hardfork] - for (let testName in testData) { - let test = testData[testName] - let parentBlock = new Block(null, { chain: 'mainnet', hardfork: hardfork }) + for (const testName in testData) { + const test = testData[testName] + const parentBlock = new Block(undefined, { chain: 'mainnet', hardfork: hardfork }) parentBlock.header.timestamp = test.parentTimestamp parentBlock.header.difficulty = test.parentDifficulty parentBlock.header.uncleHash = test.parentUncles - let block = new Block(null, { chain: 'mainnet', hardfork: hardfork }) + const block = new Block(undefined, { chain: 'mainnet', hardfork: hardfork }) block.header.timestamp = test.currentTimestamp block.header.difficulty = test.currentDifficulty block.header.number = test.currentBlockNumber @@ -44,20 +48,20 @@ tape('[Header]: difficulty tests', t => { } } - const chainTestData = { + const chainTestData: any = { mainnet: require('./difficultyMainNetwork.json').tests, ropsten: require('./difficultyRopstenConstantinople.json').tests, } - for (let chain in chainTestData) { + for (const chain in chainTestData) { const testData = chainTestData[chain] - for (let testName in testData) { - let test = testData[testName] - let parentBlock = new Block(null, { chain: chain }) + for (const testName in testData) { + const test = testData[testName] + const parentBlock = new Block(undefined, { chain: chain }) parentBlock.header.timestamp = test.parentTimestamp parentBlock.header.difficulty = test.parentDifficulty parentBlock.header.uncleHash = test.parentUncles - let block = new Block(null, { chain: chain }) + const block = new Block(undefined, { chain: chain }) block.header.timestamp = test.currentTimestamp block.header.difficulty = test.currentDifficulty block.header.number = test.currentBlockNumber diff --git a/test/from-rpc.js b/test/from-rpc.js deleted file mode 100644 index 814394b..0000000 --- a/test/from-rpc.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict' -const tape = require('tape') -const blockFromRpc = require('../from-rpc.js') -const blockData = require('./testdata-from-rpc.json') - -tape('[fromRPC]: block #2924874', function(t) { - t.test('should create a block with transactions with valid signatures', function(st) { - let block = blockFromRpc(blockData) - let allValid = block.transactions.every(tx => tx.verifySignature()) - st.equal(allValid, true, 'all transaction signatures are valid') - st.end() - }) -}) diff --git a/test/from-rpc.ts b/test/from-rpc.ts new file mode 100644 index 0000000..f8b6c4c --- /dev/null +++ b/test/from-rpc.ts @@ -0,0 +1,13 @@ +// 'use strict' +// const tape = require('tape') +// const blockFromRpc = require('../from-rpc.js') +// const blockData = require('./testdata-from-rpc.json') +// +// tape('[fromRPC]: block #2924874', function(t) { +// t.test('should create a block with transactions with valid signatures', function(st) { +// let block = blockFromRpc(blockData) +// let allValid = block.transactions.every(tx => tx.verifySignature()) +// st.equal(allValid, true, 'all transaction signatures are valid') +// st.end() +// }) +// }) diff --git a/test/header.js b/test/header.ts similarity index 65% rename from test/header.js rename to test/header.ts index 49ca9df..05f92ec 100644 --- a/test/header.js +++ b/test/header.ts @@ -1,22 +1,22 @@ -const tape = require('tape') -const Common = require('ethereumjs-common').default -const utils = require('ethereumjs-util') -const rlp = utils.rlp -const Header = require('../header.js') -const Block = require('../index.js') +import tape = require('tape') +import Common from 'ethereumjs-common' +import * as utils from 'ethereumjs-util' +import { rlp } from 'ethereumjs-util' +import { BlockHeader } from '../src/header' +import { Block } from '../src/block' tape('[Block]: Header functions', function(t) { t.test('should create with default constructor', function(st) { - function compareDefaultHeader(st, header) { + function compareDefaultHeader(st: tape.Test, header: BlockHeader) { st.deepEqual(header.parentHash, utils.zeros(32)) - st.equal(header.uncleHash.toString('hex'), utils.SHA3_RLP_ARRAY_S) + st.equal(header.uncleHash.toString('hex'), utils.KECCAK256_RLP_ARRAY_S) st.deepEqual(header.coinbase, utils.zeros(20)) st.deepEqual(header.stateRoot, utils.zeros(32)) - st.equal(header.transactionsTrie.toString('hex'), utils.SHA3_RLP_S) - st.equal(header.receiptTrie.toString('hex'), utils.SHA3_RLP_S) + st.equal(header.transactionsTrie.toString('hex'), utils.KECCAK256_RLP_S) + st.equal(header.receiptTrie.toString('hex'), utils.KECCAK256_RLP_S) st.deepEqual(header.bloom, utils.zeros(256)) st.deepEqual(header.difficulty, Buffer.from([])) - st.deepEqual(header.number, utils.intToBuffer(1150000)) + st.deepEqual(header.number, utils.toBuffer(1150000)) st.deepEqual(header.gasLimit, Buffer.from('ffffffffffffff', 'hex')) st.deepEqual(header.gasUsed, Buffer.from([])) st.deepEqual(header.timestamp, Buffer.from([])) @@ -25,10 +25,10 @@ tape('[Block]: Header functions', function(t) { st.deepEqual(header.nonce, utils.zeros(8)) } - var header = new Header() + let header = new BlockHeader() compareDefaultHeader(st, header) - var block = new Block() + const block = new Block() header = block.header compareDefaultHeader(st, header) @@ -36,9 +36,9 @@ tape('[Block]: Header functions', function(t) { }) t.test('should test header initialization', function(st) { - const header1 = new Header(null, { chain: 'ropsten' }) + const header1 = new BlockHeader(undefined, { chain: 'ropsten' }) const common = new Common('ropsten') - const header2 = new Header(null, { common: common }) + const header2 = new BlockHeader(undefined, { common: common }) header1.setGenesisParams() header2.setGenesisParams() st.strictEqual( @@ -49,7 +49,7 @@ tape('[Block]: Header functions', function(t) { st.throws( function() { - new Header(null, { chain: 'ropsten', common: common }) + new BlockHeader(undefined, { chain: 'ropsten', common: common }) }, /not allowed!$/, 'should throw on initialization with chain and common parameter', @@ -62,8 +62,8 @@ tape('[Block]: Header functions', function(t) { const bcBlockGasLimigTestData = testData.BlockGasLimit2p63m1 Object.keys(bcBlockGasLimigTestData).forEach(key => { - var parentBlock = new Block(rlp.decode(bcBlockGasLimigTestData[key].genesisRLP)) - var block = new Block(rlp.decode(bcBlockGasLimigTestData[key].blocks[0].rlp)) + const parentBlock = new Block(rlp.decode(bcBlockGasLimigTestData[key].genesisRLP)) + const block = new Block(rlp.decode(bcBlockGasLimigTestData[key].blocks[0].rlp)) st.equal(block.header.validateGasLimit(parentBlock), true) }) @@ -71,7 +71,7 @@ tape('[Block]: Header functions', function(t) { }) t.test('should test isGenesis', function(st) { - var header = new Header() + const header = new BlockHeader() st.equal(header.isGenesis(), false) header.number = Buffer.from([]) st.equal(header.isGenesis(), true) @@ -80,7 +80,7 @@ tape('[Block]: Header functions', function(t) { const testDataGenesis = require('./genesishashestest.json').test t.test('should test genesis hashes (mainnet default)', function(st) { - var header = new Header() + const header = new BlockHeader() header.setGenesisParams() st.strictEqual( header.hash().toString('hex'), @@ -91,9 +91,9 @@ tape('[Block]: Header functions', function(t) { }) t.test('should test genesis parameters (ropsten)', function(st) { - var genesisHeader = new Header(null, { chain: 'ropsten' }) + const genesisHeader = new BlockHeader(undefined, { chain: 'ropsten' }) genesisHeader.setGenesisParams() - let ropstenStateRoot = '217b0bbcfb72e2d57e28f33cb361b9983513177755dc3f33ce3e7022ed62b77b' + const ropstenStateRoot = '217b0bbcfb72e2d57e28f33cb361b9983513177755dc3f33ce3e7022ed62b77b' st.strictEqual( genesisHeader.stateRoot.toString('hex'), ropstenStateRoot, diff --git a/test/index.js b/test/index.js deleted file mode 100644 index f0db526..0000000 --- a/test/index.js +++ /dev/null @@ -1,4 +0,0 @@ -require('./header.js') -require('./block.js') -require('./difficulty.js') -require('./from-rpc.js') diff --git a/test/index.ts b/test/index.ts new file mode 100644 index 0000000..ad57656 --- /dev/null +++ b/test/index.ts @@ -0,0 +1,4 @@ +import './header' +import './block' +import './difficulty' +import './from-rpc' diff --git a/tsconfig.json b/tsconfig.json index 16b64ff..c1249c3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "extends": "@ethereumjs/config-tsc", "include": ["src/**/*.ts", "test/**/*.ts"], "compilerOptions": { - "outDir": "test-build" + "outDir": "test-build", + "esModuleInterop": true } } diff --git a/tsconfig.prod.json b/tsconfig.prod.json index 184d95b..48aa0d4 100644 --- a/tsconfig.prod.json +++ b/tsconfig.prod.json @@ -1,7 +1,8 @@ { "extends": "@ethereumjs/config-tsc", "compilerOptions": { - "outDir": "./dist" + "outDir": "./dist", + "esModuleInterop": true }, "include": ["src/**/*.ts"] } diff --git a/tslint.json b/tslint.json index 2ba21c4..d0507fe 100644 --- a/tslint.json +++ b/tslint.json @@ -1,3 +1,6 @@ { - "extends": "@ethereumjs/config-tslint" + "extends": "@ethereumjs/config-tslint", + "rules": { + "prefer-const": true + } }