Compare commits

..

226 Commits

Author SHA1 Message Date
Sky Young
f1ef06879e
Merge pull request #1 from oipwg/update-fcoin
Update fcoin to 1.1.0
2019-07-25 10:40:50 -06:00
Sky Young
c44229feed Update fcoin version to 1.1.0 2019-07-25 10:38:21 -06:00
Sky Young
e334370777 Use new class based function checking instead of prototypal checking 2019-07-25 09:01:30 -06:00
Sky Young
d1034b3a82 Use proper Transaction import 2019-07-25 08:48:41 -06:00
Sky Young
1b1ef05ada Add fcoin to package.json 2019-07-24 12:57:00 -06:00
Sky Young
dae4c318c2 Properly deserialize Block using fcoin 2019-07-24 12:56:36 -06:00
Sky Young
a062769cc4 Update flocore-lib 2019-07-23 11:10:25 -06:00
Sky Young
f3cdb94450 Update fcoin 2019-07-19 15:09:09 -06:00
Sky Young
25adf9f18f Update version 2018-05-18 17:54:22 -07:00
Sky Young
14f3c30ad3 'bitcore-lib' -> 'flocore-lib' 2018-05-15 14:58:33 -07:00
Sky Young
335bd996b5 Update package references 2018-05-15 14:52:30 -07:00
Sky Young
c898c3bc50 Update Packages 2018-04-30 12:12:50 -07:00
Chris Kleeschulte
10a21524d8
Bumped version. 2017-08-29 16:20:04 -04:00
Chris Kleeschulte
c8e273aefe
Fixed tests so that Bitcore Blocks/Txs and Bcoin Blocks/Txs can be used
in tests and code.
2017-08-21 14:03:05 -04:00
Jason Dreyzehner
8d756c6c56 chore(release): 5.0.0-beta.1 2017-08-18 19:00:28 -04:00
Jason Dreyzehner
0e333f3248 Merge remote-tracking branch 'upstream/bcoin' 2017-08-18 18:51:34 -04:00
Jason Dreyzehner
d7970b2553 chore(package): use transitional bitcore-lib 2017-08-15 16:12:43 -04:00
Chris Kleeschulte
5de3ca9eab Merge pull request #94 from gabegattis/dependency/bcoin
change bcoin dependency to 1.0.0-beta.12
2017-07-21 13:09:19 -04:00
Gabe Gattis
2fd4cbf622
change bcoin dependency to 1.0.0-beta.12 2017-07-21 12:50:04 -04:00
Chris Kleeschulte
f537eb86b9
bcoin tx and blocks support. 2017-07-21 09:05:56 -04:00
Chris Kleeschulte
59fbc03715
Bumped version. 2017-04-24 18:45:14 -04:00
Chris Kleeschulte
1ad1eed82a Merge pull request #93 from kleetus/updates/protocol
Default protocol and bitcore-lib updates
2017-04-24 18:37:23 -04:00
Chris Kleeschulte
c41681d6c2
Default protocol and bitcore-lib updates
- Default protocol should be 70001 instead of 70000 since there really
was no protocol in the 70000 series.
2017-04-24 17:47:38 -04:00
Matias Alejo Garcia
b7a6845a4f Update .travis.yml 2017-03-15 13:15:07 -03:00
Chris Kleeschulte
96c10c27ea Bumped version to v1.1.1. 2017-01-13 17:12:37 -05:00
Patrick Nagurny
e684688e4d Merge pull request #90 from kleetus/master
Dependency on substack's node-buffers
2017-01-13 15:07:13 -05:00
Chris Kleeschulte
8de9d91ee9 Dependency on substack's node-buffers
- Long-standing deprecation on Array.get has expired, node v6 no longer
supports this.
- Upstream project has not yet merged #17, therefore we must fork and
change this project's dependency.
2017-01-12 18:01:55 -05:00
Braydon Fuller
7bb9afd416 Bump package version to 1.1.0 2015-12-23 10:36:59 -05:00
Braydon Fuller
4d9f52ec98 Merge pull request #77 from smh/feature/reject-message
Implemented Reject message
2015-12-23 10:31:44 -05:00
Stein Martin Hustad
55242db152 Removed old commented code 2015-12-16 00:00:30 +01:00
Stein Martin Hustad
dbda30d4d1 Fix spacing 2015-12-16 00:00:30 +01:00
Stein Martin Hustad
0ea205d0eb Fix bug in Request.getPayload()
Apparently, node v0.12 buffer.writeUInt8 is more lenient than v0.10
2015-12-16 00:00:30 +01:00
Stein Martin Hustad
6a88aaae9b Add test for Reject message 2015-12-16 00:00:30 +01:00
Stein Martin Hustad
1dfdd66b72 Allow attributes in constructor 2015-12-16 00:00:30 +01:00
Stein Martin Hustad
576ba8a2b6 Implement Reject message 2015-12-16 00:00:30 +01:00
Braydon Fuller
5b2d3691cb Merge pull request #79 from bitjson/master
format
2015-11-17 22:41:30 -05:00
Jason Dreyzehner
c81875f014 Merge branch 'master' of github.com:bitpay/bitcore-p2p 2015-11-17 18:55:38 -05:00
Braydon Fuller
d2eeb5e09e Merge pull request #80 from unusualbob/fixTypos
Fix typos in docs
2015-10-22 11:55:46 -04:00
Rob Riddle
d951cbb3ba Fix typos in docs 2015-10-22 18:52:49 +03:00
Jason Dreyzehner
3a19287da1 format 2015-10-20 09:20:39 -04:00
Braydon Fuller
d13da49c87 Bump package version to 1.0.0 2015-10-16 16:30:47 -04:00
Braydon Fuller
1957f84380 Merge pull request #78 from bitjson/master
bitcore -> bitcore-lib
2015-10-16 15:31:05 -04:00
Jason Dreyzehner
e99803e634 bitcore -> bitcore-lib 2015-10-16 14:59:05 -04:00
Braydon Fuller
cde8888f02 Update CONTRIBUTING link 2015-08-19 13:54:20 -04:00
Braydon Fuller
053945bd89 Bump package version to 0.15.1 2015-08-19 12:33:59 -04:00
Braydon Fuller
aa4d3cea15 Bump package version to 0.15.0 2015-08-19 12:31:48 -04:00
Braydon Fuller
3404d718b1 Upgrade bitcore 0.13 2015-08-19 12:19:51 -04:00
Braydon Fuller
55e1583ade Bump package version to 0.14.2 2015-07-08 10:42:12 -04:00
Braydon Fuller
c0498ce563 Merge pull request #74 from mappum/bloom-fix
Fix Bloom filters with vData length >255
2015-07-06 13:42:11 -04:00
Matt Bell
6180e12f9c Use varint instead of uint8 for bloom filter data length 2015-07-05 17:22:32 -07:00
Matt Bell
0e0a0db2eb Add test to catch problems with large bloom filter deserialization 2015-07-05 17:22:05 -07:00
Braydon Fuller
d20e733ee5 Bump package version to 0.14.1 2015-06-04 12:40:10 -04:00
Braydon Fuller
eaef980ae8 Merge pull request #72 from pnagurny/feature/custom-messages
Support adding custom p2p messages
2015-06-04 12:14:50 -04:00
Patrick Nagurny
84cb896dd3 Added custom message docs 2015-06-04 11:58:15 -04:00
Patrick Nagurny
d4958eb05f support adding custom p2p messages 2015-06-04 11:09:24 -04:00
Manuel Araoz
05605b7b7c Bump package version to 0.14.0 2015-04-29 21:36:59 -03:00
Manuel Aráoz
bf986470cf Merge pull request #69 from maraoz/bitcore-version/v0.12.0
update bitcore version to v0.12.0
2015-04-29 21:34:34 -03:00
Manuel Araoz
c3f55d2433 update bitcore version to v0.12.0 2015-04-29 21:30:42 -03:00
Braydon Fuller
c5c90ddeb1 Merge pull request #68 from pnagurny/feature/defaultAddrPort
Make addr hash always include port
2015-04-29 13:35:41 -04:00
Patrick Nagurny
98ad4d2dd4 make port always included in addr hash even if not specified 2015-04-29 13:24:21 -04:00
Braydon Fuller
a603858c4b Bump package version to 0.13.0 2015-04-24 13:17:07 -04:00
Braydon Fuller
9799edfcd2 Merge pull request #65 from maraoz/refactor/network
Refactor network configuration for messages
2015-04-23 20:52:32 -04:00
Manuel Araoz
ed0bcc962a small changes after code review 2015-04-23 16:09:56 -03:00
Manuel Araoz
c3ead97d64 fix integration tests for testnet 2015-04-20 16:24:17 -03:00
Manuel Araoz
f01d6c98fb refactor network handling 2015-04-20 15:44:09 -03:00
Braydon Fuller
8313c4df5b Bump package version to 0.12.0 2015-04-01 16:27:34 -04:00
Manuel Aráoz
89924bb99c Merge pull request #62 from braydonf/bug/transactions-message
Transaction Message Version Bug
2015-04-01 16:54:24 -03:00
Braydon Fuller
81a9f1c1fb Removed arg argument from parent message constructor. 2015-04-01 15:26:20 -04:00
Braydon Fuller
36709faaa9 Added preconditions to addr message. 2015-04-01 12:13:26 -04:00
Braydon Fuller
81047d73bd Added preconditions to filteradd message. 2015-04-01 11:59:17 -04:00
Braydon Fuller
28b05e3cc1 Added preconditions to ping message 2015-04-01 11:55:00 -04:00
Braydon Fuller
79674a2d7b Added preconditions to pong message. 2015-04-01 11:52:35 -04:00
Braydon Fuller
cf7d06baaa Create transaction if not supplied in transaction message. 2015-04-01 11:42:51 -04:00
Braydon Fuller
101796f7e9 Improved inventory precondition checks to handle objects. 2015-04-01 11:11:06 -04:00
Braydon Fuller
3921c46507 Updated JSDocs for Message Commands 2015-03-31 19:04:45 -04:00
Braydon Fuller
f21e2439be Added preconditions to the Headers message. 2015-03-31 18:50:36 -04:00
Braydon Fuller
24ffd3f5f6 Use factory options as a seperate argument for messages. 2015-03-31 17:52:36 -04:00
Manuel Araoz
cf9a27095e Bump package version to 0.11.1 2015-03-31 16:03:27 -03:00
Braydon Fuller
91be171953 Added test for tx version bug. 2015-03-31 14:26:59 -04:00
Manuel Aráoz
8fc31e29db Merge pull request #58 from braydonf/bug/version-constructor-relay
Added default relay value for Version message.
2015-03-30 14:57:28 -03:00
Braydon Fuller
e933dac898 Added default relay value for Version message. 2015-03-30 13:49:44 -04:00
Esteban Ordano
aa0b0e2bc8 Merge pull request #59 from braydonf/bug/pool-peer-network
Pool Network to Peer
2015-03-30 14:28:41 -03:00
Esteban Ordano
da52b73796 Merge pull request #60 from braydonf/bug/magicnumber
Messages Magic Number Reference
2015-03-30 14:28:35 -03:00
Braydon Fuller
b109ff5910 Restructured message commands to use options referenced in messages 2015-03-27 18:52:03 -04:00
Braydon Fuller
1449c7deec Test for multiple message factories having a unique magicNumber 2015-03-27 15:04:09 -04:00
Braydon Fuller
c60f17f1f7 Included pool.network option for Peers instantiated from Pool 2015-03-27 14:17:24 -04:00
Braydon Fuller
833c5b4a13 Bump package version to 0.11.1 2015-03-26 14:31:45 -04:00
Eric Martindale
438638c4ab Merge pull request #57 from braydonf/bug/double-disconnect
Patched double disconnect bug.
2015-03-26 14:27:04 -04:00
Eric Martindale
4e605c18a0 Merge pull request #54 from braydonf/docs/fixes
update docs
2015-03-26 14:08:20 -04:00
Braydon Fuller
f8578ff114 Patched double disconnect bug.
- Added a test that simulates a peer abruptly disconnecting with an error
- Put an additional condition to prevent disconnecting on an error if already disconnected
- Closes #56
2015-03-26 14:00:38 -04:00
Eric Martindale
fa06ce01e9 Merge pull request #55 from braydonf/bug/addr-port
fixed bug with incoming connections not having a port on addr
2015-03-23 17:55:07 -04:00
Braydon Fuller
c555671bde fixed bug with incoming connections not having a port on addr 2015-03-23 15:10:14 -04:00
Braydon Fuller
573354a188 updated jsdocs to use closure style and fixed typos 2015-03-20 14:30:43 -04:00
Braydon Fuller
d0af31938b update api for docs/index.md 2015-03-17 22:03:55 -04:00
Manuel Araoz
df632f35c6 Bump package version to 0.10.3 2015-03-17 17:44:58 -03:00
Manuel Aráoz
487b5f0bb7 Merge pull request #48 from braydonf/feature/extensibility
Extensible Messages
2015-03-17 17:36:06 -03:00
Braydon Fuller
34c38466f7 moved inventory helper functions to builder 2015-03-17 16:01:52 -04:00
Braydon Fuller
7cfe6d1865 added unit tests for buffers.skip 2015-03-17 15:11:16 -04:00
Braydon Fuller
c0e3bdb190 removed fromObject method that is nolonger needed 2015-03-16 18:53:14 -04:00
Braydon Fuller
f6e9c437d6 upgrade bloom-filter to 0.2.0 2015-03-16 17:38:58 -04:00
Braydon Fuller
16aa98924a fixed spelling 2015-03-16 15:48:43 -04:00
Braydon Fuller
664ceb2d30 improved peer jsdoc, fixed docs for messages, and switched to use svg for readme icon 2015-03-16 15:44:50 -04:00
Braydon Fuller
8f2d0089df updated readme and fixed image scaling issue 2015-03-16 13:51:57 -04:00
Braydon Fuller
39d1ae9ac8 fix jsdocs for message commands 2015-03-16 12:59:29 -04:00
Braydon Fuller
f1aa4d3bc0 updated docs for message commands 2015-03-16 12:22:43 -04:00
Braydon Fuller
42985a7716 added jsdocs for message commands 2015-03-16 11:41:58 -04:00
Braydon Fuller
4c4d53a4c5 add jsdocs for inv, getdata and notfound 2015-03-16 11:27:05 -04:00
Braydon Fuller
50d7d37034 added jsdocs for bloomfilter and messages 2015-03-16 11:13:13 -04:00
Braydon Fuller
42c829e49c added jsdocs for inventory 2015-03-16 10:53:32 -04:00
Braydon Fuller
f17bbf5d6f updated jsdocs for peer and pool 2015-03-16 10:29:51 -04:00
Braydon Fuller
ede5f0b60c add test for seed event, and remove test stubs 2015-03-13 23:29:26 -04:00
Braydon Fuller
e62ddd93f6 added test for handling addr times and v6 addresses 2015-03-13 23:23:52 -04:00
Braydon Fuller
1545abbea0 added test for version message handling 2015-03-13 22:30:00 -04:00
Braydon Fuller
6db209b9b6 added test for message utils 2015-03-13 21:59:32 -04:00
Braydon Fuller
a15f11cc32 add test for default magic for command messages 2015-03-13 21:54:47 -04:00
Braydon Fuller
a57f864a6e updated documentation for peer and pool 2015-03-13 21:20:42 -04:00
Braydon Fuller
cfbee2d71f update documentation for messages 2015-03-13 21:13:15 -04:00
Braydon Fuller
e31f28e973 cleanup builder options 2015-03-13 20:54:50 -04:00
Braydon Fuller
6007dc6faf updated to use mapped constructers and removed build method 2015-03-13 20:49:24 -04:00
Braydon Fuller
97f39db081 added tests for pool _addConnectedPeer 2015-03-13 15:05:08 -04:00
Braydon Fuller
3b53593288 added tests for pool.listen and improved arguments for tx and block messages 2015-03-13 14:50:17 -04:00
Braydon Fuller
8c9babc093 added tests for command edge cases 2015-03-13 13:45:51 -04:00
Braydon Fuller
a8b8c59069 added default options for all command messages, and added tests 2015-03-13 12:50:55 -04:00
Manuel Araoz
826d6c40bb Bump package version to 0.10.2 2015-03-13 13:19:12 -03:00
Braydon Fuller
4ecb34e123 updated bitconid integration test to use exposed message constructors 2015-03-13 12:06:07 -04:00
Braydon Fuller
641443f6dd expose message construtors by name 2015-03-13 10:15:41 -04:00
Braydon Fuller
6461748cd5 make listening optional and fixed peer test 2015-03-12 18:52:31 -04:00
Patrick Nagurny
7f61fc62d4 listen for incoming connections 2015-03-12 18:22:45 -04:00
Braydon Fuller
faf2bb1868 updated bitcoind integration test 2015-03-12 12:17:57 -04:00
Braydon Fuller
091893b1e4 whitespace cleanup 2015-03-12 10:58:36 -04:00
Braydon Fuller
2eb7712ab8 organized commands to build from an array 2015-03-12 10:48:45 -04:00
Braydon Fuller
e460bf915d moved the rest of the commands into seperate files 2015-03-12 10:41:35 -04:00
Braydon Fuller
feda6e9370 pass options directly into command messages 2015-03-12 04:54:52 -04:00
Braydon Fuller
3c2afed5f5 moved ping and verack to seperate files 2015-03-12 04:48:58 -04:00
Braydon Fuller
6ca2f6982c Moved block and version messages and shared utils into seperate files. 2015-03-12 04:34:30 -04:00
Braydon Fuller
608e41de07 added test coverage for peer 2015-03-12 01:12:45 -04:00
Braydon Fuller
1dfe7091b4 added test for pool.sendMessage 2015-03-12 00:48:05 -04:00
Braydon Fuller
d63c1171db added test for inventory fromBufferReader 2015-03-12 00:37:15 -04:00
Braydon Fuller
7e29af4542 added missing precondition and moved buffer.skip to new file 2015-03-12 00:18:09 -04:00
Braydon Fuller
42288e8354 removed "buildFromObject" alias 2015-03-12 00:05:27 -04:00
Braydon Fuller
32a28b133a Removed bufferput dependency 2015-03-11 23:57:08 -04:00
Braydon Fuller
11bee8b900 Improved API:
- Renamed "Commands" to "builder"
- "Messages.parseMessage" to "Messages.parseBuffer"
- Changed to use private methods for "discardUntilNextMessage" and "buildFromBuffer"
- Cleaned up tests
2015-03-11 23:57:08 -04:00
Braydon Fuller
e8f0725081 Moved commands, message and messages to directory 2015-03-11 23:57:08 -04:00
Braydon Fuller
0b20724288 Changed options for Pool 2015-03-11 23:57:08 -04:00
Braydon Fuller
9a813bad96 Extensibility
- changed options in constructors
- define block and transaction constructors for block and tx messages
2015-03-11 23:57:00 -04:00
Mariano
ae93c147b7 Update README.md
Module logo added
Closes https://github.com/bitpay/bitcore/issues/1119
2015-03-06 12:28:07 -03:00
Manuel Aráoz
b7f4d5cb60 Merge pull request #45 from fanatid/filteradd
Fix filteradd in Message.COMMANDS
2015-03-04 15:21:51 -03:00
Braydon Fuller
6a3b2d0c83 added tests for commands 2015-03-04 09:55:13 -05:00
Kirill Fomichev
925dea7ad4 Fix filteradd in Message.COMMANDS 2015-03-04 17:29:37 +03:00
Braydon Fuller
7166f220a2 Merge pull request #42 from throughnothing/merkleblock
Merkleblock Message
2015-03-02 13:21:31 -05:00
Manuel Araoz
140e8254ec Bump package version to 0.10.1 2015-03-02 13:34:13 -03:00
Manuel Araoz
6c8ece5ed5 Bump package version to 0.10.0 2015-03-02 13:26:55 -03:00
Manuel Araoz
c3288e0acd upgrade bitcore version 2015-03-02 13:26:26 -03:00
William Wolf
d6b89176f2 MerkleBlock Tests 2015-02-26 08:39:56 -08:00
William Wolf
b7473e7080 Have pool propagate merkleblock events 2015-02-23 08:58:06 -08:00
William Wolf
641dfe579d Add MerkleBlock() Message 2015-02-20 13:50:15 -08:00
William Wolf
6f63e36eca add forFilteredBlock() on Inventory, GetData, NotFound 2015-02-20 13:50:15 -08:00
Manuel Aráoz
88d3a5a351 Merge pull request #39 from throughnothing/filter-messages
Add FilterLoad, FilterAdd, and FilterClear Messages
2015-02-19 16:48:25 -03:00
William Wolf
0ac40a9c47 isUndefined() fix 2015-02-19 08:56:35 -08:00
Manuel Aráoz
a9b4c078ea Merge pull request #40 from throughnothing/frelay-optional
The 'fRelay' param on Version requests is optional.
2015-02-19 11:31:34 -03:00
William Wolf
e1f2fbb7ed Small Pool.options defaults cleanup 2015-02-18 22:49:33 -08:00
William Wolf
06f285fc9f Merge pull request #3 from braydonf/frelay-optional
Increased test coverage for Pool
2015-02-18 17:48:18 -08:00
Braydon Fuller
e579182e6d Increased test coverage for Pool 2015-02-18 20:25:03 -05:00
William Wolf
d751e583b4 Remove 'size' from pool Test 2015-02-18 16:26:12 -08:00
William Wolf
fe08f5a4de Merge pull request #2 from braydonf/frelay-optional
Added jsdocs for options and changed size to maxSize
2015-02-18 16:17:05 -08:00
Braydon Fuller
4781694038 Added jsdocs for options and changed size to maxSize 2015-02-18 19:06:04 -05:00
William Wolf
63ad4e5438 Propogate 'relay' from Pool() to its Peer()s 2015-02-18 13:04:30 -08:00
William Wolf
05e5c37230 The 'fRelay' param on Version requests is optional.
I'v seen `RangeError: index out of range` crashes from nodes that don't
send it, when `bitcore-p2p` tries to `readUInt8()` the last bit.

This also adds `relay` property to `Peer` objects that is respected
when sending `Version` messages.
2015-02-18 11:21:01 -08:00
Manuel Aráoz
872dc52c63 Merge pull request #38 from throughnothing/peerconnect-event
Add 'peerconnect' event from Pool
2015-02-18 13:35:08 -03:00
Manuel Aráoz
a65edf80ae Merge pull request #37 from braydonf/feature/configuration-options
Added configuration option to only connect to trusted peers. Closes #26
2015-02-18 11:33:34 -03:00
Braydon Fuller
b643efa182 Rephrased documentation and fixed a typo. 2015-02-18 09:24:47 -05:00
Braydon Fuller
c07bc0461d Changed discover option to dnsSeed for clarity and added comments to documentation for the options. 2015-02-18 09:15:44 -05:00
William Wolf
51871a033d Merge pull request #1 from braydonf/filter-messages
Added a few more tests for buffer serialization with bitcoin core test data
2015-02-16 18:38:42 -08:00
Braydon Fuller
3d6f591ea9 Added a few more tests for buffer serialization with bitcoin core test data 2015-02-16 21:28:39 -05:00
William Wolf
abbe63666f Add FilterLoad, FilterAdd, and FilterClear Messages + BloomFilter 2015-02-16 18:15:42 -08:00
William Wolf
e0b58e25d6 Add 'peerconnect' event from Pool 2015-02-16 14:39:54 -08:00
Braydon Fuller
ae97e36ff0 Added configuration option to only connect to trusted peers. 2015-02-16 13:18:11 -05:00
Braydon Fuller
e19734593e Merge pull request #35 from throughnothing/pong-event
Fix pong type in Pool Events list?
2015-02-15 00:20:01 -05:00
William Wolf
730d08d24d Fix pong type in Pool Events list? 2015-02-14 17:33:37 -08:00
Braydon Fuller
88dbbd60e2 Merge pull request #34 from throughnothing/propagate-error-from-pool
Propagate Peer 'error' event up from Pool
2015-02-14 20:00:10 -05:00
William Wolf
146c9630ab Propagate Peer 'error' event up from Pool 2015-02-14 12:17:36 -08:00
Manuel Araoz
bb1ecb3fbc Bump package version to 0.10.0 2015-02-11 20:24:56 -03:00
Manuel Araoz
64f2f429bc upgrade bitcore 2015-02-11 20:24:10 -03:00
Esteban Ordano
6d12ec1f8e Merge pull request #33 from maraoz/lodash
remove lodash external dep and upgrade bitcore
2015-02-11 19:24:43 -03:00
Manuel Araoz
b9c90613fa remove lodash 2015-02-11 18:26:27 -03:00
Yemel Jardi
650f4618c4 Merge pull request #31 from maraoz/preconditions
various bugfixes and tests
2015-02-10 12:12:22 -03:00
Manuel Araoz
e816c8254d remove console.logs 2015-02-09 12:46:02 -03:00
Manuel Araoz
c62409c4bc increase test coverage 2015-02-06 17:54:24 -03:00
Manuel Araoz
b28f1b614d add tx inv test 2015-02-06 16:48:17 -03:00
Manuel Araoz
ee4ef8ebaf all tests passing! 2015-02-06 16:41:05 -03:00
Manuel Araoz
a58d76c875 fix Header message 2015-02-06 15:58:16 -03:00
Manuel Araoz
13361ea210 add _checkFinished 2015-02-06 15:06:45 -03:00
Manuel Araoz
7119fad398 fix ipv4/ipv6 address parsing and writing 2015-02-06 13:29:16 -03:00
Manuel Araoz
2489750913 complete Version message 2015-02-06 12:44:23 -03:00
Manuel Araoz
24b02ccaa6 fix #1018 2015-02-06 12:32:52 -03:00
Manuel Araoz
c8674a8633 working on fixing Version 2015-02-04 17:42:03 -03:00
Manuel Araoz
17e890996e add GetData unit data, and more integration tests 2015-02-03 13:07:14 -03:00
Yemel Jardi
61754b267b Merge pull request #25 from maraoz/add/getheaders
add data and tests for getheaders
2015-02-03 13:02:42 -03:00
Manuel Aráoz
124ff170ae Merge pull request #29 from braydonf/bug/integration-timeout
Increased the timeout time for integration tests
2015-02-03 01:52:46 -03:00
Braydon Fuller
a8f46e353f Increased the timeout time for integration tests
- A busy local peer may need more time to respond
- Since the tests are run optionally allowing for more time shouldn't be problematic
2015-02-02 20:05:57 -05:00
Manuel Araoz
e5fb2bba31 add data and tests for getheaders 2015-02-02 18:23:26 -03:00
Manuel Aráoz
e4741dc96e Merge pull request #24 from maraoz/add/notfound
Add notfound message
2015-02-02 18:23:25 -03:00
Manuel Araoz
d303e46f69 add docs 2015-02-02 16:24:17 -03:00
Manuel Araoz
9fcf186545 remove TODO 2015-02-02 16:20:30 -03:00
Manuel Araoz
cc7b58369b add unit tests for NotFound 2015-02-02 16:18:10 -03:00
Manuel Araoz
087c57e077 add notfound message 2015-02-02 15:56:32 -03:00
Esteban Ordano
432ffa9147 Merge pull request #15 from maraoz/fix/docs
docs improved
2015-02-02 15:42:35 -03:00
Esteban Ordano
3fa45d31df Merge pull request #22 from maraoz/improve/p2p
add bitcoind integration tests and general refactor
2015-02-02 15:42:01 -03:00
Braydon Fuller
c3b2809426 Merge pull request #23 from Klakurka/patch-1
Fixed typo.
2015-02-02 00:47:25 -05:00
David
80ea02cc9b Fixed typo. 2015-02-01 20:47:25 -08:00
Manuel Araoz
1937abac11 move bitcoind tests to separate folder 2015-01-30 17:30:39 -03:00
Manuel Araoz
98990dbc82 new bitcoind integration tests 2015-01-30 16:37:14 -03:00
Manuel Araoz
77e399cf1a remove only 2015-01-28 17:50:33 -03:00
Manuel Araoz
cdd34bf8df add tests for more messages 2015-01-28 17:50:14 -03:00
Manuel Araoz
eaca92b1c6 refactor message#fromBuffer() 2015-01-27 16:29:34 -03:00
Manuel Araoz
ce7ccc773e remove browser checks 2015-01-27 13:20:39 -03:00
Manuel Araoz
aeec0ddfda fix a test 2015-01-27 13:19:08 -03:00
Manuel Araoz
89cee5804f polish tests 2015-01-27 13:16:50 -03:00
Manuel Aráoz
e15910a113 Merge pull request #20 from braydonf/bug/bower
Remove bower.json and related docs. Fixes #18
2015-01-27 10:01:51 -03:00
Braydon Fuller
7744d2416c Remove bower.json and related docs. 2015-01-24 10:11:43 -05:00
Manuel Araoz
af6c2e8779 docs improved 2015-01-22 16:06:04 -03:00
Manuel Araoz
3de1561a5c Bump package version to 0.9.1 2015-01-22 16:02:22 -03:00
Manuel Araoz
cd45b72f8c use bleeding edge bitcore-build 2015-01-22 15:27:04 -03:00
Manuel Aráoz
31ef9f8a09 Merge pull request #14 from braydonf/bug/dependencies
Removed devDependency in dependency
2015-01-22 13:52:55 -03:00
Braydon Fuller
d2da23d834 Removed devDependency in dependency 2015-01-22 11:15:51 -05:00
Esteban Ordano
a76997af8f Bump package version to 0.9.0 2015-01-21 17:38:32 -03:00
58 changed files with 6246 additions and 1469 deletions

View File

@ -1,6 +1,7 @@
language: node_js
node_js:
- '0.10'
- '4'
- '6'
before_install:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start

View File

@ -1,11 +1,13 @@
P2P Networking capabilities for bitcore
<img src="http://bitcore.io/css/images/bitcore-p2p.svg" alt="bitcore payment protocol" height="35" width="102">
Bitcore P2P
=======
[![NPM Package](https://img.shields.io/npm/v/bitcore-p2p.svg?style=flat-square)](https://www.npmjs.org/package/bitcore-p2p)
[![Build Status](https://img.shields.io/travis/bitpay/bitcore-p2p.svg?branch=master&style=flat-square)](https://travis-ci.org/bitpay/bitcore-p2p)
[![Coverage Status](https://img.shields.io/coveralls/bitpay/bitcore-p2p.svg?style=flat-square)](https://coveralls.io/r/bitpay/bitcore-p2p?branch=master)
bitcore-p2p adds support for connecting to the bitcoin p2p network on node.
`bitcore-p2p` adds [Bitcoin protocol](https://en.bitcoin.it/wiki/Protocol_documentation) support for Bitcore.
See [the main bitcore repo](https://github.com/bitpay/bitcore) for more information.
@ -14,15 +16,12 @@ See [the main bitcore repo](https://github.com/bitpay/bitcore) for more informat
```sh
npm install bitcore-p2p
```
```sh
bower install bitcore-p2p
```
In order to connect to the bitcore network, you'll need to know the IP address of at least one node of the network. You can do that by using the known DNS servers. Then, you can connect to it:
In order to connect to the Bitcoin network, you'll need to know the IP address of at least one node of the network, or use [Pool](/docs/pool.md) to discover peers using a DNS seed.
```javascript
var Peer = require('bitcore-p2p').Peer;
var peer = new Peer('0.0.0.0');
var peer = new Peer({host: '127.0.0.1'});
peer.on('ready', function() {
// peer info
@ -50,11 +49,10 @@ Take a look at the [bitcore guide](http://bitcore.io/guide/peer.html) on the usa
## Contributing
See [CONTRIBUTING.md](https://github.com/bitpay/bitcore) on the main bitcore repo for information about how to contribute.
See [CONTRIBUTING.md](https://github.com/bitpay/bitcore/blob/master/CONTRIBUTING.md) on the main bitcore repo for information about how to contribute.
## License
Code released under [the MIT license](https://github.com/bitpay/bitcore/blob/master/LICENSE).
Copyright 2013-2015 BitPay, Inc. Bitcore is a trademark maintained by BitPay, Inc.

View File

@ -1,42 +0,0 @@
{
"name": "bitcore-p2p",
"main": "bitcore-p2p.min.js",
"version": "0.8.7",
"homepage": "http://bitcore.io",
"authors": [
"BitPay, Inc.",
{
"name": "Yemel Jardi",
"email": "yemel@bitpay.com"
},
{
"name": "Braydon Fuller",
"email": "braydon@bitpay.com"
},
{
"name": "Ryan X. Charles",
"email": "ryan@bitpay.com"
},
],
"description": "Interface to the bitcoin P2P network for bitcore",
"moduleType": [
"globals"
],
"keywords": [
"bitcoin",
"bitcore",
"btc",
"satoshi"
],
"license": "MIT",
"ignore": [
"**/.*",
"CONTRIBUTING.md",
"gulpfile.js",
"lib",
"index.js",
"karma.conf.js",
"npm-shrinkwrap.json",
"test"
]
}

View File

@ -1,32 +1,20 @@
---
title: Peer-to-Peer Networking
description: Peer-to-Peer Networking Capabilities for Bitcore
---
# Peer-to-Peer
## Description
The `bitcore-p2p` module provides peer-to-peer networking capabilites for [Bitcore](https://github.com/bitpay/bitcore), and includes [Peer](peer.md) and [Pool](pool.md) classes. A [Message](messages.md) class is also exposed, in addition to [several types of messages](messages.md). Pool will maintain connection to several peers, Peers represents a node in the bitcoin network, and Message represents data sent to and from a Peer. For detailed technical information about the bitcoin protocol, please visit the [Protocol Specification](https://en.bitcoin.it/wiki/Protocol_specification) on the Bitcoin Wiki.
The `bitcore-p2p` module provides peer-to-peer networking capabilities for [Bitcore](https://github.com/bitpay/bitcore), and includes [Peer](peer.md) and [Pool](pool.md) classes. A [Message](messages.md) class is also exposed, in addition to [several types of messages](messages.md). Pool will maintain connection to several peers, Peers represents a node in the bitcoin network, and Message represents data sent to and from a Peer. For detailed technical information about the bitcoin protocol, please visit the [Protocol Specification](https://en.bitcoin.it/wiki/Protocol_specification) on the Bitcoin Wiki.
## Installation
Peer-to-peer is implemented as a seperate module.
Peer-to-peer is implemented as a separate module.
For node projects:
```bash
npm install bitcore-p2p --save
```
For client-side projects:
```bash
bower install bitcore-p2p --save
```
## Quick Start
```javascript
var Peer = require('bitcore-p2p').Peer;
var peer = new Peer('5.9.85.34');
var peer = new Peer({host: '5.9.85.34'});
// handle events
peer.on('inv', function(message) {
@ -34,5 +22,4 @@ peer.on('inv', function(message) {
});
peer.connect();
```
```

View File

@ -1,50 +1,85 @@
title: Messages
description: A superclass for the messages of the bitcoin network
---
# Messages
The bitcoin protocol specifies a set of [messages](https://en.bitcoin.it/wiki/Protocol_specification) that can be sent from peer to peer. `bitcore-p2p` provides support for some of these messages.
To create a message, you can use any of the message constructors, here is a simple example:
```javascript
var messages = new Messages();
var message = messages.Ping();
```
There are also several convenient helpers for inventory based messages:
```javascript
message = messages.GetData.forTransaction(txHash);
message = messages.GetData.forBlock(blockHash);
message = messages.Inventory.forTransaction(txHash);
```
As well as sending "tx" and "block" messages with Bitcore instances:
```javascript
message = messages.Block(block);
message = messages.Transaction(transaction);
```
Note: A list of further messages is available below.
For advanced usage, you can also customize which constructor is used for Block and Transaction messages by passing it as an argument to Messages, for example:
```javascript
var messages = new Messages({Block: MyBlock, Transaction: MyTransaction});
```
And additionally a custom network:
```javascript
var messages = new Messages({network: Networks.testnet});
```
## List of Messages
### Version
The version message (`ver`) is used on connection creation, to advertise the type of node. The remote node will respond with its version, and no communication is possible until both peers have exchanged their versions. By default, bitcore advertises itself as named `bitcore` with the current version of the `bitcore-p2p` package.
### VerAck
Finishes the connection handshake started by the `ver` message.
### Inventory
From the bitcoin protocol spec: "Allows a node to advertise its knowledge of one or more objects. It can be received unsolicited, or in reply to getblocks.".
### GetData
From the bitcoin protocol spec: `getdata` is used in response to `inv`, to retrieve the content of a specific object, and is usually sent after receiving an `inv` packet, after filtering known elements. It can be used to retrieve transactions, but only if they are in the memory pool or relay set - arbitrary access to transactions in the chain is not allowed to avoid having clients start to depend on nodes having full transaction indexes (which modern nodes do not).
GetData inherits from Inventory, as they both have the same structure.
### Ping
### NotFound
notfound is a response to a getdata, sent if any requested data items could not be relayed, for example, because the requested transaction was not in the memory pool or relay set. Contains inventory information specifying which items were not found.
### Ping
Sent to another peer mainly to check the connection is still alive.
### Pong
Sent in response to a `ping` message.
### Address and GetAddresses
Provides information on known nodes of the network. `GetAddresses` is used to query another peer for known addresses.
### GetHeaders and Headers
`getheaders` allows a peer to query another about blockheaders. `headers` is sent in response to a `getheaders` message, containing information about block headers.
### GetBlocks and Block
Same as `getheaders` and `headers`, but the response comes one block at the time.
### Transaction
Message that contains a transaction.
## Custom Messages
It is possible to extend the default peer to peer messages and add custom ones. First you will need to create a message which resembles the default messages in `lib/messages/commands`.
Then to add the custom message:
```javascript
messages.add('custom', 'Custom', CustomMessage);
var customMessage = messages.Custom('argument');
```

View File

@ -1,46 +1,37 @@
title: Peer
description: The Peer class provides a simple interface for connecting to a node in the bitcoin network.
---
# Peer
## Description
Represents a node from the p2p bitcoin network. The Peer class supports connecting directly to other nodes or through a socks5 proxy like Tor.
## Creating a peer
The code to create a new peer looks like this:
```javascript
var Peer = require('bitcore-p2p').Peer;
// default port
var livenetPeer = new Peer('5.9.85.34');
var testnetPeer = new Peer('5.9.85.34', bitcore.testnet);
var livenetPeer = new Peer({host: '5.9.85.34'});
var testnetPeer = new Peer({host: '5.9.85.34', network: Networks.testnet});
// custom port
var livenetPeer = new Peer('5.9.85.34', 8334);
var testnetPeer = new Peer('5.9.85.34', 18334, bitcore.testnet);
var livenetPeer = new Peer({host: '5.9.85.34', port: 8334});
var testnetPeer = new Peer({host: '5.9.85.34', port: 18334, network: Networks.testnet});
// use sock5 proxy (Tor)
var peer = new Peer('5.9.85.34').setProxy('localhost', 9050);
var peer = new Peer({host: '5.9.85.34'}).setProxy('localhost', 9050);
```
## States
A peer instance is always in one of the following states:
* `disconnected`: No connection with the remote node.
* `connecting`: While establishing the connection.
* `connected`: Exchanging version packages.
* `ready`: Connection ready for sending and receiving messages.
- `disconnected`: No connection with the remote node.
- `connecting`: While establishing the connection.
- `connected`: Exchanging version packages.
- `ready`: Connection ready for sending and receiving messages.
You can subscribe to the change of those states as follows:
```javascript
var Peer = require('bitcore-p2p').Peer;
var peer = new Peer('5.9.85.34');
var peer = new Peer({host: '5.9.85.34'});
peer.on('ready', function() {
// peer info
@ -55,12 +46,11 @@ peer.connect();
```
## Handle messages
Once connected, a peer instance can send and receive messages. Every time a message arrives it's emitted as a new event. Let's see an example of this:
```javascript
var Peer = require('bitcore-p2p').Peer;
var peer = new Peer('5.9.85.34');
var peer = new Peer({host: '5.9.85.34'});
// handle events
peer.on('inv', function(message) {
@ -79,7 +69,6 @@ peer.connect();
```
## Sending messages
In order to send messages the Peer class offers the `sendMessage(message)` method, which receives an instance of a message. All supported messages can be found in the `Messages` module. For more information about messages refer to the [protocol specification](https://en.bitcoin.it/wiki/Protocol_specification).
An example for requesting other connected nodes to a peers looks like this:
@ -88,7 +77,7 @@ An example for requesting other connected nodes to a peers looks like this:
var p2p = require('bitcore-p2p')
var Peer = p2p.Peer;
var Messages = p2p.Messages;
var peer = new Peer('5.9.85.34');
var peer = new Peer({host: '5.9.85.34'});
peer.on('ready', function() {
var message = new Messages.GetAddresses();

View File

@ -1,8 +1,4 @@
title: Pool
description: A simple interface to create and maintain a set of connections to bitcoin nodes.
---
# Pool
A pool maintains a connection of [Peers](peer.md). A pool will discover peers via DNS seeds, as well as when peer addresses are announced through the network.
The quickest way to get connected is to run the following:
@ -10,9 +6,9 @@ The quickest way to get connected is to run the following:
```javascript
var Pool = require('bitcore-p2p').Pool;
var Networks = require('bitcore').Networks;
var Networks = require('bitcore-lib').Networks;
var pool = new Pool(Networks.livenet);
var pool = new Pool({network: Networks.livenet});
// connect to the network
pool.connect();
@ -24,7 +20,37 @@ pool.on('peerinv', function(peer, message) {
// will disconnect all peers
pool.disconnect()
```
For more information about Peer events please read the [Peer](peer.md) documentation. Peer events are relayed to the pool, a peer event `inv` in the pool would be `peerinv`. When a peer is disconnected the pool will try to connect to the list of known addresses to maintain connection.
## Trusted Peers
By default, peers will be added via DNS discovery and as peers are announced in the network. Configuration options can be included to connect only to specific trusted peers:
```javascript
var pool = new Pool({
network: Networks.livenet, // the network object
dnsSeed: false, // prevent seeding with DNS discovered known peers upon connecting
listenAddr: false, // prevent new peers being added from addr messages
addrs: [ // initial peers to connect to
{
ip: {
v4: '127.0.0.1'
}
}
]
});
pool.connect();
```
## Listening for Peers
It's also possible to listen to incoming socket connections to add peers to the pool. To enable this capability, you can do the following:
```javascript
var pool = new Pool({network: Networks.livenet});
pool.listen();
```
When there are incoming connections the peer will be added to the pool.

View File

@ -1,4 +1,4 @@
var bitcore = require('bitcore');
var bitcore = require('flocore-lib');
bitcore.P2P = require('./lib');
module.exports = bitcore.P2P;

221
integration/bitcoind.js Normal file
View File

@ -0,0 +1,221 @@
'use strict';
var chai = require('chai');
/* jshint unused: false */
var should = chai.should();
var sinon = require('sinon');
var bitcore = require('bitcore-lib');
var _ = bitcore.deps._;
var Random = bitcore.crypto.Random;
var BN = bitcore.crypto.BN;
var BufferUtil = bitcore.util.buffer;
var p2p = require('../');
var Peer = p2p.Peer;
var Pool = p2p.Pool;
var Networks = bitcore.Networks;
var Messages = p2p.Messages;
var Inventory = p2p.Inventory;
var Block = bitcore.Block;
var Transaction = bitcore.Transaction;
// config
var network = process.env.NETWORK === 'testnet' ? Networks.testnet : Networks.livenet;
var messages = new Messages({
network: network
});
var blockHash = {
'livenet': '000000000000000013413cf2536b491bf0988f52e90c476ffeb701c8bfdb1db9',
'testnet': '0000000058cc069d964711cd25083c0a709f4df2b34c8ff9302ce71fe5b45786'
};
var stopBlock = {
'livenet': '00000000000000000b539ef570128acb953af3dbcfc19dd8e6066949672311a1',
'testnet': '00000000d0bc4271bcefaa7eb25000e345910ba16b91eb375cd944b68624de9f'
};
var txHash = {
'livenet': '22231e8219a0617a0ded618b5dc713fdf9b0db8ebd5bb3322d3011a703119d3b',
'testnet': '22231e8219a0617a0ded618b5dc713fdf9b0db8ebd5bb3322d3011a703119d3b'
};
// These tests require a running bitcoind instance
describe('Integration with ' + network.name + ' bitcoind', function() {
this.timeout(15000);
var opts = {
host: 'localhost',
network: network.name
};
it('handshakes', function(cb) {
var peer = new Peer(opts);
peer.once('version', function(m) {
m.version.should.be.above(70000);
m.services.toString().should.equal('1');
Math.abs(new Date() - m.timestamp).should.be.below(10000); // less than 10 seconds of time difference
m.nonce.length.should.equal(8);
m.startHeight.should.be.above(300000);
cb();
});
peer.once('verack', function(m) {
should.exist(m);
m.command.should.equal('verack');
});
peer.connect();
});
var connect = function(cb) {
var peer = new Peer(opts);
peer.once('ready', function() {
cb(peer);
});
peer.once('error', function(err) {
should.not.exist(err);
});
peer.connect();
};
it('connects', function(cb) {
connect(function(peer) {
peer.version.should.be.above(70000);
_.isString(peer.subversion).should.equal(true);
_.isNumber(peer.bestHeight).should.equal(true);
cb();
});
});
it('handles inv', function(cb) {
// assumes there will be at least one transaction/block
// in the next few seconds
connect(function(peer) {
peer.once('inv', function(message) {
message.inventory[0].hash.length.should.equal(32);
cb();
});
});
});
it('handles addr', function(cb) {
connect(function(peer) {
peer.once('addr', function(message) {
message.addresses.forEach(function(address) {
(address.time instanceof Date).should.equal(true);
should.exist(address.ip);
(address.services instanceof BN).should.equal(true);
});
cb();
});
var message = messages.GetAddr();
peer.sendMessage(message);
});
});
it('requests inv detailed info', function(cb) {
connect(function(peer) {
peer.once('block', function(message) {
should.exist(message.block);
cb();
});
peer.once('tx', function(message) {
should.exist(message.transaction);
cb();
});
peer.once('inv', function(message) {
var get = messages.GetData(message.inventory);
peer.sendMessage(get);
});
});
});
it('sends tx inv and receives getdata for that tx', function(cb) {
connect(function(peer) {
var type = Inventory.TYPE.TX;
var inv = [{
type: type,
hash: new Buffer(Random.getRandomBuffer(32)) // needs to be random for repeatability
}];
peer.once('getdata', function(message) {
message.inventory[0].should.deep.equal(inv[0]);
cb();
});
var message = messages.Inventory(inv);
message.inventory[0].hash.length.should.equal(32);
peer.sendMessage(message);
});
});
it('requests block data', function(cb) {
connect(function(peer) {
peer.once('block', function(message) {
(message.block instanceof Block).should.equal(true);
cb();
});
var message = messages.GetData.forBlock(blockHash[network.name]);
peer.sendMessage(message);
});
});
var fakeHash = 'e2dfb8afe1575bfacae1a0b4afc49af7ddda69285857267bae0e22be15f74a3a';
it('handles request tx data not found', function(cb) {
connect(function(peer) {
var expected = messages.NotFound.forTransaction(fakeHash);
peer.once('notfound', function(message) {
message.command.should.equal('notfound');
message.inventory[0].type.should.equal(Inventory.TYPE.TX);
var expectedHash = expected.inventory[0].hash.toString('hex');
message.inventory[0].hash.toString('hex').should.equal(expectedHash);
cb();
});
var message = messages.GetData.forTransaction(fakeHash);
peer.sendMessage(message);
});
});
var from = [blockHash[network.name]];
var stop = stopBlock[network.name];
it('gets headers', function(cb) {
connect(function(peer) {
peer.once('headers', function(message) {
message.command.should.equal('headers');
message.headers.length.should.equal(3);
cb();
});
var message = messages.GetHeaders({
starts: from,
stop: stop
});
peer.sendMessage(message);
});
});
it('gets blocks', function(cb) {
connect(function(peer) {
peer.once('inv', function(message) {
message.command.should.equal('inv');
if (message.inventory.length === 2) {
message.inventory[0].type.should.equal(Inventory.TYPE.BLOCK);
message.inventory[1].type.should.equal(Inventory.TYPE.BLOCK);
cb();
}
});
var message = messages.GetBlocks({
starts: from,
stop: stop
});
peer.sendMessage(message);
});
});
var testInvGetData = function(expected, message, cb) {
connect(function(peer) {
peer.once('getdata', function(message) {
message.command.should.equal('getdata');
message.inventory[0].type.should.equal(expected.inventory[0].type);
var expectedHash = expected.inventory[0].hash.toString('hex');
message.inventory[0].hash.toString('hex').should.equal(expectedHash);
cb();
});
peer.sendMessage(message);
});
};
it('sends block inv and receives getdata', function(cb) {
var randomHash = new Buffer(Random.getRandomBuffer(32)); // slow buffer
var expected = messages.GetData.forBlock(randomHash);
var message = messages.Inventory.forBlock(randomHash);
testInvGetData(expected, message, cb);
});
it('sends tx inv and receives getdata', function(cb) {
var randomHash = new Buffer(Random.getRandomBuffer(32)); // slow buffer
var expected = messages.GetData.forTransaction(randomHash);
var message = messages.Inventory.forTransaction(randomHash);
testInvGetData(expected, message, cb);
});
});

42
lib/bloomfilter.js Normal file
View File

@ -0,0 +1,42 @@
'use strict';
var bitcore = require('flocore-lib');
var BloomFilter = require('bloom-filter');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
/**
* A constructor for Bloom Filters
* @see https://github.com/bitpay/bloom-filter
* @param {Buffer} - payload
*/
BloomFilter.fromBuffer = function fromBuffer(payload) {
var obj = {};
var parser = new BufferReader(payload);
var length = parser.readVarintNum();
obj.vData = [];
for(var i = 0; i < length; i++) {
obj.vData.push(parser.readUInt8());
}
obj.nHashFuncs = parser.readUInt32LE();
obj.nTweak = parser.readUInt32LE();
obj.nFlags = parser.readUInt8();
return new BloomFilter(obj);
};
/**
* @returns {Buffer}
*/
BloomFilter.prototype.toBuffer = function toBuffer() {
var bw = new BufferWriter();
bw.writeVarintNum(this.vData.length);
for(var i = 0; i < this.vData.length; i++) {
bw.writeUInt8(this.vData[i]);
}
bw.writeUInt32LE(this.nHashFuncs);
bw.writeUInt32LE(this.nTweak);
bw.writeUInt8(this.nFlags);
return bw.concat();
};
module.exports = BloomFilter;

23
lib/buffers.js Normal file
View File

@ -0,0 +1,23 @@
'use strict';
var Buffers = require('buffers');
Buffers.prototype.skip = function(i) {
if (i === 0) {
return;
}
if (i >= this.length) {
this.buffers = [];
this.length = 0;
return;
}
var pos = this.pos(i);
this.buffers = this.buffers.slice(pos.buf);
this.buffers[0] = new Buffer(this.buffers[0].slice(pos.offset));
this.length -= i;
};
module.exports = Buffers;

View File

@ -5,4 +5,4 @@ var spec = {
message: 'Internal Error on bitcore-p2p Module {0}'
};
module.exports = require('bitcore').errors.extend(spec);
module.exports = require('flocore-lib').errors.extend(spec);

View File

@ -1,7 +1,10 @@
/**
* @namespace P2P
*/
module.exports = {
Inventory: require('./inventory'),
BloomFilter: require('./bloomfilter'),
Messages: require('./messages'),
Peer: require('./peer'),
Pool: require('./pool')

121
lib/inventory.js Normal file
View File

@ -0,0 +1,121 @@
'use strict';
var bitcore = require('flocore-lib');
var $ = bitcore.util.preconditions;
var BufferUtil = bitcore.util.buffer;
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var _ = bitcore.deps._;
/**
* A constructor for inventory related Bitcoin messages such as
* "getdata", "inv" and "notfound".
* @param {Object} obj
* @param {Number} obj.type - Inventory.TYPE
* @param {Buffer} obj.hash - The hash for the inventory
* @constructor
*/
function Inventory(obj) {
this.type = obj.type;
if (!BufferUtil.isBuffer(obj.hash)) {
throw new TypeError('Unexpected hash, expected to be a buffer');
}
this.hash = obj.hash;
}
/**
* A convenience constructor for Inventory.
* @param {Number} type - Inventory.TYPE
* @param {Buffer|String} hash - The hash for the inventory
* @returns {Inventory} - A new instance of Inventory
*/
Inventory.forItem = function(type, hash) {
$.checkArgument(hash);
if (_.isString(hash)) {
hash = new Buffer(hash, 'hex');
hash = BufferUtil.reverse(hash);
}
return new Inventory({type: type, hash: hash});
};
/**
* A convenience constructor for Inventory for block inventory types.
* @param {Buffer|String} hash - The hash for the block inventory
* @returns {Inventory} - A new instance of Inventory
*/
Inventory.forBlock = function(hash) {
return Inventory.forItem(Inventory.TYPE.BLOCK, hash);
};
/**
* A convenience constructor for Inventory for filtered/merkle block inventory types.
* @param {Buffer|String} hash - The hash for the filtered block inventory
* @returns {Inventory} - A new instance of Inventory
*/
Inventory.forFilteredBlock = function(hash) {
return Inventory.forItem(Inventory.TYPE.FILTERED_BLOCK, hash);
};
/**
* A convenience constructor for Inventory for transaction inventory types.
* @param {Buffer|String} hash - The hash for the transaction inventory
* @returns {Inventory} - A new instance of Inventory
*/
Inventory.forTransaction = function(hash) {
return Inventory.forItem(Inventory.TYPE.TX, hash);
};
/**
* @returns {Buffer} - Serialized inventory
*/
Inventory.prototype.toBuffer = function() {
var bw = new BufferWriter();
bw.writeUInt32LE(this.type);
bw.write(this.hash);
return bw.concat();
};
/**
* @param {BufferWriter} bw - An instance of BufferWriter
*/
Inventory.prototype.toBufferWriter = function(bw) {
bw.writeUInt32LE(this.type);
bw.write(this.hash);
return bw;
};
/**
* @param {Buffer} payload - Serialized buffer of the inventory
*/
Inventory.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
var obj = {};
obj.type = parser.readUInt32LE();
obj.hash = parser.read(32);
return new Inventory(obj);
};
/**
* @param {BufferWriter} br - An instance of BufferWriter
*/
Inventory.fromBufferReader = function(br) {
var obj = {};
obj.type = br.readUInt32LE();
obj.hash = br.read(32);
return new Inventory(obj);
};
// https://en.bitcoin.it/wiki/Protocol_specification#Inventory_Vectors
Inventory.TYPE = {};
Inventory.TYPE.ERROR = 0;
Inventory.TYPE.TX = 1;
Inventory.TYPE.BLOCK = 2;
Inventory.TYPE.FILTERED_BLOCK = 3;
Inventory.TYPE_NAME = [
'ERROR',
'TX',
'BLOCK',
'FILTERED_BLOCK'
];
module.exports = Inventory;

View File

@ -1,713 +0,0 @@
'use strict';
/**
* @namespace P2P.Message
*/
/* jshint curly: false */
var Buffers = require('buffers');
var Put = require('bufferput');
var util = require('util');
var bitcore = require('bitcore');
var BlockHeaderModel = bitcore.BlockHeader;
var BlockModel = bitcore.Block;
var BufferReader = bitcore.encoding.BufferReader;
var BufferUtil = bitcore.util.buffer;
var Hash = bitcore.crypto.Hash;
var Random = bitcore.crypto.Random;
var TransactionModel = bitcore.Transaction;
var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8);
var PROTOCOL_VERSION = 70000;
/**
* Static helper for consuming a data buffer until the next message.
*
* @name P2P.Message#parseMessage
* @param{Network} network - the network object
* @param{Buffer} dataBuffer - the buffer to read from
* @returns{Message|undefined} A message or undefined if there is nothing to read.
*/
var parseMessage = function(network, dataBuffer) {
if (dataBuffer.length < 20) return;
// Search the next magic number
if (!discardUntilNextMessage(network, dataBuffer)) return;
var PAYLOAD_START = 16;
var payloadLen = (dataBuffer.get(PAYLOAD_START)) +
(dataBuffer.get(PAYLOAD_START + 1) << 8) +
(dataBuffer.get(PAYLOAD_START + 2) << 16) +
(dataBuffer.get(PAYLOAD_START + 3) << 24);
var messageLength = 24 + payloadLen;
if (dataBuffer.length < messageLength) return;
var command = dataBuffer.slice(4, 16).toString('ascii').replace(/\0+$/, '');
var payload = dataBuffer.slice(24, messageLength);
var checksum = dataBuffer.slice(20, 24);
var checksumConfirm = Hash.sha256sha256(payload).slice(0, 4);
if (!BufferUtil.equals(checksumConfirm, checksum)) {
dataBuffer.skip(messageLength);
return;
}
dataBuffer.skip(messageLength);
return Message.buildMessage(command, payload);
};
module.exports.parseMessage = parseMessage;
/**
* @desc Internal function that discards data until another message is found.
* @name P2P.Message#discardUntilNextMessage
*/
function discardUntilNextMessage(network, dataBuffer) {
var magicNumber = network.networkMagic;
var i = 0;
for (;;) {
// check if it's the beginning of a new message
var packageNumber = dataBuffer.slice(0, 4);
if (BufferUtil.equals(packageNumber, magicNumber)) {
dataBuffer.skip(i);
return true;
}
// did we reach the end of the buffer?
if (i > (dataBuffer.length - 4)) {
dataBuffer.skip(i);
return false;
}
i++; // continue scanning
}
}
/**
* Abstract Message that knows how to parse and serialize itself.
* Concret subclases should implement {fromBuffer} and {getPayload} methods.
* @name P2P.Message
*/
function Message() {}
/**
* @value
* @name P2P.Message.COMMANDS
*/
Message.COMMANDS = {};
/**
* Look up a message type by command name and instantiate the correct Message
* @name P2P.Message#buildMessage
*/
Message.buildMessage = function(command, payload) {
try {
var CommandClass = Message.COMMANDS[command];
return new CommandClass().fromBuffer(payload);
} catch (err) {
console.log('Error while parsing message', err);
}
};
/**
* Parse instance state from buffer.
*
* @param{Buffer} payload - the buffer to read from
* @returns{Message} The same message instance
*/
Message.prototype.fromBuffer = function(payload) {
/* jshint unused: false */
return this;
};
/**
* Serialize the payload into a buffer.
*
* @returns{Buffer} the serialized payload
*/
Message.prototype.getPayload = function() {
return BufferUtil.EMPTY_BUFFER;
};
/**
* Serialize the message into a buffer.
*
* @returns{Buffer} the serialized message
*/
Message.prototype.serialize = function(network) {
var magic = network.networkMagic;
var commandBuf = new Buffer(this.command, 'ascii');
if (commandBuf.length > 12) throw 'Command name too long';
var payload = this.getPayload();
var checksum = Hash.sha256sha256(payload).slice(0, 4);
// -- HEADER --
var message = new Put();
message.put(magic);
message.put(commandBuf);
message.pad(12 - commandBuf.length); // zero-padded
message.word32le(payload.length);
message.put(checksum);
// -- BODY --
message.put(payload);
return message.buffer();
};
module.exports.Message = Message;
/**
* The version message(`ver`) is used on connection creation, to advertise
* the type of node.The remote node will respond with its version, and no
* communication is possible until both peers have exchanged their versions.
* By default, bitcore advertises itself as named `bitcore:0.8`.
*
* @name P2P.Message.Version
* @param{string} subversion - version of the client
* @param{Buffer} nonce - a random 8 bytes buffer
*/
function Version(subversion, nonce) {
var packageInfo = require('../package.json');
this.command = 'version';
this.version = PROTOCOL_VERSION;
this.subversion = subversion || '/bitcore:' + packageInfo.version + '/';
this.nonce = nonce || CONNECTION_NONCE;
}
util.inherits(Version, Message);
Version.prototype.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
/**
* @type {number}
* @desc The version of the bitcoin protocol
*/
this.version = parser.readUInt32LE();
/**
* @type {BN}
* @desc A mapbit with service bits: what features are supported by the peer
*/
this.services = parser.readUInt64LEBN();
/**
* @type {BN}
* @desc The time this message was sent
*/
this.timestamp = parser.readUInt64LEBN();
/**
* @type {Buffer}
* @desc IPv4/6 address of the interface used to connect to this peer
*/
this.addr_me = parser.read(26);
/**
* @type {Buffer}
* @desc IPv4/6 address of the peer
*/
this.addr_you = parser.read(26);
/**
* @type {Buffer}
* @desc A random number
*/
this.nonce = parser.read(8);
/**
* @desc A random number
* @type {string}
*/
this.subversion = parser.readVarintBuf().toString();
/**
* @desc The height of the last block accepted in the blockchain by this peer
* @type {number}
*/
this.start_height = parser.readUInt32LE();
return this;
};
Version.prototype.getPayload = function() {
var put = new Put();
put.word32le(this.version); // version
put.word64le(1); // services
put.word64le(Math.round(new Date().getTime() / 1000)); // timestamp
put.pad(26); // addr_me
put.pad(26); // addr_you
put.put(this.nonce);
put.varint(this.subversion.length);
put.put(new Buffer(this.subversion, 'ascii'));
put.word32le(0);
return put.buffer();
};
module.exports.Version = Message.COMMANDS.version = Version;
/**
* From the bitcoin protocol spec: "Allows a node to advertise its knowledge of
* one or more objects. It can be received unsolicited, or in reply to
* getblocks.".
*
* @name P2P.Message.Inventory
* @param{Array} inventory - reported elements
*/
function Inventory(inventory) {
this.command = 'inv';
/**
* @name P2P.Message.Inventory.inventory
* @desc An array of objects with `{type: int, hash: buffer}` signature
* @type {Array.Buffer}
*/
this.inventory = inventory || [];
}
util.inherits(Inventory, Message);
Inventory.prototype.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
var count = parser.readVarintNum();
for (var i = 0; i < count; i++) {
this.inventory.push({
type: parser.readUInt32LE(),
hash: parser.read(32)
});
}
return this;
};
Inventory.prototype.getPayload = function() {
var put = new Put();
put.varint(this.inventory.length);
this.inventory.forEach(function(value) {
put.word32le(value.type);
put.put(value.hash);
});
return put.buffer();
};
module.exports.Inventory = Message.COMMANDS.inv = Inventory;
/**
* getdata is used in response to inv, to retrieve the content of a specific
* object, and is usually sent after receiving an inv packet, after filtering
* known elements. It can be used to retrieve transactions, but only if they
* are in the memory pool or relay set - arbitrary access to transactions in the
* chain is not allowed to avoid having clients start to depend on nodes having
* full transaction indexes (which modern nodes do not).
*
* (taken from bitcoin's protocol spec)
*
* @name P2P.Message.GetData
* @param{Array} inventory - requested elements
*/
function GetData(inventory) {
this.command = 'getdata';
this.inventory = inventory || [];
}
util.inherits(GetData, Inventory);
module.exports.GetData = GetData;
/**
* Sent to another peer mainly to check the connection is still alive.
*
* @name P2P.Message.Ping
* @param{Buffer} nonce - a random 8 bytes buffer
*/
function Ping(nonce) {
this.command = 'ping';
/**
* @desc A random number that should be returned by the peer in a pong message
* @type {number}
*/
this.nonce = nonce || CONNECTION_NONCE;
}
util.inherits(Ping, Message);
Ping.prototype.fromBuffer = function(payload) {
this.nonce = new BufferReader(payload).read(8);
return this;
};
Ping.prototype.getPayload = function() {
return this.nonce;
};
module.exports.Ping = Message.COMMANDS.ping = Ping;
/**
* Sent in response to a Ping message
*
* @name P2P.Message.Pong
* @param{Buffer} nonce - a random 8 bytes buffer
*/
function Pong(nonce) {
this.command = 'pong';
/**
* @desc A random number that must match the one sent in the corresponding `ping` message
* @type {number}
*/
this.nonce = nonce || CONNECTION_NONCE;
}
util.inherits(Pong, Ping);
module.exports.Pong = Message.COMMANDS.pong = Pong;
/**
* Message used to notify about known addresses.
*
* @name P2P.Message.Addressess
* @param{Array} addresses - array of know addresses
*/
function Addresses(addresses) {
this.command = 'addr';
/**
* @type {Array.Buffer}
* @desc An array of ipv4/6 addresses
*/
this.addresses = addresses || [];
}
util.inherits(Addresses, Message);
Addresses.prototype.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
var addrCount = Math.min(parser.readVarintNum(), 1000);
this.addresses = [];
for (var i = 0; i < addrCount; i++) {
// TODO: Time actually depends on the version of the other peer (>=31402)
var time = parser.readUInt32LE();
var services = parser.readUInt64LEBN();
// parse the ipv6 to a string
var ipv6 = [];
for (var a = 0; a < 6; a++) {
ipv6.push(parser.read(2).toString('hex'));
}
ipv6 = ipv6.join(':');
// parse the ipv4 to a string
var ipv4 = [];
for (var b = 0; b < 4; b++) {
ipv4.push(parser.read(1)[0]);
}
ipv4 = ipv4.join('.');
var port = parser.readUInt16BE();
this.addresses.push({
time: time,
services: services,
ip: { v6: ipv6, v4: ipv4 },
port: port
});
}
return this;
};
Addresses.prototype.getPayload = function() {
var put = new Put();
put.varint(this.addresses.length);
for (var i = 0; i < this.addresses.length; i++) {
put.word32le(this.addresses[i].time);
put.word64le(this.addresses[i].services);
put.put(this.addresses[i].ip);
put.word16be(this.addresses[i].port);
}
return put.buffer();
};
module.exports.Addresses = Message.COMMANDS.addr = Addresses;
/**
* Query another node for known IPV4/6 addresses.
*
* @name P2P.Message.GetAddresses
*/
function GetAddresses() {
this.command = 'getaddr';
}
util.inherits(GetAddresses, Message);
module.exports.GetAddresses = Message.COMMANDS.getaddr = GetAddresses;
/**
* Finishes the connection handshake started by the `ver` message.
*
* @name P2P.Message.VerAck
*/
function VerAck() {
this.command = 'verack';
}
util.inherits(VerAck, Message);
module.exports.VerAck = Message.COMMANDS.verack = VerAck;
/**
* A reject message should be sent when a message is not supported or
* interpreted as invalid.
*
* @name P2P.Message.Reject
*/
function Reject() {
this.command = 'reject';
}
util.inherits(Reject, Message);
// TODO: Parse REJECT message
module.exports.Reject = Message.COMMANDS.reject = Reject;
/**
* Used to send a message signed by a developer of the bitcoin project.
*
* @name P2P.Message.Alert
*/
function Alert(payload, signature) {
this.command = 'alert';
this.payload = payload || new Buffer(32);
this.signature = signature || new Buffer(32);
}
util.inherits(Alert, Message);
Alert.prototype.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
this.payload = parser.readVarintBuf(); // TODO: Use current format
this.signature = parser.readVarintBuf();
return this;
};
Alert.prototype.getPayload = function() {
var put = new Put();
put.varint(this.payload.length);
put.put(this.payload);
put.varint(this.signature.length);
put.put(this.signature);
return put.buffer();
};
module.exports.Alert = Message.COMMANDS.alert = Alert;
/**
* Sent in response to a `getheaders` message. It contains information about
* block headers.
*
* @name P2P.Message.Headers
* @param{Array} blockheaders - array of block headers
*/
function Headers(blockheaders) {
this.command = 'headers';
/**
* @type {Array.BlockHeader}
* @desc An array of `BlockHeader`
*/
this.headers = blockheaders || [];
}
util.inherits(Headers, Message);
Headers.prototype.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
var count = parser.readVarintNum();
this.headers = [];
for (var i = 0; i < count; i++) {
var header = BlockHeaderModel._fromBufferReader(parser);
this.headers.push(header);
}
return this;
};
Headers.prototype.getPayload = function() {
var put = new Put();
put.varint(this.headers.length);
for (var i = 0; i < this.headers.length; i++) {
var buffer = this.headers[i].toBuffer();
put.put(buffer);
}
return put.buffer();
};
module.exports.Headers = Message.COMMANDS.headers = Headers;
/**
* Contains information about a Block
*
* @name P2P.Message.Block
* @param {Block} block
*/
function Block(block) {
this.command = 'block';
/**
* @type {Block}
* @desc The block received
*/
this.block = block;
}
util.inherits(Block, Message);
Block.prototype.fromBuffer = function(payload) {
this.block = BlockModel(payload);
return this;
};
Block.prototype.getPayload = function() {
return this.block.toBuffer();
};
module.exports.Block = Message.COMMANDS.block = Block;
/**
* Contains information about a transaction
*
* @name P2P.Message.Transaction
* @param{Transaction} transaction
*/
function Transaction(transaction) {
this.command = 'tx';
/**
* @type {Transaction}
*/
this.transaction = transaction;
}
util.inherits(Transaction, Message);
Transaction.prototype.fromBuffer = function(payload) {
this.transaction = TransactionModel(payload);
return this;
};
Transaction.prototype.getPayload = function() {
return this.transaction.toBuffer();
};
module.exports.Transaction = Message.COMMANDS.tx = Transaction;
/**
* Query another peer about blocks. It can query for multiple block hashes,
* and the response will contain all the chains of blocks starting from those
* hashes.
*
* @name P2P.Message.GetBlocks
* @param{Array} starts - array of buffers with the starting block hashes
* @param{Buffer} [stop] - hash of the last block
*/
function GetBlocks(starts, stop) {
this.command = 'getblocks';
/**
* @type {number}
*/
this.version = PROTOCOL_VERSION;
/**
* @type {Array.Buffer}
*/
this.starts = starts || [];
/**
* @type {Array.Buffer}
* @desc Hashes to limit the amount of blocks to be sent
*/
this.stop = stop || BufferUtil.NULL_HASH;
}
util.inherits(GetBlocks, Message);
GetBlocks.prototype.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
this.version = parser.readUInt32LE();
var startCount = Math.min(parser.readVarintNum(), 500);
this.starts = [];
for (var i = 0; i < startCount; i++) {
this.starts.push(parser.read(32));
}
this.stop = parser.read(32);
return this;
};
GetBlocks.prototype.getPayload = function() {
var put = new Put();
put.word32le(this.version);
put.varint(this.starts.length);
for (var i = 0; i < this.starts.length; i++) {
if (this.starts[i].length !== 32) {
throw new Error('Invalid hash length');
}
put.put(this.starts[i]);
}
if (this.stop.length !== 32) {
throw new Error('Invalid hash length');
}
put.put(this.stop);
return put.buffer();
};
module.exports.GetBlocks = Message.COMMANDS.getblocks = GetBlocks;
/**
* Request block headers starting from a hash
*
* @name P2P.Message.GetHeaders
* @param{Array} starts - array of buffers with the starting block hashes
* @param{Buffer} [stop] - hash of the last block
*/
function GetHeaders(starts, stop) {
this.command = 'getheaders';
/**
* @type {number}
*/
this.version = PROTOCOL_VERSION;
/**
* @type {Array.Buffer}
*/
this.starts = starts || [];
/**
* @type {Array.Buffer}
*/
this.stop = stop || BufferUtil.NULL_HASH;
}
util.inherits(GetHeaders, GetBlocks);
module.exports.GetHeaders = Message.COMMANDS.getheaders = GetHeaders;
/**
* Request for transactions on the mempool
*
* @name P2P.Message.GetMempool
*/
function GetMempool() {
this.command = 'mempool';
}
util.inherits(GetMempool, Message);
module.exports.GetMempool = Message.COMMANDS.mempool = GetMempool;
// TODO: Remove this PATCH (yemel)
Buffers.prototype.skip = function (i) {
if (i === 0) return;
if (i === this.length) {
this.buffers = [];
this.length = 0;
return;
}
var pos = this.pos(i);
this.buffers = this.buffers.slice(pos.buf);
this.buffers[0] = new Buffer(this.buffers[0].slice(pos.offset));
this.length -= i;
};

108
lib/messages/builder.js Normal file
View File

@ -0,0 +1,108 @@
'use strict';
var bitcore = require('flocore-lib');
var fcoin = require('fcoin');
var Inventory = require('../inventory');
function builder(options) {
/* jshint maxstatements: 20 */
/* jshint maxcomplexity: 10 */
if (!options) {
options = {};
}
if (!options.network) {
options.network = bitcore.Networks.defaultNetwork;
}
options.Block = options.Block || fcoin.Block;
options.BlockHeader = options.BlockHeader || bitcore.BlockHeader;
options.Transaction = options.Transaction || fcoin.TX;
options.MerkleBlock = options.MerkleBlock || bitcore.MerkleBlock;
options.protocolVersion = options.protocolVersion || 70001;
var exported = {
constructors: {
Block: options.Block,
BlockHeader: options.BlockHeader,
Transaction: options.Transaction,
MerkleBlock: options.MerkleBlock
},
defaults: {
protocolVersion: options.protocolVersion,
network: options.network
},
inventoryCommands: [
'getdata',
'inv',
'notfound'
],
commandsMap: {
version: 'Version',
verack: 'VerAck',
ping: 'Ping',
pong: 'Pong',
block: 'Block',
tx: 'Transaction',
getdata: 'GetData',
headers: 'Headers',
notfound: 'NotFound',
inv: 'Inventory',
addr: 'Addresses',
alert: 'Alert',
reject: 'Reject',
merkleblock: 'MerkleBlock',
filterload: 'FilterLoad',
filteradd: 'FilterAdd',
filterclear: 'FilterClear',
getblocks: 'GetBlocks',
getheaders: 'GetHeaders',
mempool: 'MemPool',
getaddr: 'GetAddr'
},
commands: {}
};
exported.add = function(key, Command) {
exported.commands[key] = function(obj) {
return new Command(obj, options);
};
exported.commands[key]._constructor = Command;
exported.commands[key].fromBuffer = function(buffer) {
var message = exported.commands[key]();
message.setPayload(buffer);
return message;
};
};
Object.keys(exported.commandsMap).forEach(function(key) {
exported.add(key, require('./commands/' + key));
});
exported.inventoryCommands.forEach(function(command) {
// add forTransaction methods
exported.commands[command].forTransaction = function forTransaction(hash) {
return new exported.commands[command]([Inventory.forTransaction(hash)]);
};
// add forBlock methods
exported.commands[command].forBlock = function forBlock(hash) {
return new exported.commands[command]([Inventory.forBlock(hash)]);
};
// add forFilteredBlock methods
exported.commands[command].forFilteredBlock = function forFilteredBlock(hash) {
return new exported.commands[command]([Inventory.forFilteredBlock(hash)]);
};
});
return exported;
}
module.exports = builder;

View File

@ -0,0 +1,64 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
/**
* @param {Array=} arg - An array of addrs
* @param {Object=} options
* @extends Message
* @constructor
*/
function AddrMessage(arg, options) {
Message.call(this, options);
this.command = 'addr';
$.checkArgument(
_.isUndefined(arg) ||
(Array.isArray(arg) &&
!_.isUndefined(arg[0].services) &&
!_.isUndefined(arg[0].ip) &&
!_.isUndefined(arg[0].port)),
'First argument is expected to be an array of addrs'
);
this.addresses = arg;
}
inherits(AddrMessage, Message);
AddrMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
var addrCount = parser.readVarintNum();
this.addresses = [];
for (var i = 0; i < addrCount; i++) {
// todo: time only available on versions >=31402
var time = new Date(parser.readUInt32LE() * 1000);
var addr = utils.parseAddr(parser);
addr.time = time;
this.addresses.push(addr);
}
utils.checkFinished(parser);
};
AddrMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeVarintNum(this.addresses.length);
for (var i = 0; i < this.addresses.length; i++) {
var addr = this.addresses[i];
bw.writeUInt32LE(addr.time.getTime() / 1000);
utils.writeAddr(addr, bw);
}
return bw.concat();
};
module.exports = AddrMessage;

View File

@ -0,0 +1,47 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
/**
* @param {Object=} arg
* @param {Buffer=} arg.payload
* @param {Buffer=} arg.signature
* @param {Object} options
* @extends Message
* @constructor
*/
function AlertMessage(arg, options) {
Message.call(this, options);
this.command = 'alert';
if (!arg) {
arg = {};
}
this.payload = arg.payload || new Buffer(32);
this.signature = arg.signature || new Buffer(32);
}
inherits(AlertMessage, Message);
AlertMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
this.payload = parser.readVarLengthBuffer();
this.signature = parser.readVarLengthBuffer();
utils.checkFinished(parser);
};
AlertMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeVarintNum(this.payload.length);
bw.write(this.payload);
bw.writeVarintNum(this.signature.length);
bw.write(this.signature);
return bw.concat();
};
module.exports = AlertMessage;

View File

@ -0,0 +1,43 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
/**
* @param {Block=} arg - An instance of a Block
* @param {Object} options
* @param {Function} options.Block - A block constructor
* @extends Message
* @constructor
*/
function BlockMessage(arg, options) {
Message.call(this, options);
this.Block = options.Block;
this.command = 'block';
$.checkArgument(
_.isUndefined(arg) || arg instanceof this.Block,
'An instance of Block or undefined is expected'
);
this.block = arg;
}
inherits(BlockMessage, Message);
BlockMessage.prototype.setPayload = function(payload) {
if (typeof this.Block.fromRaw === 'function') {
this.block = this.Block.fromRaw(payload);
} else {
this.block = this.Block.fromBuffer(payload);
}
};
BlockMessage.prototype.getPayload = function() {
if (typeof this.Block.fromRaw === 'function') {
return this.block.toRaw();
}
return this.block.toBuffer();
};
module.exports = BlockMessage;

View File

@ -0,0 +1,45 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferUtil = bitcore.util.buffer;
var BufferWriter = bitcore.encoding.BufferWriter;
var BufferReader = bitcore.encoding.BufferReader;
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
/**
* Request peer to add data to a bloom filter already set by 'filterload'
* @param {Buffer=} data - Array of bytes representing bloom filter data
* @param {Object=} options
* @extends Message
* @constructor
*/
function FilteraddMessage(arg, options) {
Message.call(this, options);
this.command = 'filteradd';
$.checkArgument(
_.isUndefined(arg) || BufferUtil.isBuffer(arg),
'First argument is expected to be a Buffer or undefined'
);
this.data = arg || BufferUtil.EMPTY_BUFFER;
}
inherits(FilteraddMessage, Message);
FilteraddMessage.prototype.setPayload = function(payload) {
$.checkArgument(payload);
var parser = new BufferReader(payload);
this.data = parser.readVarLengthBuffer();
utils.checkFinished(parser);
};
FilteraddMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeVarintNum(this.data.length);
bw.write(this.data);
return bw.concat();
};
module.exports = FilteraddMessage;

View File

@ -0,0 +1,25 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
/**
* Request peer to clear data for a bloom filter
* @extends Message
* @constructor
*/
function FilterclearMessage(arg, options) {
Message.call(this, options);
this.command = 'filterclear';
}
inherits(FilterclearMessage, Message);
FilterclearMessage.prototype.setPayload = function() {};
FilterclearMessage.prototype.getPayload = function() {
return BufferUtil.EMPTY_BUFFER;
};
module.exports = FilterclearMessage;

View File

@ -0,0 +1,41 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
var BloomFilter = require('../../bloomfilter');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
/**
* Request peer to send inv messages based on a bloom filter
* @param {BloomFilter=} arg - An instance of BloomFilter
* @param {Object} options
* @extends Message
* @constructor
*/
function FilterloadMessage(arg, options) {
Message.call(this, options);
this.command = 'filterload';
$.checkArgument(
_.isUndefined(arg) || arg instanceof BloomFilter,
'An instance of BloomFilter or undefined is expected'
);
this.filter = arg;
}
inherits(FilterloadMessage, Message);
FilterloadMessage.prototype.setPayload = function(payload) {
this.filter = BloomFilter.fromBuffer(payload);
};
FilterloadMessage.prototype.getPayload = function() {
if(this.filter) {
return this.filter.toBuffer();
} else {
return BufferUtil.EMPTY_BUFFER;
}
};
module.exports = FilterloadMessage;

View File

@ -0,0 +1,26 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
/**
* Request information about active peers
* @extends Message
* @param {Object} options
* @constructor
*/
function GetaddrMessage(arg, options) {
Message.call(this, options);
this.command = 'getaddr';
}
inherits(GetaddrMessage, Message);
GetaddrMessage.prototype.setPayload = function() {};
GetaddrMessage.prototype.getPayload = function() {
return BufferUtil.EMPTY_BUFFER;
};
module.exports = GetaddrMessage;

View File

@ -0,0 +1,64 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var $ = bitcore.util.preconditions;
/**
* Query another peer about blocks. It can query for multiple block hashes,
* and the response will contain all the chains of blocks starting from those
* hashes.
* @param {Object=} arg
* @param {Array=} arg.starts - Array of buffers or strings with the starting block hashes
* @param {Buffer=} arg.stop - Hash of the last block
* @param {Object} options
* @extends Message
* @constructor
*/
function GetblocksMessage(arg, options) {
Message.call(this, options);
this.command = 'getblocks';
this.version = options.protocolVersion;
if (!arg) {
arg = {};
}
arg = utils.sanitizeStartStop(arg);
this.starts = arg.starts;
this.stop = arg.stop;
}
inherits(GetblocksMessage, Message);
GetblocksMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
$.checkArgument(!parser.finished(), 'No data received in payload');
this.version = parser.readUInt32LE();
var startCount = parser.readVarintNum();
this.starts = [];
for (var i = 0; i < startCount; i++) {
this.starts.push(parser.read(32));
}
this.stop = parser.read(32);
utils.checkFinished(parser);
};
GetblocksMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeUInt32LE(this.version);
bw.writeVarintNum(this.starts.length);
for (var i = 0; i < this.starts.length; i++) {
bw.write(this.starts[i]);
}
if (this.stop.length !== 32) {
throw new Error('Invalid hash length: ' + this.stop.length);
}
bw.write(this.stop);
return bw.concat();
};
module.exports = GetblocksMessage;

View File

@ -0,0 +1,45 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var _ = bitcore.deps._;
/**
* @param {Object|Array=} - options - If options is an array will use as "inventory"
* @param {Array=} options.inventory - An array of inventory items
* @extends Message
* @constructor
*/
function GetdataMessage(arg, options) {
Message.call(this, options);
this.command = 'getdata';
utils.checkInventory(arg);
this.inventory = arg;
}
inherits(GetdataMessage, Message);
GetdataMessage.prototype.setPayload = function(payload) {
this.inventory = [];
var parser = new BufferReader(payload);
var count = parser.readVarintNum();
for (var i = 0; i < count; i++) {
var type = parser.readUInt32LE();
var hash = parser.read(32);
this.inventory.push({type: type, hash: hash});
}
utils.checkFinished(parser);
};
GetdataMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
utils.writeInventory(this.inventory, bw);
return bw.concat();
};
module.exports = GetdataMessage;

View File

@ -0,0 +1,63 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var $ = bitcore.util.preconditions;
/**
* Query another peer about block headers. It can query for multiple block hashes,
* and the response will contain all the chains of blocks starting from those
* hashes.
* @param {Object=} options
* @param {Array=} options.starts - Array of buffers or strings with the starting block hashes
* @param {Buffer=} options.stop - Hash of the last block
* @extends Message
* @constructor
*/
function GetheadersMessage(arg, options) {
Message.call(this, options);
this.command = 'getheaders';
this.version = options.protocolVersion;
if (!arg) {
arg = {};
}
arg = utils.sanitizeStartStop(arg);
this.starts = arg.starts;
this.stop = arg.stop;
}
inherits(GetheadersMessage, Message);
GetheadersMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
$.checkArgument(!parser.finished(), 'No data received in payload');
this.version = parser.readUInt32LE();
var startCount = Math.min(parser.readVarintNum(), 500);
this.starts = [];
for (var i = 0; i < startCount; i++) {
this.starts.push(parser.read(32));
}
this.stop = parser.read(32);
utils.checkFinished(parser);
};
GetheadersMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeUInt32LE(this.version);
bw.writeVarintNum(this.starts.length);
for (var i = 0; i < this.starts.length; i++) {
bw.write(this.starts[i]);
}
if (this.stop.length !== 32) {
throw new Error('Invalid hash length: ' + this.stop.length);
}
bw.write(this.stop);
return bw.concat();
};
module.exports = GetheadersMessage;

View File

@ -0,0 +1,60 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var _ = bitcore.deps._;
var $ = bitcore.util.preconditions;
/**
* Sent in response to a `getheaders` message. It contains information about
* block headers.
* @param {Array} arg - An array of BlockHeader instances
* @param {Object=} options
* @param {Array=} options.headers - array of block headers
* @param {Function} options.BlockHeader - a BlockHeader constructor
* @extends Message
* @constructor
*/
function HeadersMessage(arg, options) {
Message.call(this, options);
this.BlockHeader = options.BlockHeader;
this.command = 'headers';
$.checkArgument(
_.isUndefined(arg) || (Array.isArray(arg) && arg[0] instanceof this.BlockHeader),
'First argument is expected to be an array of BlockHeader instances'
);
this.headers = arg;
}
inherits(HeadersMessage, Message);
HeadersMessage.prototype.setPayload = function(payload) {
$.checkArgument(payload && payload.length > 0, 'No data found to create Headers message');
var parser = new BufferReader(payload);
var count = parser.readVarintNum();
this.headers = [];
for (var i = 0; i < count; i++) {
var header = this.BlockHeader.fromBufferReader(parser);
this.headers.push(header);
var txn_count = parser.readUInt8();
$.checkState(txn_count === 0, 'txn_count should always be 0');
}
utils.checkFinished(parser);
};
HeadersMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeVarintNum(this.headers.length);
for (var i = 0; i < this.headers.length; i++) {
var buffer = this.headers[i].toBuffer();
bw.write(buffer);
bw.writeUInt8(0);
}
return bw.concat();
};
module.exports = HeadersMessage;

View File

@ -0,0 +1,46 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var _ = bitcore.deps._;
/**
* @param {Array=} arg - An array of inventory
* @param {Object} options
* @param {Array=} options.inventory - An array of inventory items
* @extends Message
* @constructor
*/
function InvMessage(arg, options) {
Message.call(this, options);
this.command = 'inv';
utils.checkInventory(arg);
this.inventory = arg;
}
inherits(InvMessage, Message);
InvMessage.prototype.setPayload = function(payload) {
this.inventory = [];
var parser = new BufferReader(payload);
var count = parser.readVarintNum();
for (var i = 0; i < count; i++) {
var type = parser.readUInt32LE();
var hash = parser.read(32);
this.inventory.push({type: type, hash: hash});
}
utils.checkFinished(parser);
};
InvMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
utils.writeInventory(this.inventory, bw);
return bw.concat();
};
module.exports = InvMessage;

View File

@ -0,0 +1,28 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
/**
* The mempool message sends a request to a node asking for information about
* transactions it has verified but which have not yet confirmed.
* @see https://en.bitcoin.it/wiki/Protocol_documentation#mempool
* @param {Object} options
* @extends Message
* @constructor
*/
function MempoolMessage(arg, options) {
Message.call(this, options);
this.command = 'mempool';
}
inherits(MempoolMessage, Message);
MempoolMessage.prototype.setPayload = function() {};
MempoolMessage.prototype.getPayload = function() {
return BufferUtil.EMPTY_BUFFER;
};
module.exports = MempoolMessage;

View File

@ -0,0 +1,40 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
/**
* Contains information about a MerkleBlock
* @see https://en.bitcoin.it/wiki/Protocol_documentation
* @param {MerkleBlock} arg - An instance of MerkleBlock
* @param {Object=} options
* @param {Function} options.MerkleBlock - a MerkleBlock constructor
* @extends Message
* @constructor
*/
function MerkleblockMessage(arg, options) {
Message.call(this, options);
this.MerkleBlock = options.MerkleBlock; // constructor
this.command = 'merkleblock';
$.checkArgument(
_.isUndefined(arg) || arg instanceof this.MerkleBlock,
'An instance of MerkleBlock or undefined is expected'
);
this.merkleBlock = arg;
}
inherits(MerkleblockMessage, Message);
MerkleblockMessage.prototype.setPayload = function(payload) {
$.checkArgument(BufferUtil.isBuffer(payload));
this.merkleBlock = this.MerkleBlock.fromBuffer(payload);
};
MerkleblockMessage.prototype.getPayload = function() {
return this.merkleBlock ? this.merkleBlock.toBuffer() : BufferUtil.EMPTY_BUFFER;
};
module.exports = MerkleblockMessage;

View File

@ -0,0 +1,46 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var _ = bitcore.deps._;
/**
* @param {Array} arg - An array of inventory
* @param {Object} options
* @param {Array=} options.inventory - An array of inventory items
* @extends Message
* @constructor
*/
function NotfoundMessage(arg, options) {
Message.call(this, options);
this.command = 'notfound';
utils.checkInventory(arg);
this.inventory = arg;
}
inherits(NotfoundMessage, Message);
NotfoundMessage.prototype.setPayload = function(payload) {
this.inventory = [];
var parser = new BufferReader(payload);
var count = parser.readVarintNum();
for (var i = 0; i < count; i++) {
var type = parser.readUInt32LE();
var hash = parser.read(32);
this.inventory.push({type: type, hash: hash});
}
utils.checkFinished(parser);
};
NotfoundMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
utils.writeInventory(this.inventory, bw);
return bw.concat();
};
module.exports = NotfoundMessage;

View File

@ -0,0 +1,41 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
var BufferUtil = bitcore.util.buffer;
var BufferReader = bitcore.encoding.BufferReader;
/**
* A message to confirm that a connection is still valid.
* @param {Number} arg - A nonce for the Ping message
* @param {Object=} options
* @extends Message
* @constructor
*/
function PingMessage(arg, options) {
Message.call(this, options);
this.command = 'ping';
$.checkArgument(
_.isUndefined(arg) || (BufferUtil.isBuffer(arg) && arg.length === 8),
'First argument is expected to be an 8 byte buffer'
);
this.nonce = arg || utils.getNonce();
}
inherits(PingMessage, Message);
PingMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
this.nonce = parser.read(8);
utils.checkFinished(parser);
};
PingMessage.prototype.getPayload = function() {
return this.nonce;
};
module.exports = PingMessage;

View File

@ -0,0 +1,41 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
var BufferUtil = bitcore.util.buffer;
var BufferReader = bitcore.encoding.BufferReader;
/**
* A message in response to a ping message.
* @param {Number} arg - A nonce for the Pong message
* @param {Object=} options
* @extends Message
* @constructor
*/
function PongMessage(arg, options) {
Message.call(this, options);
this.command = 'pong';
$.checkArgument(
_.isUndefined(arg) || (BufferUtil.isBuffer(arg) && arg.length === 8),
'First argument is expected to be an 8 byte buffer'
);
this.nonce = arg || utils.getNonce();
}
inherits(PongMessage, Message);
PongMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
this.nonce = parser.read(8);
utils.checkFinished(parser);
};
PongMessage.prototype.getPayload = function() {
return this.nonce;
};
module.exports = PongMessage;

View File

@ -0,0 +1,67 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
/**
* The reject message is sent when messages are rejected.
*
* @see https://en.bitcoin.it/wiki/Protocol_documentation#reject
* @param {Object=} arg - properties for the reject message
* @param {String=} arg.message - type of message rejected
* @param {Number=} arg.ccode - code relating to rejected message
* @param {String=} arg.reason - text version of reason for rejection
* @param {Buffer=} arg.data - Optional extra data provided by some errors.
* @param {Object} options
* @extends Message
* @constructor
*/
function RejectMessage(arg, options) {
if (!arg) {
arg = {};
}
Message.call(this, options);
this.command = 'reject';
this.message = arg.message;
this.ccode = arg.ccode;
this.reason = arg.reason;
this.data = arg.data;
}
inherits(RejectMessage, Message);
RejectMessage.CCODE = {
REJECT_MALFORMED: 0x01,
REJECT_INVALID: 0x10,
REJECT_OBSOLETE: 0x11,
REJECT_DUPLICATE: 0x12,
REJECT_NONSTANDARD: 0x40,
REJECT_DUST: 0x41,
REJECT_INSUFFICIENTFEE: 0x42,
REJECT_CHECKPOINT: 0x43
};
RejectMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
this.message = parser.readVarLengthBuffer().toString('utf-8');
this.ccode = parser.readUInt8();
this.reason = parser.readVarLengthBuffer().toString('utf-8');
this.data = parser.readAll();
utils.checkFinished(parser);
};
RejectMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeVarintNum(this.message.length);
bw.write(new Buffer(this.message, 'utf-8'));
bw.writeUInt8(this.ccode);
bw.writeVarintNum(this.reason.length);
bw.write(new Buffer(this.reason, 'utf-8'));
bw.write(this.data);
return bw.toBuffer();
};
module.exports = RejectMessage;

View File

@ -0,0 +1,47 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
/**
* @param {Transaction=} arg - An instance of Transaction
* @param {Object} options
* @extends Message
* @constructor
*/
function TransactionMessage(arg, options) {
Message.call(this, options);
this.command = 'tx';
this.Transaction = options.Transaction;
$.checkArgument(
_.isUndefined(arg) || arg instanceof this.Transaction,
'An instance of Transaction or undefined is expected'
);
this.transaction = arg;
if (!this.transaction) {
this.transaction = new this.Transaction();
}
}
inherits(TransactionMessage, Message);
TransactionMessage.prototype.setPayload = function(payload) {
if (typeof this.Transaction.fromRaw === 'function') {
this.transaction = this.Transaction.fromRaw(payload);
} else if (this.Transaction.prototype.fromBuffer) {
this.transaction = new this.Transaction().fromBuffer(payload);
} else {
this.transaction = this.Transaction.fromBuffer(payload);
}
};
TransactionMessage.prototype.getPayload = function() {
if (typeof this.Transaction.fromRaw === 'function') {
return this.transaction.toRaw();
}
return this.transaction.toBuffer();
};
module.exports = TransactionMessage;

View File

@ -0,0 +1,25 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
/**
* A message in response to a version message.
* @extends Message
* @constructor
*/
function VerackMessage(arg, options) {
Message.call(this, options);
this.command = 'verack';
}
inherits(VerackMessage, Message);
VerackMessage.prototype.setPayload = function() {};
VerackMessage.prototype.getPayload = function() {
return BufferUtil.EMPTY_BUFFER;
};
module.exports = VerackMessage;

View File

@ -0,0 +1,94 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var BufferWriter = bitcore.encoding.BufferWriter;
var BufferReader = bitcore.encoding.BufferReader;
var BN = bitcore.crypto.BN;
var utils = require('../utils');
var packageInfo = require('../../../package.json');
/**
* The version message is used on connection creation to advertise
* the type of node. The remote node will respond with its version, and no
* communication is possible until both peers have exchanged their versions.
*
* @see https://en.bitcoin.it/wiki/Protocol_documentation#version
* @param {Object=} arg - properties for the version message
* @param {Buffer=} arg.nonce - a random 8 byte buffer
* @param {String=} arg.subversion - version of the client
* @param {BN=} arg.services
* @param {Date=} arg.timestamp
* @param {Number=} arg.startHeight
* @param {Object} options
* @extends Message
* @constructor
*/
function VersionMessage(arg, options) {
/* jshint maxcomplexity: 10 */
if (!arg) {
arg = {};
}
Message.call(this, options);
this.command = 'version';
this.version = arg.version || options.protocolVersion;
this.nonce = arg.nonce || utils.getNonce();
this.services = arg.services || new BN(1, 10);
this.timestamp = arg.timestamp || new Date();
this.subversion = arg.subversion || '/bitcore:' + packageInfo.version + '/';
this.startHeight = arg.startHeight || 0;
this.relay = arg.relay === false ? false : true;
}
inherits(VersionMessage, Message);
VersionMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
this.version = parser.readUInt32LE();
this.services = parser.readUInt64LEBN();
this.timestamp = new Date(parser.readUInt64LEBN().toNumber() * 1000);
this.addrMe = {
services: parser.readUInt64LEBN(),
ip: utils.parseIP(parser),
port: parser.readUInt16BE()
};
this.addrYou = {
services: parser.readUInt64LEBN(),
ip: utils.parseIP(parser),
port: parser.readUInt16BE()
};
this.nonce = parser.read(8);
this.subversion = parser.readVarLengthBuffer().toString();
this.startHeight = parser.readUInt32LE();
if(parser.finished()) {
this.relay = true;
} else {
this.relay = !!parser.readUInt8();
}
utils.checkFinished(parser);
};
VersionMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeUInt32LE(this.version);
bw.writeUInt64LEBN(this.services);
var timestampBuffer = new Buffer(Array(8));
timestampBuffer.writeUInt32LE(Math.round(this.timestamp.getTime() / 1000), 0);
bw.write(timestampBuffer);
utils.writeAddr(this.addrMe, bw);
utils.writeAddr(this.addrYou, bw);
bw.write(this.nonce);
bw.writeVarintNum(this.subversion.length);
bw.write(new Buffer(this.subversion, 'ascii'));
bw.writeUInt32LE(this.startHeight);
bw.writeUInt8(this.relay);
return bw.concat();
};
module.exports = VersionMessage;

111
lib/messages/index.js Normal file
View File

@ -0,0 +1,111 @@
'use strict';
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
var Hash = bitcore.crypto.Hash;
var $ = bitcore.util.preconditions;
/**
* A factory to build Bitcoin protocol messages.
* @param {Object=} options
* @param {Network=} options.network
* @param {Function=} options.Block - A block constructor
* @param {Function=} options.BlockHeader - A block header constructor
* @param {Function=} options.MerkleBlock - A merkle block constructor
* @param {Function=} options.Transaction - A transaction constructor
* @constructor
*/
function Messages(options) {
this.builder = Messages.builder(options);
// map message constructors by name
for(var key in this.builder.commandsMap) {
var name = this.builder.commandsMap[key];
this[name] = this.builder.commands[key];
}
if (!options) {
options = {};
}
this.network = options.network || bitcore.Networks.defaultNetwork;
}
Messages.MINIMUM_LENGTH = 20;
Messages.PAYLOAD_START = 16;
Messages.Message = require('./message');
Messages.builder = require('./builder');
/**
* @param {Buffers} dataBuffer
*/
Messages.prototype.parseBuffer = function(dataBuffer) {
/* jshint maxstatements: 18 */
if (dataBuffer.length < Messages.MINIMUM_LENGTH) {
return;
}
// Search the next magic number
if (!this._discardUntilNextMessage(dataBuffer)) {
return;
}
var payloadLen = (dataBuffer.get(Messages.PAYLOAD_START)) +
(dataBuffer.get(Messages.PAYLOAD_START + 1) << 8) +
(dataBuffer.get(Messages.PAYLOAD_START + 2) << 16) +
(dataBuffer.get(Messages.PAYLOAD_START + 3) << 24);
var messageLength = 24 + payloadLen;
if (dataBuffer.length < messageLength) {
return;
}
var command = dataBuffer.slice(4, 16).toString('ascii').replace(/\0+$/, '');
var payload = dataBuffer.slice(24, messageLength);
var checksum = dataBuffer.slice(20, 24);
var checksumConfirm = Hash.sha256sha256(payload).slice(0, 4);
if (!BufferUtil.equals(checksumConfirm, checksum)) {
dataBuffer.skip(messageLength);
return;
}
dataBuffer.skip(messageLength);
return this._buildFromBuffer(command, payload);
};
Messages.prototype._discardUntilNextMessage = function(dataBuffer) {
$.checkArgument(dataBuffer);
$.checkState(this.network, 'network must be set');
var i = 0;
for (;;) {
// check if it's the beginning of a new message
var packageNumber = dataBuffer.slice(0, 4).toString('hex');
if (packageNumber === this.network.networkMagic.toString('hex')) {
dataBuffer.skip(i);
return true;
}
// did we reach the end of the buffer?
if (i > (dataBuffer.length - 4)) {
dataBuffer.skip(i);
return false;
}
i++; // continue scanning
}
};
Messages.prototype._buildFromBuffer = function(command, payload) {
if (!this.builder.commands[command]) {
throw new Error('Unsupported message command: ' + command);
}
return this.builder.commands[command].fromBuffer(payload);
};
Messages.prototype.add = function(key, name, Command) {
this.builder.add(key, Command);
this[name] = this.builder.commands[key];
};
module.exports = Messages;

43
lib/messages/message.js Normal file
View File

@ -0,0 +1,43 @@
'use strict';
var bitcore = require('flocore-lib');
var $ = bitcore.util.preconditions;
var BufferWriter = bitcore.encoding.BufferWriter;
var Hash = bitcore.crypto.Hash;
/**
* Base message that can be inherited to add an additional
* `getPayload` method to modify the message payload.
* @param {Object=} options
* @param {String=} options.command
* @param {Network=} options.network
* @constructor
*/
function Message(options) {
this.command = options.command;
this.network = options.network;
}
/**
* @returns {Buffer} - Serialized message
* @constructor
*/
Message.prototype.toBuffer = Message.prototype.serialize = function() {
$.checkState(this.network, 'Need to have a defined network to serialize message');
var commandBuf = new Buffer(Array(12));
commandBuf.write(this.command, 'ascii');
var payload = this.getPayload();
var checksum = Hash.sha256sha256(payload).slice(0, 4);
var bw = new BufferWriter();
bw.write(this.network.networkMagic);
bw.write(commandBuf);
bw.writeUInt32LE(payload.length);
bw.write(checksum);
bw.write(payload);
return bw.concat();
};
module.exports = Message;

117
lib/messages/utils.js Normal file
View File

@ -0,0 +1,117 @@
'use strict';
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
var utils;
module.exports = utils = {
checkInventory: function(arg) {
$.checkArgument(
_.isUndefined(arg) ||
(Array.isArray(arg) && arg.length === 0) ||
(Array.isArray(arg) && !_.isUndefined(arg[0].type) && !_.isUndefined(arg[0].hash)),
'Argument is expected to be an array of inventory objects'
);
},
checkFinished: function checkFinished(parser) {
if(!parser.finished()) {
throw new Error('Data still available after parsing');
}
},
getNonce: function getNonce() {
return bitcore.crypto.Random.getRandomBuffer(8);
},
writeIP: function writeIP(ip, bw) {
var words = ip.v6.split(':').map(function(s) {
return new Buffer(s, 'hex');
});
for (var i = 0; i < words.length; i++) {
var word = words[i];
bw.write(word);
}
},
writeAddr: function writeAddr(addr, bw) {
if (_.isUndefined(addr)) {
var pad = new Buffer(Array(26));
bw.write(pad);
return;
}
bw.writeUInt64LEBN(addr.services);
utils.writeIP(addr.ip, bw);
bw.writeUInt16BE(addr.port);
},
writeInventory: function writeInventory(inventory, bw) {
bw.writeVarintNum(inventory.length);
inventory.forEach(function(value) {
bw.writeUInt32LE(value.type);
bw.write(value.hash);
});
},
parseIP: function parseIP(parser) {
var ipv6 = [];
var ipv4 = [];
for (var a = 0; a < 8; a++) {
var word = parser.read(2);
ipv6.push(word.toString('hex'));
if (a >= 6) {
ipv4.push(word[0]);
ipv4.push(word[1]);
}
}
ipv6 = ipv6.join(':');
ipv4 = ipv4.join('.');
return {
v6: ipv6,
v4: ipv4
};
},
parseAddr: function parseAddr(parser) {
var services = parser.readUInt64LEBN();
var ip = utils.parseIP(parser);
var port = parser.readUInt16BE();
return {
services: services,
ip: ip,
port: port
};
},
sanitizeStartStop: function sanitizeStartStop(obj) {
/* jshint maxcomplexity: 10 */
/* jshint maxstatements: 20 */
$.checkArgument(_.isUndefined(obj.starts) || _.isArray(obj.starts));
var starts = obj.starts;
var stop = obj.stop;
if (starts) {
starts = starts.map(function(hash) {
if (_.isString(hash)) {
return BufferUtil.reverse(new Buffer(hash, 'hex'));
} else {
return hash;
}
});
} else {
starts = [];
}
for (var i = 0; i < starts.length; i++) {
if (starts[i].length !== 32) {
throw new Error('Invalid hash ' + i + ' length: ' + starts[i].length);
}
}
stop = obj.stop;
if (_.isString(stop)) {
stop = BufferUtil.reverse(new Buffer(stop, 'hex'));
}
if (!stop) {
stop = BufferUtil.NULL_HASH;
}
obj.starts = starts;
obj.stop = stop;
return obj;
}
};

View File

@ -1,58 +1,83 @@
'use strict';
var Buffers = require('buffers');
var Buffers = require('./buffers');
var EventEmitter = require('events').EventEmitter;
var Net = require('net');
var Socks5Client = require('socks5-client');
var util = require('util');
var bitcore = require('bitcore');
var bitcore = require('flocore-lib');
var bcoin = require('fcoin');
var Networks = bitcore.Networks;
var Messages = require('./messages');
var MAX_RECEIVE_BUFFER = 10000000;
var $ = bitcore.util.preconditions;
var util = require('util');
/**
* A Peer instance represents a remote bitcoin node and allows to communicate
* with it using the standar messages of the bitcoin p2p protocol.
* The Peer constructor will create an instance of Peer to send and receive messages
* using the standard Bitcoin protocol. A Peer instance represents one connection
* on the Bitcoin network. To create a new peer connection provide the host and port
* options and then invoke the connect method. Additionally, a newly connected socket
* can be provided instead of host and port.
*
* @example
* ```javascript
*
* var peer = new Peer('127.0.0.1').setProxy('127.0.0.1', 9050);
*
* var peer = new Peer({host: '127.0.0.1'}).setProxy('127.0.0.1', 9050);
* peer.on('tx', function(tx) {
* console.log('New transaction: ', tx.id);
* });
* peer.connect();
* ```
*
* @param {String} host - IP address of the remote host
* @param {Number} [port] - Port number of the remote host
* @param {Network} [network] - The context for this communication
* @param {Object} options
* @param {String} options.host - IP address of the remote host
* @param {Number} options.port - Port number of the remote host
* @param {Network} options.network - The network configuration
* @param {Boolean=} options.relay - An option to disable automatic inventory relaying from the remote peer
* @param {Socket=} options.socket - An existing connected socket
* @returns {Peer} A new instance of Peer.
* @constructor
*/
function Peer(host, port, network) {
function Peer(options) {
/* jshint maxstatements: 26 */
/* jshint maxcomplexity: 8 */
if (!(this instanceof Peer)) {
return new Peer(host, port, network);
return new Peer(options);
}
// overloading stuff
if (port instanceof Object && !network) {
network = port;
port = undefined;
if (options.socket) {
this.socket = options.socket;
this.host = this.socket.remoteAddress;
this.port = this.socket.remotePort;
this.status = Peer.STATUS.CONNECTED;
this._addSocketEventHandlers();
} else {
this.host = options.host || 'localhost';
this.status = Peer.STATUS.DISCONNECTED;
this.port = options.port;
}
this.host = host;
this.status = Peer.STATUS.DISCONNECTED;
this.network = network || Networks.livenet;
this.port = port || this.network.port;
this.network = Networks.get(options.network) || Networks.defaultNetwork;
if (!this.port) {
this.port = this.network.port;
}
this.messages = options.messages || new Messages({
network: this.network,
Block: bcoin.block,
Transaction: bcoin.tx
});
this.dataBuffer = new Buffers();
this.version = 0;
this.bestHeight = 0;
this.subversion = null;
this.relay = options.relay === false ? false : true;
this.versionSent = false;
// set message handlers
var self = this;
@ -64,16 +89,26 @@ function Peer(host, port, network) {
this.on('version', function(message) {
self.version = message.version;
self.subversion = message.subversion;
self.bestHeight = message.start_height
self.bestHeight = message.startHeight;
var verackResponse = self.messages.VerAck();
self.sendMessage(verackResponse);
if(!self.versionSent) {
self._sendVersion();
}
});
this.on('ping', function(message) {
self._sendPong(message.nonce);
});
return this;
}
util.inherits(Peer, EventEmitter);
Peer.MAX_RECEIVE_BUFFER = 10000000;
Peer.STATUS = {
DISCONNECTED: 'disconnected',
CONNECTING: 'connecting',
@ -83,15 +118,12 @@ Peer.STATUS = {
/**
* Set a socks5 proxy for the connection. Enables the use of the TOR network.
*
* @param {String} host - IP address of the proxy
* @param {Number} port - Port number of the proxy
* @returns {Peer} The same Peer instance.
*/
Peer.prototype.setProxy = function(host, port) {
if (this.status != Peer.STATUS.DISCONNECTED) {
throw Error('Invalid State');
}
$.checkState(this.status === Peer.STATUS.DISCONNECTED);
this.proxy = {
host: host,
@ -102,8 +134,7 @@ Peer.prototype.setProxy = function(host, port) {
/**
* Init the connection with the remote peer.
*
* @returns {Socket} The same peer instance.
* @returns {Peer} The same peer instance.
*/
Peer.prototype.connect = function() {
this.socket = this._getSocket();
@ -116,24 +147,38 @@ Peer.prototype.connect = function() {
self._sendVersion();
});
this.socket.on('error', self.disconnect.bind(this));
this.socket.on('end', self.disconnect.bind(this));
this.socket.on('data', function(data) {
self.dataBuffer.push(data);
if (self.dataBuffer.length > MAX_RECEIVE_BUFFER) return self.disconnect();
self._readMessage();
});
this._addSocketEventHandlers();
this.socket.connect(this.port, this.host);
return this;
};
Peer.prototype._addSocketEventHandlers = function() {
var self = this;
this.socket.on('error', self._onError.bind(this));
this.socket.on('end', self.disconnect.bind(this));
this.socket.on('data', function(data) {
self.dataBuffer.push(data);
if (self.dataBuffer.length > Peer.MAX_RECEIVE_BUFFER) {
// TODO: handle this case better
return self.disconnect();
}
self._readMessage();
});
};
Peer.prototype._onError = function(e) {
this.emit('error', e);
if (this.status !== Peer.STATUS.DISCONNECTED) {
this.disconnect();
}
};
/**
* Disconnects the remote connection.
*
* @returns {Socket} The same peer instance.
* @returns {Peer} The same peer instance.
*/
Peer.prototype.disconnect = function() {
this.status = Peer.STATUS.DISCONNECTED;
@ -144,18 +189,19 @@ Peer.prototype.disconnect = function() {
/**
* Send a Message to the remote peer.
*
* @param {Message} message - A message instance
*/
Peer.prototype.sendMessage = function(message) {
this.socket.write(message.serialize(this.network));
this.socket.write(message.toBuffer());
};
/**
* Internal function that sends VERSION message to the remote peer.
*/
Peer.prototype._sendVersion = function() {
var message = new Messages.Version();
// todo: include sending local ip address
var message = this.messages.Version({relay: this.relay});
this.versionSent = true;
this.sendMessage(message);
};
@ -163,7 +209,7 @@ Peer.prototype._sendVersion = function() {
* Send a PONG message to the remote peer.
*/
Peer.prototype._sendPong = function(nonce) {
var message = new Messages.Pong(nonce);
var message = this.messages.Pong(nonce);
this.sendMessage(message);
};
@ -171,8 +217,7 @@ Peer.prototype._sendPong = function(nonce) {
* Internal function that tries to read a message from the data buffer
*/
Peer.prototype._readMessage = function() {
var message = Messages.parseMessage(this.network, this.dataBuffer);
var message = this.messages.parseBuffer(this.dataBuffer);
if (message) {
this.emit(message.command, message);
this._readMessage();
@ -180,8 +225,7 @@ Peer.prototype._readMessage = function() {
};
/**
* Internal function that creates a socket using a proxy if neccesary.
*
* Internal function that creates a socket using a proxy if necessary.
* @returns {Socket} A Socket instance not yet connected.
*/
Peer.prototype._getSocket = function() {

View File

@ -2,12 +2,12 @@
var dns = require('dns');
var EventEmitter = require('events').EventEmitter;
var bitcore = require('bitcore');
var Networks = bitcore.Networks;
var bitcore = require('flocore-lib');
var sha256 = bitcore.crypto.Hash.sha256;
var Peer = require('./peer');
var Networks = bitcore.Networks;
var util = require('util');
var net = require('net');
function now() {
return Math.floor(new Date().getTime() / 1000);
@ -22,38 +22,63 @@ function now() {
* @example
* ```javascript
*
* var pool = new Pool(Networks.livenet);
* var pool = new Pool({network: Networks.livenet});
* pool.on('peerinv', function(peer, message) {
* // do something with the inventory announcement
* });
* pool.connect();
* ```
*
* @param {Network|String} network - The network to connect
* @param {Object=} options
* @param {Network=} options.network - The network configuration
* @param {Boolean=} options.listenAddr - Prevent new peers being added from addr messages
* @param {Boolean=} options.dnsSeed - Prevent seeding with DNS discovered known peers
* @param {Boolean=} options.relay - Prevent inventory announcements until a filter is loaded
* @param {Number=} options.maxSize - The max number of peers
* @returns {Pool}
* @constructor
*/
function Pool(network) {
function Pool(options) {
/* jshint maxcomplexity: 10 */
/* jshint maxstatements: 20 */
var self = this;
this.network = Networks.get(network) || Networks.defaultNetwork;
options = options || {};
this.keepalive = false;
this._connectedPeers = {};
this._addrs = [];
this.on('peeraddr', function peerAddrEvent(peer, message) {
var addrs = message.addresses;
var length = addrs.length;
for (var i = 0; i < length; i++) {
var addr = addrs[i];
// In case of an invalid time, assume "5 days ago"
if (addr.time <= 100000000 || addr.time > (now() + 10 * 60)) {
addr.time = now() - 5 * 24 * 60 * 60;
}
this._addAddr(addr);
this.listenAddr = options.listenAddr !== false;
this.dnsSeed = options.dnsSeed !== false;
this.maxSize = options.maxSize || Pool.MaxConnectedPeers;
this.messages = options.messages;
this.network = Networks.get(options.network) || Networks.defaultNetwork;
this.relay = options.relay === false ? false : true;
if (options.addrs) {
for(var i = 0; i < options.addrs.length; i++) {
this._addAddr(options.addrs[i]);
}
});
}
if (this.listenAddr) {
this.on('peeraddr', function peerAddrEvent(peer, message) {
var addrs = message.addresses;
var length = addrs.length;
for (var i = 0; i < length; i++) {
var addr = addrs[i];
var future = new Date().getTime() + (10 * 60 * 1000);
if (addr.time.getTime() <= 100000000000 || addr.time.getTime() > future) {
// In case of an invalid time, assume "5 days ago"
var past = new Date(new Date().getTime() - 5 * 24 * 60 * 60 * 1000);
addr.time = past;
}
this._addAddr(addr);
}
});
}
this.on('seed', function seedEvent(ips) {
ips.forEach(function(ip) {
@ -84,21 +109,21 @@ util.inherits(Pool, EventEmitter);
Pool.MaxConnectedPeers = 8;
Pool.RetrySeconds = 30;
Pool.PeerEvents = ['version', 'inv', 'getdata', 'ping', 'ping', 'addr',
'getaddr', 'verack', 'reject', 'alert', 'headers', 'block',
'tx', 'getblocks', 'getheaders'
Pool.PeerEvents = ['version', 'inv', 'getdata', 'ping', 'pong', 'addr',
'getaddr', 'verack', 'reject', 'alert', 'headers', 'block', 'merkleblock',
'tx', 'getblocks', 'getheaders', 'error', 'filterload', 'filteradd',
'filterclear'
];
/**
* Will initiatiate connection to peers, if available peers have been added to
* Will initiate connection to peers, if available peers have been added to
* the pool, it will connect to those, otherwise will use DNS seeds to find
* peers to connect. When a peer disconnects it will add another.
*/
Pool.prototype.connect = function connect() {
this.keepalive = true;
var self = this;
if (self._addrs.length === 0) {
if (this.dnsSeed) {
self._addAddrsFromSeeds();
} else {
self._fillConnections();
@ -106,7 +131,6 @@ Pool.prototype.connect = function connect() {
return this;
};
/**
* Will disconnect all peers that are connected.
*/
@ -126,12 +150,12 @@ Pool.prototype.numberConnected = function numberConnected() {
};
/**
* Will fill the conneted peers to the maximum amount.
* Will fill the connected peers to the maximum amount.
*/
Pool.prototype._fillConnections = function _fillConnections() {
var length = this._addrs.length;
for (var i = 0; i < length; i++) {
if (this.numberConnected() >= Pool.MaxConnectedPeers) {
if (this.numberConnected() >= this.maxSize) {
break;
}
var addr = this._addrs[i];
@ -162,32 +186,73 @@ Pool.prototype._removeConnectedPeer = function _removeConnectedPeer(addr) {
Pool.prototype._connectPeer = function _connectPeer(addr) {
var self = this;
function addConnectedPeer(addr) {
if (!this._connectedPeers[addr.hash]) {
var port = addr.port || self.network.port;
var ip = addr.ip.v4 || addr.ip.v6;
var peer = new Peer(ip, port, self.network);
peer.on('disconnect', function peerDisconnect() {
self.emit('peerdisconnect', peer, addr);
var peer = new Peer({
host: ip,
port: port,
messages: self.messages,
network: this.network,
relay: self.relay
});
peer.on('ready', function peerReady() {
self.emit('peerready', peer, addr);
});
Pool.PeerEvents.forEach(function addPeerEvents(event) {
peer.on(event, function peerEvent(message) {
self.emit('peer' + event, peer, message);
});
peer.on('connect', function peerConnect() {
self.emit('peerconnect', peer, addr);
});
self._addPeerEventHandlers(peer, addr);
peer.connect();
self._connectedPeers[addr.hash] = peer;
}
return this;
};
/**
* Adds a peer with a connected socket to the _connectedPeers object, and
* initializes the associated event handlers.
* @param {Socket} - socket - A new connected socket
* @param {Object} - addr - The associated addr object for the peer
*/
Pool.prototype._addConnectedPeer = function _addConnectedPeer(socket, addr) {
var self = this;
if (!this._connectedPeers[addr.hash]) {
addConnectedPeer(addr);
var peer = new Peer({
socket: socket,
network: this.network,
messages: self.messages
});
self._addPeerEventHandlers(peer, addr);
self._connectedPeers[addr.hash] = peer;
self.emit('peerconnect', peer, addr);
}
return this;
};
/**
* Will add disconnect and ready events for a peer and intialize
* handlers for relay peer message events.
*/
Pool.prototype._addPeerEventHandlers = function(peer, addr) {
var self = this;
peer.on('disconnect', function peerDisconnect() {
self.emit('peerdisconnect', peer, addr);
});
peer.on('ready', function peerReady() {
self.emit('peerready', peer, addr);
});
Pool.PeerEvents.forEach(function addPeerEvents(event) {
peer.on(event, function peerEvent(message) {
self.emit('peer' + event, peer, message);
});
});
};
/**
* Will deprioritize an addr in the list of addrs by moving it to the end
* of the array, and setting a retryTime
@ -212,6 +277,8 @@ Pool.prototype._deprioritizeAddr = function _deprioritizeAddr(addr) {
* @param {Object}
*/
Pool.prototype._addAddr = function _addAddr(addr) {
// Use default port if not specified
addr.port = addr.port || this.network.port;
// make a unique key
addr.hash = sha256(new Buffer(addr.ip.v6 + addr.ip.v4 + addr.port)).toString('hex');
@ -226,7 +293,7 @@ Pool.prototype._addAddr = function _addAddr(addr) {
if (!exists) {
this._addrs.unshift(addr);
}
return this;
return addr;
};
/**
@ -274,4 +341,41 @@ Pool.prototype.inspect = function inspect() {
this._addrs.length + '>';
};
/**
* Will send a message to all of the peers in the pool.
* @param {Message} message - An instance of the message to send
*/
Pool.prototype.sendMessage = function(message) {
// broadcast to peers
for(var key in this._connectedPeers) {
var peer = this._connectedPeers[key];
peer.sendMessage(message);
}
};
/**
* Will enable a listener for peer connections, when a peer connects
* it will be added to the pool.
*/
Pool.prototype.listen = function() {
var self = this;
// Create server
this.server = net.createServer(function(socket) {
var addr = {
ip: {}
};
if(net.isIPv6(socket.remoteAddress)) {
addr.ip.v6 = socket.remoteAddress;
} else {
addr.ip.v4 = socket.remoteAddress;
}
addr.port = socket.remotePort;
addr = self._addAddr(addr);
self._addConnectedPeer(socket, addr);
});
this.server.listen(this.network.port);
};
module.exports = Pool;

2446
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,7 @@
{
"name": "bitcore-p2p",
"version": "0.9.0",
"description": "Interface to the bitcoin P2P network for bitcore",
"author": "BitPay <dev@bitpay.com>",
"name": "flocore-p2p",
"version": "5.0.0-beta.8",
"description": "Interface to the Flo P2P network for flocore",
"main": "index.js",
"scripts": {
"lint": "gulp lint",
@ -10,39 +9,6 @@
"coverage": "gulp coverage",
"build": "gulp"
},
"contributors": [
{
"name": "Yemel Jardi",
"email": "yemel@bitpay.com"
},
{
"name": "Braydon Fuller",
"email": "braydon@bitpay.com"
},
{
"name": "Ryan X. Charles",
"email": "ryan@bitpay.com"
},
{
"name": "Eric Martindale",
"email": "eric@bitpay.com"
},
{
"name": "Philip Hutchins",
"email": "philip@bitpay.com"
},
{
"name": "Manuel Araoz",
"email": "maraoz@bitpay.com"
},
{
"name": "Esteban Ordano",
"email": "eordano@gmail.com"
},
{
"name": "Elichai Turkel"
}
],
"keywords": [
"bitcoin",
"bitcore"
@ -52,18 +18,17 @@
"url": "https://github.com/bitpay/bitcore-p2p.git"
},
"dependencies": {
"bitcore": "^0.9.0",
"bufferput": "^0.1.2",
"buffers": "^0.1.1",
"karma-detect-browsers": "^0.1.3",
"bloom-filter": "^0.2.0",
"buffers": "bitpay/node-buffers#v0.1.2-bitpay",
"fcoin": "^1.1.0",
"flocore-lib": "^0.15.2",
"socks5-client": "^0.3.6"
},
"devDependencies": {
"bitcore-build": "^0.5.0",
"bitcore-build": "git://github.com/bitpay/bitcore-build.git",
"brfs": "^1.2.0",
"chai": "~1.10.0",
"gulp": "^3.8.10",
"lodash": "^2.4.1",
"sinon": "^1.12.2"
},
"license": "MIT"

88
test/bloomfilter.js Normal file
View File

@ -0,0 +1,88 @@
'use strict';
var chai = require('chai');
var should = chai.should();
var assert = require('assert');
var bitcore = require('bitcore-lib');
var Data = require('./data/messages');
var P2P = require('../');
var BloomFilter = P2P.BloomFilter;
function getPayloadBuffer(messageBuffer) {
return new Buffer(messageBuffer.slice(48), 'hex');
}
// convert a hex string to a bytes buffer
function ParseHex(str) {
var result = [];
while (str.length >= 2) {
result.push(parseInt(str.substring(0, 2), 16));
str = str.substring(2, str.length);
}
var buf = new Buffer(result, 16);
return buf;
}
describe('BloomFilter', function() {
it('#fromBuffer and #toBuffer round trip', function() {
var testPayloadBuffer = getPayloadBuffer(Data.filterload.message);
var filter = new BloomFilter.fromBuffer(testPayloadBuffer);
filter.toBuffer().should.deep.equal(testPayloadBuffer);
});
// test data from: https://github.com/bitcoin/bitcoin/blob/master/src/test/bloom_tests.cpp
it('serialize filter with public keys added', function() {
var privateKey = bitcore.PrivateKey.fromWIF('5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C');
var publicKey = privateKey.toPublicKey();
var filter = BloomFilter.create(2, 0.001, 0, BloomFilter.BLOOM_UPDATE_ALL);
filter.insert(publicKey.toBuffer());
filter.insert(bitcore.crypto.Hash.sha256ripemd160(publicKey.toBuffer()));
var expectedFilter = BloomFilter.fromBuffer(ParseHex('038fc16b080000000000000001'));
filter.toBuffer().should.deep.equal(expectedFilter.toBuffer());
});
it('serialize to a buffer', function() {
var filter = BloomFilter.create(3, 0.01, 0, BloomFilter.BLOOM_UPDATE_ALL);
filter.insert(ParseHex('99108ad8ed9bb6274d3980bab5a85c048f0950c8'));
assert(filter.contains(ParseHex('99108ad8ed9bb6274d3980bab5a85c048f0950c8')));
// one bit different in first byte
assert(!filter.contains(ParseHex('19108ad8ed9bb6274d3980bab5a85c048f0950c8')));
filter.insert(ParseHex('b5a2c786d9ef4658287ced5914b37a1b4aa32eee'));
assert(filter.contains(ParseHex("b5a2c786d9ef4658287ced5914b37a1b4aa32eee")));
filter.insert(ParseHex('b9300670b4c5366e95b2699e8b18bc75e5f729c5'));
assert(filter.contains(ParseHex('b9300670b4c5366e95b2699e8b18bc75e5f729c5')));
var actual = filter.toBuffer();
var expected = new Buffer('03614e9b050000000000000001', 'hex');
actual.should.deep.equal(expected);
});
it('deserialize a buffer', function() {
var buffer = new Buffer('03614e9b050000000000000001', 'hex');
var filter = BloomFilter.fromBuffer(buffer);
assert(filter.contains(ParseHex('99108ad8ed9bb6274d3980bab5a85c048f0950c8')));
assert(!filter.contains(ParseHex('19108ad8ed9bb6274d3980bab5a85c048f0950c8')));
assert(filter.contains(ParseHex("b5a2c786d9ef4658287ced5914b37a1b4aa32eee")));
assert(filter.contains(ParseHex('b9300670b4c5366e95b2699e8b18bc75e5f729c5')));
});
it('#toBuffer and #fromBuffer round trip, with a large filter', function() {
var filter = BloomFilter.create(10000, 0.001);
var buffer = filter.toBuffer();
new BloomFilter.fromBuffer(buffer).should.deep.equal(filter);
});
});

63
test/buffers.js Normal file
View File

@ -0,0 +1,63 @@
'use strict';
var chai = require('chai');
var should = chai.should();
var Buffers = require('../lib/buffers');
describe('Buffers', function() {
var buffs = function buffs() {
var b = new Buffers();
b.push(new Buffer('0123', 'hex'));
b.push(new Buffer('4567', 'hex'));
b.push(new Buffer('89ab', 'hex'));
b.push(new Buffer('cdef', 'hex'));
return b;
};
it('set buffers to empty if "i" is greater than the total length', function() {
var b = buffs();
b.length.should.equal(8);
b.skip(100);
b.buffers.should.deep.equal([]);
b.length.should.equal(0);
});
it('set buffers to empty if "i" is equal than the total length', function() {
var b = buffs();
b.length.should.equal(8);
b.skip(8);
b.buffers.should.deep.equal([]);
b.length.should.equal(0);
});
it('do not skip if "i" is zero', function() {
var b = buffs();
b.skip(0);
b.length.should.equal(8);
});
it('remove part of the first buffer', function() {
var b = buffs();
b.skip(1);
b.length.should.equal(7);
b.buffers[0].should.deep.equal(new Buffer('23', 'hex'));
});
it('remove the first three buffers', function() {
var b = buffs();
b.skip(6);
b.length.should.equal(2);
should.not.exist(b.buffers[1]);
should.not.exist(b.buffers[2]);
should.not.exist(b.buffers[3]);
});
it('remove part of the fourth buffer', function() {
var b = buffs();
b.skip(7);
b.length.should.equal(1);
b.buffers[0].should.deep.equal(new Buffer('ef', 'hex'));
});
});

File diff suppressed because one or more lines are too long

View File

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Mocha</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="../node_modules/mocha/mocha.js"></script>
<script>mocha.setup('bdd')</script>
<script src="../tests.js"></script>
<script>
mocha.run();
</script>
</body>
</html>

107
test/inventory.js Normal file
View File

@ -0,0 +1,107 @@
'use strict';
/*jshint immed: false */
var should = require('chai').should();
var bitcore = require('bitcore-lib');
var P2P = require('../');
var Inventory = P2P.Inventory;
var BufferUtils = bitcore.util.buffer;
var BufferWriter = bitcore.encoding.BufferWriter;
var BufferReader = bitcore.encoding.BufferReader;
describe('Inventory', function() {
var hash = new Buffer('eb951630aba498b9a0d10f72b5ea9e39d5ff04b03dc2231e662f52057f948aa1', 'hex');
var hashedStr = BufferUtils.reverse(new Buffer(hash, 'hex')).toString('hex');
var inventoryBuffer = new Buffer(
'01000000eb951630aba498b9a0d10f72b5ea9e39d5ff04b03dc2231e662f52057f948aa1',
'hex'
);
describe('@constructor', function() {
it('create inventory', function() {
var inventory = new Inventory({type: Inventory.TYPE.TX, hash: hash});
should.exist(inventory);
});
it('error with string hash', function() {
(function() {
var inventory = new Inventory({type: Inventory.TYPE.TX, hash: hashedStr});
should.not.exist(inventory);
}).should.throw('Unexpected hash');
});
});
describe('#forItem', function() {
it('handle a string hash (reversed)', function() {
var inventory = Inventory.forItem(Inventory.TYPE.TX, hashedStr);
should.exist(inventory);
inventory.hash.should.deep.equal(new Buffer(hash, 'hex'));
});
});
describe('#forBlock', function() {
it('use correct block type', function() {
var inventory = Inventory.forBlock(hash);
should.exist(inventory);
inventory.type.should.equal(Inventory.TYPE.BLOCK);
});
});
describe('#forFilteredBlock', function() {
it('use correct filtered block type', function() {
var inventory = Inventory.forFilteredBlock(hash);
should.exist(inventory);
inventory.type.should.equal(Inventory.TYPE.FILTERED_BLOCK);
});
});
describe('#forTransaction', function() {
it('use correct filtered tx type', function() {
var inventory = Inventory.forTransaction(hash);
should.exist(inventory);
inventory.type.should.equal(Inventory.TYPE.TX);
});
});
describe('#toBuffer', function() {
it('serialize correctly', function() {
var inventory = Inventory.forTransaction(hash);
var buffer = inventory.toBuffer();
buffer.should.deep.equal(inventoryBuffer);
});
});
describe('#toBufferWriter', function() {
it('write to a buffer writer', function() {
var bw = new BufferWriter();
var inventory = Inventory.forTransaction(hash);
inventory.toBufferWriter(bw);
bw.concat().should.deep.equal(inventoryBuffer);
});
});
describe('#fromBuffer', function() {
it('deserialize a buffer', function() {
var inventory = Inventory.fromBuffer(inventoryBuffer);
should.exist(inventory);
inventory.type.should.equal(Inventory.TYPE.TX);
inventory.hash.should.deep.equal(hash);
});
});
describe('#fromBufferWriter', function() {
it('deserialize from a buffer reader', function() {
var bw = new BufferReader(inventoryBuffer);
var inventory = Inventory.fromBufferReader(bw);
should.exist(inventory);
inventory.type.should.equal(Inventory.TYPE.TX);
inventory.hash.should.deep.equal(hash);
});
});
});

View File

@ -1,355 +0,0 @@
'use strict';
var chai = require('chai');
var should = chai.should();
var bitcore = require('bitcore');
var Data = require('./data/messages');
var P2P = require('../');
var Messages = P2P.Messages;
var Networks = bitcore.Networks;
describe('Messages', function() {
describe('Version', function() {
it('should be able to create instance', function() {
var message = new Messages.Version();
message.command.should.equal('version');
message.version.should.equal(70000);
var version = require('../package.json').version;
message.subversion.should.equal('/bitcore:' + version + '/');
should.exist(message.nonce);
});
it('should be able to serialize the payload', function() {
var message = new Messages.Version();
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.Version();
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
it('should be able to parse payload', function() {
var payload = new Buffer(Data.VERSION.payload, 'hex');
new Messages.Version().fromBuffer(payload);
});
});
describe('VerAck', function() {
it('should be able to create instance', function() {
var message = new Messages.VerAck();
message.command.should.equal('verack');
});
it('should be able to serialize the payload', function() {
var message = new Messages.VerAck();
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.VerAck();
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
it('should be able to parse payload', function() {
var payload = new Buffer(Data.VERACK.payload, 'hex');
new Messages.VerAck().fromBuffer(payload);
});
});
describe('Inventory', function() {
it('should be able to create instance', function() {
var message = new Messages.Inventory();
message.command.should.equal('inv');
});
it('should be able to serialize the payload', function() {
var message = new Messages.Inventory();
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.Inventory();
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
it('should be able to parse payload', function() {
var payload = new Buffer(Data.INV.payload, 'hex');
new Messages.Inventory().fromBuffer(payload);
});
});
describe('Addresses', function() {
it('should be able to create instance', function() {
var message = new Messages.Addresses();
message.command.should.equal('addr');
});
it('should be able to serialize the payload', function() {
var message = new Messages.Addresses();
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.Addresses();
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
it('should be able to parse payload', function() {
var payload = new Buffer(Data.ADDR.payload, 'hex');
new Messages.Addresses().fromBuffer(payload);
});
});
describe('Ping', function() {
it('should be able to create instance', function() {
var message = new Messages.Ping();
message.command.should.equal('ping');
});
it('should be able to serialize the payload', function() {
var message = new Messages.Ping();
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.Ping();
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
it('should be able to parse payload', function() {
var payload = new Buffer(Data.PING.payload, 'hex');
new Messages.Ping().fromBuffer(payload);
});
});
describe('Pong', function() {
it('should be able to create instance', function() {
var message = new Messages.Pong();
message.command.should.equal('pong');
});
it('should be able to serialize the payload', function() {
var message = new Messages.Pong();
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.Pong();
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
it('should be able to parse payload', function() {
var payload = new Buffer(Data.PING.payload, 'hex');
new Messages.Pong().fromBuffer(payload);
});
});
describe('Alert', function() {
it('should be able to create instance', function() {
var message = new Messages.Alert();
message.command.should.equal('alert');
});
it('should be able to serialize the payload', function() {
var message = new Messages.Alert();
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.Alert();
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
});
describe('Reject', function() {
it('should be able to create instance', function() {
var message = new Messages.Reject();
message.command.should.equal('reject');
});
it('should be able to serialize the payload', function() {
var message = new Messages.Reject();
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.Reject();
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
});
describe('Block', function() {
var blockHex = '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000';
var block = new bitcore.Block(new Buffer(blockHex, 'hex'));
it('should be able to create instance', function() {
var message = new Messages.Block(block);
message.command.should.equal('block');
});
it('should be able to serialize the payload', function() {
var message = new Messages.Block(block);
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.Block(block);
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
});
describe('GetBlocks', function() {
it('should be able to create instance', function() {
var message = new Messages.GetBlocks();
message.command.should.equal('getblocks');
});
it('should be able to serialize the payload', function() {
var message = new Messages.GetBlocks();
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.GetBlocks();
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
});
describe('GetHeaders', function() {
it('should be able to create instance', function() {
var message = new Messages.GetHeaders();
message.command.should.equal('getheaders');
});
it('should be able to serialize the payload', function() {
var message = new Messages.GetHeaders();
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.GetHeaders();
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
});
describe('GetData', function() {
it('should be able to create instance', function() {
var message = new Messages.GetData();
message.command.should.equal('getdata');
});
it('should be able to serialize the payload', function() {
var message = new Messages.GetData();
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.GetData();
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
});
describe('GetData', function() {
it('should be able to create instance', function() {
var message = new Messages.GetData();
message.command.should.equal('getdata');
});
it('should be able to serialize the payload', function() {
var message = new Messages.GetData();
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.GetData();
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
});
describe('GetAddresses', function() {
it('should be able to create instance', function() {
var message = new Messages.GetAddresses();
message.command.should.equal('getaddr');
});
it('should be able to serialize the payload', function() {
var message = new Messages.GetAddresses();
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.GetAddresses();
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
});
describe('Headers', function() {
it('should be able to create instance', function() {
var message = new Messages.Headers();
message.command.should.equal('headers');
});
it('should be able to serialize the payload', function() {
var message = new Messages.Headers();
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.Headers();
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
});
describe('Transaction', function() {
it('should be able to create instance', function() {
var message = new Messages.Transaction(new bitcore.Transaction());
message.command.should.equal('tx');
});
it('should be able to serialize the payload', function() {
var message = new Messages.Transaction(new bitcore.Transaction());
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.Transaction(new bitcore.Transaction());
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
});
});

30
test/messages/builder.js Normal file
View File

@ -0,0 +1,30 @@
'use strict';
var should = require('chai').should();
var P2P = require('../../');
var builder = P2P.Messages.builder;
var bitcore = require('bitcore-lib');
describe('Messages Builder', function() {
describe('@constructor', function() {
it('should return commands based on default', function() {
// instantiate
var b = builder();
should.exist(b);
});
it('should return commands with customizations', function() {
// instantiate
var b = builder({
network: bitcore.Networks.testnet,
Block: bitcore.Block,
Transaction: bitcore.Transaction
});
should.exist(b);
});
});
});

View File

@ -0,0 +1,322 @@
'use strict';
var should = require('chai').should();
var expect = require('chai').expect;
var P2P = require('../../../');
var Messages = P2P.Messages;
var sinon = require('sinon');
var bitcore = require('bitcore-lib');
describe('Command Messages', function() {
var messages = new Messages();
describe('Addr', function() {
it('should error if arg is not an array of addrs', function() {
(function() {
var message = messages.Addresses(['not an addr']);
}).should.throw('First argument is expected to be an array of addrs');
});
it('should instantiate with an array of addrs', function() {
var message = messages.Addresses([{
ip: {
v4: 'localhost'
},
services: 1,
port: 1234
}]);
});
});
describe('Alert', function() {
it('should accept a transaction instance as an argument', function() {
var message = messages.Alert({
payload: new Buffer('abcdef', 'hex'),
signature: new Buffer('123456', 'hex')
});
message.payload.should.deep.equal(new Buffer('abcdef', 'hex'));
message.signature.should.deep.equal(new Buffer('123456', 'hex'));
});
});
describe('Transaction', function() {
it('should accept a transaction instance as an argument', function() {
var tx = new bitcore.Transaction();
var message = messages.Transaction(tx);
message.transaction.should.be.instanceof(bitcore.Transaction);
});
it('should create a transaction instance', function() {
var message = messages.Transaction();
message.transaction.should.be.instanceof(bitcore.Transaction);
});
it('version should remain the same', function() {
var tx = new bitcore.Transaction();
var version = Number(tx.version);
var message = messages.Transaction(tx);
message.transaction.version.should.equal(version);
});
});
describe('Block', function() {
it('should accept a block instance as an argument', function() {
var block = new bitcore.Block({
header: {},
transactions: []
});
var message = messages.Block(block);
message.block.should.be.instanceof(bitcore.Block);
});
});
describe('Pong', function() {
it('should error if nonce is not a buffer', function() {
(function() {
var message = messages.Pong('not a buffer');
}).should.throw('First argument is expected to be an 8 byte buffer');
});
it('should error if nonce buffer has invalid length', function() {
(function() {
var message = messages.Pong(new Buffer(Array(9)));
}).should.throw('First argument is expected to be an 8 byte buffer');
});
it('should set a nonce if not included', function() {
var message = messages.Pong();
should.exist(message.nonce);
message.nonce.length.should.equal(8);
});
});
describe('Ping', function() {
it('should error if nonce is not a buffer', function() {
(function() {
var message = messages.Ping('not a buffer');
}).should.throw('First argument is expected to be an 8 byte buffer');
});
it('should error if nonce buffer has invalid length', function() {
(function() {
var message = messages.Ping(new Buffer(Array(9)));
}).should.throw('First argument is expected to be an 8 byte buffer');
});
it('should set a nonce if not included', function() {
var message = messages.Ping();
should.exist(message.nonce);
message.nonce.length.should.equal(8);
});
});
describe('FilterAdd', function() {
it('should error if arg is not a buffer', function() {
(function() {
var message = messages.FilterAdd('not a buffer');
}).should.throw('First argument is expected to be a Buffer or undefined');
});
});
describe('FilterLoad', function() {
it('should return a null payload', function() {
var message = messages.FilterLoad();
var payload = message.getPayload();
payload.length.should.equal(0);
payload.should.be.instanceof(Buffer);
});
it('should error if filter is not a bloom filter', function() {
(function() {
var message = messages.FilterLoad({filter: 'not a bloom filter'});
}).should.throw('An instance of BloomFilter');
});
});
describe('Inventory', function() {
it('should error if arg is not an array', function() {
(function() {
var message = messages.Inventory({});
}).should.throw('Argument is expected to be an array of inventory objects');
});
it('should not error if arg is an empty array', function() {
var message = messages.Inventory([]);
});
it('should error if arg is not an array of inventory objects', function() {
(function() {
var message = messages.Inventory([Number(0)]);
}).should.throw('Argument is expected to be an array of inventory objects');
});
});
describe('Transaction', function() {
it('should be able to pass a custom Transaction', function(done) {
var Transaction = function(){};
Transaction.prototype.fromBuffer = function() {
done();
};
var messagesCustom = new Messages({Transaction: Transaction});
var message = messagesCustom.Transaction.fromBuffer();
should.exist(message);
});
it('should work with Transaction.fromBuffer', function(done) {
var Transaction = sinon.stub();
Transaction.fromBuffer = function() {
done();
};
var messagesCustom = new Messages({Transaction: Transaction});
var message = messagesCustom.Transaction.fromBuffer();
should.exist(message);
});
});
describe('Block', function() {
it('should be able to pass a custom Block', function(done) {
var Block = sinon.stub();
Block.fromBuffer = function() {
done();
};
var messagesCustom = new Messages({Block: Block});
var message = messagesCustom.Block.fromBuffer();
should.exist(message);
});
});
describe('GetBlocks', function() {
it('should error with invalid stop', function() {
var invalidStop = '000000';
var starts = ['000000000000000013413cf2536b491bf0988f52e90c476ffeb701c8bfdb1db9'];
(function() {
var message = messages.GetBlocks({starts: starts, stop: invalidStop});
var buffer = message.toBuffer();
should.not.exist(buffer);
}).should.throw('Invalid hash length');
});
});
describe('GetHeaders', function() {
it('should error with invalid stop', function() {
var invalidStop = '000000';
var starts = ['000000000000000013413cf2536b491bf0988f52e90c476ffeb701c8bfdb1db9'];
(function() {
var message = messages.GetHeaders({starts: starts, stop: invalidStop});
var buffer = message.toBuffer();
should.not.exist(buffer);
}).should.throw('Invalid hash length');
});
});
describe('Headers', function() {
it('should error if arg is not an array', function() {
(function() {
var message = messages.Headers({});
}).should.throw('First argument is expected to be an array');
});
it('should error if arg is an empty array', function() {
(function() {
var message = messages.Headers([]);
}).should.throw('First argument is expected to be an array');
});
it('should error if arg is not an array of BlockHeaders', function() {
(function() {
var message = messages.Headers([Number(0)]);
}).should.throw('First argument is expected to be an array');
});
});
describe('MerkleBlock', function() {
it('should return null buffer for payload', function() {
var message = messages.MerkleBlock();
var payload = message.getPayload();
payload.length.should.equal(0);
});
it('should error if merkleBlock is not a MerkleBlock', function() {
(function() {
var message = messages.MerkleBlock({merkleBlock: 'not a merkle block'});
}).should.throw('An instance of MerkleBlock');
});
});
describe('Reject', function() {
it('should set properties from arg in constructor', function() {
var message = messages.Reject({
message: 'tx',
ccode: 0x01,
reason: 'transaction is malformed',
data: new Buffer('12345678901234567890123456789012', 'hex')
});
message.message.should.equal('tx');
message.ccode.should.equal(0x01);
message.reason.should.equal('transaction is malformed');
message.data.toString('hex').should.equal('12345678901234567890123456789012');
});
it('should let arg be optional in constructor', function() {
var message = messages.Reject();
expect(message.message).to.be.undefined;
expect(message.ccode).to.be.undefined;
expect(message.reason).to.be.undefined;
expect(message.data).to.be.undefined;
});
it('should write payload correctly', function() {
var message = messages.Reject({
message: 'tx',
ccode: 0x01,
reason: 'transaction is malformed',
data: new Buffer('12345678901234567890123456789012', 'hex')
});
var payload = message.getPayload();
message = messages.Reject();
message.setPayload(payload);
message.message.should.equal('tx');
message.ccode.should.equal(0x01);
message.reason.should.equal('transaction is malformed');
message.data.toString('hex').should.equal('12345678901234567890123456789012');
});
});
describe('Version', function() {
it('should set the default relay property as true', function() {
var message = messages.Version();
should.exist(message.relay);
message.relay.should.equal(true);
});
it('should set the relay as false', function() {
var message = messages.Version({relay: false});
should.exist(message.relay);
message.relay.should.equal(false);
});
it('should set the relay as true', function() {
var message = messages.Version({relay: true});
should.exist(message.relay);
message.relay.should.equal(true);
});
});
});

216
test/messages/index.js Normal file
View File

@ -0,0 +1,216 @@
'use strict';
var chai = require('chai');
var should = chai.should();
var Buffers = require('buffers');
var P2P = require('../../');
var Messages = P2P.Messages;
var messages = new Messages();
var bitcore = require('bitcore-lib');
var Data = require('../data/messages'); //todo merge with commandData
var commandData = require('../data/messages.json');
function getPayloadBuffer(messageBuffer) {
return new Buffer(messageBuffer.slice(48), 'hex');
}
describe('Messages', function() {
var buildMessage = function(hex) {
var m = Buffers();
m.push(new Buffer(hex, 'hex'));
return m;
};
describe('@constructor', function() {
it('sets properties correctly', function() {
var network = bitcore.Networks.defaultNetwork;
var messages = new Messages({
network: network,
Block: bitcore.Block,
Transaction: bitcore.Transaction
});
should.exist(messages.builder.commands);
should.exist(messages.builder.constructors);
messages.builder.constructors.Block.should.equal(bitcore.Block);
messages.builder.constructors.Transaction.should.equal(bitcore.Transaction);
messages.network.should.deep.equal(network);
});
it('network should be unique for each set of messages', function() {
var messages = new Messages({
network: bitcore.Networks.livenet
});
var messages2 = new Messages({
network: bitcore.Networks.testnet
});
messages.network.should.deep.equal(bitcore.Networks.livenet);
messages2.network.should.deep.equal(bitcore.Networks.testnet);
var message1 = messages.Version();
message1.network.should.deep.equal(bitcore.Networks.livenet);
var message2 = messages2.Version();
message2.network.should.deep.equal(bitcore.Networks.testnet);
});
});
describe('@constructor for all command messages', function() {
var messages = new Messages();
Object.keys(messages.builder.commandsMap).forEach(function(command) {
var name = messages.builder.commandsMap[command];
it('message.' + name, function(done) {
should.exist(messages[name]);
var message = messages[name]();
should.exist(message);
message.should.be.instanceof(messages[name]._constructor);
done();
});
});
});
describe('#fromBuffer/#toBuffer round trip for all commands', function() {
var messages = new Messages();
Object.keys(messages.builder.commandsMap).forEach(function(command) {
var name = messages.builder.commandsMap[command];
it(name, function(done) {
var payloadBuffer = getPayloadBuffer(commandData[command].message);
should.exist(messages[name]);
var message = messages[name].fromBuffer(payloadBuffer);
var outputBuffer = message.getPayload();
outputBuffer.toString('hex').should.equal(payloadBuffer.toString('hex'));
outputBuffer.should.deep.equal(payloadBuffer);
var expectedBuffer = new Buffer(commandData[command].message, 'hex');
message.toBuffer().should.deep.equal(expectedBuffer);
done();
});
});
});
describe('Default Network', function() {
var messages = new Messages();
Object.keys(messages.builder.commandsMap).forEach(function(command) {
var name = messages.builder.commandsMap[command];
it(name, function() {
var message = messages[name]();
message.network.should.deep.equal(bitcore.Networks.defaultNetwork);
});
});
});
describe('messages.Version', function() {
var messages = new Messages();
it('#fromBuffer works w/o fRelay arg', function() {
var payloadBuffer = getPayloadBuffer(Data.version.messagenofrelay);
var message = messages.Version.fromBuffer(payloadBuffer);
message.relay.should.equal(true);
});
it('#relay setting works', function() {
[true, false].forEach(function(relay) {
var message = messages.Version({
relay: relay
});
message.relay.should.equal(relay);
var messageBuf = message.getPayload();
var newMessage = messages.Version.fromBuffer(messageBuf);
newMessage.relay.should.equal(relay);
});
});
});
describe('Inventory Helpers', function() {
var messages = new Messages();
var constructors = messages.builder.inventoryCommands;
var fakeHash = 'e2dfb8afe1575bfacae1a0b4afc49af7ddda69285857267bae0e22be15f74a3a';
describe('#forTransaction', function() {
constructors.forEach(function(command) {
var name = messages.builder.commandsMap[command];
it(name, function() {
should.exist(messages[name].forTransaction);
var message = messages[name].forTransaction(fakeHash);
should.exist(message);
message.should.be.instanceof(messages[name]._constructor);
});
});
});
describe('#forBlock', function() {
constructors.forEach(function(command) {
var name = messages.builder.commandsMap[command];
it(name, function() {
var message = messages[name].forBlock(fakeHash);
should.exist(message);
message.should.be.instanceof(messages[name]._constructor);
});
});
});
describe('#forFilteredBlock', function() {
constructors.forEach(function(command) {
var name = messages.builder.commandsMap[command];
it(name, function() {
var message = messages[name].forFilteredBlock(fakeHash);
should.exist(message);
message.should.be.instanceof(messages[name]._constructor);
});
});
});
});
describe('#parseBuffer', function() {
it('fails with invalid command', function() {
var invalidCommand = 'f9beb4d96d616c6963696f757300000025000000bd5e830c' +
'0102000000ec3995c1bf7269ff728818a65e53af00cbbee6b6eca8ac9ce7bc79d87' +
'7041ed8';
var fails = function() {
var bufs = buildMessage(invalidCommand);
messages.parseBuffer(bufs);
};
fails.should.throw('Unsupported message command: malicious');
});
it('ignores malformed messages', function() {
var malformed1 = 'd8c4c3d976657273696f6e000000000065000000fc970f1772110' +
'1000100000000000000ba6288540000000001000000000000000000000000000000' +
'0000ffffba8886dceab0010000000000000000000000000000000000ffff0509552' +
'2208de7e1c1ef80a1cea70f2f5361746f7368693a302e392e312fa317050001';
var malformed2 = 'f9beb4d967657464617461000000000089000000d88134740102' +
'0000006308e4a380c949dbad182747b0f7b6a89e874328ca41f37287f74a81b8f84' +
'86d';
var malformed3 = 'f9beb4d967657464617461000000000025000000616263640102' +
'00000069ebcbc34a4f9890da9aea0f773beba883a9afb1ab9ad7647dd4a1cd346c3' +
'728';
[malformed1, malformed2, malformed3].forEach(function(malformed) {
var ret = messages.parseBuffer(buildMessage(malformed));
should.not.exist(ret);
});
});
});
describe('#add', function() {
it('should add a custom message', function() {
var network = bitcore.Networks.defaultNetwork;
var messages = new Messages({
network: network,
Block: bitcore.Block,
Transaction: bitcore.Transaction
});
var CustomMessage = function(arg, options) {
this.arg = arg;
};
messages.add('custom', 'Custom', CustomMessage);
should.exist(messages.Custom);
var message = messages.Custom('hello');
message.arg.should.equal('hello');
});
});
});

39
test/messages/message.js Normal file
View File

@ -0,0 +1,39 @@
'use strict';
var should = require('chai').should();
var P2P = require('../../');
var Message = P2P.Messages.Message;
var Networks = require('bitcore-lib').Networks;
describe('Message', function() {
describe('@constructor', function() {
it('construct with magic number and command', function() {
var message = new Message({
network: {
networkMagic: 0xd9b4bef9
},
command: 'command'
});
should.exist(message);
message.command.should.equal('command');
message.network.networkMagic.should.equal(0xd9b4bef9);
});
});
describe('#toBuffer', function() {
it('serialize to a buffer', function() {
var message = new Message({
command: 'command',
network: Networks.defaultNetwork
});
message.getPayload = function() {
return new Buffer(0);
};
var buffer = message.toBuffer();
var expectedBuffer = new Buffer('f9beb4d9636f6d6d616e640000000000000000005df6e0e2', 'hex');
buffer.should.deep.equal(expectedBuffer);
});
});
});

39
test/messages/util.js Normal file
View File

@ -0,0 +1,39 @@
'use strict';
/* jshint unused: false */
var should = require('chai').should();
var utils = require('../../lib/messages/utils');
var bitcore = require('bitcore-lib');
var BufferReader = bitcore.encoding.BufferReader;
describe('Message Utils', function() {
describe('checkFinished', function() {
it('should throw an error if buffer reader is not finished', function() {
/*jshint immed: false */
var buffer = new Buffer(Array(32));
var br = new BufferReader(buffer);
(function() {
utils.checkFinished(br);
}).should.throw('Data still available after parsing');
});
});
describe('sanitizeStartStop', function() {
it('should throw an error if starts is invalid length', function() {
/*jshint immed: false */
var stop = '000000000000000013413cf2536b491bf0988f52e90c476ffeb701c8bfdb1db9';
(function() {
utils.sanitizeStartStop({starts: ['0000'], stop: stop});
}).should.throw('Invalid hash');
});
it('should keep buffers as buffers', function() {
/*jshint immed: false */
var starts = [new Buffer(Array(32))];
var obj = utils.sanitizeStartStop({starts: starts});
obj.starts[0].should.deep.equal(starts[0]);
});
});
});

View File

@ -1,6 +1,5 @@
'use strict';
var _ = require('lodash');
var chai = require('chai');
var Net = require('net');
var Socks5Client = require('socks5-client');
@ -11,16 +10,20 @@ var expect = chai.expect;
var sinon = require('sinon');
var fs = require('fs');
var bitcore = require('bitcore');
var bitcore = require('bitcore-lib');
var _ = bitcore.deps._;
var P2P = require('../');
var Peer = P2P.Peer;
var EventEmitter = require('events').EventEmitter;
var Messages = P2P.Messages;
var messages = new Messages();
var Networks = bitcore.Networks;
describe('Peer', function() {
describe('Integration test', function() {
it('parses this stream of data from a connection', function(callback) {
var peer = new P2P.Peer('');
var peer = new Peer('');
var stub = sinon.stub();
var dataCallback;
var connectCallback;
@ -44,8 +47,7 @@ describe('Peer', function() {
connectCallback = arguments[1];
}
};
stub.write = function() {
};
stub.write = function() {};
stub.connect = function() {
connectCallback();
};
@ -53,10 +55,10 @@ describe('Peer', function() {
return stub;
};
peer.on('connect', function() {
dataCallback(fs.readFileSync('./test/connection.log'));
dataCallback(fs.readFileSync('./test/data/connection.log'));
});
var check = function(message) {
received[message.command]++;
received[message.command]++;
if (_.isEqual(received, expected)) {
callback();
}
@ -69,63 +71,173 @@ describe('Peer', function() {
});
});
it('should be able to create instance', function() {
it('create instance', function() {
var peer = new Peer('localhost');
peer.host.should.equal('localhost');
peer.network.should.equal(Networks.livenet);
peer.port.should.equal(Networks.livenet.port);
});
it('should be able to create instance setting a port', function() {
var peer = new Peer('localhost', 8111);
it('create instance setting a port', function() {
var peer = new Peer({host: 'localhost', port: 8111});
peer.host.should.equal('localhost');
peer.network.should.equal(Networks.livenet);
peer.port.should.equal(8111);
});
it('should be able to create instance setting a network', function() {
var peer = new Peer('localhost', Networks.testnet);
it('create instance setting a network', function() {
var peer = new Peer({host: 'localhost', network: Networks.testnet});
peer.host.should.equal('localhost');
peer.network.should.equal(Networks.testnet);
peer.port.should.equal(Networks.testnet.port);
});
it('should be able to create instance setting port and network', function() {
var peer = new Peer('localhost', 8111, Networks.testnet);
it('create instance setting port and network', function() {
var peer = new Peer({host: 'localhost', port: 8111, network: Networks.testnet});
peer.host.should.equal('localhost');
peer.network.should.equal(Networks.testnet);
peer.port.should.equal(8111);
});
it('should support creating instance without new', function() {
var peer = Peer('localhost', 8111, Networks.testnet);
it('create instance without new', function() {
var peer = Peer({host: 'localhost', port: 8111, network: Networks.testnet});
peer.host.should.equal('localhost');
peer.network.should.equal(Networks.testnet);
peer.port.should.equal(8111);
});
if (typeof(window) === 'undefined'){
it('set a proxy', function() {
var peer, peer2, socket;
// Node.js Tests
peer = new Peer('localhost');
expect(peer.proxy).to.be.undefined();
socket = peer._getSocket();
socket.should.be.instanceof(Net.Socket);
it('should be able to set a proxy', function() {
var peer, peer2, socket;
peer2 = peer.setProxy('127.0.0.1', 9050);
peer2.proxy.host.should.equal('127.0.0.1');
peer2.proxy.port.should.equal(9050);
socket = peer2._getSocket();
socket.should.be.instanceof(Socks5Client);
peer = new Peer('localhost');
expect(peer.proxy).to.be.undefined();
socket = peer._getSocket();
socket.should.be.instanceof(Net.Socket);
peer.should.equal(peer2);
});
peer2 = peer.setProxy('127.0.0.1', 9050);
peer2.proxy.host.should.equal('127.0.0.1');
peer2.proxy.port.should.equal(9050);
socket = peer2._getSocket();
socket.should.be.instanceof(Socks5Client);
it('send pong on ping', function(done) {
var peer = new Peer({host: 'localhost'});
var pingMessage = messages.Ping();
peer.sendMessage = function(message) {
message.command.should.equal('pong');
message.nonce.should.equal(pingMessage.nonce);
done();
};
peer.emit('ping', pingMessage);
});
peer.should.equal(peer2);
it('relay error from socket', function(done) {
var peer = new Peer({host: 'localhost'});
var socket = new EventEmitter();
socket.connect = sinon.spy();
socket.destroy = sinon.spy();
peer._getSocket = function() {
return socket;
};
var error = new Error('error');
peer.on('error', function(err) {
err.should.equal(error);
done();
});
peer.connect();
peer.socket.emit('error', error);
});
}
it('will not disconnect twice on disconnect and error', function(done) {
var peer = new Peer({host: 'localhost'});
var socket = new EventEmitter();
socket.connect = sinon.stub();
socket.destroy = sinon.stub();
peer._getSocket = function() {
return socket;
};
peer.on('error', sinon.stub());
peer.connect();
var called = 0;
peer.on('disconnect', function() {
called++;
called.should.not.be.above(1);
done();
});
peer.disconnect();
peer.socket.emit('error', new Error('fake error'));
});
it('disconnect with max buffer length', function(done) {
var peer = new Peer({host: 'localhost'});
var socket = new EventEmitter();
socket.connect = sinon.spy();
peer._getSocket = function() {
return socket;
};
peer.disconnect = function() {
done();
};
peer.connect();
var buffer = new Buffer(Array(Peer.MAX_RECEIVE_BUFFER + 1));
peer.socket.emit('data', buffer);
});
it('should send version on version if not already sent', function(done) {
var peer = new Peer({host:'localhost'});
var commands = {};
peer.sendMessage = function(message) {
commands[message.command] = true;
if (commands.verack && commands.version) {
done();
}
};
peer.socket = {};
peer.emit('version', {
version: 'version',
subversion: 'subversion',
startHeight: 'startHeight'
});
});
it('should not send version on version if already sent', function(done) {
var peer = new Peer({host:'localhost'});
peer.versionSent = true;
var commands = {};
peer.sendMessage = function(message) {
message.command.should.not.equal('version');
done();
};
peer.socket = {};
peer.emit('version', {
version: 'version',
subversion: 'subversion',
startHeight: 'startHeight'
});
});
it('relay set properly', function() {
var peer = new Peer({host: 'localhost'});
peer.relay.should.equal(true);
var peer2 = new Peer({host: 'localhost', relay: false});
peer2.relay.should.equal(false);
var peer3 = new Peer({host: 'localhost', relay: true});
peer3.relay.should.equal(true);
});
it('relay setting respected', function() {
[true,false].forEach(function(relay) {
var peer = new Peer({host: 'localhost', relay: relay});
var peerSendMessageStub = sinon.stub(Peer.prototype, 'sendMessage', function(message) {
message.relay.should.equal(relay);
});
peer._sendVersion();
peerSendMessageStub.restore();
});
});
});

View File

@ -1,105 +1,565 @@
'use strict';
if (typeof(window) === 'undefined'){
var chai = require('chai');
// Node.js Tests
/* jshint unused: false */
var should = chai.should();
var expect = chai.expect;
var chai = require('chai');
var bitcore = require('bitcore-lib');
var P2P = require('../');
var Peer = P2P.Peer;
var MessagesData = require('./data/messages');
var Messages = P2P.Messages;
var messages = new Messages();
var Pool = P2P.Pool;
var Networks = bitcore.Networks;
/* jshint unused: false */
var should = chai.should();
var expect = chai.expect;
var dns = require('dns');
var sinon = require('sinon');
var net = require('net');
var bitcore = require('bitcore');
var P2P = require('../');
var Peer = P2P.Peer;
var MessagesData = require('./data/messages');
var Messages = P2P.Messages;
var Pool = P2P.Pool;
var Networks = bitcore.Networks;
function getPayloadBuffer(messageBuffer) {
return new Buffer(messageBuffer.slice(48), 'hex');
}
var dns = require('dns');
var sinon = require('sinon');
describe('Pool', function() {
describe('Pool', function() {
it('create instance', function() {
var pool = new Pool();
should.exist(pool.network);
expect(pool.network).to.satisfy(function(network) {
return network === Networks.testnet || network === Networks.livenet;
});
});
it('should be able to create instance', function() {
var pool = new Pool();
should.exist(pool.network);
expect(pool.network).to.satisfy(function(network){
if (network === Networks.testnet || network === Networks.livenet) {
return true;
it('create instance setting the network', function() {
var pool = new Pool({network: Networks.testnet});
pool.network.should.equal(Networks.testnet);
});
it('discover peers via dns', function() {
var stub = sinon.stub(dns, 'resolve', function(seed, callback) {
callback(null, ['10.10.10.1', '10.10.10.2', '10.10.10.3']);
});
var pool = new Pool({network: Networks.livenet});
pool.connect();
pool.disconnect();
pool._addrs.length.should.equal(3);
stub.restore();
});
it('optionally connect without dns seeds', function() {
sinon.stub(Peer.prototype, 'connect', function() {
this.socket = {
destroy: sinon.stub()
};
});
var stub = sinon.stub(dns, 'resolve', function(seed, callback) {
throw new Error('DNS should not be called');
});
var options = {
network: Networks.livenet,
dnsSeed: false,
maxSize: 1,
addrs: [
{
ip: {
v4: 'localhost'
}
},
{
ip: {
v4: 'localhost2'
}
}
return false;
});
});
]
};
var pool = new Pool(options);
pool.connect();
pool.disconnect();
pool._addrs.length.should.equal(2);
stub.restore();
Peer.prototype.connect.restore();
});
it('should be able to create instance setting the network', function() {
var pool = new Peer(Networks.testnet);
pool.network.should.equal(Networks.livenet);
});
it('should discover peers via dns', function() {
var stub = sinon.stub(dns, 'resolve', function(seed, callback){
callback(null, ['10.10.10.1', '10.10.10.2', '10.10.10.3']);
});
var pool = new Pool(Networks.livenet);
pool.connect();
pool.disconnect();
pool._addrs.length.should.equal(3);
stub.restore();
});
it('should not discover peers via dns', function() {
var pool = new Pool();
pool._addAddr({ip: {v4: '10.10.10.1'}});
pool.connect();
pool.disconnect();
pool._addrs.length.should.equal(1);
});
it('should add new addrs as they are announced over the network', function(done) {
// only emit an event, no need to connect
var peerConnectStub = sinon.stub(Peer.prototype, 'connect', function(){
this._readMessage();
this.emit('ready');
});
// mock a addr peer event
var peerMessageStub = sinon.stub(Peer.prototype, '_readMessage', function(){
var payload = new Buffer(MessagesData.ADDR.payload, 'hex');
var message = new Messages.Addresses().fromBuffer(payload);
this.emit(message.command, message);
});
var pool = new Pool();
pool._addAddr({ip: {v4: 'localhost'}});
// listen for the event
pool.on('peeraddr', function(peer, message) {
pool._addrs.length.should.equal(502);
// restore stubs
peerConnectStub.restore();
peerMessageStub.restore();
for (var i = 0; i < pool._addrs.length; i++) {
should.exist(pool._addrs[i].hash);
should.exist(pool._addrs[i].ip);
should.exist(pool._addrs[i].ip.v4);
it('will add addrs via options argument', function() {
var options = {
network: Networks.livenet,
dnsSeed: false,
addrs: [
{
ip: {
v4: 'localhost'
}
}
]
};
var pool = new Pool(options);
pool._addrs.length.should.equal(1);
});
// done
done();
});
pool.connect();
it('add new addrs as they are announced over the network', function(done) {
// only emit an event, no need to connect
var peerConnectStub = sinon.stub(Peer.prototype, 'connect', function() {
this._readMessage();
this.emit('ready');
});
// mock a addr peer event
var peerMessageStub = sinon.stub(Peer.prototype, '_readMessage', function() {
var payloadBuffer = getPayloadBuffer(MessagesData.addr.message);
var message = messages._buildFromBuffer('addr', payloadBuffer);
this.emit(message.command, message);
});
var options = {
network: Networks.testnet,
dnsSeed: false,
addrs: [
{
ip: {
v4: 'localhost'
}
}
]
};
var pool = new Pool(options);
// listen for the event
pool.on('peeraddr', function(peer, message) {
pool._addrs.length.should.equal(502);
// restore stubs
peerConnectStub.restore();
peerMessageStub.restore();
for (var i = 0; i < pool._addrs.length; i++) {
should.exist(pool._addrs[i].hash);
should.exist(pool._addrs[i].ip);
should.exist(pool._addrs[i].ip.v4);
}
// done
done();
});
pool.connect();
});
}
it('can optionally not listen to new addrs messages', function(done) {
// only emit an event, no need to connect
var peerConnectStub = sinon.stub(Peer.prototype, 'connect', function() {
this._readMessage();
this.emit('ready');
});
// mock a addr peer event
var peerMessageStub = sinon.stub(Peer.prototype, '_readMessage', function() {
var payloadBuffer = getPayloadBuffer(MessagesData.addr.message);
var message = messages._buildFromBuffer('addr', payloadBuffer);
this.emit(message.command, message);
});
var options = {
network: Networks.testnet,
dnsSeed: false,
listenAddr: false,
addrs: [
{
ip: {
v4: 'localhost'
}
}
]
};
var pool = new Pool(options);
// listen for the event
pool.on('peeraddr', function(peer, message) {
pool._addrs.length.should.equal(1);
// restore stubs
peerConnectStub.restore();
peerMessageStub.restore();
for (var i = 0; i < pool._addrs.length; i++) {
should.exist(pool._addrs[i].hash);
should.exist(pool._addrs[i].ip);
should.exist(pool._addrs[i].ip.v4);
}
// done
done();
});
pool.connect();
});
it('propagate connect, ready, and disconnect peer events', function(done) {
var peerConnectStub = sinon.stub(Peer.prototype, 'connect', function() {
this.emit('connect', this, {});
this.emit('ready');
});
var peerDisconnectStub = sinon.stub(Peer.prototype, 'disconnect', function() {
this.emit('disconnect', this, {});
});
var poolRemoveStub = sinon.stub(Pool.prototype, '_removeConnectedPeer', function() {});
var pool = new Pool({
dnsSeed: false,
addrs: [
{
ip: {
v4: 'localhost'
}
}
]
});
var poolDisconnectStub;
pool.on('peerconnect', function(peer, addr) {
pool.on('peerready', function(peer, addr) {
// disconnect when the peer is ready
poolDisconnectStub = sinon.stub(Pool.prototype, 'disconnect', function() {
peer.disconnect();
});
pool.disconnect();
});
});
pool.on('peerdisconnect', function(peer, addr) {
// Restore stubs
peerConnectStub.restore();
peerDisconnectStub.restore();
poolDisconnectStub.restore();
poolRemoveStub.restore();
// done
done();
});
pool.connect();
});
it('propagate relay property to peers', function(done) {
var count = 0;
var peerConnectStub = sinon.stub(Peer.prototype, 'connect', function() {
this.emit('connect', this, {});
});
[true, false].forEach(function(relay) {
var pool = new Pool({relay: relay, dnsSeed: false});
pool._addAddr({ ip: { v4: 'localhost' } });
pool.on('peerconnect', function(peer, addr) {
peer.relay.should.equal(relay);
pool.disconnect();
if(++count == 2) {
done();
}
});
pool.connect();
});
peerConnectStub.restore();
});
it('output the console correctly', function() {
var pool = new Pool();
pool.inspect().should.equal('<Pool network: livenet, connected: 0, available: 0>');
});
it('emit seederrors with error', function(done) {
var dnsStub = sinon.stub(dns, 'resolve', function(seed, callback) {
callback(new Error('A DNS error'));
});
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool.once('seederror', function(error) {
should.exist(error);
pool.disconnect();
dnsStub.restore();
done();
});
pool.connect();
});
it('emit seederrors with notfound', function(done) {
var dnsStub = sinon.stub(dns, 'resolve', function(seed, callback) {
callback(null, []);
});
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool.once('seederror', function(error) {
should.exist(error);
pool.disconnect();
dnsStub.restore();
done();
});
pool.connect();
});
it('send message to all peers', function(done) {
var message = 'message';
sinon.stub(Peer.prototype, 'connect', function() {
this.socket = {
destroy: sinon.stub()
};
var self = this;
process.nextTick(function() {
self.emit('ready');
});
});
sinon.stub(Peer.prototype, 'sendMessage', function(message) {
message.should.equal(message);
Peer.prototype.connect.restore();
Peer.prototype.sendMessage.restore();
pool.disconnect();
done();
});
var pool = new Pool({
network: Networks.livenet,
maxSize: 1,
dnsSeed: false,
addrs: [
{
ip:{
v4: 'localhost'
}
}
]
});
pool.on('peerready', function() {
pool.sendMessage(message);
});
pool.connect();
});
it('not call _fillConnections if keepalive is false on seed', function(done) {
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool._fillConnections = sinon.stub();
pool.keepalive = false;
pool.on('seed', function() {
process.nextTick(function() {
pool._fillConnections.called.should.equal(false);
done();
});
});
pool.emit('seed', []);
});
it('keep original time for handling peeraddr messages', function(done) {
var pool = new Pool({network: Networks.livenet, maxSize: 1});
var now = new Date();
pool._addAddr = function(addr) {
addr.time.should.equal(now);
done();
};
pool.emit('peeraddr', {}, {
addresses: [
{
time: now
}
]
});
});
it('replace time if time is invalid on peeraddr messages', function(done) {
var pool = new Pool({network: Networks.livenet, maxSize: 1});
var future = new Date(new Date().getTime() + 10 * 70 * 1000);
var past = new Date(new Date().getTime() - 4 * 24 * 60 * 60 * 1000); // 4 days ago
pool._addAddr = function(addr) {
addr.time.should.not.equal(future);
addr.time.getTime().should.be.below(past.getTime());
done();
};
pool.emit('peeraddr', {}, {
addresses: [
{
time: future
}
]
});
});
describe('#_removeConnectedPeer', function() {
it('disconnect peer if peer status is not disconnected', function(done) {
var pool = new Pool({network: Networks.livenet, maxSize: 1});
/* jshint sub: true */
pool._connectedPeers['hash'] = {
status: Peer.STATUS.CONNECTED,
disconnect: function() {
done();
}
};
pool._removeConnectedPeer({
hash: 'hash'
});
});
});
describe('#_connectPeer', function() {
it('connect ipv6 peer', function() {
var connectStub = sinon.stub(Peer.prototype, 'connect');
var pool = new Pool({network: Networks.livenet, maxSize: 1});
var ipv6 = '2001:0db8:85a3:0042:1000:8a2e:0370:7334';
pool._addPeerEventHandlers = sinon.stub();
pool._connectPeer({
ip: {
v6: ipv6
},
hash: 'hash'
});
/* jshint sub: true */
should.exist(pool._connectedPeers['hash']);
pool._addPeerEventHandlers.calledOnce.should.equal(true);
Peer.prototype.connect.calledOnce.should.equal(true);
connectStub.restore();
});
it('will pass network to peer', function() {
var connectStub = sinon.stub(Peer.prototype, 'connect');
var pool = new Pool({network: Networks.testnet, maxSize: 1});
var ipv6 = '2001:0db8:85a3:0042:1000:8a2e:0370:7334';
pool._addPeerEventHandlers = sinon.stub();
pool._connectPeer({
ip: {
v6: ipv6
},
hash: 'hash'
});
/* jshint sub: true */
pool._connectedPeers['hash'].network.should.equal(pool.network);
connectStub.restore();
});
});
describe('#_addConnectedPeer', function() {
it('should add a peer', function() {
/* jshint sub: true */
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool._addPeerEventHandlers = sinon.stub();
pool._addConnectedPeer({
on: sinon.stub()
}, {hash: 'hash'});
should.exist(pool._connectedPeers['hash']);
pool._addPeerEventHandlers.calledOnce.should.equal(true);
});
it('should not already added peer', function() {
/* jshint sub: true */
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool._addPeerEventHandlers = sinon.stub();
pool._connectedPeers['hash'] = {};
pool._addConnectedPeer({
on: sinon.stub()
}, {hash: 'hash'});
should.exist(pool._connectedPeers['hash']);
pool._addPeerEventHandlers.calledOnce.should.equal(false);
});
it('will pass network to peer', function() {
/* jshint sub: true */
var pool = new Pool({network: Networks.testnet, maxSize: 1});
pool._addConnectedPeer({
on: sinon.stub()
}, {hash: 'hash'});
should.exist(pool._connectedPeers['hash']);
pool._connectedPeers['hash'].network.should.equal(pool.network);
});
});
describe('#listen', function() {
it('create a server', function(done) {
var netStub = sinon.stub(net, 'createServer', function() {
return {
listen: function() {
netStub.restore();
done();
}
};
});
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool.listen();
});
it('should handle an ipv6 connection', function(done) {
var ipv6 = '2001:0db8:85a3:0042:1000:8a2e:0370:7334';
sinon.stub(net, 'createServer', function(callback) {
callback({
remoteAddress: ipv6
});
return {
listen: sinon.stub()
};
});
sinon.stub(net, 'isIPv6', function() {
return true;
});
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool._addAddr = function(addr) {
should.exist(addr.ip.v6);
addr.ip.v6.should.equal(ipv6);
net.isIPv6.restore();
net.createServer.restore();
done();
};
pool._addConnectedPeer = sinon.stub();
pool.listen();
});
it('include port for addr on incoming connections', function(done) {
var port = 12345;
sinon.stub(net, 'createServer', function(callback) {
callback({
remoteAddress: '127.0.0.1',
remotePort: port
});
return {
listen: sinon.stub()
};
});
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool._addAddr = function(addr) {
should.exist(addr.port);
addr.port.should.equal(port);
net.createServer.restore();
done();
};
pool._addConnectedPeer = sinon.stub();
pool.listen();
});
it('should handle an ipv4 connection', function(done) {
var ipv4 = '127.0.0.1';
sinon.stub(net, 'createServer', function(callback) {
callback({
remoteAddress: ipv4
});
return {
listen: sinon.stub()
};
});
sinon.stub(net, 'isIPv6', function() {
return false;
});
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool._addAddr = function(addr) {
should.exist(addr.ip.v4);
addr.ip.v4.should.equal(ipv4);
net.isIPv6.restore();
net.createServer.restore();
done();
};
pool._addConnectedPeer = sinon.stub();
pool.listen();
});
});
});