├── .github └── workflows │ └── test_and_release.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── docker ├── Dockerfile └── dashcore-node.json ├── lib ├── addresses.js ├── blocks.js ├── common.js ├── currency.js ├── governance.js ├── govobject.js ├── index.js ├── masternodes.js ├── messages.js ├── ratelimiter.js ├── service.js ├── sporks.js ├── status.js ├── transactions.js └── utils.js ├── package-lock.json ├── package.json ├── pools.json └── test ├── addresses.js ├── blocks.js ├── currency.js ├── data └── blocks.json ├── index.js ├── messages.js ├── ratelimiter.js ├── sporks.js ├── status.js ├── transactions.js └── utils.js /.github/workflows/test_and_release.yml: -------------------------------------------------------------------------------- 1 | name: Test and Release 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: 7 | - published 8 | pull_request: 9 | branches: 10 | - master 11 | - v[0-9]+.[0-9]+-dev 12 | 13 | jobs: 14 | test: 15 | name: Run Insight API tests 16 | runs-on: ubuntu-20.04 17 | timeout-minutes: 10 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - uses: actions/setup-node@v2 22 | with: 23 | node-version: '16' 24 | 25 | - name: Enable NPM cache 26 | uses: actions/cache@v2 27 | with: 28 | path: '~/.npm' 29 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 30 | restore-keys: | 31 | ${{ runner.os }}-node- 32 | 33 | - name: Install NPM dependencies 34 | run: npm ci 35 | 36 | - name: Run tests 37 | run: npm run test 38 | 39 | release-npm: 40 | name: Release NPM package 41 | runs-on: ubuntu-20.04 42 | needs: test 43 | if: ${{ github.event_name == 'release' }} 44 | steps: 45 | - uses: actions/checkout@v2 46 | 47 | - name: Check package version matches tag 48 | uses: geritol/match-tag-to-package-version@0.1.0 49 | env: 50 | TAG_PREFIX: refs/tags/v 51 | 52 | - name: Enable NPM cache 53 | uses: actions/cache@v2 54 | with: 55 | path: '~/.npm' 56 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 57 | restore-keys: | 58 | ${{ runner.os }}-node- 59 | 60 | - name: Install NPM dependencies 61 | run: npm ci 62 | 63 | - name: Set release tag 64 | uses: actions/github-script@v3 65 | id: tag 66 | with: 67 | result-encoding: string 68 | script: | 69 | const tag = context.payload.release.tag_name; 70 | const [, major, minor] = tag.match(/^v([0-9]+)\.([0-9]+)/); 71 | return (tag.includes('dev') ? `${major}.${minor}-dev` : 'latest'); 72 | 73 | - name: Publish NPM package 74 | uses: JS-DevTools/npm-publish@v1 75 | with: 76 | token: ${{ secrets.NPM_TOKEN }} 77 | tag: ${{ steps.tag.outputs.result }} 78 | 79 | release-docker: 80 | name: Release Docker image 81 | runs-on: ubuntu-20.04 82 | needs: release-npm 83 | if: ${{ github.event_name == 'release' }} 84 | steps: 85 | - uses: actions/checkout@v2 86 | 87 | - name: Set up QEMU 88 | uses: docker/setup-qemu-action@v1 89 | 90 | - name: Set up Docker BuildX 91 | uses: docker/setup-buildx-action@v1 92 | with: 93 | version: v0.7.0 94 | install: true 95 | driver-opts: image=moby/buildkit:buildx-stable-1 96 | 97 | - name: Cache Docker layers 98 | uses: actions/cache@v2 99 | with: 100 | path: /tmp/.buildx-cache 101 | key: ${{ runner.os }}-buildx-${{ github.sha }} 102 | restore-keys: | 103 | ${{ runner.os }}-buildx- 104 | 105 | - name: Login to DockerHub 106 | uses: docker/login-action@v1 107 | with: 108 | username: ${{ secrets.DOCKERHUB_USERNAME }} 109 | password: ${{ secrets.DOCKERHUB_TOKEN }} 110 | 111 | - name: Set suffix to Docker tags 112 | uses: actions/github-script@v3 113 | id: suffix 114 | with: 115 | result-encoding: string 116 | script: "return (context.payload.release.tag_name.includes('-dev') ? '-dev' : '');" 117 | 118 | - name: Set Docker tags and labels 119 | id: docker_meta 120 | uses: docker/metadata-action@v3 121 | with: 122 | images: dashpay/insight-api 123 | tags: | 124 | type=match,pattern=v(\d+),group=1 125 | type=match,pattern=v(\d+.\d+),group=1 126 | type=match,pattern=v(\d+.\d+.\d+),group=1 127 | type=match,pattern=v(.*),group=1,suffix=,enable=${{ contains(github.event.release.tag_name, '-dev') }} 128 | flavor: | 129 | latest=${{ !contains(github.event.release.tag_name, '-dev') }} 130 | suffix=${{ steps.suffix.outputs.result }} 131 | 132 | - name: Build and push Docker image 133 | id: docker_build 134 | uses: docker/build-push-action@v2 135 | with: 136 | context: docker 137 | file: docker/Dockerfile 138 | push: true 139 | tags: ${{ steps.docker_meta.outputs.tags }} 140 | labels: ${{ steps.docker_meta.outputs.labels }} 141 | cache-from: type=local,src=/tmp/.buildx-cache 142 | cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new 143 | platforms: linux/amd64,linux/arm64,linux/arm/v7 144 | build-args: VERSION=${{ github.event.release.tag_name }} 145 | 146 | # https://github.com/docker/build-push-action/issues/252 147 | # https://github.com/moby/buildkit/issues/1896 148 | 149 | - name: Run temporary fix for Docker cache action 150 | run: | 151 | rm -rf /tmp/.buildx-cache 152 | mv /tmp/.buildx-cache-new /tmp/.buildx-cache 153 | 154 | - name: Output Docker image digest 155 | run: echo ${{ steps.docker_build.outputs.digest }} 156 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # from https://github.com/github/gitignore/blob/master/Node.gitignore 2 | lib-cov 3 | *.seed 4 | *.log 5 | *.csv 6 | *.dat 7 | *.out 8 | *.pid 9 | *.gz 10 | *.swp 11 | tags 12 | pids 13 | logs 14 | results 15 | build 16 | .idea 17 | 18 | node_modules 19 | 20 | # extras 21 | *.swp 22 | *.swo 23 | *~ 24 | .project 25 | peerdb.json 26 | 27 | npm-debug.log 28 | .nodemonignore 29 | 30 | .DS_Store 31 | db/txs/* 32 | db/txs 33 | db/testnet/txs/* 34 | db/testnet/txs 35 | db/blocks/* 36 | db/blocks 37 | db/testnet/blocks/* 38 | db/testnet/blocks 39 | 40 | README.html 41 | public 42 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.swp 10 | tags 11 | pids 12 | logs 13 | results 14 | build 15 | 16 | node_modules 17 | 18 | # extras 19 | *.swp 20 | *.swo 21 | *~ 22 | .project 23 | peerdb.json 24 | 25 | npm-debug.log 26 | .nodemonignore 27 | 28 | .DS_Store 29 | db/txs/* 30 | db/txs 31 | db/testnet/txs/* 32 | db/testnet/txs 33 | db/blocks/* 34 | db/blocks 35 | db/testnet/blocks/* 36 | db/testnet/blocks 37 | 38 | README.html 39 | k* 40 | public 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2017 Bitpay 4 | Copyright (c) 2016-2017 The Dash Evolution developers 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Insight-API 2 | 3 | [![Build Status](https://github.com/dashevo/insight-api/actions/workflows/test_and_release.yml/badge.svg)](https://github.com/dashevo/insight-api/actions/workflows/test_and_release.yml) 4 | [![NPM version](https://img.shields.io/npm/v/@dashevo/insight-api.svg)](https://npmjs.org/package/@dashevo/insight-api) 5 | [![API stability](https://img.shields.io/badge/stability-stable-green.svg)](https://nodejs.org/api/documentation.html#documentation_stability_index) 6 | 7 | A Dash blockchain REST and WebSocket API Service 8 | 9 | This is a backend-only service. If you're looking for the web frontend application, take a look at https://github.com/dashevo/insight-ui. 10 | 11 | ## Table of Content 12 | - [Install](#install) 13 | - [Prerequisites](#prerequisites) 14 | - [Query Rate Limit](#query-rate-limit) 15 | - [Usage](#usage) 16 | - [API HTTP Endpoints](#api-http-endpoints) 17 | - [Block](#block) 18 | - [Block Index](#block-index) 19 | - [Raw Block](#raw-block) 20 | - [Block Summaries](#block-summaries) 21 | - [Transaction](#transaction) 22 | - [Address](#address) 23 | - [Address Properties](#address-properties) 24 | - [Unspent Outputs](#unspent-outputs) 25 | - [Unspent Outputs for Multiple Addresses](#unspent-outputs-for-multiple-addresses) 26 | - [InstantSend Transactions](#instantsend-transactions) 27 | - [Transactions by Block](#transactions-by-block) 28 | - [Transactions by Address](#transactions-by-address) 29 | - [Transactions for Multiple Addresses](#transactions-for-multiple-addresses) 30 | - [Transaction Broadcasting](#transaction-broadcasting) 31 | - [Sporks List](#sporks-list) 32 | - [Proposals Informations](#proposals-informations) 33 | - [Proposals Count](#proposals-count) 34 | - [Budget Proposal List](#budget-proposal-list) 35 | - [Budget Triggers List](#budget-triggers-list) 36 | - [Budget Proposal Detail](#budget-proposal-detail) 37 | - [Proposal Check](#proposal-check) 38 | - [Proposal Deserialization](#proposal-deserialization) 39 | - [Proposal Current Votes](#proposal-current-votes) 40 | - [Governance Budget](#governance-budget) 41 | - [Masternodes List](#masternodes-list) 42 | - [Historic Blockchain Data Sync Status](#historic-blockchain-data-sync-status) 43 | - [Live Network P2P Data Sync Status](#live-network-p2p-data-sync-status) 44 | - [Status of the Dash Network](#status-of-the-dash-network) 45 | - [Utility Methods](#utility-methods) 46 | - [Web Socket Api](#web-socket-api) 47 | - [Example Usage](#example-usage) 48 | - [Notes on Upgrading from v0.3](#notes-on-upgrading-from-v03) 49 | - [Notes on Upgrading from v0.2](#notes-on-upgrading-from-v02) 50 | - [Resources](#resources) 51 | - [License](#license) 52 | 53 | ## Install 54 | 55 | ```bash 56 | npm install -g @dashevo/dashcore-node 57 | dashcore-node create mynode 58 | cd mynode 59 | dashcore-node install @dashevo/insight-api 60 | dashcore-node start # to also start the service 61 | ``` 62 | 63 | The API endpoints will be available by default at: `http://localhost:3001/insight-api/` 64 | 65 | ### Prerequisites 66 | 67 | - [Dashcore Node Dash 6.x](https://github.com/dashevo/dashcore-node) 68 | 69 | **Note:** You can use an existing Dash data directory, however `txindex`, `addressindex`, `timestampindex` and `spentindex` need to be enabled in `dash.conf`, as well as a few other additional fields. 70 | 71 | ### Query Rate Limit 72 | 73 | To protect the server, insight-api has a built-in query rate limiter. It can be configurable in `dashcore-node.json` with: 74 | 75 | ```json /*eslint-disable */ 76 | 77 | "servicesConfig": { 78 | "insight-api": { 79 | "rateLimiterOptions": { 80 | "whitelist": ["::ffff:127.0.0.1"] 81 | } 82 | } 83 | } 84 | ``` 85 | 86 | With all the configuration options available: https://github.com/dashevo/insight-api/blob/master/lib/ratelimiter.js#L10-17 87 | 88 | Or disabled entirely with: 89 | 90 | ```json /*eslint-disable */ 91 | "servicesConfig": { 92 | "insight-api": { 93 | "disableRateLimiter": true 94 | } 95 | } 96 | ``` 97 | 98 | ## Usage 99 | 100 | Follow the install instructions above, and ... 101 | 102 | ```bash 103 | dashcore-node start 104 | ``` 105 | 106 | This will start the Insight-API listening on default port 3001. 107 | 108 | ## API HTTP Endpoints 109 | 110 | ### Block 111 | 112 | ``` 113 | /insight-api/block/[:hash] 114 | /insight-api/block/0000000006e7b38e8ab2d351239019c01de9a148b5baef58cfe52dfd9917cedc 115 | ``` 116 | 117 | ### Block Index 118 | 119 | Get block hash by height 120 | 121 | ``` 122 | /insight-api/block-index/[:height] 123 | /insight-api/block-index/0 124 | ``` 125 | 126 | This would return: 127 | 128 | ``` 129 | { 130 | "blockHash":"00000bafbc94add76cb75e2ec92894837288a481e5c005f6563d91623bf8bc2c" 131 | } 132 | ``` 133 | 134 | which is the hash of the TestNet Genesis block (0 height) 135 | 136 | ### Raw Block 137 | 138 | ``` 139 | /insight-api/rawblock/[:blockHash] 140 | ``` 141 | 142 | This would return: 143 | 144 | ``` 145 | { 146 | "rawblock":"blockhexstring..." 147 | } 148 | ``` 149 | 150 | ### Block Summaries 151 | 152 | Get block summaries by date: 153 | 154 | ``` 155 | /insight-api/blocks?limit=3&blockDate=2017-04-22 156 | ``` 157 | 158 | Example response: 159 | 160 | ``` 161 | { 162 | "blocks": [ 163 | { 164 | "height": 188928, 165 | "size": 312, 166 | "hash": "00000000ee9a976cf459240c2add1147137ca6126b7906fa13ce3d80b5cadcc7", 167 | "time": 1492905418, 168 | "txlength": 1, 169 | "poolInfo": { 170 | "poolName": "BTCC Pool", 171 | "url": "https://pool.btcc.com/" 172 | } 173 | },{...},{...} 174 | ], 175 | "length": 3, 176 | "pagination": { 177 | "next":"2017-04-23", 178 | "prev":"2017-04-21", 179 | "currentTs":1492905599, 180 | "current":"2017-04-22", 181 | "isToday":false, 182 | "more":true, 183 | "moreTs":1492905600 184 | } 185 | } 186 | ``` 187 | 188 | ### Transaction 189 | 190 | ``` 191 | /insight-api/tx/[:txid] 192 | /insight-api/tx/ebdca263fe1c75c8609ce8fe3d82a320a0b3ca840f4df995883f5dab1b9ff8d9 193 | 194 | /insight-api/rawtx/[:rawid] 195 | /insight-api/rawtx/ebdca263fe1c75c8609ce8fe3d82a320a0b3ca840f4df995883f5dab1b9ff8d9 196 | ``` 197 | 198 | ### Address 199 | 200 | ``` 201 | /insight-api/addr/[:addr][?noTxList=1][&from=&to=] 202 | /insight-api/addr/ybi3gej7Ea1MysEYLR7UMs3rMuLJH5aVsW?noTxList=1 203 | /insight-api/addr/yPv7h2i8v3dJjfSH4L3x91JSJszjdbsJJA?from=1000&to=2000 204 | 205 | /insight-api/addrs/[:addrs][?noTxList=1][&from=&to=] 206 | /insight-api/addrs/ygwNQgE5f15Ygopbs2KPRYMS4TcffqBpsz,ygw5yCtVkx3hREke4L8qDqQtnNoAiPKTSx 207 | /insight-api/addrs/ygwNQgE5f15Ygopbs2KPRYMS4TcffqBpsz,ygw5yCtVkx3hREke4L8qDqQtnNoAiPKTSx?from=1000&to=2000 208 | ``` 209 | 210 | ### Address Properties 211 | 212 | ``` 213 | /insight-api/addr/[:addr]/balance 214 | /insight-api/addr/[:addr]/totalReceived 215 | /insight-api/addr/[:addr]/totalSent 216 | /insight-api/addr/[:addr]/unconfirmedBalance 217 | 218 | /insight-api/addrs/[:addrs]/balance 219 | /insight-api/addrs/[:addrs]/totalReceived 220 | /insight-api/addrs/[:addrs]/totalSent 221 | /insight-api/addrs/[:addrs]/unconfirmedBalance 222 | ``` 223 | 224 | The response contains the value in Satoshis. 225 | 226 | ### Unspent Outputs 227 | 228 | ``` 229 | /insight-api/addr/[:addr]/utxo 230 | ``` 231 | 232 | Sample return: 233 | 234 | ``` 235 | [ 236 | { 237 | "address":"ygwNQgE5f15Ygopbs2KPRYMS4TcffqBpsz", 238 | "txid":"05d70bc1c4cf1c3afefc3250480d733b5666b19cb1f629901ded82cb2d6263d1", 239 | "vout":0, 240 | "scriptPubKey":"76a914e22dc8acf5bb5624f4beef22fb2238f8479e183f88ac", 241 | "amount":0.01194595, 242 | "satoshis":1194595, 243 | "height":142204, 244 | "confirmations":124317 245 | },{...} 246 | ] 247 | ``` 248 | 249 | ### Unspent Outputs for Multiple Addresses 250 | 251 | GET method: 252 | 253 | ``` 254 | /insight-api/addrs/[:addrs]/utxo 255 | /insight-api/addrs/ygwNQgE5f15Ygopbs2KPRYMS4TcffqBpsz,ygw5yCtVkx3hREke4L8qDqQtnNoAiPKTSx/utxo 256 | ``` 257 | 258 | POST method: 259 | 260 | ``` 261 | /insight-api/addrs/utxo 262 | ``` 263 | 264 | POST params: 265 | 266 | ``` 267 | addrs: ygwNQgE5f15Ygopbs2KPRYMS4TcffqBpsz,ygw5yCtVkx3hREke4L8qDqQtnNoAiPKTSx 268 | ``` 269 | 270 | ### InstantSend Transactions 271 | 272 | If a Transaction Lock has been observed by Insight API a 'txlock' value of true will be included in the Transaction Object. 273 | 274 | Sample output: 275 | 276 | ``` 277 | { 278 | "txid": "b7ef92d1dce458276f1189e06bf532eff78f9c504101d3d4c0dfdcd9ebbf3879", 279 | "version": 1, 280 | "locktime": 133366, 281 | "vin": [{ ... }], 282 | "vout": [{ ... }], 283 | "blockhash": "0000001ab9a138339fe4505a299525ace8cda3b9bcb258a2e5d93ed7a320bf21", 284 | "blockheight": 133367, 285 | "confirmations": 37, 286 | "time": 1483985187, 287 | "blocktime": 1483985187, 288 | "valueOut": 8.998, 289 | "size": 226, 290 | "valueIn": 8.999, 291 | "fees": 0.001, 292 | "txlock": true 293 | } 294 | ``` 295 | 296 | ### Transactions by Block 297 | 298 | ``` 299 | /insight-api/txs/?block=HASH 300 | /insight-api/txs/?block=000000000814dd7cf470bd835334ea6624ebf0291ea857a5ab37c65592726375 301 | ``` 302 | 303 | ### Transactions by Address 304 | 305 | ``` 306 | /insight-api/txs/?address=ADDR 307 | /insight-api/txs/?address=yWFfdp9nLUjy1kJczFhRuBMUjtTkTTiyMv 308 | ``` 309 | 310 | ### Transactions for Multiple Addresses 311 | 312 | GET method: 313 | 314 | ``` 315 | /insight-api/addrs/[:addrs]/txs[?from=&to=] 316 | /insight-api/addrs/ygwNQgE5f15Ygopbs2KPRYMS4TcffqBpsz,ygw5yCtVkx3hREke4L8qDqQtnNoAiPKTSx/txs?from=0&to=20 317 | ``` 318 | 319 | POST method: 320 | 321 | ``` 322 | /insight-api/addrs/txs 323 | ``` 324 | 325 | POST params: 326 | 327 | ``` 328 | addrs: ygwNQgE5f15Ygopbs2KPRYMS4TcffqBpsz,ygw5yCtVkx3hREke4L8qDqQtnNoAiPKTSx 329 | from (optional): 0 330 | to (optional): 20 331 | noAsm (optional): 1 (will omit script asm from results) 332 | noScriptSig (optional): 1 (will omit the scriptSig from all inputs) 333 | noSpent (option): 1 (will omit spent information per output) 334 | ``` 335 | 336 | Sample output: 337 | 338 | ``` 339 | { totalItems: 100, 340 | from: 0, 341 | to: 20, 342 | items: 343 | [ { txid: '3e81723d069b12983b2ef694c9782d32fca26cc978de744acbc32c3d3496e915', 344 | version: 1, 345 | locktime: 0, 346 | vin: [Object], 347 | vout: [Object], 348 | blockhash: '00000000011a135e5277f5493c52c66829792392632b8b65429cf07ad3c47a6c', 349 | confirmations: 109367, 350 | time: 1393659685, 351 | blocktime: 1393659685, 352 | valueOut: 0.3453, 353 | size: 225, 354 | firstSeenTs: undefined, 355 | valueIn: 0.3454, 356 | fees: 0.0001, 357 | txlock: false }, 358 | { ... }, 359 | { ... }, 360 | ... 361 | { ... } 362 | ] 363 | } 364 | ``` 365 | 366 | Note: if pagination params are not specified, the result is an array of transactions. 367 | 368 | ### Transaction Broadcasting 369 | 370 | #### Standard transaction 371 | 372 | POST method: 373 | 374 | ``` 375 | /insight-api/tx/send 376 | ``` 377 | 378 | POST params: 379 | 380 | ``` 381 | rawtx: "signed transaction as hex string" 382 | 383 | eg 384 | 385 | rawtx: 01000000017b1eabe0209b1fe794124575ef807057c77ada2138ae4fa8d6c4de0398a14f3f00000000494830450221008949f0cb400094ad2b5eb399d59d01c14d73d8fe6e96df1a7150deb388ab8935022079656090d7f6bac4c9a94e0aad311a4268e082a725f8aeae0573fb12ff866a5f01ffffffff01f0ca052a010000001976a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac00000000 386 | ``` 387 | 388 | POST response: 389 | 390 | ``` 391 | { 392 | txid: [:txid] 393 | } 394 | 395 | eg 396 | 397 | { 398 | txid: "c7736a0a0046d5a8cc61c8c3c2821d4d7517f5de2bc66a966011aaa79965ffba" 399 | } 400 | ``` 401 | 402 | #### InstantSend transaction 403 | 404 | Conditions : 405 | * Every inputs should have 6 confirmations. 406 | * Fee are 0.001 per input. 407 | * Transaction value should be below SPORK_5_INSTANTSEND_MAX_VALUE (see spork route) 408 | 409 | POST method: 410 | 411 | ``` 412 | /insight-api/tx/sendix 413 | ``` 414 | 415 | POST params: 416 | 417 | ``` 418 | rawtx: "signed transaction as hex string" 419 | ``` 420 | 421 | POST response: 422 | 423 | ``` 424 | { 425 | txid: [:txid] 426 | } 427 | ``` 428 | 429 | ### Sporks List 430 | 431 | GET method: 432 | 433 | ``` 434 | /insight-api/sporks 435 | ``` 436 | 437 | Sample output: 438 | 439 | ``` 440 | { 441 | "sporks": { 442 | "SPORK_2_INSTANTSEND_ENABLED":0, 443 | "SPORK_3_INSTANTSEND_BLOCK_FILTERING":0, 444 | "SPORK_5_INSTANTSEND_MAX_VALUE":2000, 445 | "SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT":0, 446 | "SPORK_9_SUPERBLOCKS_ENABLED":0, 447 | "SPORK_10_MASTERNODE_PAY_UPDATED_NODES":0, 448 | "SPORK_12_RECONSIDER_BLOCKS":0, 449 | "SPORK_13_OLD_SUPERBLOCK_FLAG":4070908800, 450 | "SPORK_14_REQUIRE_SENTINEL_FLAG":4070908800 451 | } 452 | } 453 | ``` 454 | 455 | ### Proposals Informations 456 | 457 | GET method: 458 | 459 | ``` 460 | /insight-api/gobject/info 461 | ``` 462 | 463 | Sample output: 464 | 465 | ``` 466 | { 467 | "result":{ 468 | "governanceminquorum":1, 469 | "masternodewatchdogmaxseconds":7200, 470 | "proposalfee":5, 471 | "superblockcycle":24, 472 | "lastsuperblock":79800, 473 | "nextsuperblock":79824, 474 | "maxgovobjdatasize":16384 475 | }, 476 | "error":null, 477 | "id":68537 478 | } 479 | ``` 480 | 481 | ### Proposals Count 482 | 483 | GET method: 484 | 485 | ``` 486 | /insight-api/gobject/count 487 | ``` 488 | 489 | Sample output: 490 | 491 | ``` 492 | { 493 | "result":"Governance Objects: 47 (Proposals: 7, Triggers: 40, Watchdogs: 0/0, Other: 0; Erased: 0), Votes: 1883", 494 | "error":null, 495 | "id":47025 496 | } 497 | ``` 498 | 499 | ### Budget Proposal List 500 | 501 | GET method: 502 | 503 | ``` 504 | /insight-api/gobject/list/proposal (or /insight-api/gobject/list) 505 | ``` 506 | 507 | Sample output: 508 | 509 | ``` 510 | [ 511 | { 512 | Hash: 'b6af3e70c686f660541a77bc035df2e5e46841020699ce3ec8fad786f7d1aa35', 513 | DataObject: { 514 | end_epoch: 1513555200, 515 | name: 'flare03', 516 | payment_address: 'yViyoK3NwfH5GXRo7e4DEYkzzhBjDNQaQG', 517 | payment_amount: 5, 518 | start_epoch: 1482105600, 519 | type: 1, 520 | url: 'https://www.dash.org' 521 | }, 522 | AbsoluteYesCount: 40, 523 | YesCount: 40, 524 | NoCount: 0, 525 | AbstainCount: 0 526 | } 527 | ] 528 | ``` 529 | 530 | ### Budget Triggers List 531 | 532 | GET method: 533 | 534 | ``` 535 | /insight-api/gobject/list/trigger 536 | ``` 537 | 538 | Sample output: 539 | 540 | ``` 541 | [ 542 | { 543 | "Hash":"fa2a7505c52438b2ca3d14def1c2cdcb59d7ccca417920182f04fcb9be968f00", 544 | "DataObject":{"type":2}, 545 | "AbsoluteYesCount":53, 546 | "YesCount":53, 547 | "NoCount":0, 548 | "AbstainCount":0 549 | } 550 | ] 551 | ``` 552 | 553 | ### Budget Proposal Detail 554 | 555 | GET method: 556 | 557 | ``` 558 | /insight-api/gobject/get/[:hash] 559 | /insight-api/gobject/get/b6af3e70c686f660541a77bc035df2e5e46841020699ce3ec8fad786f7d1aa35 560 | ``` 561 | 562 | Sample output: 563 | 564 | ``` 565 | [ { Hash: 'b6af3e70c686f660541a77bc035df2e5e46841020699ce3ec8fad786f7d1aa35', 566 | CollateralHash: '24a71d8f221659717560365d2914bc7a00f82ffb8f8c68e7fffce5f35aa23b90', 567 | DataHex: '5b5b2270726f706f73616c222c7b22656e645f65706f6368223a313531333535353230302c226e616d65223a22666c6172653033222c227061796d656e745f61646472657373223a22795669796f4b334e776648354758526f3765344445596b7a7a68426a444e51615147222c227061796d656e745f616d6f756e74223a352c2273746172745f65706f6368223a313438323130353630302c2274797065223a312c2275726c223a2268747470733a2f2f64617368646f742e696f2f702f666c6172653033227d5d5d', 568 | DataObject: { 569 | end_epoch: 1513555200, 570 | name: 'flare03', 571 | payment_address: 'yViyoK3NwfH5GXRo7e4DEYkzzhBjDNQaQG', 572 | payment_amount: 5, 573 | start_epoch: 1482105600, 574 | type: 1, 575 | url: 'https://www.dash.org' 576 | }, 577 | CreationTime: 1482223714, 578 | FundingResult: { 579 | AbsoluteYesCount: 40, 580 | YesCount: 40, 581 | NoCount: 0, 582 | AbstainCount: 0 583 | }, 584 | ValidResult: { 585 | AbsoluteYesCount: 74, 586 | YesCount: 74, 587 | NoCount: 0, 588 | AbstainCount: 0 589 | }, 590 | DeleteResult: { 591 | AbsoluteYesCount: 0, 592 | YesCount: 0, 593 | NoCount: 0, 594 | AbstainCount: 0 595 | }, 596 | EndorsedResult: { 597 | AbsoluteYesCount: 0, 598 | YesCount: 0, 599 | NoCount: 0, 600 | AbstainCount: 0 601 | } } ] 602 | ``` 603 | 604 | ### Proposal Check 605 | 606 | GET method: 607 | 608 | ``` 609 | /insight-api/gobject/check/[:hexData] 610 | /insight-api/gobject/check/5b5b2270726f706f736[..] 611 | ``` 612 | 613 | Sample output: 614 | 615 | ``` 616 | {"Object status":"OK"} 617 | ``` 618 | 619 | ### Proposal Deserialization 620 | 621 | GET method: 622 | 623 | ``` 624 | /insight-api/gobject/deserialize/[:hexData] 625 | /insight-api/gobject/deserialize/5b5b2270726f706f736[..] 626 | ``` 627 | 628 | Sample output: 629 | 630 | ``` 631 | { 632 | "result":"[[\"proposal\",{\"end_epoch\":1519848619,\"name\":\"ghijklmnopqrstuvwxyz01234567891519097947\",\"payment_address\":\"yik5HAgVAgjH1oZKjcDfvcf22bwBNbSYzB\",\"payment_amount\":10,\"start_epoch\":1519097947,\"type\":1,\"url\":\"https://www.dashcentral.org/p/test_proposal_1519097947\"}]]", 633 | "error":null, 634 | "id":78637 635 | } 636 | ``` 637 | 638 | ### Proposal Current Votes 639 | 640 | GET method: 641 | 642 | ``` 643 | /insight-api/gobject/votes/current/[:hash] 644 | /insight-api/gobject/votes/current/fbda8cdc1f48917f53b7d63fbce81c85d6dedd3d0e476e979926dfd154b84034 645 | ``` 646 | 647 | Sample output: 648 | 649 | ``` 650 | { 651 | "result":"[[\"proposal\",{\"end_epoch\":1519848619,\"name\":\"ghijklmnopqrstuvwxyz01234567891519097947\",\"payment_address\":\"yik5HAgVAgjH1oZKjcDfvcf22bwBNbSYzB\",\"payment_amount\":10,\"start_epoch\":1519097947,\"type\":1,\"url\":\"https://www.dashcentral.org/p/test_proposal_1519097947\"}]]", 652 | "error":null, 653 | "id":78637 654 | } 655 | ``` 656 | 657 | ### Governance Budget 658 | 659 | GET method: 660 | 661 | ``` 662 | /insight-api/governance/budget/[:blockIndex] 663 | /insight-api/governance/budget/79872 664 | ``` 665 | 666 | Sample output: 667 | 668 | ``` 669 | { 670 | "result":"60.00", 671 | "error":null, 672 | "id":75619 673 | } 674 | ``` 675 | 676 | ### Submit Proposal 677 | 678 | POST method: 679 | 680 | ``` 681 | /insight-api/gobject/submit 682 | ``` 683 | 684 | Example input: 685 | 686 | ``` 687 | { 688 | "parentHash":"abc", 689 | "revision":1, 690 | "time":10009, 691 | "dataHex":"abc", 692 | "feeTxId":"abc" 693 | } 694 | ``` 695 | 696 | Sample output: 697 | 698 | ``` 699 | { 700 | "result":"60.00", 701 | "error":null, 702 | "id":75619 703 | } 704 | ``` 705 | 706 | ### Masternodes List 707 | 708 | ``` 709 | deprecated until full support for v0.13 deterministic masternode list 710 | ``` 711 | 712 | ### Validate Masternode 713 | 714 | ``` 715 | deprecated until full support for v0.13 deterministic masternode list 716 | ``` 717 | 718 | ### Historic Blockchain Data Sync Status 719 | 720 | ``` 721 | /insight-api/sync 722 | ``` 723 | 724 | ### Live Network P2P Data Sync Status 725 | 726 | ``` 727 | /insight-api/peer 728 | ``` 729 | 730 | ### Status of the Dash Network 731 | 732 | ``` 733 | /insight-api/status?q=xxx 734 | ``` 735 | 736 | Where "xxx" can be: 737 | 738 | * getInfo 739 | * getDifficulty 740 | * getBestBlockHash 741 | * getBestChainLock 742 | * getLastBlockHash 743 | 744 | ### Utility Methods 745 | 746 | ``` 747 | /insight-api/utils/estimatefee[?nbBlocks=2] 748 | ``` 749 | 750 | ## Web Socket API 751 | 752 | The web socket API is served using [socket.io](http://socket.io). 753 | 754 | The following are the events published by Insight: 755 | 756 | `tx`: new transaction received from network, txlock boolean is set true if a matching txlock event has been observed. This event is published in the 'inv' room. Data will be a app/models/Transaction object. 757 | 758 | Sample output: 759 | 760 | ``` 761 | { 762 | "txid":"00c1b1acb310b87085c7deaaeba478cef5dc9519fab87a4d943ecbb39bd5b053", 763 | "txlock": false, 764 | "processed":false 765 | ... 766 | } 767 | ``` 768 | 769 | `txlock`: InstantSend transaction received from network, this event is published alongside the 'tx' event when a transaction lock event occurs. Data will be a app/models/Transaction object. 770 | Sample output: 771 | 772 | ``` 773 | { 774 | "txid":"00c1b1acb310b87085c7deaaeba478cef5dc9519fab87a4d943ecbb39bd5b053", 775 | "processed":false 776 | ... 777 | } 778 | ``` 779 | 780 | `block`: new block received from network. This event is published in the `inv` room. Data will be a app/models/Block object. 781 | Sample output: 782 | 783 | ``` 784 | { 785 | "hash":"000000004a3d187c430cd6a5e988aca3b19e1f1d1727a50dead6c8ac26899b96", 786 | "time":1389789343, 787 | ... 788 | } 789 | ``` 790 | 791 | ``: new transaction concerning received from network. This event is published in the `` room. 792 | 793 | `status`: every 1% increment on the sync task, this event will be triggered. This event is published in the `sync` room. 794 | 795 | Sample output: 796 | 797 | ``` 798 | { 799 | blocksToSync: 164141, 800 | syncedBlocks: 475, 801 | upToExisting: true, 802 | scanningBackward: true, 803 | isEndGenesis: true, 804 | end: "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943", 805 | isStartGenesis: false, 806 | start: "000000009f929800556a8f3cfdbe57c187f2f679e351b12f7011bfc276c41b6d" 807 | } 808 | ``` 809 | 810 | ### Example Usage 811 | 812 | The following html page connects to the socket.io insight API and listens for new transactions. 813 | 814 | ```html 815 | 816 | 817 | 818 | 835 | 836 | 837 | ``` 838 | 839 | ## Notes on Upgrading from v0.3 840 | 841 | The unspent outputs format now has `satoshis` and `height`: 842 | 843 | ``` 844 | [ 845 | { 846 | "address":"mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs", 847 | "txid":"d5f8a96faccf79d4c087fa217627bb1120e83f8ea1a7d84b1de4277ead9bbac1", 848 | "vout":0, 849 | "scriptPubKey":"76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac", 850 | "amount":0.000006, 851 | "satoshis":600, 852 | "confirmations":0, 853 | "ts":1461349425 854 | }, 855 | { 856 | "address": "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs", 857 | "txid": "bc9df3b92120feaee4edc80963d8ed59d6a78ea0defef3ec3cb374f2015bfc6e", 858 | "vout": 1, 859 | "scriptPubKey": "76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac", 860 | "amount": 0.12345678, 861 | "satoshis: 12345678, 862 | "confirmations": 1, 863 | "height": 300001 864 | } 865 | ] 866 | ``` 867 | 868 | The `timestamp` property will only be set for unconfirmed transactions and `height` can be used for determining block order. The `confirmationsFromCache` is nolonger set or necessary, confirmation count is only cached for the time between blocks. 869 | 870 | There is a new `GET` endpoint or raw blocks at `/rawblock/`: 871 | 872 | Response format: 873 | 874 | ``` 875 | { 876 | "rawblock": "blockhexstring..." 877 | } 878 | ``` 879 | 880 | There are a few changes to the `GET` endpoint for `/addr/[:address]`: 881 | 882 | - The list of txids in an address summary does not include orphaned transactions 883 | - The txids will be sorted in block order 884 | - The list of txids will be limited at 1000 txids 885 | - There are two new query options "from" and "to" for pagination of the txids (e.g. `/addr/[:address]?from=1000&to=2000`) 886 | 887 | Some additional general notes: 888 | - The transaction history for an address will be sorted in block order 889 | - The response for the `/sync` endpoint does not include `startTs` and `endTs` as the sync is no longer relevant as indexes are built in dashd. 890 | - The endpoint for `/peer` is no longer relevant connection to dashd is via ZMQ. 891 | - `/tx` endpoint results will now include block height, and spentTx related fields will be set to `null` if unspent. 892 | - `/block` endpoint results does not include `confirmations` and will include `poolInfo`. 893 | 894 | ## Notes on Upgrading from v0.2 895 | 896 | Some of the fields and methods are not supported: 897 | 898 | The `/tx/` endpoint JSON response will not include the following fields on the "vin" 899 | object: 900 | - `doubleSpentTxId` // double spends are not currently tracked 901 | - `isConfirmed` // confirmation of the previous output 902 | - `confirmations` // confirmations of the previous output 903 | - `unconfirmedInput` 904 | 905 | The `/tx/` endpoint JSON response will not include the following fields on the "vout" 906 | object. 907 | - `spentTs` 908 | 909 | The `/status?q=getTxOutSetInfo` method has also been removed due to the query being very slow and locking dashd. 910 | 911 | Plug-in support for Insight API is also no longer available, as well as the endpoints: 912 | - `/email/retrieve` 913 | - `/rates/:code` 914 | 915 | Caching support has not yet been added in the v0.3 upgrade. 916 | 917 | ## Resources 918 | 919 | - (Medium)[How to setup a Dash Instant-Send Transaction using Insight API - The comprehensive way](https://medium.com/@obusco/setup-instant-send-transaction-the-comprehensive-way-a80a8a0572e) 920 | 921 | ## Contributing 922 | 923 | Feel free to dive in! [Open an issue](https://github.com/dashevo/insight-api/issues/new) or submit PRs. 924 | 925 | ## License 926 | 927 | [MIT](LICENSE) © Dash Core Group, Inc. 928 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine 2 | 3 | RUN apk add --update --no-cache \ 4 | git \ 5 | curl \ 6 | libzmq \ 7 | zeromq-dev \ 8 | python3 \ 9 | make \ 10 | cmake \ 11 | g++ 12 | 13 | WORKDIR /insight 14 | 15 | # Copy dashcore-node 16 | RUN git clone --branch master --single-branch --depth 1 https://github.com/dashevo/dashcore-node.git . 17 | 18 | # Copy config file 19 | COPY dashcore-node.json . 20 | 21 | ARG VERSION 22 | 23 | # Install npm packages 24 | RUN npm ci 25 | 26 | # Install Insight API module 27 | RUN bin/dashcore-node install @dashevo/insight-api@${VERSION} 28 | 29 | FROM node:16-alpine 30 | 31 | LABEL maintainer="Dash Developers " 32 | LABEL description="Dockerised Insight API" 33 | 34 | WORKDIR /insight 35 | 36 | # Copy project files 37 | COPY --from=0 /insight/ . 38 | 39 | EXPOSE 3001 40 | 41 | CMD ["bin/dashcore-node", "start"] 42 | -------------------------------------------------------------------------------- /docker/dashcore-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "network": "livenet", 3 | "port": 3001, 4 | "services": [ 5 | "dashd", 6 | "web", 7 | "@dashevo/insight-api" 8 | ], 9 | "servicesConfig": { 10 | "dashd": { 11 | "connect": [{ 12 | "rpchost": "127.0.0.1", 13 | "rpcport": 9998, 14 | "rpcuser": "dashrpc", 15 | "rpcpassword": "password", 16 | "zmqpubrawtx": "tcp://127.0.0.1:29998", 17 | "zmqpubhashblock": "tcp://127.0.0.1:29998" 18 | }] 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/addresses.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var dashcore = require('@dashevo/dashcore-lib'); 4 | var async = require('async'); 5 | var TxController = require('./transactions'); 6 | var Common = require('./common'); 7 | 8 | function AddressController(node) { 9 | this.node = node; 10 | this.txController = new TxController(node); 11 | this.common = new Common({log: this.node.log}); 12 | } 13 | 14 | AddressController.prototype.show = function(req, res) { 15 | var self = this; 16 | var options = { 17 | noTxList: parseInt(req.query.noTxList) 18 | }; 19 | 20 | if (req.query.from && req.query.to) { 21 | options.from = parseInt(req.query.from); 22 | options.to = parseInt(req.query.to); 23 | } 24 | var addresses = req.addr ? req.addr : req.addrs; 25 | 26 | this.getAddressSummary(addresses, options, function(err, data) { 27 | if(err) { 28 | return self.common.handleErrors(err, res); 29 | } 30 | 31 | res.jsonp(data); 32 | }); 33 | }; 34 | 35 | AddressController.prototype.balance = function(req, res) { 36 | this.addressSummarySubQuery(req, res, 'balanceSat'); 37 | }; 38 | 39 | AddressController.prototype.totalReceived = function(req, res) { 40 | this.addressSummarySubQuery(req, res, 'totalReceivedSat'); 41 | }; 42 | 43 | AddressController.prototype.totalSent = function(req, res) { 44 | this.addressSummarySubQuery(req, res, 'totalSentSat'); 45 | }; 46 | 47 | AddressController.prototype.unconfirmedBalance = function(req, res) { 48 | this.addressSummarySubQuery(req, res, 'unconfirmedBalanceSat'); 49 | }; 50 | 51 | AddressController.prototype.addressSummarySubQuery = function(req, res, param) { 52 | var self = this; 53 | var addresses = req.addr ? req.addr : req.addrs; 54 | this.getAddressSummary(addresses, {}, function(err, data) { 55 | if(err) { 56 | return self.common.handleErrors(err, res); 57 | } 58 | 59 | res.jsonp(data[param]); 60 | }); 61 | }; 62 | 63 | AddressController.prototype.getAddressSummary = function(address, options, callback) { 64 | 65 | this.node.getAddressSummary(address, options, function(err, summary) { 66 | if(err) { 67 | return callback(err); 68 | } 69 | 70 | var transformed = { 71 | addrStr: address, 72 | balance: summary.balance / 1e8, 73 | balanceSat: summary.balance, 74 | totalReceived: summary.totalReceived / 1e8, 75 | totalReceivedSat: summary.totalReceived, 76 | totalSent: summary.totalSpent / 1e8, 77 | totalSentSat: summary.totalSpent, 78 | unconfirmedBalance: summary.unconfirmedBalance / 1e8, 79 | unconfirmedBalanceSat: summary.unconfirmedBalance, 80 | unconfirmedTxApperances: summary.unconfirmedAppearances, // will be deprecated in a future update 81 | unconfirmedAppearances: summary.unconfirmedAppearances, 82 | txApperances: summary.appearances, // will be deprecated in a future update 83 | txAppearances: summary.appearances, 84 | transactions: summary.txids 85 | }; 86 | 87 | callback(null, transformed); 88 | }); 89 | }; 90 | 91 | AddressController.prototype.checkAddr = function(req, res, next) { 92 | req.addr = req.params.addr; 93 | this.check(req, res, next, [req.addr]); 94 | }; 95 | 96 | AddressController.prototype.checkAddrs = function(req, res, next) { 97 | if(req.body.addrs) { 98 | req.addrs = req.body.addrs.split(','); 99 | } else { 100 | req.addrs = req.params.addrs.split(','); 101 | } 102 | 103 | this.check(req, res, next, req.addrs); 104 | }; 105 | 106 | AddressController.prototype.check = function(req, res, next, addresses) { 107 | var self = this; 108 | if(!addresses.length || !addresses[0]) { 109 | return self.common.handleErrors({ 110 | message: 'Must include address', 111 | code: 1 112 | }, res); 113 | } 114 | 115 | for(var i = 0; i < addresses.length; i++) { 116 | try { 117 | var a = new dashcore.Address(addresses[i]); 118 | } catch(e) { 119 | return self.common.handleErrors({ 120 | message: 'Invalid address: ' + e.message, 121 | code: 1 122 | }, res); 123 | } 124 | } 125 | 126 | next(); 127 | }; 128 | 129 | AddressController.prototype.utxo = function(req, res) { 130 | var self = this; 131 | 132 | this.node.getAddressUnspentOutputs(req.addr, {}, function(err, utxos) { 133 | if(err) { 134 | return self.common.handleErrors(err, res); 135 | } else if (!utxos.length) { 136 | return res.jsonp([]); 137 | } 138 | res.jsonp(utxos.map(self.transformUtxo.bind(self))); 139 | }); 140 | }; 141 | 142 | AddressController.prototype.multiutxo = function(req, res) { 143 | var self = this; 144 | this.node.getAddressUnspentOutputs(req.addrs, true, function(err, utxos) { 145 | if(err && err.code === -5) { 146 | return res.jsonp([]); 147 | } else if(err) { 148 | return self.common.handleErrors(err, res); 149 | } 150 | 151 | res.jsonp(utxos.map(self.transformUtxo.bind(self))); 152 | }); 153 | }; 154 | 155 | AddressController.prototype.transformUtxo = function(utxoArg) { 156 | var utxo = { 157 | address: utxoArg.address, 158 | txid: utxoArg.txid, 159 | vout: utxoArg.outputIndex, 160 | scriptPubKey: utxoArg.script, 161 | amount: utxoArg.satoshis / 1e8, 162 | satoshis: utxoArg.satoshis 163 | }; 164 | if (utxoArg.height && utxoArg.height > 0) { 165 | utxo.height = utxoArg.height; 166 | utxo.confirmations = this.node.services.dashd.height - utxoArg.height + 1; 167 | } else { 168 | utxo.confirmations = 0; 169 | } 170 | if (utxoArg.timestamp) { 171 | utxo.ts = utxoArg.timestamp; 172 | } 173 | return utxo; 174 | }; 175 | 176 | AddressController.prototype._getTransformOptions = function(req) { 177 | return { 178 | noAsm: parseInt(req.query.noAsm) ? true : false, 179 | noScriptSig: parseInt(req.query.noScriptSig) ? true : false, 180 | noSpent: parseInt(req.query.noSpent) ? true : false 181 | }; 182 | }; 183 | 184 | AddressController.prototype.multitxs = function(req, res, next) { 185 | var self = this; 186 | 187 | var options = { 188 | from: parseInt(req.query.from) || parseInt(req.body.from) || 0 189 | }; 190 | 191 | options.to = parseInt(req.query.to) || parseInt(req.body.to) || parseInt(options.from) + 10; 192 | options.fromHeight = parseInt(req.query.fromHeight) || parseInt(req.body.fromHeight) || undefined; 193 | options.toHeight = parseInt(req.query.toHeight) || parseInt(req.body.toHeight) || undefined; 194 | 195 | self.node.getAddressHistory(req.addrs, options, function(err, result) { 196 | if(err) { 197 | return self.common.handleErrors(err, res); 198 | } 199 | 200 | var transformOptions = self._getTransformOptions(req); 201 | 202 | self.transformAddressHistoryForMultiTxs(result.items, transformOptions, function(err, items) { 203 | if (err) { 204 | return self.common.handleErrors(err, res); 205 | } 206 | const txHistory = { 207 | totalItems: result.totalCount, 208 | from: options.from, 209 | to: Math.min(options.to, result.totalCount) 210 | }; 211 | if (options.fromHeight !== undefined){ 212 | txHistory.fromHeight = options.fromHeight; 213 | } 214 | if (options.toHeight !== undefined){ 215 | txHistory.toHeight = options.toHeight; 216 | } 217 | txHistory.items = items; 218 | res.jsonp(txHistory); 219 | }); 220 | 221 | }); 222 | }; 223 | 224 | AddressController.prototype.transformAddressHistoryForMultiTxs = function(txinfos, options, callback) { 225 | var self = this; 226 | 227 | var items = txinfos.map(function(txinfo) { 228 | return txinfo.tx; 229 | }).filter(function(value, index, self) { 230 | return self.indexOf(value) === index; 231 | }); 232 | 233 | async.map( 234 | items, 235 | function(item, next) { 236 | self.txController.transformTransaction(item, options, next); 237 | }, 238 | callback 239 | ); 240 | }; 241 | 242 | AddressController.prototype.paginatedUtxo = function(req, res, next) { 243 | var self = this; 244 | 245 | var options = { 246 | from: parseInt(req.query.from) || parseInt(req.body.from) || 0 247 | }; 248 | 249 | options.to = parseInt(req.query.to) || parseInt(req.body.to) || parseInt(options.from) + 1000; 250 | options.fromHeight = parseInt(req.query.fromHeight) || parseInt(req.body.fromHeight) || undefined; 251 | options.toHeight = parseInt(req.query.toHeight) || parseInt(req.body.toHeight) || undefined; 252 | 253 | if (options.from > options.to) { 254 | return self.common.handleErrors({ 255 | message: 'fromArg higher than toArg', 256 | code: 2 257 | }, res); 258 | } 259 | if ((options.to - options.from) > 1000) { 260 | return self.common.handleErrors({ 261 | message: 'range exceeds max of 1000', 262 | code: 3 263 | }, res); 264 | } 265 | 266 | self.node.getAddressUnspentOutputsPaginated(req.addrs, options, function(err, utxos) { 267 | if(err) { 268 | return self.common.handleErrors(err, res); 269 | } 270 | if (!utxos) { 271 | return self.common.handleErrors({ 272 | message: 'utxos object is null or undefined', 273 | code: 4 274 | }, res); 275 | } 276 | const result = { 277 | totalItems: utxos.totalCount, 278 | from: options.from, 279 | to: Math.min(options.to, utxos.totalCount) 280 | }; 281 | if (options.fromHeight !== undefined){ 282 | result.fromHeight = options.fromHeight; 283 | } 284 | if (options.toHeight !== undefined){ 285 | result.toHeight = options.toHeight; 286 | } 287 | result.items = utxos.items; 288 | res.jsonp(result); 289 | }); 290 | }; 291 | 292 | module.exports = AddressController; 293 | -------------------------------------------------------------------------------- /lib/blocks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var async = require('async'); 4 | var dashcore = require('@dashevo/dashcore-lib'); 5 | var _ = dashcore.deps._; 6 | var pools = require('../pools.json'); 7 | var BN = dashcore.crypto.BN; 8 | var LRU = require('lru-cache'); 9 | var Common = require('./common'); 10 | 11 | function BlockController(options) { 12 | var self = this; 13 | this.node = options.node; 14 | 15 | this.blockSummaryCache = new LRU(options.blockSummaryCacheSize || BlockController.DEFAULT_BLOCKSUMMARY_CACHE_SIZE); 16 | this.blockCacheConfirmations = 6; 17 | this.blockCache = new LRU(options.blockCacheSize || BlockController.DEFAULT_BLOCK_CACHE_SIZE); 18 | 19 | this.poolStrings = {}; 20 | pools.forEach(function(pool) { 21 | pool.searchStrings.forEach(function(s) { 22 | self.poolStrings[s] = { 23 | poolName: pool.poolName, 24 | url: pool.url 25 | }; 26 | }); 27 | }); 28 | 29 | this.common = new Common({log: this.node.log}); 30 | } 31 | 32 | var BLOCK_LIMIT = 200; 33 | 34 | BlockController.DEFAULT_BLOCKSUMMARY_CACHE_SIZE = 1000000; 35 | BlockController.DEFAULT_BLOCK_CACHE_SIZE = 1000; 36 | 37 | function isHexadecimal(hash) { 38 | if (!_.isString(hash)) { 39 | return false; 40 | } 41 | return /^[0-9a-fA-F]+$/.test(hash); 42 | } 43 | 44 | BlockController.prototype.checkBlockHash = function(req, res, next) { 45 | var self = this; 46 | var hash = req.params.blockHash; 47 | if (hash.length < 64 || !isHexadecimal(hash)) { 48 | this.node.services.dashd.getBlockHeader(parseInt(hash), function(err, info) { 49 | if (err) { 50 | return self.common.handleErrors(err, res); 51 | } 52 | req.params.blockHash = info.hash; 53 | next(); 54 | }); 55 | } else { 56 | next(); 57 | } 58 | }; 59 | 60 | /** 61 | * Find block header by hash ... 62 | */ 63 | BlockController.prototype.blockHeader = function(req, res, next) { 64 | var self = this; 65 | var hash = req.params.blockHash; 66 | 67 | self.node.services.dashd.getBlockHeader(hash, function(err, info) { 68 | if (err) { 69 | return self.common.handleErrors(err, res); 70 | } 71 | req.blockHeader = info; 72 | next(); 73 | }); 74 | }; 75 | 76 | /** 77 | * Retrieve an array of blockHeaders from a starting hash point 78 | * By default (i.e no nbOfBlock specified) it will return only 25 blocks 79 | */ 80 | BlockController.prototype.blockHeaders = function(req, res, next) { 81 | var self = this; 82 | var blockIdentifier = req.params.blockIdentifier; 83 | var nbOfBlockToFetch = ((req.params.hasOwnProperty('nbOfBlock')) ? (parseInt(req.params.nbOfBlock)>0 && parseInt(req.params.nbOfBlock) || false) : false) || 25; 84 | var cb = function(err, headers) { 85 | if (err) { 86 | return self.common.handleErrors(err, res); 87 | } 88 | else{ 89 | res.jsonp({headers:headers}); 90 | } 91 | }; 92 | 93 | if(blockIdentifier.length===64){ 94 | var hash = blockIdentifier; 95 | var result = self.node.services.dashd.getBlockHeaders(hash, cb, nbOfBlockToFetch); 96 | }else{ 97 | var height = blockIdentifier; 98 | var result = self.node.services.dashd.getBlockHeaders(height, cb, nbOfBlockToFetch); 99 | } 100 | 101 | }; 102 | 103 | /** 104 | * Find block by hash ... 105 | */ 106 | BlockController.prototype.block = function(req, res, next) { 107 | var self = this; 108 | var hash = req.params.blockHash; 109 | var blockCached = self.blockCache.get(hash); 110 | 111 | if (blockCached) { 112 | blockCached.confirmations = self.node.services.dashd.height - blockCached.height + 1; 113 | req.block = blockCached; 114 | next(); 115 | } else { 116 | self.node.getBlock(hash, function(err, block) { 117 | if((err && err.code === -5) || (err && err.code === -8)) { 118 | return self.common.handleErrors(null, res); 119 | } else if(err) { 120 | return self.common.handleErrors(err, res); 121 | } 122 | self.node.services.dashd.getBlockHeader(hash, function(err, info) { 123 | if (err) { 124 | return self.common.handleErrors(err, res); 125 | } 126 | var blockResult = self.transformBlock(block, info); 127 | self.getBlockReward(blockResult.previousblockhash, function(err,reward) { 128 | if (err) { 129 | return self.common.handleErrors(err, res); 130 | } 131 | blockResult.reward = reward; 132 | 133 | if (blockResult.confirmations >= self.blockCacheConfirmations) { 134 | self.blockCache.set(hash, blockResult); 135 | } 136 | req.block = blockResult; 137 | next(); 138 | }); 139 | }); 140 | }); 141 | } 142 | }; 143 | 144 | /** 145 | * Find rawblock by hash and height... 146 | */ 147 | BlockController.prototype.rawBlock = function(req, res, next) { 148 | var self = this; 149 | var blockHash = req.params.blockHash; 150 | 151 | self.node.getRawBlock(blockHash, function(err, blockBuffer) { 152 | if((err && err.code === -5) || (err && err.code === -8)) { 153 | return self.common.handleErrors(null, res); 154 | } else if(err) { 155 | return self.common.handleErrors(err, res); 156 | } 157 | req.rawBlock = { 158 | rawblock: blockBuffer.toString('hex') 159 | }; 160 | next(); 161 | }); 162 | 163 | }; 164 | 165 | BlockController.prototype._normalizePrevHash = function(hash) { 166 | // TODO fix dashcore to give back null instead of null hash 167 | if (hash !== '0000000000000000000000000000000000000000000000000000000000000000') { 168 | return hash; 169 | } else { 170 | return null; 171 | } 172 | }; 173 | 174 | BlockController.prototype.transformBlock = function(block, info) { 175 | var blockObj = block.toObject(); 176 | var transactionIds = blockObj.transactions.map(function(tx) { 177 | return tx.hash; 178 | }); 179 | return { 180 | hash: block.hash, 181 | size: block.toBuffer().length, 182 | height: info.height, 183 | version: blockObj.header.version, 184 | merkleroot: blockObj.header.merkleRoot, 185 | tx: transactionIds, 186 | time: blockObj.header.time, 187 | nonce: blockObj.header.nonce, 188 | bits: blockObj.header.bits.toString(16), 189 | difficulty: parseFloat(info.difficulty), 190 | chainwork: info.chainWork, 191 | confirmations: info.confirmations, 192 | previousblockhash: this._normalizePrevHash(blockObj.header.prevHash), 193 | nextblockhash: info.nextHash, 194 | reward: null, 195 | isMainChain: (info.confirmations !== -1), 196 | poolInfo: this.getPoolInfo(block) 197 | }; 198 | }; 199 | 200 | /** 201 | * Show block 202 | */ 203 | BlockController.prototype.show = function(req, res) { 204 | if (req.block) { 205 | res.jsonp(req.block); 206 | } 207 | }; 208 | 209 | BlockController.prototype.showRaw = function(req, res) { 210 | if (req.rawBlock) { 211 | res.jsonp(req.rawBlock); 212 | } 213 | }; 214 | 215 | BlockController.prototype.blockIndex = function(req, res) { 216 | var self = this; 217 | var height = req.params.height; 218 | this.node.services.dashd.getBlockHeader(parseInt(height), function(err, info) { 219 | if (err) { 220 | return self.common.handleErrors(err, res); 221 | } 222 | res.jsonp({ 223 | blockHash: info.hash 224 | }); 225 | }); 226 | }; 227 | 228 | BlockController.prototype._getBlockSummary = function(hash, moreTimestamp, next) { 229 | var self = this; 230 | 231 | function finish(result) { 232 | if (moreTimestamp > result.time) { 233 | moreTimestamp = result.time; 234 | } 235 | return next(null, result); 236 | } 237 | 238 | var summaryCache = self.blockSummaryCache.get(hash); 239 | 240 | if (summaryCache) { 241 | finish(summaryCache); 242 | } else { 243 | self.node.services.dashd.getRawBlock(hash, function(err, blockBuffer) { 244 | if (err) { 245 | return next(err); 246 | } 247 | 248 | var br = new dashcore.encoding.BufferReader(blockBuffer); 249 | 250 | // take a shortcut to get number of transactions and the blocksize. 251 | // Also reads the coinbase transaction and only that. 252 | // Old code parsed all transactions in every block _and_ then encoded 253 | // them all back together to get the binary size of the block. 254 | // FIXME: This code might still read the whole block. Fixing that 255 | // would require changes in dashcore-node. 256 | var header = dashcore.BlockHeader.fromBufferReader(br); 257 | var info = {}; 258 | var txlength = br.readVarintNum(); 259 | info.transactions = [dashcore.Transaction().fromBufferReader(br)]; 260 | 261 | self.node.services.dashd.getBlockHeader(hash, function(err, blockHeader) { 262 | if (err) { 263 | return next(err); 264 | } 265 | var height = blockHeader.height; 266 | 267 | var summary = { 268 | height: height, 269 | size: blockBuffer.length, 270 | hash: hash, 271 | time: header.time, 272 | txlength: txlength, 273 | poolInfo: self.getPoolInfo(info) 274 | }; 275 | 276 | var confirmations = self.node.services.dashd.height - height + 1; 277 | if (confirmations >= self.blockCacheConfirmations) { 278 | self.blockSummaryCache.set(hash, summary); 279 | } 280 | 281 | finish(summary); 282 | }); 283 | }); 284 | 285 | } 286 | }; 287 | 288 | // List blocks by date 289 | BlockController.prototype.list = function(req, res) { 290 | var self = this; 291 | 292 | var dateStr; 293 | var todayStr = this.formatTimestamp(new Date()); 294 | var isToday; 295 | 296 | if (req.query.blockDate) { 297 | dateStr = req.query.blockDate; 298 | var datePattern = /\d{4}-\d{2}-\d{2}/; 299 | if(!datePattern.test(dateStr)) { 300 | return self.common.handleErrors(new Error('Please use yyyy-mm-dd format'), res); 301 | } 302 | 303 | isToday = dateStr === todayStr; 304 | } else { 305 | dateStr = todayStr; 306 | isToday = true; 307 | } 308 | 309 | var gte = Math.round((new Date(dateStr)).getTime() / 1000); 310 | 311 | //pagination 312 | var lte = parseInt(req.query.startTimestamp) || gte + 86400; 313 | var prev = this.formatTimestamp(new Date((gte - 86400) * 1000)); 314 | var next = lte ? this.formatTimestamp(new Date(lte * 1000)) : null; 315 | var limit = parseInt(req.query.limit || BLOCK_LIMIT); 316 | var more = false; 317 | var moreTimestamp = lte; 318 | 319 | self.node.services.dashd.getBlockHashesByTimestamp(lte, gte, function(err, hashes) { 320 | if(err) { 321 | return self.common.handleErrors(err, res); 322 | } 323 | 324 | hashes.reverse(); 325 | 326 | if(hashes.length > limit) { 327 | more = true; 328 | hashes = hashes.slice(0, limit); 329 | } 330 | 331 | async.mapSeries( 332 | hashes, 333 | function(hash, next) { 334 | self._getBlockSummary(hash, moreTimestamp, next); 335 | }, 336 | function(err, blocks) { 337 | if(err) { 338 | return self.common.handleErrors(err, res); 339 | } 340 | 341 | blocks.sort(function(a, b) { 342 | return b.height - a.height; 343 | }); 344 | 345 | var data = { 346 | blocks: blocks, 347 | length: blocks.length, 348 | pagination: { 349 | next: next, 350 | prev: prev, 351 | currentTs: lte - 1, 352 | current: dateStr, 353 | isToday: isToday, 354 | more: more 355 | } 356 | }; 357 | 358 | if(more) { 359 | data.pagination.moreTs = moreTimestamp; 360 | } 361 | 362 | res.jsonp(data); 363 | } 364 | ); 365 | }); 366 | }; 367 | 368 | BlockController.prototype.getPoolInfo = function(block) { 369 | var coinbaseBuffer = block.transactions[0].inputs[0]._scriptBuffer; 370 | 371 | for(var k in this.poolStrings) { 372 | if (coinbaseBuffer.toString('utf-8').match(k)) { 373 | return this.poolStrings[k]; 374 | } 375 | } 376 | 377 | return {}; 378 | }; 379 | 380 | //helper to convert timestamps to yyyy-mm-dd format 381 | BlockController.prototype.formatTimestamp = function(date) { 382 | var yyyy = date.getUTCFullYear().toString(); 383 | var mm = (date.getUTCMonth() + 1).toString(); // getMonth() is zero-based 384 | var dd = date.getUTCDate().toString(); 385 | 386 | return yyyy + '-' + (mm[1] ? mm : '0' + mm[0]) + '-' + (dd[1] ? dd : '0' + dd[0]); //padding 387 | }; 388 | 389 | /** 390 | * Previous blockHeader is used to determine block reward 391 | */ 392 | BlockController.prototype.getPreviousBlock = function(prevHash, cb) { 393 | var self = this; 394 | //On genesis block, the previousHash will be null. 395 | if(prevHash===null) cb(null,null); 396 | else { 397 | self.node.getBlock(prevHash, function (err, block) { 398 | if ((err && err.code === -5) || (err && err.code === -8)) { 399 | return self.common.handleErrors(null, block); 400 | } else if (err) { 401 | return self.common.handleErrors(err, block); 402 | } 403 | self.node.services.dashd.getBlockHeader(prevHash, function (err, info) { 404 | if (err) { 405 | return self.common.handleErrors(err, info); 406 | } 407 | cb(null, info); // return blockHeader 408 | }); 409 | }); 410 | } 411 | }; 412 | 413 | BlockController.prototype.getBlockReward = function(prevHash, cb) { 414 | var self = this; 415 | var nSubsidyHalvingInterval = 210240; 416 | var nBudgetPaymentsStartBlock = 100000; 417 | var nSubsidyBase; 418 | 419 | // block reward is based on the previous block diff / height 420 | self.getPreviousBlock(prevHash, function(err, info) { 421 | if(err) cb(err, null); 422 | //if we seek for prevHash of genesis, previousBlock return an info being null. 423 | if(info===null){ 424 | //Genesis reward is 50. 425 | cb(null, parseFloat("50".toString(10)).toFixed(8)); 426 | } 427 | else{ 428 | var dDiff = info.difficulty; 429 | var nPrevHeight = info.height; 430 | 431 | if (nPrevHeight < 5465) { 432 | // Early ages... 433 | // 1111/((x+1)^2) 434 | nSubsidyBase = (1111.0 / (Math.pow((dDiff+1.0),2.0))); 435 | if(nSubsidyBase > 500) nSubsidyBase = 500; 436 | else if(nSubsidyBase < 1) nSubsidyBase = 1; 437 | } else if (nPrevHeight < 17000 || (dDiff <= 75 && nPrevHeight < 24000)) { 438 | // CPU mining era 439 | // 11111/(((x+51)/6)^2) 440 | nSubsidyBase = (11111.0 / (Math.pow((dDiff+51.0)/6.0,2.0))); 441 | if(nSubsidyBase > 500) nSubsidyBase = 500; 442 | else if(nSubsidyBase < 25) nSubsidyBase = 25; 443 | } else { 444 | // GPU/ASIC mining era 445 | // 2222222/(((x+2600)/9)^2) 446 | nSubsidyBase = (2222222.0 / (Math.pow((dDiff+2600.0)/9.0,2.0))); 447 | if(nSubsidyBase > 25) nSubsidyBase = 25; 448 | else if(nSubsidyBase < 5) nSubsidyBase = 5; 449 | } 450 | 451 | var nSubsidy = nSubsidyBase; 452 | 453 | // yearly decline of production by ~7.1% per year, projected ~18M coins max by year 2050+. 454 | for (var i = nSubsidyHalvingInterval; i <= nPrevHeight; i += nSubsidyHalvingInterval) { 455 | nSubsidy -= nSubsidy/14; 456 | } 457 | 458 | // Hard fork to reduce the block reward by 10 extra percent (allowing budget/superblocks) 459 | var nSuperblockPart = (nPrevHeight > nBudgetPaymentsStartBlock) ? nSubsidy/10 : 0; 460 | var reward = nSubsidy - nSuperblockPart; 461 | 462 | cb(null, parseFloat(reward.toString(10)).toFixed(8)); 463 | } 464 | }); 465 | }; 466 | 467 | module.exports = BlockController; 468 | -------------------------------------------------------------------------------- /lib/common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Common(options) { 4 | this.log = options.log; 5 | } 6 | 7 | Common.prototype.notReady = function (err, res, p) { 8 | res.status(503).send('Server not yet ready. Sync Percentage:' + p); 9 | }; 10 | 11 | Common.prototype.handleErrors = function (err, res) { 12 | if (err) { 13 | if (err.code) { 14 | res.status(400).send(err.message + '. Code:' + err.code); 15 | } else { 16 | this.log.error(err.stack); 17 | res.status(503).send(err.message); 18 | } 19 | } else { 20 | res.status(404).send('Not found'); 21 | } 22 | }; 23 | 24 | module.exports = Common; 25 | -------------------------------------------------------------------------------- /lib/currency.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var request = require('request'); 4 | 5 | function CurrencyController(options) { 6 | this.node = options.node; 7 | var refresh = options.currencyRefresh || CurrencyController.DEFAULT_CURRENCY_DELAY; 8 | this.currencyDelay = refresh * 60000; 9 | this.exchange_rates = { 10 | dash_usd: 0.00, 11 | btc_usd: 0.00, 12 | btc_dash: 0.00 13 | }; 14 | this.timestamp = Date.now(); 15 | } 16 | 17 | CurrencyController.DEFAULT_CURRENCY_DELAY = 10; 18 | 19 | CurrencyController.prototype.index = function(req, res) { 20 | var self = this; 21 | var currentTime = Date.now(); 22 | if (self.exchange_rates.dash_usd === 0.00 || currentTime >= (self.timestamp + self.currencyDelay)) { 23 | self.timestamp = currentTime; 24 | request('https://www.dashcentral.org/api/v1/public', function(err, response, body) { 25 | if (err) { 26 | self.node.log.error(err); 27 | } 28 | if (!err && response.statusCode === 200) { 29 | var response = JSON.parse(body); 30 | self.exchange_rates = response.exchange_rates; 31 | self.exchange_rates.bitstamp = response.exchange_rates.dash_usd; // backwards compatibility 32 | } 33 | res.jsonp({ 34 | status: 200, 35 | data: self.exchange_rates 36 | }); 37 | }); 38 | } else { 39 | res.jsonp({ 40 | status: 200, 41 | data: self.exchange_rates 42 | }); 43 | } 44 | 45 | }; 46 | 47 | module.exports = CurrencyController; 48 | -------------------------------------------------------------------------------- /lib/governance.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Common = require('./common'); 4 | 5 | function GovernanceController(node) { 6 | this.node = node; 7 | this.common = new Common({log: this.node.log}); 8 | } 9 | 10 | GovernanceController.prototype.getSuperBlockBudget = function (req, res) { 11 | var self = this; 12 | var blockindex = req.params.blockindex || 0; 13 | this.node.services.dashd.getSuperBlockBudget(blockindex,function (err, result) { 14 | if(err) { return self.common.handleErrors(err, res);} 15 | res.jsonp(result); 16 | }) 17 | } 18 | module.exports = GovernanceController; 19 | -------------------------------------------------------------------------------- /lib/govobject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Common = require('./common'); 4 | 5 | function GovObjectController(node) { 6 | this.node = node; 7 | this.common = new Common({log: this.node.log}); 8 | } 9 | 10 | GovObjectController.prototype.submit = function (req, res) { 11 | var self = this; 12 | 13 | var parentHash = req.body.parentHash || (function(){throw new Error('missing parentHash')}()); 14 | var revision = req.body.revision || (function(){throw new Error('missing revision')}()); 15 | var time = req.body.time || (function(){throw new Error('missing time')}()) 16 | var dataHex = req.body.dataHex || (function(){throw new Error('missing dataHex')}()); 17 | var feeTxId = req.body.feeTxId || (function(){throw new Error('missing feeTxId')}()); 18 | 19 | this.node.services.dashd.govObjectSubmit(parentHash, revision, time, dataHex, feeTxId, function(err, result) { 20 | if(err) { return self.common.handleErrors(err, res);} 21 | res.jsonp(result) 22 | }); 23 | } 24 | 25 | GovObjectController.prototype.list = function(req, res) { 26 | var options = { 27 | type:1//by default display proposal 28 | }; 29 | if (req.params.filter) { 30 | if (req.params.filter === 'proposal') options.type = 1; 31 | if (req.params.filter === 'trigger') options.type = 2; 32 | } 33 | 34 | this.govObjectList(options, function(err, result) { 35 | if (err) { 36 | return self.common.handleErrors(err, res); 37 | } 38 | 39 | res.jsonp(result); 40 | }); 41 | 42 | }; 43 | 44 | GovObjectController.prototype.govObjectList = function(options, callback) { 45 | this.node.services.dashd.govObjectList(options, function(err, result) { 46 | if (err) { 47 | return callback(err); 48 | } 49 | callback(null, result); 50 | }); 51 | 52 | }; 53 | 54 | 55 | GovObjectController.prototype.show = function(req, res) { 56 | var self = this; 57 | var options = {}; 58 | 59 | this.getHash(req.hash, function(err, data) { 60 | if(err) { 61 | return self.common.handleErrors(err, res); 62 | } 63 | 64 | res.jsonp(data); 65 | }); 66 | 67 | }; 68 | 69 | GovObjectController.prototype.getHash = function(hash, callback) { 70 | 71 | this.node.services.dashd.govObjectHash(hash, function(err, result) { 72 | if (err) { 73 | return callback(err); 74 | } 75 | 76 | callback(null, result); 77 | }); 78 | 79 | }; 80 | 81 | /** 82 | * Verifies that the GovObject Hash provided is valid. 83 | * 84 | * @param req 85 | * @param res 86 | * @param next 87 | */ 88 | GovObjectController.prototype.validateHash = function(req, res, next) { 89 | req.hash = req.params.hash; 90 | this.isValidHash(req, res, next, [req.hash]); 91 | }; 92 | 93 | GovObjectController.prototype.isValidHash = function(req, res, next, hash) { 94 | // TODO: Implement some type of validation 95 | if(hash) next(); 96 | }; 97 | 98 | 99 | GovObjectController.prototype.govObjectCheck = function(req, res) { 100 | var self = this; 101 | var hexdata = req.params.hexdata; 102 | 103 | this.node.services.dashd.govObjectCheck(hexdata, function(err, result) { 104 | if (err) { 105 | return self.common.handleErrors(err, res); 106 | } 107 | res.jsonp(result); 108 | }); 109 | }; 110 | 111 | GovObjectController.prototype.getInfo = function (req, res) { 112 | var self = this; 113 | this.node.services.dashd.govObjectInfo(function (err, result) { 114 | if(err) { return self.common.handleErrors(err, res);} 115 | res.jsonp(result); 116 | }) 117 | } 118 | 119 | GovObjectController.prototype.getCount = function (req, res) { 120 | var self = this; 121 | this.node.services.dashd.govCount(function (err, result) { 122 | if(err) { return self.common.handleErrors(err, res);} 123 | res.jsonp(result); 124 | }) 125 | } 126 | 127 | GovObjectController.prototype.govObjectVotes = function (req, res) { 128 | var self = this; 129 | var govHash = req.params.hash; 130 | 131 | this.node.services.dashd.getVotes(govHash, function (err, result) { 132 | if(err) { return self.common.handleErrors(err, res);} 133 | res.jsonp(result); 134 | }) 135 | } 136 | 137 | GovObjectController.prototype.govObjectCurrentVotes = function (req, res) { 138 | var self = this; 139 | var govHash = req.params.hash; 140 | 141 | this.node.services.dashd.getCurrentVotes(govHash, function (err, result) { 142 | if(err) { return self.common.handleErrors(err, res);} 143 | res.jsonp(result); 144 | }) 145 | } 146 | 147 | GovObjectController.prototype.govObjectDeserialize = function (req, res) { 148 | var self = this; 149 | var hexdata = req.params.hexdata; 150 | 151 | this.node.services.dashd.govObjectDeserialize(hexdata, function (err, result) { 152 | if(err) { return self.common.handleErrors(err, res);} 153 | res.jsonp(result); 154 | }) 155 | } 156 | module.exports = GovObjectController; 157 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Writable = require('stream').Writable; 4 | var bodyParser = require('body-parser'); 5 | var compression = require('compression'); 6 | var BaseService = require('./service'); 7 | var inherits = require('util').inherits; 8 | var BlockController = require('./blocks'); 9 | var TxController = require('./transactions'); 10 | var AddressController = require('./addresses'); 11 | var GovObjectController = require('./govobject'); 12 | var GovernanceController = require('./governance'); 13 | var StatusController = require('./status'); 14 | var MessagesController = require('./messages'); 15 | var MasternodesController = require('./masternodes'); 16 | var UtilsController = require('./utils'); 17 | var CurrencyController = require('./currency'); 18 | var SporkController = require('./sporks'); 19 | var RateLimiter = require('./ratelimiter'); 20 | var morgan = require('morgan'); 21 | var dashcore = require('@dashevo/dashcore-lib'); 22 | var _ = dashcore.deps._; 23 | var $ = dashcore.util.preconditions; 24 | var Transaction = dashcore.Transaction; 25 | var EventEmitter = require('events').EventEmitter; 26 | 27 | /** 28 | * A service for Bitcore to enable HTTP routes to query information about the blockchain. 29 | * 30 | * @param {Object} options 31 | * @param {Boolean} options.enableCache - This will enable cache-control headers 32 | * @param {Number} options.cacheShortSeconds - The time to cache short lived cache responses. 33 | * @param {Number} options.cacheLongSeconds - The time to cache long lived cache responses. 34 | * @param {String} options.routePrefix - The URL route prefix 35 | */ 36 | var InsightAPI = function(options) { 37 | BaseService.call(this, options); 38 | 39 | // in minutes 40 | this.currencyRefresh = options.currencyRefresh || CurrencyController.DEFAULT_CURRENCY_DELAY; 41 | 42 | this.subscriptions = { 43 | inv: [] 44 | }; 45 | 46 | if (!_.isUndefined(options.enableCache)) { 47 | $.checkArgument(_.isBoolean(options.enableCache)); 48 | this.enableCache = options.enableCache; 49 | } 50 | this.cacheShortSeconds = options.cacheShortSeconds; 51 | this.cacheLongSeconds = options.cacheLongSeconds; 52 | 53 | this.rateLimiterOptions = options.rateLimiterOptions; 54 | this.disableRateLimiter = options.disableRateLimiter; 55 | 56 | this.blockSummaryCacheSize = options.blockSummaryCacheSize || BlockController.DEFAULT_BLOCKSUMMARY_CACHE_SIZE; 57 | this.blockCacheSize = options.blockCacheSize || BlockController.DEFAULT_BLOCK_CACHE_SIZE; 58 | 59 | if (!_.isUndefined(options.routePrefix)) { 60 | this.routePrefix = options.routePrefix; 61 | } else { 62 | this.routePrefix = 'insight-api'; 63 | } 64 | 65 | this.txController = new TxController(this.node); 66 | }; 67 | 68 | InsightAPI.dependencies = ['dashd', 'web']; 69 | 70 | inherits(InsightAPI, BaseService); 71 | 72 | InsightAPI.prototype.cache = function(maxAge) { 73 | var self = this; 74 | return function(req, res, next) { 75 | if (self.enableCache) { 76 | res.header('Cache-Control', 'public, max-age=' + maxAge); 77 | } 78 | next(); 79 | }; 80 | }; 81 | 82 | InsightAPI.prototype.cacheShort = function() { 83 | var seconds = this.cacheShortSeconds || 30; // thirty seconds 84 | return this.cache(seconds); 85 | }; 86 | 87 | InsightAPI.prototype.cacheLong = function() { 88 | var seconds = this.cacheLongSeconds || 86400; // one day 89 | return this.cache(seconds); 90 | }; 91 | 92 | InsightAPI.prototype.getRoutePrefix = function() { 93 | return this.routePrefix; 94 | }; 95 | 96 | InsightAPI.prototype.start = function(callback) { 97 | this.node.services.dashd.on('tx', this.transactionEventHandler.bind(this)); 98 | this.node.services.dashd.on('txlock', this.transactionLockEventHandler.bind(this)); 99 | this.node.services.dashd.on('block', this.blockEventHandler.bind(this)); 100 | setImmediate(callback); 101 | }; 102 | 103 | InsightAPI.prototype.createLogInfoStream = function() { 104 | var self = this; 105 | 106 | function Log(options) { 107 | Writable.call(this, options); 108 | } 109 | inherits(Log, Writable); 110 | 111 | Log.prototype._write = function (chunk, enc, callback) { 112 | self.node.log.info(chunk.slice(0, chunk.length - 1)); // remove new line and pass to logger 113 | callback(); 114 | }; 115 | var stream = new Log(); 116 | 117 | return stream; 118 | }; 119 | 120 | InsightAPI.prototype.getRemoteAddress = function(req) { 121 | if (req.headers['cf-connecting-ip']) { 122 | return req.headers['cf-connecting-ip']; 123 | } 124 | return req.socket.remoteAddress; 125 | }; 126 | 127 | InsightAPI.prototype._getRateLimiter = function() { 128 | var rateLimiterOptions = _.isUndefined(this.rateLimiterOptions) ? {} : _.clone(this.rateLimiterOptions); 129 | rateLimiterOptions.node = this.node; 130 | var limiter = new RateLimiter(rateLimiterOptions); 131 | return limiter; 132 | }; 133 | 134 | InsightAPI.prototype.setupRoutes = function(app) { 135 | 136 | var self = this; 137 | 138 | //Enable rate limiter 139 | if (!this.disableRateLimiter) { 140 | var limiter = this._getRateLimiter(); 141 | app.use(limiter.middleware()); 142 | } 143 | 144 | //Setup logging 145 | morgan.token('remote-forward-addr', function(req){ 146 | return self.getRemoteAddress(req); 147 | }); 148 | var height = (typeof self.node.services !== 'undefined') ? ((self.node.services.dashd.height) ? self.node.services.dashd.height : 0) : 0; 149 | var logFormat = '{'+height +'} :remote-forward-addr ":method :url" :status :res[content-length] :response-time ":user-agent" '; 150 | var logStream = this.createLogInfoStream(); 151 | app.use(morgan(logFormat, {stream: logStream})); 152 | 153 | //Enable compression 154 | app.use(compression()); 155 | 156 | //Enable urlencoded data 157 | app.use(bodyParser.urlencoded({extended: true})); 158 | 159 | //Enable CORS 160 | app.use(function(req, res, next) { 161 | 162 | res.header('Access-Control-Allow-Origin', '*'); 163 | res.header('Access-Control-Allow-Methods', 'GET, HEAD, PUT, POST, OPTIONS'); 164 | res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Content-Length, Cache-Control, cf-connecting-ip'); 165 | 166 | var method = req.method && req.method.toUpperCase && req.method.toUpperCase(); 167 | 168 | if (method === 'OPTIONS') { 169 | res.statusCode = 204; 170 | res.end(); 171 | } else { 172 | next(); 173 | } 174 | }); 175 | 176 | //Block routes 177 | var blockOptions = { 178 | node: this.node, 179 | blockSummaryCacheSize: this.blockSummaryCacheSize, 180 | blockCacheSize: this.blockCacheSize 181 | }; 182 | var blocks = new BlockController(blockOptions); 183 | app.get('/blocks', this.cacheShort(), blocks.list.bind(blocks)); 184 | 185 | app.get('/block/:blockHash', this.cacheShort(), blocks.checkBlockHash.bind(blocks), blocks.show.bind(blocks)); 186 | app.param('blockHash', blocks.block.bind(blocks)); 187 | 188 | app.get('/rawblock/:blockHash', this.cacheLong(), blocks.checkBlockHash.bind(blocks), blocks.showRaw.bind(blocks)); 189 | app.param('blockHash', blocks.rawBlock.bind(blocks)); 190 | 191 | app.get('/block-index/:height', this.cacheShort(), blocks.blockIndex.bind(blocks)); 192 | app.param('height', blocks.blockIndex.bind(blocks)); 193 | 194 | //Return 25 next blocks from blockHash or blockHeight 195 | app.get('/block-headers/:blockIdentifier',this.cacheLong(),blocks.blockHeaders.bind(blocks)); 196 | //Return nbOfBlock's blocks next from blockHash or blockHeight 197 | app.get('/block-headers/:blockIdentifier/:nbOfBlock',this.cacheLong(),blocks.blockHeaders.bind(blocks)); 198 | 199 | // Transaction routes 200 | var transactions = new TxController(this.node); 201 | app.get('/tx/:txid', this.cacheShort(), transactions.show.bind(transactions)); 202 | app.param('txid', transactions.transaction.bind(transactions)); 203 | app.get('/txs', this.cacheShort(), transactions.list.bind(transactions)); 204 | app.post('/tx/send', transactions.send.bind(transactions)); 205 | app.post('/tx/sendix', transactions.sendix.bind(transactions)); 206 | 207 | // Raw Routes 208 | app.get('/rawtx/:txid', this.cacheLong(), transactions.showRaw.bind(transactions)); 209 | app.param('txid', transactions.rawTransaction.bind(transactions)); 210 | 211 | // Address routes 212 | var addresses = new AddressController(this.node); 213 | app.get('/addr/:addr', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.show.bind(addresses)); 214 | app.get('/addrs/:addrs', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.show.bind(addresses)); 215 | app.get('/addr/:addr/utxo', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.utxo.bind(addresses)); 216 | app.get('/addrs/:addrs/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); 217 | app.post('/addrs/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); 218 | app.get('/addrs/:addrs/utxopage', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.paginatedUtxo.bind(addresses)); 219 | app.post('/addrs/utxopage', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.paginatedUtxo.bind(addresses)); 220 | app.get('/addrs/:addrs/txs', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses)); 221 | app.post('/addrs/txs', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses)); 222 | 223 | // Address property routes 224 | app.get('/addr/:addr/balance', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.balance.bind(addresses)); 225 | app.get('/addr/:addr/totalReceived', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.totalReceived.bind(addresses)); 226 | app.get('/addr/:addr/totalSent', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.totalSent.bind(addresses)); 227 | app.get('/addr/:addr/unconfirmedBalance', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.unconfirmedBalance.bind(addresses)); 228 | app.get('/addrs/:addrs/balance', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.balance.bind(addresses)); 229 | app.get('/addrs/:addrs/totalReceived', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.totalReceived.bind(addresses)); 230 | app.get('/addrs/:addrs/totalSent', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.totalSent.bind(addresses)); 231 | app.get('/addrs/:addrs/unconfirmedBalance', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.unconfirmedBalance.bind(addresses)); 232 | 233 | //Governance Routes 234 | var govObject = new GovObjectController(this.node); 235 | app.post('/gobject/submit', this.cacheShort(), govObject.submit.bind(govObject)); 236 | app.get('/gobject/list', this.cacheShort(), govObject.list.bind(govObject)); 237 | app.get('/gobject/list/:filter', this.cacheShort(), govObject.list.bind(govObject)); 238 | app.get('/gobject/get/:hash', this.cacheShort(), govObject.validateHash.bind(govObject), govObject.show.bind(govObject)); 239 | app.get('/gobject/check/:hexdata', this.cacheShort(), govObject.govObjectCheck.bind(govObject)); 240 | app.get('/gobject/info', this.cacheShort(), govObject.getInfo.bind(govObject)); 241 | app.get('/gobject/count', this.cacheShort(), govObject.getCount.bind(govObject)); 242 | app.get('/gobject/deserialize/:hexdata', this.cacheShort(), govObject.govObjectDeserialize.bind(govObject)); 243 | app.get('/gobject/votes/current/:hash', this.cacheShort(), govObject.govObjectCurrentVotes.bind(govObject)); 244 | app.get('/gobject/votes/:hash', this.cacheShort(), govObject.govObjectVotes.bind(govObject)); 245 | 246 | var governance = new GovernanceController(this.node); 247 | app.get('/governance/budget/:blockindex', this.cacheShort(), governance.getSuperBlockBudget.bind(governance)); 248 | 249 | //Masternodes 250 | var masternodes = new MasternodesController(this.node); 251 | app.get('/masternodes/list', this.cacheShort(), masternodes.list.bind(masternodes)); 252 | app.get('/masternodes/validate/:payee', this.cacheShort(), masternodes.validate.bind(masternodes)); 253 | // Status route 254 | var status = new StatusController(this.node); 255 | app.get('/status', this.cacheShort(), status.show.bind(status)); 256 | app.get('/sync', this.cacheShort(), status.sync.bind(status)); 257 | app.get('/peer', this.cacheShort(), status.peer.bind(status)); 258 | app.get('/version', this.cacheShort(), status.version.bind(status)); 259 | 260 | // Address routes 261 | var messages = new MessagesController(this.node); 262 | app.get('/messages/verify', messages.verify.bind(messages)); 263 | app.post('/messages/verify', messages.verify.bind(messages)); 264 | 265 | //Spork routes 266 | var sporks = new SporkController(this.node); 267 | app.get('/sporks', sporks.list.bind(sporks)); 268 | 269 | // Utils route 270 | var utils = new UtilsController(this.node); 271 | app.get('/utils/estimatefee', utils.estimateFee.bind(utils)); 272 | 273 | // Currency 274 | var currency = new CurrencyController({ 275 | node: this.node, 276 | currencyRefresh: this.currencyRefresh 277 | }); 278 | app.get('/currency', currency.index.bind(currency)); 279 | 280 | // Not Found 281 | app.use(function(req, res) { 282 | res.status(404).jsonp({ 283 | status: 404, 284 | url: req.originalUrl, 285 | error: 'Not found' 286 | }); 287 | }); 288 | 289 | }; 290 | 291 | InsightAPI.prototype.getPublishEvents = function() { 292 | return [ 293 | { 294 | name: 'inv', 295 | scope: this, 296 | subscribe: this.subscribe.bind(this), 297 | unsubscribe: this.unsubscribe.bind(this), 298 | extraEvents: ['tx', 'txlock', 'block'] 299 | } 300 | ]; 301 | }; 302 | 303 | InsightAPI.prototype.blockEventHandler = function(hashBuffer) { 304 | // Notify inv subscribers 305 | for (var i = 0; i < this.subscriptions.inv.length; i++) { 306 | this.subscriptions.inv[i].emit('block', hashBuffer.toString('hex')); 307 | } 308 | }; 309 | InsightAPI.prototype.transactionEventHandler = function(txBuffer) { 310 | try { 311 | var tx = new Transaction().fromBuffer(txBuffer); 312 | } 313 | catch(error) { 314 | console.log(`${error}\nbad txBuffer: ${txBuffer.toString('hex')}`); 315 | } 316 | var result = this.txController.transformInvTransaction(tx); 317 | 318 | for (var i = 0; i < this.subscriptions.inv.length; i++) { 319 | this.subscriptions.inv[i].emit('tx', result); 320 | } 321 | }; 322 | InsightAPI.prototype.transactionLockEventHandler = function(txBuffer) { 323 | try { 324 | var tx = new Transaction().fromBuffer(txBuffer); 325 | } 326 | catch(error) { 327 | console.log(`${error}\nbad txBuffer: ${txBuffer.toString('hex')}`); 328 | } 329 | var result = this.txController.transformInvTransaction(tx, true); 330 | 331 | for (var i = 0; i < this.subscriptions.inv.length; i++) { 332 | this.subscriptions.inv[i].emit('txlock', result); 333 | } 334 | }; 335 | 336 | InsightAPI.prototype.subscribe = function(emitter) { 337 | $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter'); 338 | 339 | var emitters = this.subscriptions.inv; 340 | var index = emitters.indexOf(emitter); 341 | if(index === -1) { 342 | emitters.push(emitter); 343 | } 344 | }; 345 | 346 | InsightAPI.prototype.unsubscribe = function(emitter) { 347 | $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter'); 348 | 349 | var emitters = this.subscriptions.inv; 350 | var index = emitters.indexOf(emitter); 351 | if(index > -1) { 352 | emitters.splice(index, 1); 353 | } 354 | }; 355 | 356 | module.exports = InsightAPI; 357 | -------------------------------------------------------------------------------- /lib/masternodes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Common = require('./common'); 4 | var dashcore = require('@dashevo/dashcore-lib'); 5 | var _ = dashcore.deps._; 6 | var deprecatedMessage = 'This endpoint has been deprecated and will be replaced with a new endpoint compatible with the deterministic masternode list introduced in v0.13'; 7 | 8 | function MasternodeController(node) { 9 | this.node = node; 10 | this.common = new Common({log: this.node.log}); 11 | } 12 | 13 | MasternodeController.prototype.list = function(req, res) { 14 | res.jsonp(deprecatedMessage); 15 | }; 16 | 17 | MasternodeController.prototype.validate = function (req, res, next) { 18 | res.jsonp(deprecatedMessage); 19 | }; 20 | 21 | MasternodeController.prototype.getMNList = function(callback) { 22 | this.node.services.dashd.getMNList(function(err, result){ 23 | var MNList = result || []; 24 | if (err) { 25 | return callback(err); 26 | } 27 | callback(null,MNList); 28 | }); 29 | }; 30 | 31 | module.exports = MasternodeController; 32 | -------------------------------------------------------------------------------- /lib/messages.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var dashcore = require('@dashevo/dashcore-lib'); 4 | var _ = dashcore.deps._; 5 | var Message = dashcore.Message; 6 | var Common = require('./common'); 7 | 8 | function MessagesController(node) { 9 | this.node = node; 10 | this.common = new Common({log: this.node.log}); 11 | } 12 | 13 | MessagesController.prototype.verify = function(req, res) { 14 | var self = this; 15 | var address = req.body.address || req.query.address; 16 | var signature = req.body.signature || req.query.signature; 17 | var message = req.body.message || req.query.message; 18 | if(_.isUndefined(address) || _.isUndefined(signature) || _.isUndefined(message)) { 19 | return self.common.handleErrors({ 20 | message: 'Missing parameters (expected "address", "signature" and "message")', 21 | code: 1 22 | }, res); 23 | } 24 | var valid; 25 | try { 26 | valid = new Message(message).verify(address, signature); 27 | } catch(err) { 28 | return self.common.handleErrors({ 29 | message: 'Unexpected error: ' + err.message, 30 | code: 1 31 | }, res); 32 | } 33 | res.json({'result': valid}); 34 | }; 35 | 36 | module.exports = MessagesController; 37 | -------------------------------------------------------------------------------- /lib/ratelimiter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var THREE_HOURS = 3 * 60 * 60 * 1000; 4 | 5 | /** 6 | * A rate limiter to be used as an express middleware. 7 | * 8 | * @param {Object} options 9 | * @param {Object} options.node - The bitcore node object 10 | * @param {Number} options.limit - Number of requests for normal rate limiter 11 | * @param {Number} options.interval - Interval of the normal rate limiter 12 | * @param {Array} options.whitelist - IP addresses that should have whitelist rate limiting 13 | * @param {Array} options.blacklist - IP addresses that should be blacklist rate limiting 14 | * @param {Number} options.whitelistLimit - Number of requests for whitelisted clients 15 | * @param {Number} options.whitelistInterval - Interval for whitelisted clients 16 | * @param {Number} options.blacklistLimit - Number of requests for blacklisted clients 17 | * @param {Number} options.blacklistInterval - Interval for blacklisted clients 18 | */ 19 | function RateLimiter(options) { 20 | if (!(this instanceof RateLimiter)) { 21 | return new RateLimiter(options); 22 | } 23 | 24 | if (!options){ 25 | options = {}; 26 | } 27 | 28 | this.node = options.node; 29 | this.clients = {}; 30 | this.whitelist = options.whitelist || []; 31 | this.blacklist = options.blacklist || []; 32 | 33 | this.config = { 34 | whitelist: { 35 | totalRequests: options.whitelistLimit || 3 * 60 * 60 * 10, // 108,000 36 | interval: options.whitelistInterval || THREE_HOURS 37 | }, 38 | blacklist: { 39 | totalRequests: options.blacklistLimit || 0, 40 | interval: options.blacklistInterval || THREE_HOURS 41 | }, 42 | normal: { 43 | totalRequests: options.limit || 3 * 60 * 60, // 10,800 44 | interval: options.interval || THREE_HOURS 45 | } 46 | }; 47 | 48 | } 49 | 50 | RateLimiter.prototype.middleware = function() { 51 | var self = this; 52 | return function(req, res, next) { 53 | self._middleware(req, res, next); 54 | }; 55 | }; 56 | 57 | RateLimiter.prototype._middleware = function(req, res, next) { 58 | 59 | var name = this.getClientName(req); 60 | var client = this.clients[name]; 61 | 62 | res.ratelimit = { 63 | clients: this.clients, 64 | exceeded: false 65 | }; 66 | 67 | if (!client) { 68 | client = this.addClient(name); 69 | } 70 | 71 | res.setHeader('X-RateLimit-Limit', this.config[client.type].totalRequests); 72 | res.setHeader('X-RateLimit-Remaining', this.config[client.type].totalRequests - client.visits); 73 | 74 | res.ratelimit.exceeded = this.exceeded(client); 75 | res.ratelimit.client = client; 76 | 77 | if (!this.exceeded(client)) { 78 | client.visits++; 79 | next(); 80 | } else { 81 | this.node.log.warn('Rate limited:', client); 82 | res.status(429).jsonp({ 83 | status: 429, 84 | error: 'Rate limit exceeded' 85 | }); 86 | } 87 | }; 88 | 89 | RateLimiter.prototype.exceeded = function(client) { 90 | if (this.config[client.type].totalRequests === -1) { 91 | return false; 92 | } else { 93 | return client.visits > this.config[client.type].totalRequests; 94 | } 95 | }; 96 | 97 | RateLimiter.prototype.getClientType = function(name) { 98 | if (this.whitelist.indexOf(name) > -1) { 99 | return 'whitelist'; 100 | } 101 | if (this.blacklist.indexOf(name) > -1) { 102 | return 'blacklist'; 103 | } 104 | return 'normal'; 105 | }; 106 | 107 | RateLimiter.prototype.getClientName = function(req) { 108 | var name = req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress; 109 | return name; 110 | }; 111 | 112 | RateLimiter.prototype.addClient = function(name) { 113 | var self = this; 114 | 115 | var client = { 116 | name: name, 117 | type: this.getClientType(name), 118 | visits: 1 119 | }; 120 | 121 | var resetTime = this.config[client.type].interval; 122 | 123 | setTimeout(function() { 124 | delete self.clients[name]; 125 | }, resetTime).unref(); 126 | 127 | this.clients[name] = client; 128 | 129 | return client; 130 | 131 | }; 132 | 133 | module.exports = RateLimiter; 134 | -------------------------------------------------------------------------------- /lib/service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var EventEmitter = require('events').EventEmitter; 5 | 6 | var Service = function(options) { 7 | EventEmitter.call(this); 8 | 9 | this.node = options.node; 10 | this.name = options.name; 11 | }; 12 | 13 | util.inherits(Service, EventEmitter); 14 | 15 | /** 16 | * Describes the dependencies that should be loaded before this service. 17 | */ 18 | Service.dependencies = []; 19 | 20 | /** 21 | * blockHandler 22 | * @param {Block} block - the block being added or removed from the chain 23 | * @param {Boolean} add - whether the block is being added or removed 24 | * @param {Function} callback - call with the leveldb database operations to perform 25 | */ 26 | Service.prototype.blockHandler = function(block, add, callback) { 27 | // implement in the child class 28 | setImmediate(function() { 29 | callback(null, []); 30 | }); 31 | }; 32 | 33 | /** 34 | * the bus events available for subscription 35 | * @return {Array} an array of event info 36 | */ 37 | Service.prototype.getPublishEvents = function() { 38 | // Example: 39 | // return [ 40 | // ['eventname', this, this.subscribeEvent, this.unsubscribeEvent], 41 | // ]; 42 | return []; 43 | }; 44 | 45 | /** 46 | * the API methods to expose 47 | * @return {Array} return array of methods 48 | */ 49 | Service.prototype.getAPIMethods = function() { 50 | // Example: 51 | // return [ 52 | // ['getData', this, this.getData, 1] 53 | // ]; 54 | 55 | return []; 56 | }; 57 | 58 | // Example: 59 | // Service.prototype.getData = function(arg1, callback) { 60 | // 61 | // }; 62 | 63 | /** 64 | * Function which is called when module is first initialized 65 | */ 66 | Service.prototype.start = function(done) { 67 | setImmediate(done); 68 | }; 69 | 70 | /** 71 | * Function to be called when bitcore-node is stopped 72 | */ 73 | Service.prototype.stop = function(done) { 74 | setImmediate(done); 75 | }; 76 | 77 | /** 78 | * Setup express routes 79 | * @param {Express} app 80 | */ 81 | Service.prototype.setupRoutes = function(app) { 82 | // Setup express routes here 83 | }; 84 | 85 | Service.prototype.getRoutePrefix = function() { 86 | return this.name; 87 | }; 88 | 89 | 90 | 91 | module.exports = Service; 92 | -------------------------------------------------------------------------------- /lib/sporks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var async = require('async'); 5 | var Common = require('./common'); 6 | 7 | function SporkController(node) { 8 | this.node = node; 9 | this.common = new Common({log: this.node.log}); 10 | } 11 | 12 | SporkController.prototype.list = function(req, res){ 13 | var self = this; 14 | this.getSpork(function(err, result) { 15 | if (err) { 16 | return self.common.handleErrors(err, res); 17 | } 18 | res.jsonp(result); 19 | }); 20 | }; 21 | 22 | SporkController.prototype.getSpork = function(callback) { 23 | this.node.services.dashd.getSpork(function(err, result){ 24 | var SporksList = result || []; 25 | if (err) { 26 | return callback(err); 27 | } 28 | callback(null,SporksList); 29 | }); 30 | }; 31 | module.exports = SporkController; -------------------------------------------------------------------------------- /lib/status.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Common = require('./common'); 4 | var insightVersion = require('../package.json').version || null; 5 | 6 | function StatusController(node) { 7 | this.node = node; 8 | this.common = new Common({log: this.node.log}); 9 | } 10 | 11 | StatusController.prototype.show = function(req, res) { 12 | var self = this; 13 | var option = req.query.q; 14 | 15 | switch(option) { 16 | case 'getDifficulty': 17 | this.getDifficulty(function(err, result) { 18 | if (err) { 19 | return self.common.handleErrors(err, res); 20 | } 21 | res.jsonp(result); 22 | }); 23 | break; 24 | case 'getLastBlockHash': 25 | res.jsonp(this.getLastBlockHash()); 26 | break; 27 | case 'getBestBlockHash': 28 | this.getBestBlockHash(function(err, result) { 29 | if (err) { 30 | return self.common.handleErrors(err, res); 31 | } 32 | res.jsonp(result); 33 | }); 34 | break; 35 | case 'getBestChainLock': 36 | this.getBestChainLock(function (err, result) { 37 | if (err) { 38 | return self.common.handleErrors(err, res); 39 | } 40 | res.jsonp(result); 41 | }); 42 | break; 43 | case 'getInfo': 44 | default: 45 | this.getInfo(function(err, result) { 46 | if (err) { 47 | return self.common.handleErrors(err, res); 48 | } 49 | res.jsonp({ 50 | info: result 51 | }); 52 | }); 53 | } 54 | }; 55 | 56 | StatusController.prototype.getInfo = function(callback) { 57 | this.node.services.dashd.getInfo(function(err, result) { 58 | if (err) { 59 | return callback(err); 60 | } 61 | var info = { 62 | version: result.version, 63 | insightversion: insightVersion, 64 | protocolversion: result.protocolVersion, 65 | blocks: result.blocks, 66 | timeoffset: result.timeOffset, 67 | connections: result.connections, 68 | proxy: result.proxy, 69 | difficulty: result.difficulty, 70 | testnet: result.testnet, 71 | relayfee: result.relayFee, 72 | errors: result.errors, 73 | network: result.network 74 | }; 75 | callback(null, info); 76 | }); 77 | }; 78 | 79 | StatusController.prototype.getLastBlockHash = function() { 80 | var hash = this.node.services.dashd.tiphash; 81 | return { 82 | syncTipHash: hash, 83 | lastblockhash: hash 84 | }; 85 | }; 86 | 87 | StatusController.prototype.getBestBlockHash = function(callback) { 88 | this.node.services.dashd.getBestBlockHash(function(err, hash) { 89 | if (err) { 90 | return callback(err); 91 | } 92 | callback(null, { 93 | bestblockhash: hash 94 | }); 95 | }); 96 | }; 97 | 98 | StatusController.prototype.getBestChainLock = function (callback) { 99 | this.node.services.dashd.getBestChainLock(function (err, result) { 100 | if (err) { 101 | return callback(err); 102 | } 103 | callback(null, { 104 | bestchainlock: result 105 | }); 106 | }); 107 | }; 108 | 109 | StatusController.prototype.getDifficulty = function(callback) { 110 | this.node.services.dashd.getInfo(function(err, info) { 111 | if (err) { 112 | return callback(err); 113 | } 114 | callback(null, { 115 | difficulty: info.difficulty 116 | }); 117 | }); 118 | }; 119 | 120 | StatusController.prototype.sync = function(req, res) { 121 | var self = this; 122 | var status = 'syncing'; 123 | 124 | this.node.services.dashd.isSynced(function(err, synced) { 125 | if (err) { 126 | return self.common.handleErrors(err, res); 127 | } 128 | if (synced) { 129 | status = 'finished'; 130 | } 131 | 132 | self.node.services.dashd.syncPercentage(function(err, percentage) { 133 | if (err) { 134 | return self.common.handleErrors(err, res); 135 | } 136 | var info = { 137 | status: status, 138 | blockChainHeight: self.node.services.dashd.height, 139 | syncPercentage: Math.round(percentage), 140 | height: self.node.services.dashd.height, 141 | error: null, 142 | type: 'bitcore node' 143 | }; 144 | 145 | res.jsonp(info); 146 | 147 | }); 148 | 149 | }); 150 | 151 | }; 152 | 153 | // Hard coded to make insight ui happy, but not applicable 154 | StatusController.prototype.peer = function(req, res) { 155 | res.jsonp({ 156 | connected: true, 157 | host: '127.0.0.1', 158 | port: null 159 | }); 160 | }; 161 | 162 | StatusController.prototype.version = function(req, res) { 163 | var pjson = require('../package.json'); 164 | res.jsonp({ 165 | version: pjson.version 166 | }); 167 | }; 168 | 169 | module.exports = StatusController; 170 | -------------------------------------------------------------------------------- /lib/transactions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var dashcore = require('@dashevo/dashcore-lib'); 4 | var _ = dashcore.deps._; 5 | var $ = dashcore.util.preconditions; 6 | var Common = require('./common'); 7 | var async = require('async'); 8 | 9 | var MAXINT = 0xffffffff; // Math.pow(2, 32) - 1; 10 | 11 | function TxController(node) { 12 | this.node = node; 13 | this.common = new Common({log: this.node.log}); 14 | } 15 | 16 | TxController.prototype.show = function(req, res) { 17 | if (req.transaction) { 18 | res.jsonp(req.transaction); 19 | } 20 | }; 21 | 22 | /** 23 | * Find transaction by hash ... 24 | */ 25 | TxController.prototype.transaction = function(req, res, next) { 26 | var self = this; 27 | var txid = req.params.txid; 28 | 29 | this.node.getDetailedTransaction(txid, function(err, transaction) { 30 | if (err && err.code === -5) { 31 | return self.common.handleErrors(null, res); 32 | } else if(err) { 33 | return self.common.handleErrors(err, res); 34 | } 35 | 36 | self.transformTransaction(transaction, function(err, transformedTransaction) { 37 | if (err) { 38 | return self.common.handleErrors(err, res); 39 | } 40 | req.transaction = transformedTransaction; 41 | next(); 42 | }); 43 | 44 | }); 45 | }; 46 | 47 | TxController.prototype.transformTransaction = function(transaction, options, callback) { 48 | if (_.isFunction(options)) { 49 | callback = options; 50 | options = {}; 51 | } 52 | $.checkArgument(_.isFunction(callback)); 53 | 54 | var confirmations = 0; 55 | if(transaction.height >= 0) { 56 | confirmations = this.node.services.dashd.height - transaction.height + 1; 57 | } 58 | 59 | var transformed = { 60 | txid: transaction.hash, 61 | version: transaction.version, 62 | }; 63 | if (transaction.type) { 64 | transformed.type = transaction.type; 65 | } 66 | transformed.locktime = transaction.locktime; 67 | if (transaction.extraPayloadSize) { 68 | transformed.extraPayloadSize = transaction.extraPayloadSize; 69 | } 70 | if (transaction.extraPayload) { 71 | transformed.extraPayload = transaction.extraPayload; 72 | } 73 | 74 | if(transaction.coinbase) { 75 | transformed.vin = [ 76 | { 77 | coinbase: transaction.inputs[0].script, 78 | sequence: transaction.inputs[0].sequence, 79 | n: 0 80 | } 81 | ]; 82 | } else { 83 | transformed.vin = transaction.inputs.map(this.transformInput.bind(this, options)); 84 | } 85 | 86 | transformed.vout = transaction.outputs.map(this.transformOutput.bind(this, options)); 87 | 88 | transformed.blockhash = transaction.blockHash; 89 | transformed.blockheight = transaction.height; 90 | transformed.confirmations = confirmations; 91 | // TODO consider mempool txs with receivedTime? 92 | var time = transaction.blockTimestamp ? transaction.blockTimestamp : Math.round(Date.now() / 1000); 93 | transformed.time = time; 94 | if (transformed.confirmations) { 95 | transformed.blocktime = transformed.time; 96 | } 97 | 98 | if(transaction.coinbase) { 99 | transformed.isCoinBase = true; 100 | } 101 | 102 | transformed.valueOut = transaction.outputSatoshis / 1e8; 103 | transformed.size = transaction.hex.length / 2; // in bytes 104 | if (!transaction.coinbase) { 105 | transformed.valueIn = transaction.inputSatoshis / 1e8; 106 | transformed.fees = transaction.feeSatoshis / 1e8; 107 | } 108 | 109 | transformed.txlock = transaction.txlock; 110 | 111 | if (transaction.proRegTx !== undefined) { 112 | transformed.proRegTx = transaction.proRegTx; 113 | } 114 | if (transaction.proUpServTx !== undefined) { 115 | transformed.proUpServTx = transaction.proUpServTx; 116 | } 117 | if (transaction.proUpRegTx !== undefined) { 118 | transformed.proUpRegTx = transaction.proUpRegTx; 119 | } 120 | if (transaction.proUpRevTx !== undefined) { 121 | transformed.proUpRevTx = transaction.proUpRevTx; 122 | } 123 | if (transaction.cbTx !== undefined) { 124 | transformed.cbTx = transaction.cbTx; 125 | } 126 | if (transaction.qcTx !== undefined) { 127 | transformed.qcTx = transaction.qcTx; 128 | } 129 | if (transaction.mnhfTx !== undefined) { 130 | transformed.mnhfTx = transaction.mnhfTx; 131 | } 132 | 133 | callback(null, transformed); 134 | }; 135 | 136 | TxController.prototype.transformInput = function(options, input, index) { 137 | // Input scripts are validated and can be assumed to be valid 138 | var transformed = { 139 | txid: input.prevTxId, 140 | vout: input.outputIndex, 141 | sequence: input.sequence, 142 | n: index 143 | }; 144 | 145 | if (!options.noScriptSig) { 146 | transformed.scriptSig = { 147 | hex: input.script 148 | }; 149 | if (!options.noAsm) { 150 | transformed.scriptSig.asm = input.scriptAsm; 151 | } 152 | } 153 | 154 | transformed.addr = input.address; 155 | transformed.valueSat = input.satoshis; 156 | transformed.value = input.satoshis / 1e8; 157 | transformed.doubleSpentTxID = null; // TODO 158 | //transformed.isConfirmed = null; // TODO 159 | //transformed.confirmations = null; // TODO 160 | //transformed.unconfirmedInput = null; // TODO 161 | 162 | return transformed; 163 | }; 164 | 165 | TxController.prototype.transformOutput = function(options, output, index) { 166 | var transformed = { 167 | value: (output.satoshis / 1e8).toFixed(8), 168 | n: index, 169 | scriptPubKey: { 170 | hex: output.script 171 | } 172 | }; 173 | 174 | if (!options.noAsm) { 175 | transformed.scriptPubKey.asm = output.scriptAsm; 176 | } 177 | 178 | if (!options.noSpent) { 179 | transformed.spentTxId = output.spentTxId || null; 180 | transformed.spentIndex = _.isUndefined(output.spentIndex) ? null : output.spentIndex; 181 | transformed.spentHeight = output.spentHeight || null; 182 | } 183 | 184 | if (output.address) { 185 | transformed.scriptPubKey.addresses = [output.address]; 186 | var address = dashcore.Address(output.address); //TODO return type from dashcore-node 187 | transformed.scriptPubKey.type = address.type; 188 | } 189 | return transformed; 190 | }; 191 | 192 | TxController.prototype.transformInvTransaction = function(transaction, isLocked = false) { 193 | var self = this; 194 | 195 | var valueOut = 0; 196 | var vout = []; 197 | for (var i = 0; i < transaction.outputs.length; i++) { 198 | var output = transaction.outputs[i]; 199 | valueOut += output.satoshis; 200 | if (output.script) { 201 | var address = output.script.toAddress(self.node.network); 202 | if (address) { 203 | var obj = {}; 204 | obj[address.toString()] = output.satoshis; 205 | vout.push(obj); 206 | } 207 | } 208 | } 209 | 210 | var isRBF = _.some(_.map(transaction.inputs, 'sequenceNumber'), function(seq) { 211 | return seq < MAXINT - 1; 212 | }); 213 | 214 | var transformed = { 215 | txid: transaction.hash, 216 | valueOut: valueOut / 1e8, 217 | vout: vout, 218 | isRBF: isRBF, 219 | txlock: isLocked 220 | }; 221 | 222 | return transformed; 223 | }; 224 | 225 | TxController.prototype.rawTransaction = function(req, res, next) { 226 | var self = this; 227 | var txid = req.params.txid; 228 | 229 | this.node.getTransaction(txid, function(err, transaction) { 230 | if (err && err.code === -5) { 231 | return self.common.handleErrors(null, res); 232 | } else if(err) { 233 | return self.common.handleErrors(err, res); 234 | } 235 | 236 | req.rawTransaction = { 237 | 'rawtx': transaction.toBuffer().toString('hex') 238 | }; 239 | 240 | next(); 241 | }); 242 | }; 243 | 244 | TxController.prototype.showRaw = function(req, res) { 245 | if (req.rawTransaction) { 246 | res.jsonp(req.rawTransaction); 247 | } 248 | }; 249 | 250 | TxController.prototype.list = function(req, res) { 251 | var self = this; 252 | 253 | var blockHash = req.query.block; 254 | var address = req.query.address; 255 | var page = parseInt(req.query.pageNum) || 0; 256 | var pageLength = 10; 257 | var pagesTotal = 1; 258 | 259 | if(blockHash) { 260 | self.node.getBlockOverview(blockHash, function(err, block) { 261 | if(err && err.code === -5) { 262 | return self.common.handleErrors(null, res); 263 | } else if(err) { 264 | return self.common.handleErrors(err, res); 265 | } 266 | 267 | var totalTxs = block.txids.length; 268 | var txids; 269 | 270 | if(!_.isUndefined(page)) { 271 | var start = page * pageLength; 272 | txids = block.txids.slice(start, start + pageLength); 273 | pagesTotal = Math.ceil(totalTxs / pageLength); 274 | } else { 275 | txids = block.txids; 276 | } 277 | 278 | async.mapSeries(txids, function(txid, next) { 279 | self.node.getDetailedTransaction(txid, function(err, transaction) { 280 | if (err) { 281 | return next(err); 282 | } 283 | self.transformTransaction(transaction, next); 284 | }); 285 | }, function(err, transformed) { 286 | if(err) { 287 | return self.common.handleErrors(err, res); 288 | } 289 | 290 | res.jsonp({ 291 | pagesTotal: pagesTotal, 292 | txs: transformed 293 | }); 294 | }); 295 | 296 | }); 297 | } else if(address) { 298 | var options = { 299 | from: page * pageLength, 300 | to: (page + 1) * pageLength 301 | }; 302 | 303 | self.node.getAddressHistory(address, options, function(err, result) { 304 | if(err) { 305 | return self.common.handleErrors(err, res); 306 | } 307 | 308 | var txs = result.items.map(function(info) { 309 | return info.tx; 310 | }).filter(function(value, index, self) { 311 | return self.indexOf(value) === index; 312 | }); 313 | 314 | async.map( 315 | txs, 316 | function(tx, next) { 317 | self.transformTransaction(tx, next); 318 | }, 319 | function(err, transformed) { 320 | if (err) { 321 | return self.common.handleErrors(err, res); 322 | } 323 | res.jsonp({ 324 | pagesTotal: Math.ceil(result.totalCount / pageLength), 325 | txs: transformed 326 | }); 327 | } 328 | ); 329 | }); 330 | } else { 331 | return self.common.handleErrors(new Error('Block hash or address expected'), res); 332 | } 333 | }; 334 | 335 | TxController.prototype.send = function(req, res) { 336 | var self = this; 337 | if(_.isUndefined(req.body.rawtx)){ 338 | return self.common.handleErrors({ 339 | message:"Missing parameter (expected 'rawtx' a string)", 340 | code:1 341 | }, res); 342 | } 343 | this.node.sendTransaction(req.body.rawtx, function(err, txid) { 344 | if(err) { 345 | // TODO handle specific errors 346 | return self.common.handleErrors(err, res); 347 | } 348 | 349 | res.json({'txid': txid}); 350 | }); 351 | }; 352 | //Handler for InstantSend 353 | TxController.prototype.sendix = function(req, res) { 354 | var self = this; 355 | if(_.isUndefined(req.body.rawtx)){ 356 | return self.common.handleErrors({ 357 | message:"Missing parameter (expected 'rawtx' a string)", 358 | code:1 359 | }, res); 360 | } 361 | var options = {maxFeeRate: 0.00015000, isInstantSend: true}; 362 | this.node.sendTransaction(req.body.rawtx, options, function(err, txid) { 363 | if(err) { 364 | // TODO handle specific errors 365 | return self.common.handleErrors(err, res); 366 | } 367 | res.json({'txid': txid}); 368 | }); 369 | }; 370 | module.exports = TxController; 371 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var async = require('async'); 5 | var Common = require('./common'); 6 | 7 | function UtilsController(node) { 8 | this.node = node; 9 | this.common = new Common({log: this.node.log}); 10 | } 11 | 12 | UtilsController.prototype.estimateFee = function(req, res) { 13 | var self = this; 14 | var args = req.query.nbBlocks || '2'; 15 | var nbBlocks = args.split(','); 16 | 17 | async.map(nbBlocks, function(n, next) { 18 | var num = parseInt(n); 19 | // Insight and Bitcoin JSON-RPC return bitcoin for this value (instead of satoshis). 20 | self.node.services.dashd.estimateFee(num, function(err, fee) { 21 | if (err) { 22 | return next(err); 23 | } 24 | next(null, [num, fee]); 25 | }); 26 | }, function(err, result) { 27 | if (err) { 28 | return self.common.handleErrors(err, res); 29 | } 30 | res.jsonp(_.fromPairs(result)); 31 | }); 32 | 33 | }; 34 | 35 | module.exports = UtilsController; 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dashevo/insight-api", 3 | "description": "A Dash blockchain REST and WebSocket API Service", 4 | "version": "4.0.8", 5 | "repository": "git://github.com/dashevo/insight-api.git", 6 | "contributors": [ 7 | { 8 | "name": "Gustavo Cortez", 9 | "email": "cmgustavo83@gmail.com" 10 | }, 11 | { 12 | "name": "Ivan Socolsky", 13 | "email": "jungans@gmail.com" 14 | }, 15 | { 16 | "name": "Juan Ignacio Sosa Lopez", 17 | "email": "bechilandia@gmail.com" 18 | }, 19 | { 20 | "name": "Manuel Araoz", 21 | "email": "manuelaraoz@gmail.com" 22 | }, 23 | { 24 | "name": "Matias Alejo Garcia", 25 | "email": "ematiu@gmail.com" 26 | }, 27 | { 28 | "name": "Mario Colque", 29 | "email": "colquemario@gmail.com" 30 | }, 31 | { 32 | "name": "Patrick Nagurny", 33 | "email": "patrick@bitpay.com" 34 | }, 35 | { 36 | "name": "Braydon Fuller", 37 | "email": "braydon@bitpay.com" 38 | } 39 | ], 40 | "bugs": { 41 | "url": "https://github.com/dashevo/insight-api/issues" 42 | }, 43 | "homepage": "https://github.com/dashevo/insight-api", 44 | "license": "MIT", 45 | "keywords": [ 46 | "insight", 47 | "insight api", 48 | "blockchain", 49 | "dash api", 50 | "blockchain api", 51 | "json", 52 | "bitcore-dash", 53 | "dashcore" 54 | ], 55 | "engines": { 56 | "node": ">=6" 57 | }, 58 | "scripts": { 59 | "test": "NODE_ENV=test mocha -R spec --recursive" 60 | }, 61 | "main": "lib/index.js", 62 | "dependencies": { 63 | "@dashevo/dashcore-lib": "^0.22.0", 64 | "async": "^2.6.2", 65 | "body-parser": "^1.19.0", 66 | "compression": "^1.7.4", 67 | "lodash": "^4.17.20", 68 | "lru-cache": "^5.1.1", 69 | "morgan": "^1.9.1", 70 | "request": "^2.88.0" 71 | }, 72 | "devDependencies": { 73 | "chai": "^4.2.0", 74 | "mocha": "^10.0.0", 75 | "proxyquire": "^1.8.0", 76 | "should": "^13.2.3", 77 | "sinon": "^7.3.2" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /pools.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "poolName": "50BTC", 4 | "url": "https://50btc.com/", 5 | "searchStrings": [ 6 | "50BTC.com", 7 | "50btc.com" 8 | ] 9 | }, 10 | { 11 | "poolName": "175btc", 12 | "url": "http://www.175btc.com/", 13 | "searchStrings": [ 14 | "Mined By 175btc.com" 15 | ] 16 | }, 17 | { 18 | "poolName": "ASICminer", 19 | "url": "https://bitcointalk.org/index.php?topic=99497.0", 20 | "searchStrings": [ 21 | "Mined By ASICMiner" 22 | ] 23 | }, 24 | { 25 | "poolName": "AntMiner", 26 | "url": "https://bitmaintech.com/", 27 | "searchStrings": [ 28 | "AntPool" 29 | ] 30 | }, 31 | { 32 | "poolName": "agentD", 33 | "url": "http://", 34 | "searchStrings": [ 35 | "agentD" 36 | ] 37 | }, 38 | { 39 | "poolName": "Bitfury", 40 | "url": "http://bitfury.org/", 41 | "searchStrings": [ 42 | "2av0id51pct" 43 | ] 44 | }, 45 | { 46 | "poolName": "BitMinter", 47 | "url": "https://bitminter.com/", 48 | "searchStrings": [ 49 | "BitMinter" 50 | ] 51 | }, 52 | { 53 | "poolName": "Bitparking", 54 | "url": "http://bitparking.com/", 55 | "searchStrings": [ 56 | "bitparking" 57 | ] 58 | }, 59 | { 60 | "poolName": "BTC Guild", 61 | "url": "https://www.btcguild.com/", 62 | "searchStrings": [ 63 | "Mined by BTC Guild", 64 | "BTC Guild" 65 | ] 66 | }, 67 | { 68 | "poolName": "bcpool.io", 69 | "url": "https://bcpool.io/", 70 | "searchStrings": [ 71 | "bcpool" 72 | ] 73 | }, 74 | { 75 | "poolName": "Discus Fish", 76 | "url": "http://f2pool.com/", 77 | "searchStrings": [ 78 | "七彩神仙鱼", 79 | "Made in China", 80 | "Mined by user" 81 | ] 82 | }, 83 | { 84 | "poolName": "Discus Fish Solo", 85 | "url": "http://f2pool.com/", 86 | "searchStrings": [ 87 | "For Pierce and Paul" 88 | ] 89 | }, 90 | { 91 | "poolName": "Cointerra", 92 | "url": "http://cointerra.com/", 93 | "searchStrings": [ 94 | "cointerra" 95 | ] 96 | }, 97 | { 98 | "poolName": "CybtcPool", 99 | "url": "https://cybtc.info/", 100 | "searchStrings": [ 101 | "/CybtcPool/" 102 | ] 103 | }, 104 | { 105 | "poolName": "Eligius", 106 | "url": "http://eligius.st/", 107 | "searchStrings": [ 108 | "Eligius" 109 | ] 110 | }, 111 | { 112 | "poolName": "EclipseMC", 113 | "url": "https://eclipsemc.com/", 114 | "searchStrings": [ 115 | "Josh Zerlan was here!", 116 | "EclipseMC", 117 | "Aluminum Falcon" 118 | ] 119 | }, 120 | { 121 | "poolName": "GIVE-ME-COINS", 122 | "url": "https://give-me-coins.com/", 123 | "searchStrings": [ 124 | "Mined at GIVE-ME-COINS.com" 125 | ] 126 | }, 127 | { 128 | "poolName": "ghash.io", 129 | "url": "https://ghash.io/", 130 | "searchStrings": [ 131 | "ghash.io", 132 | "GHash.IO" 133 | ] 134 | }, 135 | { 136 | "poolName": "HHTT", 137 | "url": "http://hhtt.1209k.com/", 138 | "searchStrings": [ 139 | "HHTT" 140 | ] 141 | }, 142 | { 143 | "poolName": "KNCminer", 144 | "url": "https://www.kncminer.com/", 145 | "searchStrings": [ 146 | "KnCMiner" 147 | ] 148 | }, 149 | { 150 | "poolName": "Luxor", 151 | "url": "https://mining.luxor.tech/", 152 | "searchStrings": [ 153 | "LUXOR" 154 | ] 155 | }, 156 | { 157 | "poolName": "Megabigpower", 158 | "url": "http://megabigpower.com/", 159 | "searchStrings": [ 160 | "megabigpower.com" 161 | ] 162 | }, 163 | { 164 | "poolName": "MultiCoin", 165 | "url": "https://multicoin.co/", 166 | "searchStrings": [ 167 | "MultiCoin.co" 168 | ] 169 | }, 170 | { 171 | "poolName": "Mt Red", 172 | "url": "https://mtred.com/‎", 173 | "searchStrings": [ 174 | "/mtred/" 175 | ] 176 | }, 177 | { 178 | "poolName": "MaxBTC", 179 | "url": "https://www.maxbtc.com", 180 | "searchStrings": [ 181 | "MaxBTC" 182 | ] 183 | }, 184 | { 185 | "poolName": "NMCbit", 186 | "url": "http://nmcbit.com/", 187 | "searchStrings": [ 188 | "nmcbit.com" 189 | ] 190 | }, 191 | { 192 | "poolName": "ozcoin", 193 | "url": "https://ozco.in/", 194 | "searchStrings": [ 195 | "ozco.in", 196 | "ozcoin" 197 | ] 198 | }, 199 | { 200 | "poolName": "Poolin", 201 | "url": "https://poolin.com/", 202 | "searchStrings": [ 203 | "/poolin.com/" 204 | ] 205 | }, 206 | { 207 | "poolName": "Polmine.pl", 208 | "url": "https://polmine.pl", 209 | "searchStrings": [ 210 | "by polmine.pl" 211 | ] 212 | }, 213 | { 214 | "poolName": "simplecoin", 215 | "url": "http://simplecoin.us/", 216 | "searchStrings": [ 217 | "simplecoin.us ftw" 218 | ] 219 | }, 220 | { 221 | "poolName": "SlushPool", 222 | "url": "https://slushpool.com/", 223 | "searchStrings": [ 224 | "/slush/" 225 | ] 226 | }, 227 | { 228 | "poolName": "TripleMining", 229 | "url": "https://www.triplemining.com/", 230 | "searchStrings": [ 231 | "Triplemining.com" 232 | ] 233 | }, 234 | { 235 | "poolName": "wizkid057", 236 | "url": "http://wizkid057.com/btc", 237 | "searchStrings": [ 238 | "wizkid057" 239 | ] 240 | }, 241 | { 242 | "poolName": "Yourbtc.net", 243 | "url": "http://yourbtc.net/", 244 | "searchStrings": [ 245 | "yourbtc.net" 246 | ] 247 | }, 248 | { 249 | "poolName": "BTCChina Pool", 250 | "url": "https://pool.btcchina.com/", 251 | "searchStrings": [ 252 | "BTCChina Pool", 253 | "btcchina.com" 254 | ] 255 | }, 256 | { 257 | "poolName":"BTCC Pool", 258 | "url":"https://pool.btcc.com/", 259 | "searchStrings":[ 260 | "/BTCC/" 261 | ] 262 | }, 263 | { 264 | "poolName":"Mining Pool Hub", 265 | "url":"https://miningpoolhub.com/", 266 | "searchStrings":[ 267 | "/mph/" 268 | ] 269 | }, 270 | { 271 | "poolName":"CoinMine.pl", 272 | "url":"https://www2.coinmine.pl/", 273 | "searchStrings":[ 274 | "/CoinMinePL/" 275 | ] 276 | }, 277 | { 278 | "poolName":"ViaBTC", 279 | "url":"https://viabtc.com/", 280 | "searchStrings":[ 281 | "/ViaBTC/" 282 | ] 283 | }, 284 | { 285 | "poolName":"DASH.TOP", 286 | "url":"http://dash.btc.top/", 287 | "searchStrings":[ 288 | "/DASH.TOP/" 289 | ] 290 | }, 291 | { 292 | "poolName":"beePool.org", 293 | "url":"http://www.beepool.org/", 294 | "searchStrings":[ 295 | "/BeePool.org/" 296 | ] 297 | }, 298 | { 299 | "poolName":"ZPOOL", 300 | "url":"https://www.zpool.ca/", 301 | "searchStrings":[ 302 | "www.zpool.ca" 303 | ] 304 | }, 305 | { 306 | "poolName":"Node Stratum Pool", 307 | "url":"https://github.com/zone117x/node-stratum-pool", 308 | "searchStrings":[ 309 | "/nodeStratum/" 310 | ] 311 | }, 312 | { 313 | "poolName":"Stratum Pool", 314 | "url":"https://github.com/dashpay/dash-stratum", 315 | "searchStrings":[ 316 | "/stratum/" 317 | ] 318 | } 319 | ] 320 | -------------------------------------------------------------------------------- /test/addresses.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var sinon = require('sinon'); 3 | var should = require('should'); 4 | var AddressController = require('../lib/addresses'); 5 | var _ = require('lodash'); 6 | var dashcore = require('@dashevo/dashcore-lib'); 7 | 8 | var txinfos = { 9 | totalCount: 2, 10 | items: [ 11 | { 12 | 'address': 'yRCQiUCPSpHWZDJZ2tr8MvBAh1VHqhVd7w', 13 | 'satoshis': 2782729129, 14 | 'height': 534105, 15 | 'confirmations': 123, 16 | 'timestamp': 1441068774, 17 | 'fees': 35436, 18 | 'outputIndexes': [ 19 | 0 20 | ], 21 | 'inputIndexes': [], 22 | 'tx': { 23 | 'hash': 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', 24 | 'version': 1, 25 | 'inputs': [ 26 | { 27 | 'prevTxId': 'ea5e5bafbf29cdf6f6097ab344128477e67889d4d6203cb43594836daa6cc425', 28 | 'outputIndex': 1, 29 | 'sequenceNumber': 4294967294, 30 | 'script': '483045022100f4d169783bef70e3943d2a617cce55d9fe4e33fc6f9880b8277265e2f619a97002201238648abcdf52960500664e969046d41755f7fc371971ebc78002fc418465a6012103acdcd31d51272403ce0829447e59e2ac9e08ed0bf92011cbf7420addf24534e6', 31 | 'scriptString': '72 0x3045022100f4d169783bef70e3943d2a617cce55d9fe4e33fc6f9880b8277265e2f619a97002201238648abcdf52960500664e969046d41755f7fc371971ebc78002fc418465a601 33 0x03acdcd31d51272403ce0829447e59e2ac9e08ed0bf92011cbf7420addf24534e6', 32 | 'output': { 33 | 'satoshis': 2796764565, 34 | 'script': '76a91488b1fe8aec5ae4358a11447a2f22b2781faedb9b88ac' 35 | } 36 | } 37 | ], 38 | 'outputs': [ 39 | { 40 | 'satoshis': 2782729129, 41 | 'script': '76a9143583efb5e64a4668c6c54bb5fcc30af4417b4f2d88ac' 42 | }, 43 | { 44 | 'satoshis': 14000000, 45 | 'script': '76a9149713201957f42379e574d7c70d506ee49c2c8ad688ac' 46 | } 47 | ], 48 | 'nLockTime': 534089 49 | } 50 | }, 51 | { 52 | 'address': 'yRCQiUCPSpHWZDJZ2tr8MvBAh1VHqhVd7w', 53 | 'satoshis': -2782729129, 54 | 'height': 534110, 55 | 'confirmations': 118, 56 | 'timestamp': 1441072817, 57 | 'fees': 35437, 58 | 'outputIndexes': [], 59 | 'inputIndexes': [ 60 | '0' 61 | ], 62 | 'tx': { 63 | 'hash': '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3', 64 | 'version': 1, 65 | 'inputs': [ 66 | { 67 | 'prevTxId': 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', 68 | 'outputIndex': 0, 69 | 'sequenceNumber': 4294967294, 70 | 'script': '47304402201ee69281db6b95bb1aa3074059b67581635b719e8f64e4c2694db6ec56ad9447022011e91528996ea459b1fb2c0b59363fecbefe4bc2ca90f7b2382bdaa358f2d5640121034cc057b12a68ee79df998004b9a1341bbb18b17ea4939bebaa3bac001e940f24', 71 | 'scriptString': '71 0x304402201ee69281db6b95bb1aa3074059b67581635b719e8f64e4c2694db6ec56ad9447022011e91528996ea459b1fb2c0b59363fecbefe4bc2ca90f7b2382bdaa358f2d56401 33 0x034cc057b12a68ee79df998004b9a1341bbb18b17ea4939bebaa3bac001e940f24', 72 | 'output': { 73 | 'satoshis': 2782729129, 74 | 'script': '76a9143583efb5e64a4668c6c54bb5fcc30af4417b4f2d88ac' 75 | } 76 | } 77 | ], 78 | 'outputs': [ 79 | { 80 | 'satoshis': 2764693692, 81 | 'script': '76a91456e446bc3489543d8324c6d0271524c0bd0506dd88ac' 82 | }, 83 | { 84 | 'satoshis': 18000000, 85 | 'script': '76a914011d2963b619186a318f768dddfd98cd553912a088ac' 86 | } 87 | ], 88 | 'nLockTime': 534099 89 | } 90 | } 91 | ] 92 | }; 93 | 94 | var tx = { 95 | height: 534181, 96 | blockTimestamp: 1441116143, 97 | blockHash: '0000000000000041ddc94ecf4f86a456a83b2e320c36c6f0c13ff92c7e75f013', 98 | hex: '0100000002f379708395d0a0357514205a3758a0317926428356e54a09089852fc6f7297ea010000008a473044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e40141040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964dffffffffb758ffd4c31693d9620f326385404530a079d5e60a90b94e46d3c2dbc29c0a98020000008a473044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b014104d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2ffffffff03605b0300000000001976a914b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d88ac40992d03000000001976a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac256c0400000000001976a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac00000000', 99 | hash: '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 100 | txlock: false, 101 | version: 1, 102 | inputSatoshis: 53839829, 103 | outputSatoshis: 53829829, 104 | feeSatoshis: 10000, 105 | inputs: [ 106 | { 107 | address: 'yU4ALabEhMFaXtqJBGRKySW4qCXQG365uR', 108 | prevTxId: 'ea97726ffc529808094ae5568342267931a058375a20147535a0d095837079f3', 109 | outputIndex: 1, 110 | sequence: 4294967295, 111 | script: '473044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e40141040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d', 112 | scriptAsm: '3044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e401 040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d', 113 | satoshis: 53540000, 114 | }, 115 | { 116 | address: 'ygKnjKcpevopnXUQFD9CrQ4huRmspzbajv', 117 | prevTxId: '980a9cc2dbc2d3464eb9900ae6d579a03045408563320f62d99316c3d4ff58b7', 118 | outputIndex: 2, 119 | sequence: 4294967295, 120 | script: '473044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b014104d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2', 121 | scriptAsm: '3044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b01 04d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2', 122 | satoshis: 299829, 123 | } 124 | ], 125 | outputs: [ 126 | { 127 | satoshis: 220000, 128 | script: '76a914b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d88ac', 129 | scriptAsm: 'OP_DUP OP_HASH160 b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d OP_EQUALVERIFY OP_CHECKSIG', 130 | address: 'ydFWt96q1AJzG7rQQPYygGpnRG3djmG8nJ' 131 | }, 132 | { 133 | satoshis: 53320000, 134 | address: 'yfYhcwQkixQFgYV8trXmXTajQynSNbRBFE', 135 | script: '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac', 136 | scriptAsm: 'OP_DUP OP_HASH160 d2ec20bb8e5f25a52f730384b803d95683250e0b OP_EQUALVERIFY OP_CHECKSIG' 137 | }, 138 | { 139 | address: 'yUN2ZHVcy18UFmSAcrYWZgyz1NyhzY9BD7', 140 | satoshis: 289829, 141 | script: '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac', 142 | scriptAsm: 'OP_DUP OP_HASH160 583df9fa56ad961051e00ca93e68dfaf1eab9ec5 OP_EQUALVERIFY OP_CHECKSIG' 143 | } 144 | ], 145 | locktime: 0 146 | }; 147 | 148 | var txinfos2 = { 149 | totalCount: 1, 150 | items: [ 151 | { 152 | tx: tx 153 | } 154 | ] 155 | }; 156 | 157 | var utxos = [ 158 | { 159 | 'address': 'yfYhcwQkixQFgYV8trXmXTajQynSNbRBFE', 160 | 'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 161 | 'outputIndex': 1, 162 | 'timestamp': 1441116143, 163 | 'satoshis': 53320000, 164 | 'script': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac', 165 | 'height': 534181, 166 | 'confirmations': 50 167 | }, 168 | { 169 | 'address': 'yUN2ZHVcy18UFmSAcrYWZgyz1NyhzY9BD7', 170 | 'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 171 | 'outputIndex': 2, 172 | 'timestamp': 1441116143, 173 | 'satoshis': 289829, 174 | 'script': '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac', 175 | 'height': 534181, 176 | 'confirmations': 50 177 | } 178 | ]; 179 | 180 | describe('Addresses', function() { 181 | var summary = { 182 | balance: 0, 183 | totalReceived: 2782729129, 184 | totalSpent: 2782729129, 185 | unconfirmedBalance: 0, 186 | appearances: 2, 187 | unconfirmedAppearances: 0, 188 | txids: [ 189 | 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', 190 | '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3' 191 | ] 192 | }; 193 | describe('/addr/:addr', function() { 194 | var node = { 195 | getAddressSummary: sinon.stub().callsArgWith(2, null, summary) 196 | }; 197 | 198 | var addresses = new AddressController(node); 199 | var req = { 200 | addr: 'yRCQiUCPSpHWZDJZ2tr8MvBAh1VHqhVd7w', 201 | query: {} 202 | }; 203 | 204 | it('should have correct data', function(done) { 205 | var insight = { 206 | 'addrStr': 'yRCQiUCPSpHWZDJZ2tr8MvBAh1VHqhVd7w', 207 | 'balance': 0, 208 | 'balanceSat': 0, 209 | 'totalReceived': 27.82729129, 210 | 'totalReceivedSat': 2782729129, 211 | 'totalSent': 27.82729129, 212 | 'totalSentSat': 2782729129, 213 | 'unconfirmedBalance': 0, 214 | 'unconfirmedBalanceSat': 0, 215 | 'unconfirmedTxApperances': 0, 216 | 'unconfirmedAppearances': 0, 217 | 'txApperances': 2, 218 | 'txAppearances': 2, 219 | 'transactions': [ 220 | 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', 221 | '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3' 222 | ] 223 | }; 224 | 225 | var res = { 226 | jsonp: function(data) { 227 | should(data).eql(insight); 228 | done(); 229 | } 230 | }; 231 | addresses.show(req, res); 232 | }); 233 | 234 | it('handle error', function() { 235 | var testnode = {}; 236 | testnode.log = {}; 237 | testnode.log.error = sinon.stub(); 238 | var controller = new AddressController(testnode); 239 | controller.getAddressSummary = sinon.stub().callsArgWith(2, new Error('test')); 240 | var req = { 241 | query: { 242 | noTxList: 1 243 | }, 244 | addr: 'yRCQiUCPSpHWZDJZ2tr8MvBAh1VHqhVd7w' 245 | }; 246 | var send = sinon.stub(); 247 | var status = sinon.stub().returns({send: send}); 248 | var res = { 249 | status: status 250 | }; 251 | controller.show(req, res); 252 | send.callCount.should.equal(1); 253 | status.callCount.should.equal(1); 254 | status.args[0][0].should.equal(503); 255 | send.args[0][0].should.equal('test'); 256 | }); 257 | 258 | it('/balance', function(done) { 259 | var insight = 0; 260 | 261 | var res = { 262 | jsonp: function(data) { 263 | should(data).eql(insight); 264 | done(); 265 | } 266 | }; 267 | addresses.balance(req, res); 268 | }); 269 | 270 | it('/totalReceived', function(done) { 271 | var insight = 2782729129; 272 | 273 | var res = { 274 | jsonp: function(data) { 275 | should(data).eql(insight); 276 | done(); 277 | } 278 | }; 279 | 280 | addresses.totalReceived(req, res); 281 | }); 282 | 283 | it('/totalSent', function(done) { 284 | var insight = 2782729129; 285 | 286 | var res = { 287 | jsonp: function(data) { 288 | should(data).eql(insight); 289 | done(); 290 | } 291 | }; 292 | 293 | addresses.totalSent(req, res); 294 | }); 295 | 296 | it('/unconfirmedBalance', function(done) { 297 | var insight = 0; 298 | 299 | var res = { 300 | jsonp: function(data) { 301 | should(data).eql(insight); 302 | done(); 303 | } 304 | }; 305 | 306 | addresses.unconfirmedBalance(req, res); 307 | }); 308 | }); 309 | 310 | describe('/addr/:addr/utxo', function() { 311 | it('should have correct data', function(done) { 312 | var insight = [ 313 | { 314 | 'address': 'yfYhcwQkixQFgYV8trXmXTajQynSNbRBFE', 315 | 'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 316 | 'vout': 1, 317 | 'ts': 1441116143, 318 | 'scriptPubKey': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac', 319 | 'amount': 0.5332, 320 | 'confirmations': 50, 321 | 'height': 534181, 322 | 'satoshis': 53320000, 323 | 'confirmationsFromCache': true 324 | } 325 | ]; 326 | 327 | var todos = [ 328 | { 329 | confirmationsFromCache: true 330 | } 331 | ]; 332 | 333 | var node = { 334 | services: { 335 | dashd: { 336 | height: 534230 337 | } 338 | }, 339 | getAddressUnspentOutputs: sinon.stub().callsArgWith(2, null, utxos.slice(0, 1)) 340 | }; 341 | 342 | var addresses = new AddressController(node); 343 | 344 | var req = { 345 | addr: 'yfYhcwQkixQFgYV8trXmXTajQynSNbRBFE' 346 | }; 347 | 348 | var res = { 349 | jsonp: function(data) { 350 | var merged = _.merge(data, todos); 351 | should(merged).eql(insight); 352 | done(); 353 | } 354 | }; 355 | 356 | addresses.utxo(req, res); 357 | }); 358 | }); 359 | 360 | describe('/addrs/:addrs/utxo', function() { 361 | it('should have the correct data', function(done) { 362 | var insight = [ 363 | { 364 | 'address': 'yfYhcwQkixQFgYV8trXmXTajQynSNbRBFE', 365 | 'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 366 | 'vout': 1, 367 | 'ts': 1441116143, 368 | 'scriptPubKey': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac', 369 | 'amount': 0.5332, 370 | 'height': 534181, 371 | 'satoshis': 53320000, 372 | 'confirmations': 50, 373 | 'confirmationsFromCache': true 374 | }, 375 | { 376 | 'address': 'yUN2ZHVcy18UFmSAcrYWZgyz1NyhzY9BD7', 377 | 'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 378 | 'vout': 2, 379 | 'ts': 1441116143, 380 | 'scriptPubKey': '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac', 381 | 'amount': 0.00289829, 382 | 'height': 534181, 383 | 'satoshis': 289829, 384 | 'confirmations': 50, 385 | 'confirmationsFromCache': true 386 | } 387 | ]; 388 | 389 | var todos = [ 390 | { 391 | confirmationsFromCache: true 392 | }, { 393 | confirmationsFromCache: true 394 | } 395 | ]; 396 | 397 | var node = { 398 | services: { 399 | dashd: { 400 | height: 534230 401 | } 402 | }, 403 | getAddressUnspentOutputs: sinon.stub().callsArgWith(2, null, utxos) 404 | }; 405 | 406 | var addresses = new AddressController(node); 407 | 408 | var req = { 409 | addrs: 'yfYhcwQkixQFgYV8trXmXTajQynSNbRBFE,yUN2ZHVcy18UFmSAcrYWZgyz1NyhzY9BD7' 410 | }; 411 | 412 | var res = { 413 | jsonp: function(data) { 414 | var merged = _.merge(data, todos); 415 | should(merged).eql(insight); 416 | done(); 417 | } 418 | }; 419 | 420 | addresses.multiutxo(req, res); 421 | }); 422 | }); 423 | 424 | describe('/addrs/:addrs/txs', function() { 425 | it('should have correct data', function(done) { 426 | var insight = { 427 | 'totalItems': 1, 428 | 'from': 0, 429 | 'to': 1, 430 | 'items': [ 431 | { 432 | 'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 433 | 'txlock': false, 434 | 'version': 1, 435 | 'locktime': 0, 436 | 'vin': [ 437 | { 438 | 'txid': 'ea97726ffc529808094ae5568342267931a058375a20147535a0d095837079f3', 439 | 'vout': 1, 440 | 'scriptSig': { 441 | 'asm': '3044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e401 040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d', 442 | 'hex': '473044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e40141040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d' 443 | }, 444 | 'sequence': 4294967295, 445 | 'n': 0, 446 | 'addr': 'yU4ALabEhMFaXtqJBGRKySW4qCXQG365uR', 447 | 'valueSat': 53540000, 448 | 'value': 0.5354, 449 | 'doubleSpentTxID': null 450 | }, 451 | { 452 | 'txid': '980a9cc2dbc2d3464eb9900ae6d579a03045408563320f62d99316c3d4ff58b7', 453 | 'vout': 2, 454 | 'scriptSig': { 455 | 'asm': '3044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b01 04d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2', 456 | 'hex': '473044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b014104d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2' 457 | }, 458 | 'sequence': 4294967295, 459 | 'n': 1, 460 | 'addr': 'ygKnjKcpevopnXUQFD9CrQ4huRmspzbajv', 461 | 'valueSat': 299829, 462 | 'value': 0.00299829, 463 | 'doubleSpentTxID': null 464 | } 465 | ], 466 | 'vout': [ 467 | { 468 | 'value': '0.00220000', 469 | 'n': 0, 470 | 'scriptPubKey': { 471 | 'asm': 'OP_DUP OP_HASH160 b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d OP_EQUALVERIFY OP_CHECKSIG', 472 | 'hex': '76a914b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d88ac', 473 | 'reqSigs': 1, 474 | 'type': 'pubkeyhash', 475 | 'addresses': [ 476 | 'ydFWt96q1AJzG7rQQPYygGpnRG3djmG8nJ' 477 | ] 478 | }, 479 | 'spentHeight': null, 480 | 'spentIndex': null, 481 | 'spentTxId': null 482 | }, 483 | { 484 | 'value': '0.53320000', 485 | 'n': 1, 486 | 'scriptPubKey': { 487 | 'asm': 'OP_DUP OP_HASH160 d2ec20bb8e5f25a52f730384b803d95683250e0b OP_EQUALVERIFY OP_CHECKSIG', 488 | 'hex': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac', 489 | 'reqSigs': 1, 490 | 'type': 'pubkeyhash', 491 | 'addresses': [ 492 | 'yfYhcwQkixQFgYV8trXmXTajQynSNbRBFE' 493 | ], 494 | }, 495 | 'spentHeight': null, 496 | 'spentIndex': null, 497 | 'spentTxId': null 498 | }, 499 | { 500 | 'value': '0.00289829', 501 | 'n': 2, 502 | 'scriptPubKey': { 503 | 'asm': 'OP_DUP OP_HASH160 583df9fa56ad961051e00ca93e68dfaf1eab9ec5 OP_EQUALVERIFY OP_CHECKSIG', 504 | 'hex': '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac', 505 | 'reqSigs': 1, 506 | 'type': 'pubkeyhash', 507 | 'addresses': [ 508 | 'yUN2ZHVcy18UFmSAcrYWZgyz1NyhzY9BD7' 509 | ] 510 | }, 511 | 'spentHeight': null, 512 | 'spentIndex': null, 513 | 'spentTxId': null 514 | } 515 | ], 516 | 'blockhash': '0000000000000041ddc94ecf4f86a456a83b2e320c36c6f0c13ff92c7e75f013', 517 | 'blockheight': 534181, 518 | 'confirmations': 52, 519 | 'time': 1441116143, 520 | 'blocktime': 1441116143, 521 | 'valueOut': 0.53829829, 522 | 'size': 470, 523 | 'valueIn': 0.53839829, 524 | 'fees': 0.0001, 525 | 'firstSeenTs': 1441108193 526 | } 527 | ] 528 | }; 529 | 530 | var todos = { 531 | 'items': [ 532 | { 533 | 'vout': [ 534 | { 535 | 'scriptPubKey': { 536 | 'reqSigs': 1, 537 | } 538 | }, 539 | { 540 | 'scriptPubKey': { 541 | 'reqSigs': 1, 542 | } 543 | }, 544 | { 545 | 'scriptPubKey': { 546 | 'reqSigs': 1, 547 | } 548 | } 549 | ], 550 | 'firstSeenTs': 1441108193 551 | } 552 | ] 553 | }; 554 | 555 | var node = { 556 | getAddressHistory: sinon.stub().callsArgWith(2, null, txinfos2), 557 | services: { 558 | dashd: { 559 | height: 534232 560 | } 561 | }, 562 | network: 'testnet' 563 | }; 564 | 565 | var addresses = new AddressController(node); 566 | 567 | var req = { 568 | addrs: 'yfYhcwQkixQFgYV8trXmXTajQynSNbRBFE,yUN2ZHVcy18UFmSAcrYWZgyz1NyhzY9BD7', 569 | query: {}, 570 | body: {} 571 | }; 572 | 573 | var res = { 574 | jsonp: function(data) { 575 | var merged = _.merge(data, todos); 576 | should(merged).eql(insight); 577 | done(); 578 | } 579 | }; 580 | 581 | addresses.multitxs(req, res); 582 | }); 583 | it('should have trimmed data', function(done) { 584 | var insight = { 585 | 'totalItems': 1, 586 | 'from': 0, 587 | 'to': 1, 588 | 'items': [ 589 | { 590 | 'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 591 | 'txlock': false, 592 | 'version': 1, 593 | 'locktime': 0, 594 | 'vin': [ 595 | { 596 | 'txid': 'ea97726ffc529808094ae5568342267931a058375a20147535a0d095837079f3', 597 | 'vout': 1, 598 | 'sequence': 4294967295, 599 | 'n': 0, 600 | 'addr': 'yU4ALabEhMFaXtqJBGRKySW4qCXQG365uR', 601 | 'valueSat': 53540000, 602 | 'value': 0.5354, 603 | 'doubleSpentTxID': null 604 | }, 605 | { 606 | 'txid': '980a9cc2dbc2d3464eb9900ae6d579a03045408563320f62d99316c3d4ff58b7', 607 | 'vout': 2, 608 | 'sequence': 4294967295, 609 | 'n': 1, 610 | 'addr': 'ygKnjKcpevopnXUQFD9CrQ4huRmspzbajv', 611 | 'valueSat': 299829, 612 | 'value': 0.00299829, 613 | 'doubleSpentTxID': null 614 | } 615 | ], 616 | 'vout': [ 617 | { 618 | 'value': '0.00220000', 619 | 'n': 0, 620 | 'scriptPubKey': { 621 | 'hex': '76a914b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d88ac', 622 | 'reqSigs': 1, 623 | 'type': 'pubkeyhash', 624 | 'addresses': [ 625 | 'ydFWt96q1AJzG7rQQPYygGpnRG3djmG8nJ' 626 | ] 627 | } 628 | }, 629 | { 630 | 'value': '0.53320000', 631 | 'n': 1, 632 | 'scriptPubKey': { 633 | 'hex': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac', 634 | 'reqSigs': 1, 635 | 'type': 'pubkeyhash', 636 | 'addresses': [ 637 | 'yfYhcwQkixQFgYV8trXmXTajQynSNbRBFE' 638 | ], 639 | } 640 | }, 641 | { 642 | 'value': '0.00289829', 643 | 'n': 2, 644 | 'scriptPubKey': { 645 | 'hex': '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac', 646 | 'reqSigs': 1, 647 | 'type': 'pubkeyhash', 648 | 'addresses': [ 649 | 'yUN2ZHVcy18UFmSAcrYWZgyz1NyhzY9BD7' 650 | ] 651 | } 652 | } 653 | ], 654 | 'blockhash': '0000000000000041ddc94ecf4f86a456a83b2e320c36c6f0c13ff92c7e75f013', 655 | 'blockheight': 534181, 656 | 'confirmations': 52, 657 | 'time': 1441116143, 658 | 'blocktime': 1441116143, 659 | 'valueOut': 0.53829829, 660 | 'size': 470, 661 | 'valueIn': 0.53839829, 662 | 'fees': 0.0001, 663 | 'firstSeenTs': 1441108193 664 | } 665 | ] 666 | }; 667 | 668 | var todos = { 669 | 'items': [ 670 | { 671 | 'vout': [ 672 | { 673 | 'scriptPubKey': { 674 | 'reqSigs': 1, 675 | } 676 | }, 677 | { 678 | 'scriptPubKey': { 679 | 'reqSigs': 1, 680 | } 681 | }, 682 | { 683 | 'scriptPubKey': { 684 | 'reqSigs': 1, 685 | } 686 | } 687 | ], 688 | 'firstSeenTs': 1441108193 689 | } 690 | ] 691 | }; 692 | 693 | var node = { 694 | getAddressHistory: sinon.stub().callsArgWith(2, null, txinfos2), 695 | services: { 696 | dashd: { 697 | height: 534232 698 | } 699 | }, 700 | network: 'testnet' 701 | }; 702 | 703 | var addresses = new AddressController(node); 704 | 705 | var req = { 706 | addrs: 'yfYhcwQkixQFgYV8trXmXTajQynSNbRBFE,yUN2ZHVcy18UFmSAcrYWZgyz1NyhzY9BD7', 707 | query: {noSpent: '1', noScriptSig: '1', noAsm: '1'}, 708 | body: {} 709 | }; 710 | 711 | var res = { 712 | jsonp: function(data) { 713 | var merged = _.merge(data, todos); 714 | should(merged).eql(insight); 715 | done(); 716 | } 717 | }; 718 | 719 | addresses.multitxs(req, res); 720 | }); 721 | }); 722 | describe('#_getTransformOptions', function() { 723 | it('will return false with value of string "0"', function() { 724 | var node = {}; 725 | var addresses = new AddressController(node); 726 | var req = { 727 | query: { 728 | noAsm: '0', 729 | noScriptSig: '0', 730 | noSpent: '0' 731 | } 732 | }; 733 | var options = addresses._getTransformOptions(req); 734 | options.should.eql({ 735 | noAsm: false, 736 | noScriptSig: false, 737 | noSpent: false 738 | }); 739 | }); 740 | it('will return true with value of string "1"', function() { 741 | var node = {}; 742 | var addresses = new AddressController(node); 743 | var req = { 744 | query: { 745 | noAsm: '1', 746 | noScriptSig: '1', 747 | noSpent: '1' 748 | } 749 | }; 750 | var options = addresses._getTransformOptions(req); 751 | options.should.eql({ 752 | noAsm: true, 753 | noScriptSig: true, 754 | noSpent: true 755 | }); 756 | }); 757 | it('will return true with value of number "1"', function() { 758 | var node = {}; 759 | var addresses = new AddressController(node); 760 | var req = { 761 | query: { 762 | noAsm: 1, 763 | noScriptSig: 1, 764 | noSpent: 1 765 | } 766 | }; 767 | var options = addresses._getTransformOptions(req); 768 | options.should.eql({ 769 | noAsm: true, 770 | noScriptSig: true, 771 | noSpent: true 772 | }); 773 | }); 774 | }); 775 | }); 776 | -------------------------------------------------------------------------------- /test/blocks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var sinon = require('sinon'); 5 | var BlockController = require('../lib/blocks'); 6 | var dashcore = require('@dashevo/dashcore-lib'); 7 | var _ = require('lodash'); 8 | 9 | var blocks = require('./data/blocks.json'); 10 | 11 | var blockIndexes = { 12 | '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7': { 13 | hash: '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7', 14 | chainWork: '0000000000000000000000000000000000000000000000054626b1839ade284a', 15 | prevHash: '00000000000001a55f3214e9172eb34b20e0bc5bd6b8007f3f149fca2c8991a4', 16 | nextHash: '000000000001e866a8057cde0c650796cb8a59e0e6038dc31c69d7ca6649627d', 17 | confirmations: 119, 18 | height: 533974 19 | }, 20 | '000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7': { 21 | hash: '000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7', 22 | chainWork: '00000000000000000000000000000000000000000000000544ea52e1575ca753', 23 | prevHash: '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441', 24 | confirmations: 119, 25 | height: 533951 26 | }, 27 | '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441': { 28 | hash: '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441', 29 | chainWork: '00000000000000000000000000000000000000000000000544ea52e0575ba752', 30 | prevHash: '000000000001b9c41e6c4a7b81a068b50cf3f522ee4ac1e942e75ec16e090547', 31 | height: 533950 32 | }, 33 | '000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4': { 34 | hash: '000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4', 35 | prevHash: '00000000000000000a9d74a7b527f7b995fc21ceae5aa21087b443469351a362', 36 | height: 375493 37 | }, 38 | '0000000000108d4b9231f4ec99ab5dc970b6ec740745f44eee0754f67d598ac3': { 39 | hash: '0000000000108d4b9231f4ec99ab5dc970b6ec740745f44eee0754f67d598ac3', 40 | prevHash: '00000000000a9b54c020f1151ae80c811c116bd9e8a48f1a3b4606be32e8323a', 41 | height: 99999, 42 | difficulty: 3919.936330381069 43 | }, 44 | '000000000008b8c4e86d070a78c978957ae7f0f127ff91aae6e4b0964c92d0b5': { 45 | hash: '000000000008b8c4e86d070a78c978957ae7f0f127ff91aae6e4b0964c92d0b5', 46 | prevHash: '000000000011e27b13e5c27ec41f4ab2ed404ca6b1cf5b0c98e1a7bce1ea8183', 47 | height: 299999, 48 | difficulty: 2375.159827856423 49 | }, 50 | '000000000000c53bf17a98b9ee042d6d4c3faf37d7a9f5c1335cce6df896f2f4': { 51 | hash: '000000000000c53bf17a98b9ee042d6d4c3faf37d7a9f5c1335cce6df896f2f4', 52 | chainWork: '00000000000000000000000000000000000000000000000147108bdaa532b7cf', 53 | prevHash: '00000000000025fbeac57a69598c7aa4c26954e33aba1e1883bb4bf168e8e6b4', 54 | nextHash: '000000000000a0b730b5be60e65b4a730d1fdcf1d023c9e42c0e5bf4a059f709', 55 | confirmations: 42, 56 | height: 599999, 57 | difficulty: 75786.58855499285 58 | }, 59 | '0000047d24635e347be3aaaeb66c26be94901a2f962feccd4f95090191f208c1':{ 60 | hash: '0000047d24635e347be3aaaeb66c26be94901a2f962feccd4f95090191f208c1', 61 | chainWork: '0000000000000000000000000000000000000000000000000000000000200011', 62 | prevHash: '00000bafbc94add76cb75e2ec92894837288a481e5c005f6563d91623bf8bc2c', 63 | nextHash: '00000c6264fab4ba2d23990396f42a76aa4822f03cbc7634b79f4dfea36fccc2', 64 | confirmations: 40493, 65 | height: 1, 66 | difficulty: 0.0002441371325370145 67 | }, 68 | 1:{ 69 | hash: '0000047d24635e347be3aaaeb66c26be94901a2f962feccd4f95090191f208c1', 70 | chainWork: '0000000000000000000000000000000000000000000000000000000000200011', 71 | prevHash: '00000bafbc94add76cb75e2ec92894837288a481e5c005f6563d91623bf8bc2c', 72 | nextHash: '00000c6264fab4ba2d23990396f42a76aa4822f03cbc7634b79f4dfea36fccc2', 73 | confirmations: 40493, 74 | height: 1, 75 | difficulty: 0.0002441371325370145 76 | }, 77 | 533974: { 78 | hash: '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7', 79 | chainWork: '0000000000000000000000000000000000000000000000054626b1839ade284a', 80 | prevHash: '00000000000001a55f3214e9172eb34b20e0bc5bd6b8007f3f149fca2c8991a4', 81 | height: 533974 82 | }, 83 | 599999: { 84 | hash: '000000000000c53bf17a98b9ee042d6d4c3faf37d7a9f5c1335cce6df896f2f4', 85 | chainWork: '00000000000000000000000000000000000000000000000147108bdaa532b7cf', 86 | prevHash: '00000000000025fbeac57a69598c7aa4c26954e33aba1e1883bb4bf168e8e6b4', 87 | height: 599999 88 | } 89 | }; 90 | 91 | describe('Blocks', function() { 92 | describe('/blocks/:blockHash route', function() { 93 | var insight = { 94 | 'hash': '000000000000c53bf17a98b9ee042d6d4c3faf37d7a9f5c1335cce6df896f2f4', 95 | 'confirmations': 42, 96 | 'size': 1879, 97 | 'height': 599999, 98 | 'version': 3, 99 | 'merkleroot': 'a8f28c6d5c0461f2d6c2edfa2f550788d95b9d5fdbeb4215aa1fa415bc64509f', 100 | 'tx': [ 101 | "18587d7adb7d65fef1ddfdad6e7c18743490aafd12f2ff0de8e348ee9ecaba20", 102 | "0873850cc8f00a88b7040cb1852421f280c672f34fbdae63baf2d32dec757c5a", 103 | "800182f2605a3c2a115394c08117314732c71e3fd7ca3eee70f0fa964fa64f53", 104 | "5716a3d89dd47928892c9ab16cd2103a0423517a92d343f227a71916bbdbbe07", 105 | "585cd3307d36cb0f4191e410426a19192b41aa77f120cbfd3afb6dc0d3b5a71b" 106 | ], 107 | 'time': 1483795357, 108 | 'nonce': 671103908, 109 | 'bits': '1b00dd5f', 110 | 'difficulty': 75786.58855499285, 111 | 'chainwork': '00000000000000000000000000000000000000000000000147108bdaa532b7cf', 112 | 'previousblockhash': '00000000000025fbeac57a69598c7aa4c26954e33aba1e1883bb4bf168e8e6b4', 113 | 'nextblockhash': '000000000000a0b730b5be60e65b4a730d1fdcf1d023c9e42c0e5bf4a059f709', 114 | 'reward': '3.88010204', 115 | 'isMainChain': true, 116 | 'poolInfo': { 117 | 'poolName': 'Node Stratum Pool', 118 | 'url': 'https://github.com/zone117x/node-stratum-pool' 119 | } 120 | }; 121 | 122 | var dashcoreBlock = dashcore.Block.fromBuffer(new Buffer(blocks['000000000000c53bf17a98b9ee042d6d4c3faf37d7a9f5c1335cce6df896f2f4'], 'hex')); 123 | 124 | var node = { 125 | log: sinon.stub(), 126 | getBlock: sinon.stub().callsArgWith(1, null, dashcoreBlock), 127 | services: { 128 | dashd: { 129 | getBlockHeader: sinon.stub().callsArgWith(1, null, blockIndexes['000000000000c53bf17a98b9ee042d6d4c3faf37d7a9f5c1335cce6df896f2f4']), 130 | isMainChain: sinon.stub().returns(true), 131 | height: 599999 132 | } 133 | } 134 | }; 135 | 136 | it('block data should be correct', function(done) { 137 | var controller = new BlockController({node: node}); 138 | var hash = '000000000000c53bf17a98b9ee042d6d4c3faf37d7a9f5c1335cce6df896f2f4'; 139 | var req = { 140 | params: { 141 | blockHash: hash 142 | } 143 | }; 144 | var res = {}; 145 | var next = function() { 146 | should.exist(req.block); 147 | var block = req.block; 148 | should(block).eql(insight); 149 | done(); 150 | }; 151 | controller.block(req, res, next); 152 | }); 153 | 154 | it('block pool info should be correct', function(done) { 155 | var block = dashcore.Block.fromString(blocks['000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4']); 156 | var node = { 157 | log: sinon.stub(), 158 | getBlock: sinon.stub().callsArgWith(1, null, block), 159 | services: { 160 | dashd: { 161 | getBlockHeader: sinon.stub().callsArgWith(1, null, blockIndexes['000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4']), 162 | isMainChain: sinon.stub().returns(true), 163 | height: 534092 164 | } 165 | } 166 | }; 167 | var controller = new BlockController({node: node}); 168 | var req = { 169 | params: { 170 | blockHash: hash 171 | } 172 | }; 173 | var res = {}; 174 | var next = function() { 175 | should.exist(req.block); 176 | var block = req.block; 177 | req.block.poolInfo.poolName.should.equal('Discus Fish'); 178 | req.block.poolInfo.url.should.equal('http://f2pool.com/'); 179 | done(); 180 | }; 181 | 182 | var hash = '000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4'; 183 | 184 | controller.block(req, res, next); 185 | }); 186 | 187 | }); 188 | 189 | describe('/blocks route', function() { 190 | 191 | var insight = { 192 | 'blocks': [ 193 | { 194 | 'height': 533951, 195 | 'size': 206, 196 | 'hash': '000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7', 197 | 'time': 1440978683, 198 | 'txlength': 1, 199 | 'poolInfo': { 200 | 'poolName': 'AntMiner', 201 | 'url': 'https://bitmaintech.com/' 202 | } 203 | }, 204 | { 205 | 'height': 533950, 206 | 'size': 206, 207 | 'hash': '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441', 208 | 'time': 1440977479, 209 | 'txlength': 1, 210 | 'poolInfo': { 211 | 'poolName': 'AntMiner', 212 | 'url': 'https://bitmaintech.com/' 213 | } 214 | } 215 | ], 216 | 'length': 2, 217 | 'pagination': { 218 | 'current': '2015-08-30', 219 | 'currentTs': 1440979199, 220 | 'isToday': false, 221 | 'more': false, 222 | 'next': '2015-08-31', 223 | 'prev': '2015-08-29' 224 | } 225 | }; 226 | 227 | var stub = sinon.stub(); 228 | stub.onFirstCall().callsArgWith(1, null, new Buffer(blocks['000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7'], 'hex')); 229 | stub.onSecondCall().callsArgWith(1, null, new Buffer(blocks['00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441'], 'hex')); 230 | 231 | var hashes = [ 232 | '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441', 233 | '000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7' 234 | ]; 235 | var node = { 236 | log: sinon.stub(), 237 | services: { 238 | dashd: { 239 | getRawBlock: stub, 240 | getBlockHeader: function(hash, callback) { 241 | callback(null, blockIndexes[hash]); 242 | }, 243 | getBlockHashesByTimestamp: sinon.stub().callsArgWith(2, null, hashes) 244 | } 245 | } 246 | }; 247 | 248 | it('should have correct data', function(done) { 249 | var blocks = new BlockController({node: node}); 250 | 251 | var req = { 252 | query: { 253 | limit: 2, 254 | blockDate: '2015-08-30' 255 | } 256 | }; 257 | 258 | var res = { 259 | jsonp: function(data) { 260 | should(data).eql(insight); 261 | done(); 262 | } 263 | }; 264 | 265 | blocks.list(req, res); 266 | }); 267 | }); 268 | 269 | describe('/block-index/:height route', function() { 270 | var node = { 271 | log: sinon.stub(), 272 | services: { 273 | dashd: { 274 | getBlockHeader: function(height, callback) { 275 | callback(null, blockIndexes[height]); 276 | } 277 | } 278 | } 279 | }; 280 | 281 | it('should have correct data', function(done) { 282 | var blocks = new BlockController({node: node}); 283 | 284 | var insight = { 285 | 'blockHash': '000000000000c53bf17a98b9ee042d6d4c3faf37d7a9f5c1335cce6df896f2f4' 286 | }; 287 | 288 | var height = 599999; 289 | 290 | var req = { 291 | params: { 292 | height: height 293 | } 294 | }; 295 | var res = { 296 | jsonp: function(data) { 297 | should(data).eql(insight); 298 | done(); 299 | } 300 | }; 301 | 302 | blocks.blockIndex(req, res); 303 | }); 304 | }); 305 | 306 | it('reward for block 100000 should be correct', function(done) { 307 | var blockHex = "020000003a32e832be06463b1a8fa4e8d96b111c810ce81a15f120c0549b0a00000000009e3281ffb800307fb215d10faadee1fcc92002a54d922bb790b44f75917486b2663cbf53e8b7101b733405000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff27039f8601062f503253482f04663cbf530802997806ba0000000d2f6e6f64655374726174756d2f00000000010065cd1d000000001976a914e8a031de61cb5aa0e314d9f6d172fc68c147ea0d88ac00000000"; 308 | var dashcoreBlock = dashcore.Block.fromBuffer(new Buffer(blockHex, 'hex')); 309 | 310 | var node = { 311 | log: sinon.stub(), 312 | getBlock: sinon.stub().callsArgWith(1, null, dashcoreBlock), 313 | services: { 314 | dashd: { 315 | getBlockHeader: sinon.stub().callsArgWith(1, null, blockIndexes['0000000000108d4b9231f4ec99ab5dc970b6ec740745f44eee0754f67d598ac3']), 316 | isMainChain: sinon.stub().returns(true), 317 | height: 99999 318 | } 319 | } 320 | }; 321 | var blocks = new BlockController({node: node}); 322 | 323 | var cb = function(err, res) { 324 | should.exist(res); 325 | var reward = res; 326 | should(reward).eql('5.00000000'); 327 | done(); 328 | }; 329 | 330 | blocks.getBlockReward('0000000000108d4b9231f4ec99ab5dc970b6ec740745f44eee0754f67d598ac3', cb); // should return reward for block 100000 331 | }); 332 | 333 | it('reward for block 300000 should be correct', function(done) { 334 | var blockHex = "030000008381eae1bca7e1980c5bcfb1a64c40edb24a1fc47ec2e5137be21100000000002c3f75ecbf0fcb67cc1db6416feebe3b37689d94e44fbf1ce1db0de40ac3c67fa4c59f5582971b1be8127dc00301000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2703df9304062f503253482f0428c59f55083ffb2267400000000d2f6e6f64655374726174756d2f000000000242517011000000001976a9142cd46be3ceeacca983e0fea3b88f26b08a26c29b88ace824c70f000000001976a9141a43f6689de0f8f960b5701d25bef262ed156ff288ac000000000100000005aecc9172838a9a06f51058663241fd19ac7818c160bcc8677e67f4f449a03d0e060000006b483045022100808e7a26a05d19df10d2d2a954b3c8ddbca3189b3c9a30bd9bcb88c00d115c590220279dba16bf467950e3856180032953debf6184d28aeb019ceb1d2d6ac6d1eaf1012102268eb6bff9ab78578c7821021a1c659c35089f0c6bd0da8045fa3687d24e8bb2ffffffffaecc9172838a9a06f51058663241fd19ac7818c160bcc8677e67f4f449a03d0e0c0000006a47304402201f6678f8c6952aad9b1492b7e113a1cf8ab3fd04cf9b798ed7c09ed16092c1b90220624c35f40f96a5dd68ee82ede60e0ffadeb8a6ddfe8d0a63f577d330c9e473eb012102be102a3c89dc959c0e12285acf3fb21031cc55a14006ab2c00bd3c978db1f322ffffffffaecc9172838a9a06f51058663241fd19ac7818c160bcc8677e67f4f449a03d0e100000006a4730440220216a94a0bad8b383f08f0bac745186ae79efc049fb73af43fadd672c2ca643dc02206ba06e9c8ff35c1bde1681a595e3a04a11f1a46a4f75148e2f76a6e52e043bcb0121021a4bf485c7f4800722b3603b4883c81fca92561bca1b793d4647541ef0191f35ffffffffbe11ba2509e6fdb54516cca6f74281e477e59d6a7bdb9e89769e67ed9295893f040000006a47304402201e5d7c85c16fab24168380a5ab1a0cd6e65e035fed9f086345bcc77b05e73c5002201ca5bf0b412535ec64d55f5bfff7cba3284aa7e1fd12a5c6daffc5a0596d12f501210207e14c6db2841e22d6747b2b3db4ef81610e3df2f20e373b3c7ebb1b49ff9e2cffffffff05ddac92f6e576345fb19f65b81467aba7b3a9e5a2ac19fb58f0deb02eb6e710040000006b483045022100d0c0a2db07d269d1d3762047f36fa2ca584d25109e4f91225994a7aebe5c9edb0220517ee2fcb2ccb1cd6c2c0956122e1806299f24dae7b71884987653de3d0536a101210347532de25ee7bec167adc3b3e2f23478f6c67ec1abe86d828a42914ed929241effffffff01000c7742030000001976a9140ddbd93f176b458fbbbe751db05af1e5935487b688ac00000000010000004090de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe9090000006a4730440220109c0eedb88360cab0f063918076a55f93c3d79dfed0927bfa6de55914fe4a4b022004ee426eee5b5938c15a75b8de3ab03036afcd782bdc4e1a00d39905472d54f1812103ffde1c56a015638d6f5a1c1c84ad72dcf291b966c3491e56a1105d692473c66effffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe9070000006b483045022100af1f1ff7463771de2e6f88da3d63da1d87e8d99a1b27f946413b70a15c7fe60702206dc96e3ec3791ead93fd53170e114675496e53aa0559fda210501c43800fd436812103eaf9ec79bd111e49ed9a4540bc99262b3bde136a0dab130747f68e334f6266daffffffff3296f199939691182fb1b3f13768057df754cad9a5e158250d3b7c5a82e9d06a280000006a4730440220139a681eaa4c38d0eb2cf4dd92e763f34ca3f817533f6e177bdf70f03e5f183802204c3bbcb9bc31375bcbc8448d654d0c314310ccb4bdb75ebb5c981cf26a176e32812102eb81a4673bfab8ac482daa14f43e70cf3e7d1586841c8ddcc92880852549b82affffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c1431200000006a47304402204f8740d4846fd8d21ea4aba50c70c780696e750449d571d0edf35101fc0acf36022055ccbf8ae8a790381852e658ca57d11442bffe635830c081f2293c40a0a957ed812103e166b63f9c4053521d6432bec4f74c29e76bbfc98f47fce6cde5a08a5a1d9792ffffffff32777d149455b68647cb5d7f6fbbd0cb102d1d7875622663bd8514a7f3ee6ffd070000006a4730440220705cddd094757206653d4d429c7a96f81ed94fb4ae08b83bf0556570ea2b370102202568f0e57cfa2c4422da4cc12c0f114c539c37122c4a6360438f320c8155be348121026e6a1f9fd53f471919dda78f0c9aa883bcd0b1de961b467ea3c1a6d386b6c9dfffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe90d0000006b483045022100bc84186931b1f241d35f30ba6456921cc503ef11ab416e9d099af7f2d86ae9c3022058de2d9050ce7f69f385372719dfbc7fe6664c1ce1169fbc790e526f8c84e4fb812102a0c89b927909db1be1b96124ae51553889ddca2d11a9d07fab7d6d531131a0f3ffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c1431050000006b483045022100fed188dea82e5050726872dbbaf5fd077942271012a42d1a6ef97d2498af3126022017785895a69d6c72660bd4d3b9b0f81e9aea063343457fdf9b1b7d4eaa88a5018121034295cb75b476e67f839b30536c83cefb8b4aef96a73d5286c8a0991e0c1746e5ffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c1431130000006a47304402202feae1abb36f185d1773099f115d063e615248ea443b103a27b1a99765bcb08c02202d8187b0ab27035b2d2515dbfd8305e263ae4baebfd31f8c3842524dc6f0eb838121029e7286f8ab39b0ee1f29dfaf89be2e779656fbecef9ddc773cc7ce230b26c8fdffffffff3296f199939691182fb1b3f13768057df754cad9a5e158250d3b7c5a82e9d06a2e0000006a473044022057cc0562a502d7ae1f4d4870b15d97b9e1b279bcee13f8239b6de55968a405600220402ae45ed8fcf63bacc2f544a7d7d9b050ca6d1ccc6a4466faa8fc123893a65b8121027359c49fb7aecf988350921c416ef0b97802ad11056e550ccee4a3e79a711274ffffffff3296f199939691182fb1b3f13768057df754cad9a5e158250d3b7c5a82e9d06a040000006b483045022100aaae4d09b2790cc753ed1d80668b6d47126ef63df5a3c5e2b2a04f559846eb96022031b9c4bb5654b8a19b06020499dc5078390529c0eb6b8aa249e3f7f07a8891fe812102f0d411c423fa6b2c96d17fe6d4e46ff315b71aadb79f3479248e0e957097e2f4ffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c1431300000006b483045022100a675d8c0dad95bf03e63cc278a0c4d73afba4aaf3be0db65cf7974d3b573466302205cdbdfd379f0ef8a7f1ed0c165a9dce6883b1cc425b37cdd308d9e22300387a5812102835261c3049bc9f7444789aeb1f2ebabe57c3de8f25dcbb4216fe34515dd5e41ffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c1431010000006a47304402205edffcb6b4117e05a5550422d03977852d6f659693344fa33a15fe62478295b80220407cc3b56d47b8f634344041eaebe45374ff2280d597d083e1cbd201662d25988121032fe650990eee0c25503de333960104e292b05cd8d2dc3cf4f9d2f4715034871effffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c14312f0000006a47304402205c940f4ef993f77285db167e9439b507dbf02d0da3e86cff84b3e75c2453854a0220390278e18ce0dc42d799d94fd4500874702907ed7b9543eefe4a5a73db342211812102e6e9d83db9b6ab8f7b4b0ae7f9b8e5c403d66890d076b2452180572639ab86b1ffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c14310e0000006a473044022065a4ed1f556c00040fa12ea8cc9c902a205cbb742430e41e5968905da982b31e022012a4714e5c08cab48f843f356a6745310f9eaba94e3dab77a2b42e8e1d7a44cb812103aab32304804afe1138647396e06c3605fd95a1108ebe129f0322f9c401bf5631ffffffff3296f199939691182fb1b3f13768057df754cad9a5e158250d3b7c5a82e9d06a230000006b483045022100958601719b12fd1ad2fd4dc6153a79eab30e90edcd386459a5a3eaf0ed22b60d022078c571e267c205df02eb867fa9b3c56f738bb9dcedc54ac598e4b6c0b78a8c63812103ee968748f2e047fed16d4b0fd31a9f456f55482915a19195e0631e6256fd7607ffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c1431000000006b483045022100899bac918e37589e465cc4160a8c36d79904fa356edcc9c22d09d57f670d1d89022054be922ccd2eaac80e758caa48057763abe0b4032f4e9e78a564514199b441d4812103ca78a7976605211d782624d28b6d334be228a4ae4951b1c29d24d0883f49c432ffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c14312e0000006a47304402204f10b27d9114e260471c7dd73c233175973441dccbef3063e4aa03bb1230e2a10220763f356d8ea8e88402127779f2782bf3e08c14d4eac87af380fdbeea0b2e74908121034a24cb93ab16517abe31b665ab2448372bfca76d9e3ee7fcc70f8916646e879bffffffff32777d149455b68647cb5d7f6fbbd0cb102d1d7875622663bd8514a7f3ee6ffd050000006a473044022036ca72d7ef7790726501ba41144acd50d1cac173b8297d4e1bbbc7a9e57b3210022007ebe415b9c39a0fbba62127d4361f64b7e809334ce75d0adea39f16dacb72df812103200090b368fdf0f14cf4c94d6b9d855b5806a48ca6a875326e82fa1f59159675ffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c14312b0000006a47304402205d3fa1de5ac768bcffb40ed3f026fbb1f19d9fd851ad6576c7aff1975d2fb05f0220024043fc7ec00589a30ba89f649ce9441d9af27c48d2e3231a1c339b88f3d841812102a956c56686b6cd7636921de1fc33d9ffa0327a232eab3a5b6b72e78a9fd5d4a8ffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe9130000006a47304402204ce74e3ccd8c1f0819bad21409a22a0848e790857292e65d4222cff4b8a4c558022023afb5d0bfbb79e657adcd796729960c744be70b7c66bf43f785e1a9dea7a27a812103a5bb6d0811b15b264570a266bdcf843960a48e80db549cea8472d988f04cb3e7ffffffff32777d149455b68647cb5d7f6fbbd0cb102d1d7875622663bd8514a7f3ee6ffd0800000069463043022032d4a44211c3288409fce27a696dbadb05e7140e4c0a51436f20bf5227175c7e021f0b06e73596ce62b3a2fb352f56be1992aebbd0e26bc423cab6f28ff73c297a8121025f02a2d74dd0f6cc3999f1309972b8e27dde923fc712e07d92ca67377ffba853ffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe9290000006b483045022100cbf6a82f864def3114a4141264c0c5f6893bb1f0c610549da91a58643a81f28d02201284f565a836328741b12e05e7abe256d5b535d22f04e5c46ba5e48088d88c70812103fcedd8cfbe56e069fca5868c7bf18aa39051a05818e5f8922f04a8840a4c96e0ffffffff32777d149455b68647cb5d7f6fbbd0cb102d1d7875622663bd8514a7f3ee6ffd100000006a4730440220219ee93087f1d72a813ba381a487047e9e8b241a9df18599c728d21f36676207022020804e37391812bf2796f809b59959bb2fcb011758e07590b729c01b46fefd23812102803359fceb6bf09fc354b1489bd29f3a43f5a4fcaeec19b69b9e0184fdcd9bcbffffffff32777d149455b68647cb5d7f6fbbd0cb102d1d7875622663bd8514a7f3ee6ffd1c0000006a473044022045f3189a88dbc5af199af43356d078dbe86363af87871b70e4222ceb3ad907d002204b828982960fecd52c52dfc27cc3f89656888c0a94d1a5e29272d32ad8ef66db81210339d9b82eccba46ace6972c6bb43a38ed261d0a8f7e8540127aa9ee682eba1f23ffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe9170000006b4830450221009558cc9335e48431d41c17584af34d3d0a9a4d2e23b8bf9339d1827eee0a3127022045f224c998faccb02eb2435292fc4fa664f14fc42db5184b8294d81035f32090812103c2c80a7b6d783489a467768108be77d495fff1e8c888efc5c662ceff6ea2f33effffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c1431060000006a473044022067d242d653717679faf8546e16ea25431feea55d6f4ac515d17d51641776cbba022042be1d753b9d0bec57d64febec35518b5f8119b6f1890e680b9b0a5d09e9eee6812103ac5a23611f0fa3b33b61547f2c0951794b5447f76667f4dd2ffbdbb72cb3495dffffffff32777d149455b68647cb5d7f6fbbd0cb102d1d7875622663bd8514a7f3ee6ffd130000006a47304402203e524ac8922e6356a9717fd80a20093ef3c152832c21d908b1b36fd592da4fa502204c441cdbd71e4664e9dc7ad0b8aa0557e7ec655f2e827a26e478a93e38be912681210318413762a713aab03aae0b63a071f0afb5a659dc474762c49a9fdfbd04ed8006ffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe9380000006a473044022026250b9ebc5ad79824a0460ba2bd5edc63a0842196e995a45a940bd24233293e022013c98107e94bfc7e53919bcde22322b6b9600ef5fb2511dfb800da01877623f881210304a22fe23c9aa9cb35b732295ecddf1e7ea06ff1c65ef16d410c49d821a7b38dffffffff3296f199939691182fb1b3f13768057df754cad9a5e158250d3b7c5a82e9d06a390000006a473044022044727b42807eb5c47e8901bd630d0d9aeea3b366b383aca587f941c63163f4a002205530336c3fff01e40ebdb21c22b335e31ad15e2c235e7f93f580f29d8da779e6812103bffd1d85b5430874242396c5493795f1d748f2fc2ae97273a918656970b697dbffffffff32777d149455b68647cb5d7f6fbbd0cb102d1d7875622663bd8514a7f3ee6ffd180000006b483045022100f2c2e98ff13c57a6ef4a12bfdc49063c7bca6b89e66a465048ee628f787d19a50220631d4e63d8bbb85349418304f1ed2e84bc8b36368468dbb089e0fa0ce2569cfc812102337b2258dfd3c848d154a01b18718791ee8d38d132ae66186b319fdf8b834388ffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe91e0000006b483045022100f6fa258cf77f58d6f32a0beaa495f65f9994b2118214f9b0828bfa7c4752e5b302207eb7b9d72df545723ea8bba11f12178161f8e0858593906f0497694cbfa924c6812102c8c94486074f917a0099f965dec88ec7c53951b622977c0330f82bd4895f2e1affffffff3296f199939691182fb1b3f13768057df754cad9a5e158250d3b7c5a82e9d06a350000006b483045022100f321c9db666e2ce02acaaa9d35c5f8a1dbbf18f1914de784bed74e1ec2a9a8af022013c3fcd42879ad85822010c0f82371ca3a2379f2b278f5a46a315dd0206d020981210392d578c38c0a05ebdae07e1a249384699e156b2fad4de023a9df191822bb3679ffffffff3296f199939691182fb1b3f13768057df754cad9a5e158250d3b7c5a82e9d06a0a0000006b483045022100de13f5943aa3167a74bf32c7e83b9ff484ed9429ed873ca3ab8a7900c51fc0a6022067de1eee6ab93193227cff145469faa16186c389d4d5288f2e2749ed9d441db18121021314b19bdd47e33d91d7c1190b49e448cb4e31b913e288751959b066face0c59ffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c14311f0000006b483045022100a719cfccc38a2296fb0f18889b41083b3a7bbb0b94eb26e7f1e041ecea65a6ee022072f6fef281ae9221c1e426a98b1f11cbaaede9f0104a0edf117f0cbefadadcdf812103a871bfaffaf9bd75a44525b53481c1c53a13c4b5dafab24d45a507b2cf91565affffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c14311e0000006b483045022100d7fc043c424d979b4492b847af8d28f41744148d556b75f90ab3ca511f29a68b022006a42f19a8184531c9e7853537efb247720fcc8bf099bf5a91bad0d626b119a3812102e270d63fe42daec1653b7a8bb23a25bbb12813f6ac3e7f977dee1abc3ba06e97ffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe9340000006a473044022029ed0639ce154fc2480d1910ebcad16e45346b43fc8ded7de43c3c202f4b3a4902201d86bf1f31587ee122e70e9a2bed41bd7783c2c8c796b78b55ed3595bc5b92d68121023eac08b4cb61e9a79480e037cf8d401f4ef87a883088db1e67225ad2a379d8feffffffff32777d149455b68647cb5d7f6fbbd0cb102d1d7875622663bd8514a7f3ee6ffd150000006a473044022047bb64f8353377f455616e36a8494afd8883da5e220d191dda95da97448d068402203b54eeaaef210c098fcc56a422a651365e89d27eee12095fde69ca4c24387255812102b0dc18c827a8dc5db88829a2f409c422e929a5169956c9a08278995e562c318cffffffff3296f199939691182fb1b3f13768057df754cad9a5e158250d3b7c5a82e9d06a310000006a4730440220163df0bf08480daacbcf3038b43a8d2454ae610f724bb173ad14eaa19281885a02207be4707e31280eae3cf32387a40e3cb40025f8b0bbf61aab37171111bdd6c888812102d83cc4ffd8d29fa66d27146c2dfe4bf264d6e5d8139e88be50bb5868f7dd7de8ffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe90b0000006a47304402207bf47fd8bfa72743cf9eafc6db332c8eaad1777dfc35e10705c309b161574cf602203e96cf87c49214e803bb2e07ea444c94688b6d51e6f44726e700903700f7ab40812103b14d30d2a0f6724cc362c288fbfdcc7d8cb6fc9c959d34832fd5bf6fa1aa34baffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe9120000006b48304502210088d7337015623f90aa1378634d5e1ff66366aff2d1edc1f0fe76c4d68b2686600220431b3fabcda2d0d8e78556b954257b3ee581ae8c5a81419b300cf268404ec6d9812102ec61694d0125ae42dd0d7bbe665fbb1b49efca57947aeb54f6415eefd6327f97ffffffff32777d149455b68647cb5d7f6fbbd0cb102d1d7875622663bd8514a7f3ee6ffd0f0000006b483045022100fada257a5b7b812c00e158751e4fae70199b007d9719814e14eeb25cf700242a022060b4498b79c5fb6c170eaed74537c7a6e720c1dc018ec3729225298f96051a95812103be50f973aa0c14e74056e5abc5402903da1958f15176d71af59b908412203a9effffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe92a0000006b483045022100f9961198d6e148fe6715509e0208545aba888855a7d4e10a2bfb59731a7fb9a902206677c115fbdb00e35295acc4d84c0e7ef2fc9080a9bf07f61a2e18b8bcded2ab812103b5acc7454c65a8d9f877b29e15185143fd3fc3bc30a27f63ae86e94ee869990cffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe91b0000006a47304402203ed8904c3950f44a171d76a9d1e75d6e0c46ed4192d31c24ab7c14026e793d81022023e71aefd736f364c904a55678c445821858bcb2e49d6827174148753f62bddf8121028754ab47a49febc6264b0c119a9cb40b6f3d96daffa92a49122e49477a81e0daffffffff32777d149455b68647cb5d7f6fbbd0cb102d1d7875622663bd8514a7f3ee6ffd190000006b483045022100bd6ec79d409fc3c737a70ba7eacfd375866ae4a80de0f4915f6ab24766590add02201424a49abd56c8e65b9e314d120bb770cb8c2dbbaf84fbc3707616eefe472ea6812102780cc93b0f1cef4a0d9ef40b876174fa5f75ffc3726a616c1bc87a1b1f92c850ffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c1431240000006a4730440220281542fce27a0e527c798de6aabf23ed90ec45f29292744f33bfb4077a6faa2c022030926f2096d758a5be0bb9d33b1ad8a8a5608ce06d07068c0a87739a37a430308121039e4d2a87f21af5ef25fd7d2fb552429f1d45788c4600c28715fcdc42b31f6078ffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe9160000006b483045022100cd4ad87a379512d6797063ba3f083bc21f46e394e6041091476bbe49fa83ae6702204462afaebfbe9cd11cc957b00ee18415e4d392af4173b9195f67242c8b398df6812102dac644326958f48ce419d721aa0afb2bf06c31aaab620fc4cafcbd6a4f3f58ceffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c14310c0000006b483045022100b14ec44cc5d89d74f706707cdaab149efb3b420db9695279c70c8b5f8520a9ea02205b5a4556e4384935b81fa2b3a73e31cf06a20c3106a2ec6a3e3bfa6d3b43b05b81210260b805aed62333ab68ce6bc3190c44323c88cf09f61310dca1fc6dbb99d31797ffffffff32777d149455b68647cb5d7f6fbbd0cb102d1d7875622663bd8514a7f3ee6ffd160000006a47304402204e05f51d546e90a1d7580c7a6153fa38c91e5402840b0fbce5063887942e5a9702205d53d4796b24fd8c535ae5a89d646c12c7d5c41e499cb6ababcdede15770b9fe81210314630d2130aa2057d97587180235347fa879d37ce4de95653f98453d4051baf5ffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe9230000006a47304402203854dfc39f5ee8a0fee117647cf5a63f511c290103b3c9a116d5200f1d23fc3b0220448b61c87a39384a1fdde0f520c35a052cc11e06a50e2bb81081dfc9d1020490812102453fe584f64717fde2299fecee5ef2a3b5a67d7f0dca631fc547ab88679ffdd5ffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c14311d0000006b48304502210097f6c257cff9e0af61286f078c2b1660dfab4ed181e00699f79975ee9639221e022065662548749cf8beb7a287ff2001d9878d0010d1bcb406bdc65c0bbd7697dc828121025eccc4646b7601706a2132e1dacf69e93cc633b544182cb3c52a3c73e21841fbffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe91d0000006a47304402203b1d68fa673694b1b65aee2115b84ae5d639b1ea5ad722acdd15399fba97e0df02204c4e5fc5e763619fde9d905c2a0107038526baa30387295a7cf0223fbc2de0898121030d9a39061a202f0e1aea97c8fc49e6f122626c6cfea1a448366ba23f9c1eca41ffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe9080000006a47304402205e62a8ce7754a85032f704a7a0148fd3bf536803a958e1cb18d90fa172e8fda802202c66255d92157e87ec506cf645f65a65f5970f9b77257189fe4397de05da27d88121036178374d9c595b1058adcd62bade25702150f5c1891b051f255206dae566d55dffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c1431230000006b483045022100db60bf8a562ab5ee2b5c8c4997c262569780c7f39123965ad18d3981167b7b2f022026fd28421ca9180a297ff7acc4a1d6017c3d63e5919872aaf80ec21ded096907812102be8d6eada9979d683eddd9a958a65abc58e99aea3b6b3a6ffbcb68a4802962deffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c14310f0000006a473044022077257849ea9d26b1f3b0057c8cacaeee8470dbf4d74c05d69b103bb356bb7fd9022028ed2a1db4791a0901174120464503b1d1e6ce4cec055463c57c8c047454d197812102e57b8d3a9c08815d2ab5ba875589503bcd37d4bdf086924ebf7acc88e7db44d4ffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe91c0000006b483045022100fe6669949c66af1c58c1a587d33672e7389015a00a2021a59bd0c90ef915dad00220771bca8347df920d421e126f162734a29085287cdf8edf11cf7d2b7aa33b062d812102569c35a39cea8d4fe695ce4211b74c16bce2acd31178ea991563694210a3ee72ffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe9140000006a473044022041b114f7ef7cf4f896451b5efd9eb9834bbba10fc26f2e318c76f2c74236185202203daeea155ebdcbd0f86eb697b267b993a7f764341f9c0bb76439e917d52ca65981210348ac88b3fc3fc7ab15e032a0f9e4c1577961a643f0ca0455b2aaec05c289d9deffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe9260000006a47304402206d132876718596eee88970a29051fee1a13520ac7068c70ae36849b370a4ad040220254916ccdd233aa59f31d59156fc6c304e6c101a631b11173bcfb3d36833c1c9812102397e169a430eba75322aab6f180e15dc38d195f99dafe2d411fe852a3413a038ffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe92f0000006a4730440220227f6b1a6d53fad8b9c95728382b626522d1fcf785aff1a475f8011b1fd2d86b022076a673e6b078c15c58146d3c6d7e0ebf2b59b57795a70f6d7b93411b04ed10fd8121033564f3a1f03752041badeae2f8c29b1df0dfc46ed6015af60f06a0e96c7fe2e8ffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe9220000006b483045022100e41540eaf417c7e13017df61300a020d2f1bd6e9553dd2249a08874b91bd6b6e02200e14d7c8d75c721d613306dd1419048e4e00e986b19a4e1ac8f5cd165ae522ee81210221c82aab8336c42bad314b9fcc92217f2b74bc658f0ec6d7986d326d4b82e07cffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c1431140000006a47304402205579e91e8002f995eb6b7a17dd3bc2fe24fe3017f9424489ab43eade0c76caa502203713afe61f04978a5443ba23e5e46491b12c1ff7dca0eb510488dfc00e11218f812102fe7ecb2d9c709ea28c70b50a16c4909170632382a8719d53bdfa62120a6a9d15ffffffff32777d149455b68647cb5d7f6fbbd0cb102d1d7875622663bd8514a7f3ee6ffd1b0000006b483045022100d1c2e68a643fc933b00092ee2cca92739f599f27cb8dcaf52d094390ec83f94d02201706d69bca71ab64dcf37b1a07a8066cb6ec881d6bff8942f1f1f1623ef47a2181210257d69228af265326bfd310acdcaf88fa72acb48ff76665bb0fad8b301fb56ca5ffffffff90de0725f3cec2b92ba88633d8b6a84e5e9134a6b625b637dbb78595cf53cbe9030000006b483045022100fab970d463cb4a59ade3ebf660c8d762413901253446d79d50ad22a95ff5f2350220781f72c6e50fb3febb076d8dec9292f4a77ba1e4cb2b13357fbd7c853c821f5d812102613688a6189c514546b0c5ca7872867ae2eda9b1c40e33fab36e634c6618107fffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c1431270000006a4730440220073d4e0037f53863e0a796946e1b94381f485489f754d305d6903b37ff3c5f88022045d82eab959c80c91e3ef242e404f5c82acbd0958e496213444cbe345ca1e2a58121024f26d46e706a156b9ea394c4b22c7abd0000582a6c9ac09b704656e48da83e74ffffffff8cbf4e39f7fd52d88e2df4b352d0f8813970d752cba039cc70d519a7387c1431150000006a473044022026bf58f38cf788023c29766ef8dbb48bebd514dce9952820fe2174bd91d7f273022061b716f3780b2b4f4585ee306ed2054fc0e08db82af985a64081f2223e6e59a18121037dbb6453bfdc01e0cebfeee62c7d5437b23ee96939f4af68d03f56c641857e81ffffffff49e4969800000000001976a91405807a55a537a82513d4556cb5a6666b590e6da988ace4969800000000001976a914294b6a51a685c5f025aa6e599c5c35a63d54aaca88ace4969800000000001976a91458c825d27bffee80a7d2e786d2765de881d7cf6388ace4969800000000001976a91426f764faec2e3578e497475de719e829748b957c88ace4969800000000001976a91442f1439a9de062504502013bdb9620e91554788288ace8e4f505000000001976a914c01aa46078410e93ac2c466d363a36572e795d1788ace4969800000000001976a914dd11c82def5d540d5fb5b60ff6bfa03b181bc0c288ace4969800000000001976a9147b0db16f3809b75ad052fd4f6c544b71ab27ab7188ac10f19a3b000000001976a91403cec867cb9dca1550cef46810d725445d2cfcaf88ace4969800000000001976a9146d91508d42354e73de48bd6a19e2616898994d7688ace8e4f505000000001976a9145e23a325005dbc722b69d7e45c9f9cd8e270800f88ace8e4f505000000001976a914d804b688d558e73257eacf96e6ac67cc8756e9d088ace4969800000000001976a9149ce69d05baaed49b73c6e05472d18d6ba0551dff88ace4969800000000001976a91493a5cffb7d4720a8cc3ce925016b6b6da3c0e4b388ace4969800000000001976a9147803cbc5e20ed36003f180b97838912de85cd67988ace4969800000000001976a9141aae2d33bd5c844baeafd0f7668755c64362f58c88ace4969800000000001976a914c50a37572303e2dbf34b65f8c3ccd7c924ae484588ac10f19a3b000000001976a914f0195d604b084a668b54a88670430816fa939da188ace4969800000000001976a9143188e961ecfa8954717855386e5185affaf6556d88ace8e4f505000000001976a9141ece55c324d814a0d1d3d6133efe6cd0564733d088ace4969800000000001976a914dbce204ebb3e3b9602233c61639fc59017093c9b88ace8e4f505000000001976a9144015435c0d7863b36bdb8436c1d038c16a5053c388ac10f19a3b000000001976a9145a0ae65f8402103aa3a61ac69c5a70917bfed35388ace4969800000000001976a914c0c92c3ce6baa361557cb6b0ad428d6feae5097b88ac10f19a3b000000001976a9140f8052471e8c77dd407cedcdf0bc7577726cedfb88ace8e4f505000000001976a9147cebf9ed9213984f08b7e20aaa3f01ad92dadcde88ace4969800000000001976a914f3c75f4aa71bfd50671470255f7010c6808147f988ace4969800000000001976a914f82bb05904faf7895c44f7dc81149740d78b0dbc88ace4969800000000001976a91480aa1bb0c02756406405dfd979410ac73f0c084288ac10f19a3b000000001976a9147613e5235764074819cb843696e5695e74ebed7688ac10f19a3b000000001976a9149bcb1a29975ad15d1ebb32dc392871f82880ff3788ace8e4f505000000001976a91462cac8c1f1eb59e4fec096170e6fdff2b736a8a788ace8e4f505000000001976a914dbc9874d290def6199121ba2bdce665d24dee90b88ace4969800000000001976a9142b41ea581630c1ff5bf1af10f7d1d9caf8ee1dbd88ac10f19a3b000000001976a9140bb9089e00d7fd230497faa65e3e4706127675f188ace8e4f505000000001976a9148ba381a0e7c316f65124d2c6fadf0a1963677c5788ace8e4f505000000001976a914119209ce1fbe619adf77dd88f2b7b4889231ce3888ace4969800000000001976a914e5f743066c7eee78a48379b1ec7065d7addcbff288ace4969800000000001976a914f4265d5ae2f74de6b383c73948f7ad99ae54fd7388ace4969800000000001976a9147a1947cb5fd37458186177dab2287e76b3b536ce88ace4969800000000001976a914c2a4bdaeead51604f47ce03fb7784726405b7daa88ace4969800000000001976a9143cec49f7e1f22c923e82f36c8e7cae2ac6d355d288ace8e4f505000000001976a914bcd1e8ea7a663c6e778f1df1febf99a65db7ddb388ace4969800000000001976a914caa2f58dd0ca3a5fd2ad20501e2c45189c0371b488ace4969800000000001976a914a4da72c8f7863de0bb724db4fd0c209b946c2c9988ace4969800000000001976a91420c40584dcca1e85f236fb894840fba98b1193e688ace4969800000000001976a914ca0887bc0850525f4e74221a2ad09ca0b758524888ace4969800000000001976a914d423c2493ffd29c40a9b66ce560237a96d98452788ace8e4f505000000001976a914c075f549165ebe13c78e7dbe276eac2a1f05e7dd88ace4969800000000001976a914a1d1af92bcadce895f1720fb93d84907e9beb8ce88ace4969800000000001976a9149faea5910f60c46cfcf7def52bd7bc3361397b7e88ace4969800000000001976a914bc88e01798cc32e518228f214e40a147bafc0e7988ace4969800000000001976a914bce9d5ff8e8182503832d405ebe0e73cefddfb9f88ace4969800000000001976a914e7eec50a08d50f8642bdb1f543bd3138ba2cb40288ace4969800000000001976a9144bea95f12e1b24c45b1ea2effbfcf048e7873c5988ace8e4f505000000001976a91419b33e4b66f0255e294d00132a31372afb87769388ac10f19a3b000000001976a9140b69c4eff5187f13d19be3036bc2b8d6c91fcc9f88ace4969800000000001976a9149a47a35d3048849d2c68ca1d7949dd761d55620d88ace4969800000000001976a9144e2f8ab9184be0c9133a2d12584dd6b45286678088ace4969800000000001976a9144e0e2ee5f071a7908fb00594d062fc78cf1b4ebe88ace4969800000000001976a9147861cff7e96cd9794752e08174e380c453e3078f88ace4969800000000001976a9144f4e4d31c55bc454d49a12853849fe59f2dd194388ace8e4f505000000001976a91407576148b25f413df00591c31f156cd25150cb2388ac10f19a3b000000001976a914f932a459fa0f76014a4b611d20e0c31c4b85839788ace4969800000000001976a914a5aaf1fc7109c59c1daf5e30c8c055b43591377c88ace4969800000000001976a9142e273b3fd047b980d40bb0846dcad030e2bb157f88ace8e4f505000000001976a91433ef614de2df9c3cf25a47afbbdb8b007acda2b888ace4969800000000001976a914e69820e3a9b2ff40be2281d6ad00ef691eb591fc88ace4969800000000001976a914cd4a99e5a227553fd2b98d43d6f4e3230eb6b63488ace4969800000000001976a91430c669db3d1eb4f44703c780145767a67a0303f488ace8e4f505000000001976a914e3f76b2197a348a26e65dd5c4fd63f581613171c88ace4969800000000001976a9141d67208c91b3567a995903e2d43df3a4ef6e506688ace8e4f505000000001976a9148ed7307323fb75bf03042a2172bd52a03f9041f288ac00000000"; 335 | var dashcoreBlock = dashcore.Block.fromBuffer(new Buffer(blockHex, 'hex')); 336 | 337 | var node = { 338 | log: sinon.stub(), 339 | getBlock: sinon.stub().callsArgWith(1, null, dashcoreBlock), 340 | services: { 341 | dashd: { 342 | getBlockHeader: sinon.stub().callsArgWith(1, null, blockIndexes['000000000008b8c4e86d070a78c978957ae7f0f127ff91aae6e4b0964c92d0b5']), 343 | isMainChain: sinon.stub().returns(true), 344 | height: 299999 345 | } 346 | } 347 | }; 348 | var blocks = new BlockController({node: node}); 349 | 350 | var cb = function(err, res) { 351 | should.exist(res); 352 | var reward = res; 353 | should(reward).eql('6.07737750'); 354 | done(); 355 | }; 356 | 357 | blocks.getBlockReward('000000000008b8c4e86d070a78c978957ae7f0f127ff91aae6e4b0964c92d0b5', cb); // should return reward for block 300000 358 | }); 359 | 360 | it('reward for block 600000 should be correct', function(done) { 361 | var blockHex = "03000000b4e6e868f14bbb83181eba3ae35469c2a47a8c59697ac5eafb250000000000009f5064bc15a41faa1542ebdb5f9d5bd98807552ffaedc2d6f261045c6d8cf2a89deb70585fdd001ba43b00280501000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4c03bf2709049deb705808fabe6d6d333633346330663831373530373766356d6dbefa085870eb1b040927be034cff01000000000000004ffffff2810000000d2f6e6f64655374726174756d2f00000000023805980b000000001976a9145569df50109cb001c7a5fba1e0dbd767ae2f5a7188ac3505980b000000001976a914c2d28f46e57a684421a9ca80a435588415102a0988ac0000000001000000046d5bf4aec8c669f2860e560a32634618eda74b483a31f00bf6bac2bece726a65000000006a4730440220010bac2a9700c669398907d60456f9741bc333c20b3a7a1564f820dcd83b023202203af850dda733e9f4e5545f3c0bd33bec113d741bc7eb1c424a789d636b4316a2012103a20a03d33115ce6e1629c0e6ace4cb7dac6af2bcfff6138af6933c58ed6387c5ffffffffc185041b341944e1ebe614169ff3d5795d07ce50f53e81a3b0ac6e5ade06aa82000000006a4730440220509f47d819ccbe88ba558c0eb83ce97a5d471d6c8847c6e5cd7b25db1923af1f02207286be70688eaef1b5027c68fa99717b253ddc36a057e0626ef550b9215169f00121028b13cf1966978ddaa91cae99f3b404091b160a60aa850500a2b1e86c49df07bafffffffff4007cea51cfc72a4a1143c3c73a4c66cf5fd021f7bafdf8c34930ba7946d7e9010000006a473044022058a31a6bdce095431dbddbc55b1754ff6c77cf257965f23e44edef7bb9e0720602202ce5e517150d018e7a28819e63c106f97e0958e42dae5d13b7bda8cf7de0ea2001210349a209258d9fd11f7d16e8bbe2e29e5426e90fb7903f05c14cf011de03fa5834ffffffff1c6ab0474df1180c5bad134831a03fa238e700d197902d3bdee8e0fa2ad45729000000006b483045022100c640dac4e39e5e97a098aef8ca0f83c013b91eb3c98e34573a7d1c1b4535370b0220634dcf9e73ca7697945a53feea1361d311e0a5a8fede6214e3ab52aa5ca9a4590121028b13cf1966978ddaa91cae99f3b404091b160a60aa850500a2b1e86c49df07baffffffff025b590f00000000001976a914a165a2776f8a472fb8d332be07e41521ab97156d88ac5b60d017000000001976a9145a09f90b02870515ad0065bd8eb6e717fb8c963888ac000000000100000002818565ce56113b3466b936b9a8d4bc1097afbb8ee919a000fabd905240efad7e010000006a47304402206a04d3aa60f887319e9cf7cf1a196f6001b386a2001f0965e9e7f7f868a1f6580220038baf2655900f830bf7f24a02a51467251984f728f24abb572f5093789ce01701210208cbf31eaab51b2393b00abf0f553a9e7174708ce4d368e560f65ed1d2fc6f3cffffffff780509166c5054c43ebd1e72c4f0d3ce562506fb20f274119211bf3d66ef397e010000006a47304402205c17d8412df0878f1e34c9b0d92b15c9c75aa391b3e60749d6924449911ffd880220792ab1fd54991c9a1ec6e3c477d94f22324682240ba060a689b45255c6c642600121022445fa7659cef564f92bb206d053f70430023cbce40a6399b70b23a39286a557ffffffff02e2275f00000000001976a914d4ecf63c88a5ca90612cea491da826305acc2dd988aca65afc05000000001976a9142159f2ff038bc3b2b4819fe81e7a4c7a9a2149d788ac000000000100000001413dd865945aa7d2d1035215e9ba3967d5fbcce0d69130ca830e921e48615b4e000000006a473044022038bc98fe75ec9c595e54c8bf154ee71d95d065b96d2889121dd160d2e857cd1702204546335bb8d81daf95081a1cde2dfe3b772f2b5fb518630002449a37aab4ce428121022ca24383508ca116313bb6bb6d9d904cc430d82c750ea9eab0f1413544970e14ffffffff0140420f00000000001976a914fb132787955b3da362ccf2574a5f0afad4f0f9b888ac00000000010000000285010877636076e4ed45acfb271b0e1001b8dbba0fe5ce3a66895c84a2d20c2e090000006b483045022100d018055686bcf95fa9362e173577da498e007cbd09f6ed600d083a2ffcaae99502202349e7252e11e335bdafb8ea0784a053f0f1b3ace8168a219d3502b9cf44b9660121037785407b8ac4507ebf6dfbf27126e035151718dc4d511b0269c0d8f2eb8506d5ffffffffb12206ecd84b6356dbb212f9a559587885414c6484a85894148bf54a2416d5ec030000006a47304402204b2b50dd4d180668a220baf5bd828560c44dfeb9b4383f56a4a8881593a2cbbf0220185e091d3bece026b73582d02800bd0350461480567199bb4b46afd54556317101210394c8dd37541b4f9bac1eae41da303d2fffc150d1f0141b98e9a3f58742f161a5ffffffff0200093d00000000001976a9141b71e2815a05c2b368107188ed960918be85d59f88acbeda1000000000001976a91416e710a6a840b4bdf4f820d5861f60d7973133a488ac00000000"; 362 | var dashcoreBlock = dashcore.Block.fromBuffer(new Buffer(blockHex, 'hex')); 363 | 364 | var node = { 365 | log: sinon.stub(), 366 | getBlock: sinon.stub().callsArgWith(1, null, dashcoreBlock), 367 | services: { 368 | dashd: { 369 | getBlockHeader: sinon.stub().callsArgWith(1, null, blockIndexes['000000000000c53bf17a98b9ee042d6d4c3faf37d7a9f5c1335cce6df896f2f4']), 370 | isMainChain: sinon.stub().returns(true), 371 | height: 599999 372 | } 373 | } 374 | }; 375 | var blocks = new BlockController({node: node}); 376 | 377 | var cb = function(err, res) { 378 | should.exist(res); 379 | var reward = res; 380 | should(reward).eql('3.88010204'); 381 | done(); 382 | }; 383 | 384 | blocks.getBlockReward('000000000000c53bf17a98b9ee042d6d4c3faf37d7a9f5c1335cce6df896f2f4', cb); // should return difficulty of block 100000 385 | }); 386 | }); 387 | 388 | describe('#getHeaders', function(){ 389 | describe('/block-headers/:height route', function() { 390 | var node = { 391 | log: sinon.stub(), 392 | services: { 393 | dashd: { 394 | getBlockHeaders: function(blockIdentifier, callback, nbBlock) { 395 | var result = []; 396 | for(var i = 0; i