From f1d2009418370ea958ed5e7a2461bfef89e5444c Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Fri, 5 Dec 2014 11:39:30 -0300 Subject: [PATCH 01/21] Add transport/connection class --- index.js | 4 + lib/transport/connection.js | 575 +++++++++++++++++++++++++++++++++++ lib/util/buffer.js | 6 + test/transport/connection.js | 41 +++ 4 files changed, 626 insertions(+) create mode 100644 lib/transport/connection.js create mode 100644 test/transport/connection.js diff --git a/index.js b/index.js index 627909f..acc7437 100644 --- a/index.js +++ b/index.js @@ -24,6 +24,10 @@ bitcore.util.bitcoin = require('./lib/util/bitcoin'); bitcore.util.buffer = require('./lib/util/buffer'); bitcore.util.js = require('./lib/util/js'); +// transport +bitcore.transport = {}; +bitcore.transport.Connection = require('./lib/transport/connection'); + // errors thrown by the library bitcore.errors = require('./lib/errors'); diff --git a/lib/transport/connection.js b/lib/transport/connection.js new file mode 100644 index 0000000..b9495d4 --- /dev/null +++ b/lib/transport/connection.js @@ -0,0 +1,575 @@ +'use strict'; + +var MAX_RECEIVE_BUFFER = 10000000; +var PROTOCOL_VERSION = 70000; + +var Util = require('util'); +var Put = require('bufferput'); +var Buffers = require('buffers'); +var buffertools = require('buffertools'); + +// PATCH TODO: Remove (yemel) +Buffers.prototype.skip = function (i) { + if (i == 0) { + return; + } else 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; +}; + +var networks = require('../networks'); +var Block = require('../block'); +var Transaction = require('../transaction'); +var BufferUtil = require('../util/buffer'); +var BufferReader = require('../encoding/bufferreader'); +var Hash = require('../crypto/hash'); +var Random = require('../crypto/random'); +var nonce = Random.getPseudoRandomBuffer(8); +var EventEmitter = require('events').EventEmitter; + +var BIP0031_VERSION = 60000; + +function Connection(socket, peer, opts) { + this.config = opts || {}; + + this.network = this.config.network || networks.livenet; + this.socket = socket; + this.peer = peer; + + // check for socks5 proxy options and construct a proxied socket + if (this.config.proxy) { + var Socks5Client = require('socks5-client'); + this.socket = new Socks5Client(this.config.proxy.host, this.config.proxy.port); + } + + // A connection is considered "active" once we have received verack + this.active = false; + // The version incoming packages are interpreted as + this.recvVer = 0; + // The version outgoing packages are sent as + this.sendVer = 0; + // The (claimed) height of the remote peer's block chain + this.bestHeight = 0; + // Is this an inbound connection? + this.inbound = !!this.socket.server; + // Have we sent a getaddr on this connection? + this.getaddr = false; + + // Receive buffer + this.buffers = new Buffers(); + + // Starting 20 Feb 2012, Version 0.2 is obsolete + // This is the same behavior as the official client + if (new Date().getTime() > 1329696000000) { + this.recvVer = 209; + this.sendVer = 209; + } + + this.setupHandlers(); +} +Util.inherits(Connection, EventEmitter); + +Connection.prototype.open = function(callback) { + if (typeof callback === 'function') this.once('connect', callback); + this.socket.connect(this.peer.port, this.peer.host); + return this; +}; + +Connection.prototype.setupHandlers = function() { + this.socket.addListener('connect', this.handleConnect.bind(this)); + this.socket.addListener('error', this.handleError.bind(this)); + this.socket.addListener('end', this.handleDisconnect.bind(this)); + this.socket.addListener('data', (function(data) { + var dumpLen = 35; + console.debug('[' + this.peer + '] ' + + 'Recieved ' + data.length + ' bytes of data:'); + console.debug('... ' + buffertools.toHex(data.slice(0, dumpLen > data.length ? + data.length : dumpLen)) + + (data.length > dumpLen ? '...' : '')); + }).bind(this)); + this.socket.addListener('data', this.handleData.bind(this)); +}; + +Connection.prototype.handleConnect = function() { + if (!this.inbound) { + this.sendVersion(); + } + this.emit('connect', { + conn: this, + socket: this.socket, + peer: this.peer + }); +}; + +Connection.prototype.handleError = function(err) { + if (err.errno == 110 || err.errno == 'ETIMEDOUT') { + console.info('connection timed out for ' + this.peer); + } else if (err.errno == 111 || err.errno == 'ECONNREFUSED') { + console.info('connection refused for ' + this.peer); + } else { + console.warn('connection with ' + this.peer + ' ' + err.toString()); + } + this.emit('error', { + conn: this, + socket: this.socket, + peer: this.peer, + err: err + }); +}; + +Connection.prototype.handleDisconnect = function() { + this.emit('disconnect', { + conn: this, + socket: this.socket, + peer: this.peer + }); +}; + +Connection.prototype.handleMessage = function(message) { + if (!message) { + // Parser was unable to make sense of the message, drop it + return; + } + + try { + switch (message.command) { + case 'version': + // Did we connect to ourself? + if (buffertools.compare(nonce, message.nonce) === 0) { + this.socket.end(); + return; + } + + if (this.inbound) { + this.sendVersion(); + } + + if (message.version >= 209) { + this.sendMessage('verack', new Buffer([])); + } + this.sendVer = Math.min(message.version, PROTOCOL_VERSION); + if (message.version < 209) { + this.recvVer = Math.min(message.version, PROTOCOL_VERSION); + } else { + // We won't start expecting a checksum until after we've received + // the 'verack' message. + this.once('verack', (function() { + this.recvVer = message.version; + }).bind(this)); + } + this.bestHeight = message.start_height; + break; + + case 'verack': + this.recvVer = Math.min(message.version, PROTOCOL_VERSION); + this.active = true; + break; + + case 'ping': + if ('object' === typeof message.nonce) { + this.sendPong(message.nonce); + } + break; + } + } catch (e) { + console.err('Error while handling "' + message.command + '" message from ' + + this.peer + ':\n' + + (e.stack ? e.stack : e.toString())); + return; + } + this.emit(message.command, { + conn: this, + socket: this.socket, + peer: this.peer, + message: message + }); +}; + +Connection.prototype.sendPong = function(nonce) { + this.sendMessage('pong', nonce); +}; + +Connection.prototype.sendVersion = function() { + var subversion = '/BitcoinX:0.1/'; + + var put = new Put(); + put.word32le(PROTOCOL_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(nonce); + put.varint(subversion.length); + put.put(new Buffer(subversion, 'ascii')); + put.word32le(0); + + this.sendMessage('version', put.buffer()); +}; + +Connection.prototype.sendGetBlocks = function(starts, stop, wantHeaders) { + // Default value for stop is 0 to get as many blocks as possible (500) + stop = stop || BufferUtil.NULL_HASH; + + var put = new Put(); + + // https://en.bitcoin.it/wiki/Protocol_specification#getblocks + put.word32le(this.sendVer); + put.varint(starts.length); + + for (var i = 0; i < starts.length; i++) { + if (starts[i].length != 32) { + throw new Error('Invalid hash length'); + } + + put.put(starts[i]); + } + + var stopBuffer = new Buffer(stop, 'binary'); + if (stopBuffer.length != 32) { + throw new Error('Invalid hash length'); + } + + put.put(stopBuffer); + + var command = 'getblocks'; + if (wantHeaders) + command = 'getheaders'; + this.sendMessage(command, put.buffer()); +}; + +Connection.prototype.sendGetHeaders = function(starts, stop) { + this.sendGetBlocks(starts, stop, true); +}; + +Connection.prototype.sendGetData = function(invs) { + var put = new Put(); + put.varint(invs.length); + for (var i = 0; i < invs.length; i++) { + put.word32le(invs[i].type); + put.put(invs[i].hash); + } + this.sendMessage('getdata', put.buffer()); +}; + +Connection.prototype.sendGetAddr = function(invs) { + var put = new Put(); + this.sendMessage('getaddr', put.buffer()); +}; + +Connection.prototype.sendInv = function(data) { + if (!Array.isArray(data)) data = [data]; + var put = new Put(); + put.varint(data.length); + data.forEach(function(value) { + if (value instanceof Block) { + // Block + put.word32le(2); // MSG_BLOCK + } else { + // Transaction + put.word32le(1); // MSG_TX + } + put.put(value.getHash()); + }); + this.sendMessage('inv', put.buffer()); +}; + +Connection.prototype.sendHeaders = function(headers) { + var put = new Put(); + put.varint(headers.length); + headers.forEach(function(header) { + put.put(header); + + // Indicate 0 transactions + put.word8(0); + }); + this.sendMessage('headers', put.buffer()); +}; + +Connection.prototype.sendTx = function(tx) { + this.sendMessage('tx', tx.serialize()); +}; + +Connection.prototype.sendBlock = function(block, txs) { + var put = new Put(); + + // Block header + put.put(block.getHeader()); + + // List of transactions + put.varint(txs.length); + txs.forEach(function(tx) { + put.put(tx.serialize()); + }); + + this.sendMessage('block', put.buffer()); +}; + +Connection.prototype.sendMessage = function(command, payload) { + try { + var magic = this.network.magic; + var commandBuf = new Buffer(command, 'ascii'); + if (commandBuf.length > 12) throw 'Command name too long'; + + var checksum; + if (this.sendVer >= 209) { + checksum = Hash.sha256sha256(payload).slice(0, 4); + } else { + checksum = new Buffer([]); + } + + var message = new Put(); // -- HEADER -- + message.put(magic); // magic bytes + message.put(commandBuf); // command name + message.pad(12 - commandBuf.length); // zero-padded + message.word32le(payload.length); // payload length + message.put(checksum); // checksum + // -- BODY -- + message.put(payload); // payload data + + var buffer = message.buffer(); + + console.debug('[' + this.peer + '] ' + + 'Sending message ' + command + ' (' + payload.length + ' bytes)'); + + this.socket.write(buffer); + } catch (err) { + // TODO: We should catch this error one level higher in order to better + // determine how to react to it. For now though, ignoring it will do. + console.err('Error while sending message to peer ' + this.peer + ': ' + + (err.stack ? err.stack : err.toString())); + } +}; + +Connection.prototype.handleData = function(data) { + this.buffers.push(data); + + if (this.buffers.length > MAX_RECEIVE_BUFFER) { + console.err('Peer ' + this.peer + ' exceeded maxreceivebuffer, disconnecting.' + + (err.stack ? err.stack : err.toString())); + this.socket.destroy(); + return; + } + + this.processData(); +}; + +Connection.prototype.processData = function() { + // If there are less than 20 bytes there can't be a message yet. + if (this.buffers.length < 20) return; + + var magic = this.network.magic; + var i = 0; + for (;;) { + if (this.buffers.get(i) === magic[0] && + this.buffers.get(i + 1) === magic[1] && + this.buffers.get(i + 2) === magic[2] && + this.buffers.get(i + 3) === magic[3]) { + if (i !== 0) { + console.debug('[' + this.peer + '] ' + + 'Received ' + i + + ' bytes of inter-message garbage: '); + console.debug('... ' + this.buffers.slice(0, i)); + + this.buffers.skip(i); + } + break; + } + + if (i > (this.buffers.length - 4)) { + this.buffers.skip(i); + return; + } + i++; + } + + var payloadLen = (this.buffers.get(16)) + + (this.buffers.get(17) << 8) + + (this.buffers.get(18) << 16) + + (this.buffers.get(19) << 24); + + var startPos = (this.recvVer >= 209) ? 24 : 20; + var endPos = startPos + payloadLen; + + if (this.buffers.length < endPos) return; + + var command = this.buffers.slice(4, 16).toString('ascii').replace(/\0+$/, ''); + var payload = this.buffers.slice(startPos, endPos); + var checksum = (this.recvVer >= 209) ? this.buffers.slice(20, 24) : null; + + console.debug('[' + this.peer + '] ' + + 'Received message ' + command + + ' (' + payloadLen + ' bytes)'); + + if (checksum !== null) { + var checksumConfirm = Hash.sha256sha256(payload).slice(0, 4); + if (buffertools.compare(checksumConfirm, checksum) !== 0) { + console.err('[' + this.peer + '] ' + + 'Checksum failed', { + cmd: command, + expected: checksumConfirm.toString('hex'), + actual: checksum.toString('hex') + }); + return; + } + } + + var message; + try { + message = this.parseMessage(command, payload); + } catch (e) { + console.err('Error while parsing message ' + command + ' from ' + + this.peer + ':\n' + + (e.stack ? e.stack : e.toString())); + } + + if (message) { + this.handleMessage(message); + } + + this.buffers.skip(endPos); + this.processData(); +}; + +Connection.prototype.parseMessage = function(command, payload) { + var parser = new BufferReader(payload); + + var data = { + command: command + }; + + var i; + + switch (command) { + case 'version': // https://en.bitcoin.it/wiki/Protocol_specification#version + data.version = parser.readUInt32LE(); + data.services = parser.readUInt64LEBN(); + data.timestamp = parser.readUInt64LEBN(); + data.addr_me = parser.read(26); + data.addr_you = parser.read(26); + data.nonce = parser.read(8); + data.subversion = parser.readVarintBuf(); + data.start_height = parser.readUInt32LE(); + break; + + case 'inv': + case 'getdata': + data.count = parser.readVarintNum(); + + data.invs = []; + for (i = 0; i < data.count; i++) { + data.invs.push({ + type: parser.readUInt32LE(), + hash: parser.read(32) + }); + } + break; + + case 'headers': + data.count = parser.readVarintNum(); + + data.headers = []; + for (i = 0; i < data.count; i++) { + var header = Block.fromBufferReader(parser); + data.headers.push(header); + } + break; + + case 'block': + var block = Block.fromBufferReader(parser); + + data.block = block; + data.version = block.version; + data.prev_hash = block.prev_hash; + data.merkle_root = block.merkle_root; + data.timestamp = block.timestamp; + data.bits = block.bits; + data.nonce = block.nonce; + + data.txs = block.txs; + + data.size = payload.length; + break; + + case 'tx': + var tx = Transaction.fromBufferReader(parser); + return { + command: command, + version: tx.version, + lock_time: tx.lock_time, + ins: tx.ins, + outs: tx.outs, + tx: tx, + }; + + case 'getblocks': + case 'getheaders': + // parse out the version + data.version = parser.readUInt32LE(); + + // TODO: Limit block locator size? + // reference implementation limits to 500 results + var startCount = parser.readVarintNum(); + + data.starts = []; + for (i = 0; i < startCount; i++) { + data.starts.push(parser.read(32)); + } + data.stop = parser.read(32); + break; + + case 'addr': + var addrCount = parser.readVarintNum(); + + // Enforce a maximum number of addresses per message + if (addrCount > 1000) { + addrCount = 1000; + } + + data.addrs = []; + for (i = 0; i < addrCount; i++) { + // TODO: Time actually depends on the version of the other peer (>=31402) + data.addrs.push({ + time: parser.readUInt32LE(), + services: parser.readUInt64LEBN(), + ip: parser.read(16), + port: parser.readUInt16BE() + }); + } + break; + + case 'alert': + data.payload = parser.readVarintBuf(); + data.signature = parser.readVarintBuf(); + break; + + case 'ping': + if (this.recvVer > BIP0031_VERSION) { + data.nonce = parser.read(8); + } + break; + + case 'getaddr': + case 'verack': + case 'reject': + // Empty message, nothing to parse + break; + + default: + console.err('Connection.parseMessage(): Command not implemented', { + cmd: command + }); + + // This tells the calling function not to issue an event + return null; + } + + return data; +}; + +module.exports = Connection; \ No newline at end of file diff --git a/lib/util/buffer.js b/lib/util/buffer.js index 73a60b3..a84023e 100644 --- a/lib/util/buffer.js +++ b/lib/util/buffer.js @@ -2,6 +2,7 @@ var buffer = require('buffer'); var assert = require('assert'); +var buffertools = require('buffertools'); var js = require('./js'); @@ -19,6 +20,11 @@ function equals(a, b) { } module.exports = { + NULL_HASH: buffertools.fill(new Buffer(32), 0), + EMPTY_BUFFER: new Buffer(0), + ZERO_VALUE: buffertools.fill(new Buffer(8), 0), + INT64_MAX: new Buffer('ffffffffffffffff', 'hex'), + /** * Returns true if the given argument is an instance of a buffer. Tests for * both node's Buffer and Uint8Array diff --git a/test/transport/connection.js b/test/transport/connection.js new file mode 100644 index 0000000..2b52b48 --- /dev/null +++ b/test/transport/connection.js @@ -0,0 +1,41 @@ +'use strict'; + +var chai = require('chai'); +var bitcore = require('../..'); + +var should = chai.should(); + +var ConnectionModule = bitcore.transport.Connection; +var Connection; +var nop = function() {}; + +describe.only('Connection', function() { + it('should initialze the main object', function() { + should.exist(ConnectionModule); + }); + it('should be able to create class', function() { + Connection = ConnectionModule; + should.exist(Connection); + }); + it('should be able to create instance', function() { + var mSocket = {server: null, addListener: nop}, + mPeer; + var c = new Connection(mSocket, mPeer); + should.exist(c); + }); + + if (typeof process !== 'undefined' && process.versions) { //node-only tests + it('should create a proxied socket if instructed', function() { + var mPeer; + var c = new Connection(null, mPeer, { + proxy: { host: 'localhost', port: 9050 } + }); + should.exist(c.socket); + }); + }; +}); + + + + + From 4faa1a4a469efdb60ad7085523335bc8a3cb4367 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Fri, 5 Dec 2014 11:43:36 -0300 Subject: [PATCH 02/21] Add transport/peer class --- index.js | 1 + lib/transport/peer.js | 56 ++++++++++++++++++++++++++++++++++++ test/transport/connection.js | 9 ++---- test/transport/peer.js | 52 +++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 lib/transport/peer.js create mode 100644 test/transport/peer.js diff --git a/index.js b/index.js index acc7437..bbb3f6a 100644 --- a/index.js +++ b/index.js @@ -27,6 +27,7 @@ bitcore.util.js = require('./lib/util/js'); // transport bitcore.transport = {}; bitcore.transport.Connection = require('./lib/transport/connection'); +bitcore.transport.Peer = require('./lib/transport/peer'); // errors thrown by the library bitcore.errors = require('./lib/errors'); diff --git a/lib/transport/peer.js b/lib/transport/peer.js new file mode 100644 index 0000000..6547c77 --- /dev/null +++ b/lib/transport/peer.js @@ -0,0 +1,56 @@ +var Net = require('net'); +var Put = require('bufferput'); +var buffertools = require('buffertools'); + +function Peer(host, port, services) { + if ("string" === typeof host) { + if (host.indexOf(':') && !port) { + var parts = host.split(':'); + host = parts[0]; + port = parts[1]; + } + this.host = host; + this.port = +port || 8333; + } else if (host instanceof Peer) { + this.host = host.host; + this.port = host.port; + } else if (Buffer.isBuffer(host)) { + if (buffertools.compare(Peer.IPV6_IPV4_PADDING, host.slice(0, 12)) != 0) { + throw new Error('IPV6 not supported yet! Cannot instantiate host.'); + } + this.host = Array.prototype.slice.apply(host.slice(12)).join('.'); + this.port = +port || 8333; + } else { + throw new Error('Could not instantiate peer, invalid parameter type: ' + + typeof host); + } + + this.services = (services) ? services : null; + this.lastSeen = 0; +}; + +Peer.IPV6_IPV4_PADDING = new Buffer([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255]); + +Peer.prototype.createConnection = function() { + this.connection = Net.createConnection(this.port, this.host); + return this.connection; +}; + +Peer.prototype.getHostAsBuffer = function() { + return new Buffer(this.host.split('.')); +}; + +Peer.prototype.toString = function() { + return this.host + ":" + this.port; +}; + +Peer.prototype.toBuffer = function() { + var put = new Put(); + put.word32le(this.lastSeen); + put.word64le(this.services); + put.put(this.getHostAsBuffer()); + put.word16be(this.port); + return put.buffer(); +}; + +module.exports = Peer; \ No newline at end of file diff --git a/test/transport/connection.js b/test/transport/connection.js index 2b52b48..9826fa0 100644 --- a/test/transport/connection.js +++ b/test/transport/connection.js @@ -9,14 +9,16 @@ var ConnectionModule = bitcore.transport.Connection; var Connection; var nop = function() {}; -describe.only('Connection', function() { +describe('Connection', function() { it('should initialze the main object', function() { should.exist(ConnectionModule); }); + it('should be able to create class', function() { Connection = ConnectionModule; should.exist(Connection); }); + it('should be able to create instance', function() { var mSocket = {server: null, addListener: nop}, mPeer; @@ -34,8 +36,3 @@ describe.only('Connection', function() { }); }; }); - - - - - diff --git a/test/transport/peer.js b/test/transport/peer.js new file mode 100644 index 0000000..b23c63b --- /dev/null +++ b/test/transport/peer.js @@ -0,0 +1,52 @@ +'use strict'; + +var chai = require('chai'); +var bitcore = require('../..'); + +var should = chai.should(); + +var PeerModule = bitcore.transport.Peer; +var Peer; + +describe('Peer', function() { + it('should initialze the main object', function() { + should.exist(PeerModule); + }); + + it('should be able to create class', function() { + Peer = PeerModule; + should.exist(Peer); + }); + + it('should be able to create instance', function() { + var p = new Peer('localhost', 8333); + should.exist(p); + }); + + it('should be able to create instance', function() { + var p = new Peer('localhost:8333'); + should.exist(p); + }); + + it('should be able to create instance', function() { + var p = new Peer('localhost:8333'); + var p2 = new Peer(p); + should.exist(p2); + }); + + it('should not be able to create instance', function() { + should.throw(function() { + new Peer(8333); + }); + }); + + it('should be able to create instance', function() { + var p = new Peer('localhost', 8333); + p.toString().should.equal('localhost:8333'); + }); + + it('check host as buffer', function() { + var p = new Peer('127.0.0.1', 8333); + p.getHostAsBuffer().toString('hex').should.equal('7f000001'); + }); +}); From a1e8f3c596b5b8766ca657fe7fb841cfef15b148 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Fri, 5 Dec 2014 13:10:09 -0300 Subject: [PATCH 03/21] Add transport/peermanager class --- index.js | 1 + lib/networks.js | 20 ++- lib/transport/connection.js | 7 +- lib/transport/peermanager.js | 313 ++++++++++++++++++++++++++++++++++ test/transport/peermanager.js | 42 +++++ 5 files changed, 378 insertions(+), 5 deletions(-) create mode 100644 lib/transport/peermanager.js create mode 100644 test/transport/peermanager.js diff --git a/index.js b/index.js index bbb3f6a..4c8712f 100644 --- a/index.js +++ b/index.js @@ -28,6 +28,7 @@ bitcore.util.js = require('./lib/util/js'); bitcore.transport = {}; bitcore.transport.Connection = require('./lib/transport/connection'); bitcore.transport.Peer = require('./lib/transport/peer'); +bitcore.transport.PeerManager = require('./lib/transport/peermanager'); // errors thrown by the library bitcore.errors = require('./lib/errors'); diff --git a/lib/networks.js b/lib/networks.js index fff435f..0955fed 100644 --- a/lib/networks.js +++ b/lib/networks.js @@ -21,7 +21,16 @@ _.extend(livenet, { privatekey: 0x80, scripthash: 0x05, xpubkey: 0x0488b21e, - xprivkey: 0x0488ade4 + xprivkey: 0x0488ade4, + port: 8333, + dnsSeeds: [ + 'seed.bitcoin.sipa.be', + 'dnsseed.bluematt.me', + 'dnsseed.bitcoin.dashjr.org', + 'seed.bitcoinstats.com', + 'seed.bitnodes.io', + 'bitseed.xf2.org' + ] }); /** @@ -36,15 +45,22 @@ _.extend(testnet, { privatekey: 0xef, scripthash: 0xc4, xpubkey: 0x043587cf, - xprivkey: 0x04358394 + xprivkey: 0x04358394, + port: 18333, + dnsSeeds: [ + 'testnet-seed.bitcoin.petertodd.org', + 'testnet-seed.bluematt.me' + ], }); var networkMaps = {}; _.each(_.values(livenet), function(value) { + if (_.isArray(value)) return; networkMaps[value] = livenet; }); _.each(_.values(testnet), function(value) { + if (_.isArray(value)) return; networkMaps[value] = testnet; }); diff --git a/lib/transport/connection.js b/lib/transport/connection.js index b9495d4..a7ecd32 100644 --- a/lib/transport/connection.js +++ b/lib/transport/connection.js @@ -3,7 +3,7 @@ var MAX_RECEIVE_BUFFER = 10000000; var PROTOCOL_VERSION = 70000; -var Util = require('util'); +var util = require('util'); var Put = require('bufferput'); var Buffers = require('buffers'); var buffertools = require('buffertools'); @@ -30,9 +30,10 @@ var BufferUtil = require('../util/buffer'); var BufferReader = require('../encoding/bufferreader'); var Hash = require('../crypto/hash'); var Random = require('../crypto/random'); -var nonce = Random.getPseudoRandomBuffer(8); var EventEmitter = require('events').EventEmitter; +var nonce = Random.getPseudoRandomBuffer(8); + var BIP0031_VERSION = 60000; function Connection(socket, peer, opts) { @@ -73,7 +74,7 @@ function Connection(socket, peer, opts) { this.setupHandlers(); } -Util.inherits(Connection, EventEmitter); +util.inherits(Connection, EventEmitter); Connection.prototype.open = function(callback) { if (typeof callback === 'function') this.once('connect', callback); diff --git a/lib/transport/peermanager.js b/lib/transport/peermanager.js new file mode 100644 index 0000000..fc3e403 --- /dev/null +++ b/lib/transport/peermanager.js @@ -0,0 +1,313 @@ +'use strict'; + +var dns = require('dns'); +var util = require('util'); +var async = require('async'); + +var Connection = require('./connection'); +var Peer = require('./peer'); +var networks = require('../networks'); + +var GetAdjustedTime = function() { + // TODO: Implement actual adjustment + return Math.floor(new Date().getTime() / 1000); +}; + +function PeerManager(config) { + // extend defaults with config + this.config = config || {}; + this.config.network = this.config.network || networks.livenet; + + this.active = false; + this.timer = null; + + this.peers = []; + this.pool = []; + this.connections = []; + this.isConnected = false; + this.peerDiscovery = false; + + // Move these to the Node's settings object + this.interval = 5000; + this.minConnections = 8; + this.minKnownPeers = 10; + + // keep track of tried seeds and results + this.seeds = { + resolved: [], + failed: [] + }; +} + +var EventEmitter = require('events').EventEmitter; +util.inherits(PeerManager, EventEmitter); +PeerManager.Connection = Connection; + +PeerManager.prototype.start = function() { + this.active = true; + if (!this.timer) { + this.timer = setInterval(this.checkStatus.bind(this), this.interval); + } +}; + +PeerManager.prototype.stop = function() { + this.active = false; + if (this.timer) { + clearInterval(this.timer); + this.timer = null; + } + for (var i = 0; i < this.connections.length; i++) { + this.connections[i].socket.end(); + }; +}; + +PeerManager.prototype.addPeer = function(peer, port) { + if (peer instanceof Peer) { + this.peers.push(peer); + } else if ("string" == typeof peer) { + this.addPeer(new Peer(peer, port)); + } else { + console.error('Node.addPeer(): Invalid value provided for peer', { + val: peer + }); + throw 'Node.addPeer(): Invalid value provided for peer.'; + } +}; + +PeerManager.prototype.removePeer = function(peer) { + var index = this.peers.indexOf(peer); + var exists = !!~index; + if (exists) this.peers.splice(index, 1); + return exists; +}; + +PeerManager.prototype.checkStatus = function checkStatus() { + // Make sure we are connected to all forcePeers + if (this.peers.length) { + var peerIndex = {}; + this.peers.forEach(function(peer) { + peerIndex[peer.toString()] = peer; + }); + + // Ignore the ones we're already connected to + this.connections.forEach(function(conn) { + var peerName = conn.peer.toString(); + if ("undefined" !== peerIndex[peerName]) { + delete peerIndex[peerName]; + } + }); + + // for debug purposes, print how many of our peers are actually connected + var connected = 0 + this.peers.forEach(function(p) { + if (p.connection && !p.connection._connecting) connected++ + }); + console.debug(connected + ' of ' + this.peers.length + ' peers connected'); + + Object.keys(peerIndex).forEach(function(i) { + this.connectTo(peerIndex[i]); + }.bind(this)); + } +}; + +PeerManager.prototype.connectTo = function(peer) { + console.info('connecting to ' + peer); + try { + return this.addConnection(peer.createConnection(), peer); + } catch (e) { + console.error('creating connection', e); + return null; + } +}; + +PeerManager.prototype.addConnection = function(socketConn, peer) { + var conn = new Connection(socketConn, peer, this.config); + this.connections.push(conn); + this.emit('connection', conn); + + conn.addListener('version', this.handleVersion.bind(this)); + conn.addListener('verack', this.handleReady.bind(this)); + conn.addListener('addr', this.handleAddr.bind(this)); + conn.addListener('getaddr', this.handleGetAddr.bind(this)); + conn.addListener('error', this.handleError.bind(this)); + conn.addListener('disconnect', this.handleDisconnect.bind(this)); + + return conn; +}; + +PeerManager.prototype.handleVersion = function(e) { + e.peer.version = e.message.version; + e.peer.start_height = e.message.start_height; + + if (!e.conn.inbound) { + // TODO: Advertise our address (if listening) + } + // Get recent addresses + if (this.peerDiscovery && + (e.message.version >= 31402 || this.peers.length < 1000)) { + e.conn.sendGetAddr(); + e.conn.getaddr = true; + } +}; + +PeerManager.prototype.handleReady = function(e) { + console.info('connected to ' + e.conn.peer.host + ':' + e.conn.peer.port); + this.emit('connect', { + pm: this, + conn: e.conn, + socket: e.socket, + peer: e.peer + }); + + if (this.isConnected == false) { + this.emit('netConnected', e); + this.isConnected = true; + } +}; + +PeerManager.prototype.handleAddr = function(e) { + if (!this.peerDiscovery) return; + + var now = GetAdjustedTime(); + e.message.addrs.forEach(function(addr) { + try { + // 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; + } + var peer = new Peer(addr.ip, addr.port, addr.services); + peer.lastSeen = addr.time; + + // TODO: Handle duplicate peers + this.peers.push(peer); + + // TODO: Handle addr relay + } catch (e) { + console.warn("Invalid addr received: " + e.message); + } + }.bind(this)); + if (e.message.addrs.length < 1000) { + e.conn.getaddr = false; + } +}; + +PeerManager.prototype.handleGetAddr = function(e) { + // TODO: Reply with addr message. +}; + +PeerManager.prototype.handleError = function(e) { + console.error('unkown error with peer ' + e.peer + ' (disconnecting): ' + e.err); + this.handleDisconnect.apply(this, [].slice.call(arguments)); +}; + +PeerManager.prototype.handleDisconnect = function(e) { + console.info('disconnected from peer ' + e.peer); + var i = this.connections.indexOf(e.conn); + if (i != -1) this.connections.splice(i, 1); + + this.removePeer(e.peer); + if (this.pool.length) { + console.info('replacing peer using the pool of ' + this.pool.length + ' seeds'); + this.addPeer(this.pool.pop()); + } + + if (!this.connections.length) { + this.emit('netDisconnected'); + this.isConnected = false; + } +}; + +PeerManager.prototype.getActiveConnection = function() { + var activeConnections = this.connections.filter(function(conn) { + return conn.active; + }); + + if (activeConnections.length) { + var randomIndex = Math.floor(Math.random() * activeConnections.length); + var candidate = activeConnections[randomIndex]; + if (candidate.socket.writable) { + return candidate; + } else { + // Socket is not writable, remove it from active connections + activeConnections.splice(randomIndex, 1); + + // Then try again + // TODO: This causes an infinite recursion when all connections are dead, + // although it shouldn't. + return this.getActiveConnection(); + } + } else { + return null; + } +}; + +PeerManager.prototype.getActiveConnections = function() { + return this.connections.slice(0); +}; + +PeerManager.prototype.discover = function(options, callback) { + var self = this; + var seeds = this.config.network.dnsSeeds; + + self.limit = options.limit || 12; + + var dnsExecutor = seeds.map(function(seed) { + return function(done) { + // have we already resolved this seed? + if (~self.seeds.resolved.indexOf(seed)) { + // if so, just pass back cached peer list + return done(null, self.seeds.results[seed]); + } + + // has this seed failed to resolve? + if (~self.seeds.failed.indexOf(seed)) { + // if so, pass back empty results + return done(null, []); + } + + console.info('resolving dns seed ' + seed); + + dns.resolve(seed, function(err, peers) { + if (err) { + console.error('failed to resolve dns seed ' + seed, err); + self.seeds.failed.push(seed); + return done(null, []); + } + + console.info('found ' + peers.length + ' peers from ' + seed); + self.seeds.resolved.push(seed); + + // transform that list into a list of Peer instances + peers = peers.map(function(ip) { + return new Peer(ip, self.config.network.port); + }); + + peers.forEach(function(p) { + if (self.peers.length < self.limit) self.addPeer(p); + else self.pool.push(p); + }); + + self.emit('peers', peers); + + return done(null, peers); + }); + + }; + }); + + // try resolving all seeds + async.parallel(dnsExecutor, function(err, results) { + var peers = []; + + // consolidate all resolved peers into one list + results.forEach(function(peerlist) { + peers = peers.concat(peerlist); + }); + + if (typeof callback === 'function') callback(null, peers); + }); + + return self; +}; + +module.exports = PeerManager; \ No newline at end of file diff --git a/test/transport/peermanager.js b/test/transport/peermanager.js new file mode 100644 index 0000000..60a13ee --- /dev/null +++ b/test/transport/peermanager.js @@ -0,0 +1,42 @@ +'use strict'; + +var chai = require('chai'); +var bitcore = require('../..'); + +var should = chai.should(); + +var PeerManager = bitcore.transport.PeerManager; + +describe('PeerManager', function() { + it('should be able to create class', function() { + should.exist(PeerManager); + }); + + it('should be able to create instance', function() { + var pm = new PeerManager(); + should.exist(pm); + }); + + it('should be able to start instance', function() { + var pm = new PeerManager(); + pm.start.bind(pm).should.not.throw(); + }); + + it('should be able to stop instance', function() { + var pm = new PeerManager(); + pm.start(); + pm.stop.bind(pm).should.not.throw(); + }); + + it('should extend default config with passed config', function() { + var pm = new PeerManager({ + proxy: { + host: 'localhost', + port: 9050 + } + }); + + should.exist(pm.config.network); + should.exist(pm.config.proxy); + }); +}); From a9e54a7856b54d585d386c0d11bbbedcca44c194 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 9 Dec 2014 05:30:16 -0300 Subject: [PATCH 04/21] Progress on sending and receiving messages --- index.js | 1 + lib/networks.js | 4 + lib/transport/message.js | 182 +++++++++++++++++++++++++ lib/transport/peer2.js | 285 +++++++++++++++++++++++++++++++++++++++ test/transport/peer2.js | 73 ++++++++++ 5 files changed, 545 insertions(+) create mode 100644 lib/transport/message.js create mode 100644 lib/transport/peer2.js create mode 100644 test/transport/peer2.js diff --git a/index.js b/index.js index 4c8712f..03a492e 100644 --- a/index.js +++ b/index.js @@ -28,6 +28,7 @@ bitcore.util.js = require('./lib/util/js'); bitcore.transport = {}; bitcore.transport.Connection = require('./lib/transport/connection'); bitcore.transport.Peer = require('./lib/transport/peer'); +bitcore.transport.Peer2 = require('./lib/transport/peer2'); bitcore.transport.PeerManager = require('./lib/transport/peermanager'); // errors thrown by the library diff --git a/lib/networks.js b/lib/networks.js index 0955fed..8b6b85f 100644 --- a/lib/networks.js +++ b/lib/networks.js @@ -9,6 +9,8 @@ var _ = require('lodash'); */ function Network() {} +var hex = function(hex) { return new Buffer(hex, 'hex'); }; + /** * @instance * @member Network#livenet @@ -22,6 +24,7 @@ _.extend(livenet, { scripthash: 0x05, xpubkey: 0x0488b21e, xprivkey: 0x0488ade4, + networkMagic: hex('f9beb4d9'), port: 8333, dnsSeeds: [ 'seed.bitcoin.sipa.be', @@ -46,6 +49,7 @@ _.extend(testnet, { scripthash: 0xc4, xpubkey: 0x043587cf, xprivkey: 0x04358394, + networkMagic: hex('0b110907'), port: 18333, dnsSeeds: [ 'testnet-seed.bitcoin.petertodd.org', diff --git a/lib/transport/message.js b/lib/transport/message.js new file mode 100644 index 0000000..a94d0e6 --- /dev/null +++ b/lib/transport/message.js @@ -0,0 +1,182 @@ +'use strict'; + +var Put = require('bufferput'); + +var Random = require('../crypto/random'); +var BufferReader = require('../encoding/bufferreader'); +var Block = require('../block'); + +var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8); +var PROTOCOL_VERSION = 70000; + +var MESSAGES = { + 'version' : Version, + 'verack': VerAck, + 'inv': Inventory, + 'ping': Ping, + 'pong': Pong +} + +module.exports.buildMessage = function(command, payload) { + var Message = MESSAGES[command]; + try { + console.log('Message Class', Message); + return Message.fromBuffer(payload); + } catch (err) { + console.log('Error while parrsing message', command); + console.log(err); + } +} + +// ====== VERSION MESSAGE ====== +function Version(subversion, nonce) { + this.command = 'version'; + this.subversion = subversion || '/BitcoinX:0.1/'; + this.nonce = nonce || CONNECTION_NONCE; +} + +Version.fromBuffer = function(payload) { + var message = new Version(); + + var parser = new BufferReader(payload); + message.version = parser.readUInt32LE(); + message.services = parser.readUInt64LEBN(); + message.timestamp = parser.readUInt64LEBN(); + message.addr_me = parser.read(26); + message.addr_you = parser.read(26); + message.nonce = parser.read(8); + message.subversion = parser.readVarintBuf(); + message.start_height = parser.readUInt32LE(); + + return message; +} + +Version.prototype.serialize = function() { + var put = new Put(); + put.word32le(PROTOCOL_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 = Version; + +// ====== INV MESSAGE ====== +function Inventory(inventory) { + this.command = 'inv'; + this.inventory = inventory || []; +} + +Inventory.fromBuffer = function(payload) { + var message = new Inventory(); + + var parser = new BufferReader(payload); + var count = parser.readVarintNum(); + for (var i = 0; i < count; i++) { + message.inventory.push({ + type: parser.readUInt32LE(), + hash: parser.read(32) + }); + } + + return message; +} + +Inventory.prototype.serialize = function() { + var put = new Put(); + + put.varint(this.inventory.length); + this.inventory.forEach(function(value) { + value instanceof Block ? put.word32le(2) : put.word32le(1); + put.put(value.getHash()); + }); + + return put.buffer(); +} + + +// ====== PING/PONG MESSAGE ====== +function Ping(nonce) { + this.command = 'ping'; + this.nonce = nonce || CONNECTION_NONCE; +} + +Ping.fromBuffer = function(payload) { + var nonce = new BufferReader(payload).read(8); + return new Ping(nonce); +} + +Ping.prototype.serialize = function() { + return this.nonce; +} + +function Pong(nonce) { + this.command = 'pong'; + this.nonce = nonce || CONNECTION_NONCE; +} + +Pong.fromBuffer = Ping.fromBuffer; +Pong.prototype.serialize = Ping.prototype.serialize; + + +// ====== VARIOUS MESSAGE ====== + + + +function GetAddr() {}; + +function VerAck() {}; +VerAck.fromBuffer = function() { + return new VerAck(); +} + +function Reject() {}; + +function Ping(payload) { + var parser = new BufferReader(payload); + + this.nonce = parser.read(8); +}; + +// ====== PING MESSAGE ====== +function Address(payload) { + var parser = new BufferReader(payload); + + var addrCount = parser.readVarintNum(); + addrCount = Math.min(addrCount, 1000); + + this.addresses = []; + for (i = 0; i < addrCount; i++) { + // TODO: Time actually depends on the version of the other peer (>=31402) + this.addresses.push({ + time: parser.readUInt32LE(), + services: parser.readUInt64LEBN(), + ip: parser.read(16), // TODO: Parse IP Address + port: parser.readUInt16BE() + }); + } +}; + +function GetHeaders(payload) { + var parser = new BufferReader(payload); + + this.version = parser.readUInt32LE(); + + var startCount = parser.readVarintNum(); + startCount = Math.min(startCount, 500); + + this.starts = []; + for (i = 0; i < startCount; i++) { + this.starts.push(parser.read(32)); + } + + this.stop = parser.read(32); +} + diff --git a/lib/transport/peer2.js b/lib/transport/peer2.js new file mode 100644 index 0000000..511b66c --- /dev/null +++ b/lib/transport/peer2.js @@ -0,0 +1,285 @@ +'use strict'; + +var Net = require('net'); +var Put = require('bufferput'); +var Buffers = require('buffers'); +var buffertools = require('buffertools'); +var Socks5Client = require('socks5-client'); +var EventEmitter = require('events').EventEmitter; + +var Networks = require('../networks'); +var Hash = require('../crypto/hash'); +var Message = require('./message'); + +var MAX_RECEIVE_BUFFER = 10000000; + +/** + * A Peer instance represents a remote bitcoin node and allows to communicate + * with it using the standar messages of the bitcoin p2p protocol. + * + * @example + * + * var peer = new Peer('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 + * @returns {Peer} A new instance of Peer. + * @constructor + */ +function Peer(host, port, network) { + if (!(this instanceof Peer)) { + return new Peer(host, port, network); + } + + // overloading stuff + if (port instanceof Object && !network) { + network = port; + port = undefined; + } + + this.host = host; + this.status = Peer.STATUS.DISCONNECTED; + this.network = network || Networks.livenet; + this.port = port || this.network.port; + + this.dataBuffer = new Buffers(); + + this.version = 0; + this.bestHeight = 0; + this.subversion = null; + + // set message handlers + var self = this; + this.on('verack', function() { + self.status = Peer.STATUS.READY; + self.emit('ready'); + }); + + this.on('version', function(message) { + self.version = message.version; + self.subversion = message.subversion; + self.bestHeight = message.start_height + }); + + this.on('ping', function(message) { + self.sendPong(message.nonce); + }); + +} +util.inherits(Peer, EventEmitter); + +Peer.STATUS = { + DISCONNECTED: 'disconnected', + CONNECTING: 'connecting', + CONNECTED: 'connected', + READY: 'ready' +}; + +/** + * 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'); + } + + this.proxy = { + host: host, + port: port + }; + return this; +}; + +/** + * Init the connection with the remote peer. + * + * @returns {Socket} The same peer instance. + */ +Peer.prototype.connect = function() { + this.socket = this._getSocket(); + this.status = Peer.STATUS.CONNECTING; + + var self = this; + this.socket.on('connect', function(ev) { + self.status = Peer.STATUS.CONNECTED; + self.emit('connect'); + 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.socket.connect(this.port, this.host); + return this; +}; + +/** + * Disconnects the remote connection. + * + * @returns {Socket} The same peer instance. + */ +Peer.prototype.disconnect = function() { + this.status = Peer.STATUS.DISCONNECTED; + this.socket.destroy(); + this.emit('disconnect'); + return this; +}; + +/** + * Internal function that tries to read a message from the data buffer + */ +Peer.prototype._readMessage = function() { + if (this.dataBuffer.length < 20) return; + var magic = this.network.networkMagic; + + // Search the next magic number + if (!this._discardUntilNextMessage()) return; + + var PAYLOAD_START = 16; + var payloadLen = (this.dataBuffer.get(PAYLOAD_START)) + + (this.dataBuffer.get(PAYLOAD_START + 1) << 8) + + (this.dataBuffer.get(PAYLOAD_START + 2) << 16) + + (this.dataBuffer.get(PAYLOAD_START + 3) << 24); + + var messageLength = 24 + payloadLen; + if (this.dataBuffer.length < messageLength) return; + + var command = this.dataBuffer.slice(4, 16).toString('ascii').replace(/\0+$/, ''); + var payload = this.dataBuffer.slice(24, messageLength); + var checksum = this.dataBuffer.slice(20, 24); + + var checksumConfirm = Hash.sha256sha256(payload).slice(0, 4); + if (buffertools.compare(checksumConfirm, checksum) !== 0) { + this.dataBuffer.skip(messageLength); + return; + } + + console.log('we have a message:', command); + var message = Message.buildMessage(command, payload); + if (message) this.emit(command, message); + console.log('Emiting message', command, message); + + this.dataBuffer.skip(messageLength); + this._readMessage(); +}; + +/** + * Internal function that discards data until founds the next message. + */ +Peer.prototype._discardUntilNextMessage = function() { + var magicNumber = this.network.networkMagic; + + var i = 0; + for (;;) { + // check if it's the beginning of a new message + var packageNumber = this.dataBuffer.slice(0, 4); + if (buffertools.compare(packageNumber, magicNumber) == 0) { + this.dataBuffer.skip(i); + return true; + } + + // did we reach the end of the buffer? + if (i > (this.dataBuffer.length - 4)) { + this.dataBuffer.skip(i); + return false; + } + + i++; // continue scanning + } +} + +/** + * Internal function that sends VERSION message to the remote peer. + */ +Peer.prototype._sendVersion = function() { + var message = new Message.Version(); + this._sendMessage(message.command, message.serialize()); +}; + +/** + * Send a PING message to the remote peer. + */ +Peer.prototype.sendPing = function(nonce) { + var message = new Message.Pong(nonce); + this._sendMessage(message.command, message.serialize()); +}; + +/** + * Send a PONG message to the remote peer. + */ +Peer.prototype.sendPong = function(nonce) { + var message = new Message.Pong(nonce); + this._sendMessage(message.command, message.serialize()); +}; + +/** + * Internal function that sends a message to the remote peer. + */ +Peer.prototype._sendMessage = function(command, payload) { + var magic = this.network.networkMagic; + var commandBuf = new Buffer(command, 'ascii'); + if (commandBuf.length > 12) throw 'Command name too long'; + + var checksum = Hash.sha256sha256(payload).slice(0, 4); + + // -- HEADER -- + var message = new Put(); + message.put(magic); // magic bytes + message.put(commandBuf); // command name + message.pad(12 - commandBuf.length); // zero-padded + message.word32le(payload.length); // payload length + message.put(checksum); // checksum + + // -- BODY -- + message.put(payload); // payload data + + this.socket.write(message.buffer()); +}; + +/** + * Internal function that creates a socket using a proxy if neccesary. + * + * @returns {Socket} A Socket instance not yet connected. + */ +Peer.prototype._getSocket = function() { + if (this.proxy) { + return new Socks5Client(this.proxy.host, this.proxy.port); + } + + return new Net.Socket(); +}; + + +// 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; +}; + +module.exports = Peer; diff --git a/test/transport/peer2.js b/test/transport/peer2.js new file mode 100644 index 0000000..2f23362 --- /dev/null +++ b/test/transport/peer2.js @@ -0,0 +1,73 @@ +'use strict'; + +var chai = require('chai'); +var Net = require('net'); +var Socks5Client = require('socks5-client'); + +var should = chai.should(); +var expect = chai.expect; + +var bitcore = require('../..'); +var Peer = bitcore.transport.Peer2; +var Networks = bitcore.Networks; + +describe.only('Peer2', function() { + + it('should be able to 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); + 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); + 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); + 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); + peer.host.should.equal('localhost'); + peer.network.should.equal(Networks.testnet); + peer.port.should.equal(8111); + }); + + it('should be able to set a proxy', function() { + var peer, peer2, socket; + + peer = new Peer('localhost'); + expect(peer.proxy).to.be.undefined; + socket = peer._getSocket(); + socket.should.be.instanceof(Net.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.should.equal(peer2); + }); + + it('should create connection', function() { + var peer = new Peer('localhost').connect(); + }); + + +}); From 68f50b2f613efa8b1411e3872cb72704c3b24812 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 9 Dec 2014 11:21:11 -0300 Subject: [PATCH 05/21] Add messages serialization --- lib/transport/message.js | 255 +++++++++++++++++++++++++++++++-------- lib/util/buffer.js | 2 - 2 files changed, 206 insertions(+), 51 deletions(-) diff --git a/lib/transport/message.js b/lib/transport/message.js index a94d0e6..8745ad7 100644 --- a/lib/transport/message.js +++ b/lib/transport/message.js @@ -2,9 +2,10 @@ var Put = require('bufferput'); -var Random = require('../crypto/random'); -var BufferReader = require('../encoding/bufferreader'); var Block = require('../block'); +var BufferReader = require('../encoding/bufferreader'); +var BufferUtil = require('../util/buffer'); +var Random = require('../crypto/random'); var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8); var PROTOCOL_VERSION = 70000; @@ -14,7 +15,10 @@ var MESSAGES = { 'verack': VerAck, 'inv': Inventory, 'ping': Ping, - 'pong': Pong + 'pong': Pong, + 'addr': Addresses, + 'getaddr': GetAddresses, + 'reject': Reject } module.exports.buildMessage = function(command, payload) { @@ -31,9 +35,10 @@ module.exports.buildMessage = function(command, payload) { // ====== VERSION MESSAGE ====== function Version(subversion, nonce) { this.command = 'version'; + this.version = PROTOCOL_VERSION; this.subversion = subversion || '/BitcoinX:0.1/'; this.nonce = nonce || CONNECTION_NONCE; -} +}; Version.fromBuffer = function(payload) { var message = new Version(); @@ -49,11 +54,11 @@ Version.fromBuffer = function(payload) { message.start_height = parser.readUInt32LE(); return message; -} +}; Version.prototype.serialize = function() { var put = new Put(); - put.word32le(PROTOCOL_VERSION); // version + put.word32le(this.version); // version put.word64le(1); // services put.word64le(Math.round(new Date().getTime() / 1000)); // timestamp put.pad(26); // addr_me @@ -64,11 +69,11 @@ Version.prototype.serialize = function() { put.word32le(0); return put.buffer(); -} +}; module.exports.Version = Version; -// ====== INV MESSAGE ====== +// ====== INV/GETDATA MESSAGE ====== function Inventory(inventory) { this.command = 'inv'; this.inventory = inventory || []; @@ -87,7 +92,7 @@ Inventory.fromBuffer = function(payload) { } return message; -} +}; Inventory.prototype.serialize = function() { var put = new Put(); @@ -99,8 +104,15 @@ Inventory.prototype.serialize = function() { }); return put.buffer(); +}; + +function GetData(inventory) { + this.command = 'getdata'; + this.inventory = inventory || []; } +GetData.fromBuffer = Inventory.fromBuffer; +GetData.prototype.serialize = Inventory.prototype.serialize; // ====== PING/PONG MESSAGE ====== function Ping(nonce) { @@ -111,11 +123,11 @@ function Ping(nonce) { Ping.fromBuffer = function(payload) { var nonce = new BufferReader(payload).read(8); return new Ping(nonce); -} +}; Ping.prototype.serialize = function() { return this.nonce; -} +}; function Pong(nonce) { this.command = 'pong'; @@ -126,57 +138,202 @@ Pong.fromBuffer = Ping.fromBuffer; Pong.prototype.serialize = Ping.prototype.serialize; -// ====== VARIOUS MESSAGE ====== - - - -function GetAddr() {}; - -function VerAck() {}; -VerAck.fromBuffer = function() { - return new VerAck(); +// ====== ADDR MESSAGE ====== +function Addresses(nonce) { + this.command = 'addr'; + this.addresses = []; } -function Reject() {}; +Address.fromBuffer = function(payload) { + var message = new Address(); -function Ping(payload) { var parser = new BufferReader(payload); + var addrCount = Math.min(parser.readVarintNum(), 1000); - this.nonce = parser.read(8); -}; - -// ====== PING MESSAGE ====== -function Address(payload) { - var parser = new BufferReader(payload); - - var addrCount = parser.readVarintNum(); - addrCount = Math.min(addrCount, 1000); - - this.addresses = []; - for (i = 0; i < addrCount; i++) { + message.addresses = []; + for (var i = 0; i < addrCount; i++) { // TODO: Time actually depends on the version of the other peer (>=31402) - this.addresses.push({ + message.addresses.push({ time: parser.readUInt32LE(), services: parser.readUInt64LEBN(), - ip: parser.read(16), // TODO: Parse IP Address + ip: parser.read(16), port: parser.readUInt16BE() }); } + + return message; }; -function GetHeaders(payload) { - var parser = new BufferReader(payload); +Address.prototype.serialize = function() { + return BufferUtil.EMPTY_BUFFER; // TODO +}; - this.version = parser.readUInt32LE(); - - var startCount = parser.readVarintNum(); - startCount = Math.min(startCount, 500); - - this.starts = []; - for (i = 0; i < startCount; i++) { - this.starts.push(parser.read(32)); - } - - this.stop = parser.read(32); +// ====== GETADDR MESSAGE ====== +function GetAddresses() { + this.command = 'getaddr'; } +GetAddresses.fromBuffer = function() { + return new GetAddresses(); +}; + +GetAddresses.prototype.serialize = function() { + return BufferUtil.EMPTY_BUFFER; +}; + + +// ====== VERACK MESSAGE ====== +function VerAck() { + this.command = 'verack'; +} + +VerAck.fromBuffer = function() { + return new VerAck(); +}; + +VerAck.prototype.serialize = function() { + return BufferUtil.EMPTY_BUFFER; +}; + +// ====== REJECT MESSAGE ====== +// TODO: Parse REJECT message +function Reject() { + this.command = 'reject'; +} + +Reject.fromBuffer = function() { + return new Reject(); +}; + +Reject.prototype.serialize = function() { + return BufferUtil.EMPTY_BUFFER; +}; + +// ====== ALERT MESSAGE ====== +function Alert(payload) { + this.command = 'reject'; +} + +Alert.fromBuffer = function() { + var message = new Alert(); + + var parser = new BufferReader(payload); + message.payload = parser.readVarintBuf(); // TODO: Use current format + message.signature = parser.readVarintBuf(); + return message; +}; + +Alert.prototype.serialize = function() { + return BufferUtil.EMPTY_BUFFER; // TODO: Serialize +}; + +// ====== HEADERS MESSAGE ====== +function Headers(blockheaders) { + this.command = 'headers'; + this.headers = blockheaders || []; +} + +Headers.fromBuffer = function() { + var message = new Headers(); + + var parser = new BufferReader(payload); + var count = parser.readVarintNum(); + + message.headers = []; + for (i = 0; i < count; i++) { + var header = Block().fromBufferReader(parser); + message.headers.push(header); + } + + return message; +}; + +Headers.prototype.serialize = function() { + return BufferUtil.EMPTY_BUFFER; // TODO: Serialize +}; + +// ====== BLOCK MESSAGE ====== +function Block(block) { + this.command = 'block'; + this.block = block; +} + +Block.fromBuffer = function() { + var parser = new BufferReader(payload); + var block = Block().fromBufferReader(parser); + return new Block(block); +}; + +Block.prototype.serialize = function() { + return BufferUtil.EMPTY_BUFFER; // TODO: Serialize +}; + +// ====== TX MESSAGE ====== +function Transaction(transaction) { + this.command = 'tx'; + this.transaction = transaction; +} + +Transaction.fromBuffer = function() { + var parser = new BufferReader(payload); + var transaction = Transaction().fromBufferReader(parser); + return new Transaction(transaction); +}; + +Transaction.prototype.serialize = function() { + return BufferUtil.EMPTY_BUFFER; // TODO: Serialize +}; + +// ====== GETBLOCKS/GETHEADERS MESSAGE ====== +function GetBlocks(starts, stop) { + this.command = 'getblocks'; + this.version = PROTOCOL_VERSION; + this.starts = starts || []; + this.stop = stop || BufferUtil.NULL_HASH; +} + +GetBlocks.fromBuffer = function() { + var message = new GetBlocks(); + + var parser = new BufferReader(payload); + message.version = parser.readUInt32LE(); + + var startCount = Math.min(parser.readVarintNum(), 500); + message.starts = []; + for (var i = 0; i < startCount; i++) { + message.starts.push(parser.read(32)); + } + message.stop = parser.read(32); +}; + +GetBlocks.prototype.serialize = function() { + var put = new Put(); + put.word32le(this.version); + put.varint(this.starts.length); + + for (var i = 0; i < 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(); +}; + +function GetHeaders(starts, stop) { + this.command = 'getheaders'; + this.version = PROTOCOL_VERSION; + this.starts = starts || []; + this.stop = stop || BufferUtil.NULL_HASH; +} + +GetHeaders.fromBuffer = GetBlocks.fromBuffer; +GetHeaders.prototype.serialize = GetBlocks.prototype.serialize; + + diff --git a/lib/util/buffer.js b/lib/util/buffer.js index a84023e..deb9f3a 100644 --- a/lib/util/buffer.js +++ b/lib/util/buffer.js @@ -22,8 +22,6 @@ function equals(a, b) { module.exports = { NULL_HASH: buffertools.fill(new Buffer(32), 0), EMPTY_BUFFER: new Buffer(0), - ZERO_VALUE: buffertools.fill(new Buffer(8), 0), - INT64_MAX: new Buffer('ffffffffffffffff', 'hex'), /** * Returns true if the given argument is an instance of a buffer. Tests for From 5dc124a438c9b04ecdc3fa4cf9ade351feffac79 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 9 Dec 2014 11:34:41 -0300 Subject: [PATCH 06/21] Add inheritance to messages and expose them as a module --- lib/transport/message.js | 71 ++++++++++++++++++++++++++-------------- lib/transport/peer2.js | 1 + 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/lib/transport/message.js b/lib/transport/message.js index 8745ad7..dd2adba 100644 --- a/lib/transport/message.js +++ b/lib/transport/message.js @@ -25,7 +25,7 @@ module.exports.buildMessage = function(command, payload) { var Message = MESSAGES[command]; try { console.log('Message Class', Message); - return Message.fromBuffer(payload); + return new Message().fromBuffer(payload); } catch (err) { console.log('Error while parrsing message', command); console.log(err); @@ -40,7 +40,7 @@ function Version(subversion, nonce) { this.nonce = nonce || CONNECTION_NONCE; }; -Version.fromBuffer = function(payload) { +Version.prototype.fromBuffer = function(payload) { var message = new Version(); var parser = new BufferReader(payload); @@ -73,13 +73,13 @@ Version.prototype.serialize = function() { module.exports.Version = Version; -// ====== INV/GETDATA MESSAGE ====== +// ====== INV MESSAGE ====== function Inventory(inventory) { this.command = 'inv'; this.inventory = inventory || []; } -Inventory.fromBuffer = function(payload) { +Inventory.prototype.fromBuffer = function(payload) { var message = new Inventory(); var parser = new BufferReader(payload); @@ -106,21 +106,24 @@ Inventory.prototype.serialize = function() { return put.buffer(); }; +module.exports.Inventory = Inventory; + +// ====== GETDATA MESSAGE ====== function GetData(inventory) { this.command = 'getdata'; this.inventory = inventory || []; } -GetData.fromBuffer = Inventory.fromBuffer; -GetData.prototype.serialize = Inventory.prototype.serialize; +util.inherits(GetData, Inventory); +module.exports.GetData = GetData; -// ====== PING/PONG MESSAGE ====== +// ====== PING MESSAGE ====== function Ping(nonce) { this.command = 'ping'; this.nonce = nonce || CONNECTION_NONCE; } -Ping.fromBuffer = function(payload) { +Ping.prototype.fromBuffer = function(payload) { var nonce = new BufferReader(payload).read(8); return new Ping(nonce); }; @@ -129,14 +132,16 @@ Ping.prototype.serialize = function() { return this.nonce; }; +module.exports.Ping = Ping; + +// ====== PONG MESSAGE ====== function Pong(nonce) { this.command = 'pong'; this.nonce = nonce || CONNECTION_NONCE; } -Pong.fromBuffer = Ping.fromBuffer; -Pong.prototype.serialize = Ping.prototype.serialize; - +util.inherits(Pong, Ping); +module.exports.Pong = Pong; // ====== ADDR MESSAGE ====== function Addresses(nonce) { @@ -144,7 +149,7 @@ function Addresses(nonce) { this.addresses = []; } -Address.fromBuffer = function(payload) { +Address.prototype.fromBuffer = function(payload) { var message = new Address(); var parser = new BufferReader(payload); @@ -168,12 +173,14 @@ Address.prototype.serialize = function() { return BufferUtil.EMPTY_BUFFER; // TODO }; +module.exports.Address = Address; + // ====== GETADDR MESSAGE ====== function GetAddresses() { this.command = 'getaddr'; } -GetAddresses.fromBuffer = function() { +GetAddresses.prototype.fromBuffer = function() { return new GetAddresses(); }; @@ -181,13 +188,14 @@ GetAddresses.prototype.serialize = function() { return BufferUtil.EMPTY_BUFFER; }; +module.exports.GetAddresses = GetAddresses; // ====== VERACK MESSAGE ====== function VerAck() { this.command = 'verack'; } -VerAck.fromBuffer = function() { +VerAck.prototype.fromBuffer = function() { return new VerAck(); }; @@ -195,13 +203,15 @@ VerAck.prototype.serialize = function() { return BufferUtil.EMPTY_BUFFER; }; +module.exports.VerAck = VerAck; + // ====== REJECT MESSAGE ====== // TODO: Parse REJECT message function Reject() { this.command = 'reject'; } -Reject.fromBuffer = function() { +Reject.prototype.fromBuffer = function() { return new Reject(); }; @@ -209,12 +219,14 @@ Reject.prototype.serialize = function() { return BufferUtil.EMPTY_BUFFER; }; +module.exports.Reject = Reject; + // ====== ALERT MESSAGE ====== function Alert(payload) { this.command = 'reject'; } -Alert.fromBuffer = function() { +Alert.prototype.fromBuffer = function() { var message = new Alert(); var parser = new BufferReader(payload); @@ -227,13 +239,15 @@ Alert.prototype.serialize = function() { return BufferUtil.EMPTY_BUFFER; // TODO: Serialize }; +module.exports.Alert = Alert; + // ====== HEADERS MESSAGE ====== function Headers(blockheaders) { this.command = 'headers'; this.headers = blockheaders || []; } -Headers.fromBuffer = function() { +Headers.prototype.fromBuffer = function() { var message = new Headers(); var parser = new BufferReader(payload); @@ -252,13 +266,15 @@ Headers.prototype.serialize = function() { return BufferUtil.EMPTY_BUFFER; // TODO: Serialize }; +module.exports.Headers = Headers; + // ====== BLOCK MESSAGE ====== function Block(block) { this.command = 'block'; this.block = block; } -Block.fromBuffer = function() { +Block.prototype.fromBuffer = function() { var parser = new BufferReader(payload); var block = Block().fromBufferReader(parser); return new Block(block); @@ -268,13 +284,15 @@ Block.prototype.serialize = function() { return BufferUtil.EMPTY_BUFFER; // TODO: Serialize }; +module.exports.Block = Block; + // ====== TX MESSAGE ====== function Transaction(transaction) { this.command = 'tx'; this.transaction = transaction; } -Transaction.fromBuffer = function() { +Transaction.prototype.fromBuffer = function() { var parser = new BufferReader(payload); var transaction = Transaction().fromBufferReader(parser); return new Transaction(transaction); @@ -284,7 +302,9 @@ Transaction.prototype.serialize = function() { return BufferUtil.EMPTY_BUFFER; // TODO: Serialize }; -// ====== GETBLOCKS/GETHEADERS MESSAGE ====== +module.exports.Transaction = Transaction; + +// ====== GETBLOCKS MESSAGE ====== function GetBlocks(starts, stop) { this.command = 'getblocks'; this.version = PROTOCOL_VERSION; @@ -292,7 +312,7 @@ function GetBlocks(starts, stop) { this.stop = stop || BufferUtil.NULL_HASH; } -GetBlocks.fromBuffer = function() { +GetBlocks.prototype.fromBuffer = function() { var message = new GetBlocks(); var parser = new BufferReader(payload); @@ -326,6 +346,9 @@ GetBlocks.prototype.serialize = function() { return put.buffer(); }; +module.exports.GetBlocks = GetBlocks; + +// ====== GETHEADERS MESSAGE ====== function GetHeaders(starts, stop) { this.command = 'getheaders'; this.version = PROTOCOL_VERSION; @@ -333,7 +356,5 @@ function GetHeaders(starts, stop) { this.stop = stop || BufferUtil.NULL_HASH; } -GetHeaders.fromBuffer = GetBlocks.fromBuffer; -GetHeaders.prototype.serialize = GetBlocks.prototype.serialize; - - +util.inherits(GetHeaders, GetBlocks); +module.exports.GetHeaders = GetHeaders; diff --git a/lib/transport/peer2.js b/lib/transport/peer2.js index 511b66c..525e106 100644 --- a/lib/transport/peer2.js +++ b/lib/transport/peer2.js @@ -1,5 +1,6 @@ 'use strict'; +var util = require('util'); var Net = require('net'); var Put = require('bufferput'); var Buffers = require('buffers'); From 8eca7285e0813d03b29f2cdf0d22b272df79b452 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 9 Dec 2014 18:02:27 -0300 Subject: [PATCH 07/21] Refactor and documentation --- lib/transport/message.js | 416 +++++++++++++++++++++++++++------------ lib/transport/peer2.js | 133 ++----------- 2 files changed, 309 insertions(+), 240 deletions(-) diff --git a/lib/transport/message.js b/lib/transport/message.js index dd2adba..4aaec14 100644 --- a/lib/transport/message.js +++ b/lib/transport/message.js @@ -1,62 +1,175 @@ 'use strict'; +var Buffers = require('buffers'); +var buffertools = require('buffertools'); var Put = require('bufferput'); +var util = require('util'); var Block = require('../block'); var BufferReader = require('../encoding/bufferreader'); var BufferUtil = require('../util/buffer'); +var Hash = require('../crypto/hash'); var Random = require('../crypto/random'); var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8); var PROTOCOL_VERSION = 70000; -var MESSAGES = { - 'version' : Version, - 'verack': VerAck, - 'inv': Inventory, - 'ping': Ping, - 'pong': Pong, - 'addr': Addresses, - 'getaddr': GetAddresses, - 'reject': Reject -} +/** + * Static helper for consuming a data buffer until the next message. + * + * @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; -module.exports.buildMessage = function(command, payload) { - var Message = MESSAGES[command]; - try { - console.log('Message Class', Message); - return new Message().fromBuffer(payload); - } catch (err) { - console.log('Error while parrsing message', command); - console.log(err); + // 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 (buffertools.compare(checksumConfirm, checksum) !== 0) { + dataBuffer.skip(messageLength); + return; + } + + dataBuffer.skip(messageLength); + return Message.buildMessage(command, payload); +}; + +module.exports.parseMessage = parseMessage; + +/** + * Internal function that discards data until founds the next message. + */ +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 (buffertools.compare(packageNumber, magicNumber) == 0) { + 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 } } -// ====== VERSION MESSAGE ====== +/** + * Abstract Message that knows how to parse and serialize itself. + * Concret subclases should implement {fromBuffer} and {getPayload} methods. + */ +function Message() {}; + +Message.COMMANDS = {}; + +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); + throw 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) { + 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(); +} + +/** + * Version Message + * + * @param{string} subversion - version of the client + * @param{Buffer} nonce - a random 8 bytes buffer + */ function Version(subversion, nonce) { this.command = 'version'; this.version = PROTOCOL_VERSION; this.subversion = subversion || '/BitcoinX:0.1/'; this.nonce = nonce || CONNECTION_NONCE; }; +util.inherits(Version, Message); Version.prototype.fromBuffer = function(payload) { - var message = new Version(); - var parser = new BufferReader(payload); - message.version = parser.readUInt32LE(); - message.services = parser.readUInt64LEBN(); - message.timestamp = parser.readUInt64LEBN(); - message.addr_me = parser.read(26); - message.addr_you = parser.read(26); - message.nonce = parser.read(8); - message.subversion = parser.readVarintBuf(); - message.start_height = parser.readUInt32LE(); + this.version = parser.readUInt32LE(); + this.services = parser.readUInt64LEBN(); + this.timestamp = parser.readUInt64LEBN(); + this.addr_me = parser.read(26); + this.addr_you = parser.read(26); + this.nonce = parser.read(8); + this.subversion = parser.readVarintBuf(); + this.start_height = parser.readUInt32LE(); - return message; + return this; }; -Version.prototype.serialize = function() { +Version.prototype.getPayload = function() { var put = new Put(); put.word32le(this.version); // version put.word64le(1); // services @@ -71,30 +184,33 @@ Version.prototype.serialize = function() { return put.buffer(); }; -module.exports.Version = Version; +module.exports.Version = Message.COMMANDS['version'] = Version; -// ====== INV MESSAGE ====== +/** + * Inv Message + * + * @param{Array} inventory - reported elements + */ function Inventory(inventory) { this.command = 'inv'; this.inventory = inventory || []; } +util.inherits(Inventory, Message); Inventory.prototype.fromBuffer = function(payload) { - var message = new Inventory(); - var parser = new BufferReader(payload); var count = parser.readVarintNum(); for (var i = 0; i < count; i++) { - message.inventory.push({ + this.inventory.push({ type: parser.readUInt32LE(), hash: parser.read(32) }); } - return message; + return this; }; -Inventory.prototype.serialize = function() { +Inventory.prototype.getPayload = function() { var put = new Put(); put.varint(this.inventory.length); @@ -106,9 +222,13 @@ Inventory.prototype.serialize = function() { return put.buffer(); }; -module.exports.Inventory = Inventory; +module.exports.Inventory = Message.COMMANDS['inv'] = Inventory; -// ====== GETDATA MESSAGE ====== +/** + * Getdata Message + * + * @param{Array} inventory - requested elements + */ function GetData(inventory) { this.command = 'getdata'; this.inventory = inventory || []; @@ -117,24 +237,33 @@ function GetData(inventory) { util.inherits(GetData, Inventory); module.exports.GetData = GetData; -// ====== PING MESSAGE ====== +/** + * Ping Message + * + * @param{Buffer} nonce - a random 8 bytes buffer + */ function Ping(nonce) { this.command = 'ping'; this.nonce = nonce || CONNECTION_NONCE; } +util.inherits(Ping, Message); Ping.prototype.fromBuffer = function(payload) { - var nonce = new BufferReader(payload).read(8); - return new Ping(nonce); + this.nonce = new BufferReader(payload).read(8); + return this; }; -Ping.prototype.serialize = function() { +Ping.prototype.getPayload = function() { return this.nonce; }; -module.exports.Ping = Ping; +module.exports.Ping = Message.COMMANDS['ping'] = Ping; -// ====== PONG MESSAGE ====== +/** + * Pong Message + * + * @param{Buffer} nonce - a random 8 bytes buffer + */ function Pong(nonce) { this.command = 'pong'; this.nonce = nonce || CONNECTION_NONCE; @@ -143,22 +272,25 @@ function Pong(nonce) { util.inherits(Pong, Ping); module.exports.Pong = Pong; -// ====== ADDR MESSAGE ====== -function Addresses(nonce) { +/** + * Addr Message + * + * @param{Array} addresses - array of know addresses + */ +function Addresses(addresses) { this.command = 'addr'; - this.addresses = []; + this.addresses = addresses || []; } +util.inherits(Addresses, Message); -Address.prototype.fromBuffer = function(payload) { - var message = new Address(); - +Addresses.prototype.fromBuffer = function(payload) { var parser = new BufferReader(payload); var addrCount = Math.min(parser.readVarintNum(), 1000); - message.addresses = []; + this.addresses = []; for (var i = 0; i < addrCount; i++) { // TODO: Time actually depends on the version of the other peer (>=31402) - message.addresses.push({ + this.addresses.push({ time: parser.readUInt32LE(), services: parser.readUInt64LEBN(), ip: parser.read(16), @@ -166,167 +298,177 @@ Address.prototype.fromBuffer = function(payload) { }); } - return message; + return this; }; -Address.prototype.serialize = function() { +Addresses.prototype.getPayload = function() { return BufferUtil.EMPTY_BUFFER; // TODO }; -module.exports.Address = Address; +module.exports.Addresses = Message.COMMANDS['addr'] = Addresses; -// ====== GETADDR MESSAGE ====== +/** + * GetAddr Message + * + */ function GetAddresses() { this.command = 'getaddr'; } -GetAddresses.prototype.fromBuffer = function() { - return new GetAddresses(); -}; +util.inherits(GetAddresses, Message); +module.exports.GetAddresses = Message.COMMANDS['getaddr'] = GetAddresses; -GetAddresses.prototype.serialize = function() { - return BufferUtil.EMPTY_BUFFER; -}; - -module.exports.GetAddresses = GetAddresses; - -// ====== VERACK MESSAGE ====== +/** + * Verack Message + * + */ function VerAck() { this.command = 'verack'; } -VerAck.prototype.fromBuffer = function() { - return new VerAck(); -}; +util.inherits(VerAck, Message); +module.exports.VerAck = Message.COMMANDS['verack'] = VerAck; -VerAck.prototype.serialize = function() { - return BufferUtil.EMPTY_BUFFER; -}; - -module.exports.VerAck = VerAck; - -// ====== REJECT MESSAGE ====== -// TODO: Parse REJECT message +/** + * Reject Message + * + */ function Reject() { this.command = 'reject'; } +util.inherits(Reject, Message); -Reject.prototype.fromBuffer = function() { - return new Reject(); -}; +// TODO: Parse REJECT message -Reject.prototype.serialize = function() { - return BufferUtil.EMPTY_BUFFER; -}; +module.exports.Reject = Message.COMMANDS['reject'] = Reject; -module.exports.Reject = Reject; - -// ====== ALERT MESSAGE ====== -function Alert(payload) { - this.command = 'reject'; +/** + * Alert Message + * + */ +function Alert() { + this.command = 'alert'; } +util.inherits(Alert, Message); Alert.prototype.fromBuffer = function() { - var message = new Alert(); - var parser = new BufferReader(payload); - message.payload = parser.readVarintBuf(); // TODO: Use current format - message.signature = parser.readVarintBuf(); - return message; + this.payload = parser.readVarintBuf(); // TODO: Use current format + this.signature = parser.readVarintBuf(); + return this; }; -Alert.prototype.serialize = function() { +Alert.prototype.getPayload = function() { return BufferUtil.EMPTY_BUFFER; // TODO: Serialize }; -module.exports.Alert = Alert; +module.exports.Alert = Message.COMMANDS['alert'] = Alert; -// ====== HEADERS MESSAGE ====== +/** + * Headers Message + * + * @param{Array} blockheaders - array of block headers + */ function Headers(blockheaders) { this.command = 'headers'; this.headers = blockheaders || []; } +util.inherits(Headers, Message); Headers.prototype.fromBuffer = function() { - var message = new Headers(); - var parser = new BufferReader(payload); var count = parser.readVarintNum(); - message.headers = []; + this.headers = []; for (i = 0; i < count; i++) { var header = Block().fromBufferReader(parser); - message.headers.push(header); + this.headers.push(header); } - return message; + return this; }; -Headers.prototype.serialize = function() { +Headers.prototype.getPayload = function() { return BufferUtil.EMPTY_BUFFER; // TODO: Serialize }; -module.exports.Headers = Headers; +module.exports.Headers = Message.COMMANDS['headers'] = Headers; -// ====== BLOCK MESSAGE ====== +/** + * Block Message + * + * @param{Block} block + */ function Block(block) { this.command = 'block'; this.block = block; } +util.inherits(Block, Message); Block.prototype.fromBuffer = function() { var parser = new BufferReader(payload); - var block = Block().fromBufferReader(parser); - return new Block(block); + this.block = Block().fromBufferReader(parser); + return this; }; -Block.prototype.serialize = function() { +Block.prototype.getPayload = function() { return BufferUtil.EMPTY_BUFFER; // TODO: Serialize }; -module.exports.Block = Block; +module.exports.Block = Message.COMMANDS['block'] = Block; -// ====== TX MESSAGE ====== +/** + * Tx Message + * + * @param{Transaction} transaction + */ function Transaction(transaction) { this.command = 'tx'; this.transaction = transaction; } +util.inherits(Transaction, Message); Transaction.prototype.fromBuffer = function() { var parser = new BufferReader(payload); - var transaction = Transaction().fromBufferReader(parser); - return new Transaction(transaction); + this.transaction = Transaction().fromBufferReader(parser); + return this; }; -Transaction.prototype.serialize = function() { +Transaction.prototype.getPayload = function() { return BufferUtil.EMPTY_BUFFER; // TODO: Serialize }; -module.exports.Transaction = Transaction; +module.exports.Transaction = Message.COMMANDS['tx'] = Transaction; -// ====== GETBLOCKS MESSAGE ====== +/** + * Getblocks Message + * + * @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'; this.version = PROTOCOL_VERSION; this.starts = starts || []; this.stop = stop || BufferUtil.NULL_HASH; } +util.inherits(GetBlocks, Message); GetBlocks.prototype.fromBuffer = function() { - var message = new GetBlocks(); - var parser = new BufferReader(payload); - message.version = parser.readUInt32LE(); + this.version = parser.readUInt32LE(); var startCount = Math.min(parser.readVarintNum(), 500); - message.starts = []; + this.starts = []; for (var i = 0; i < startCount; i++) { - message.starts.push(parser.read(32)); + this.starts.push(parser.read(32)); } - message.stop = parser.read(32); + this.stop = parser.read(32); + + return this; }; -GetBlocks.prototype.serialize = function() { +GetBlocks.prototype.getPayload = function() { var put = new Put(); put.word32le(this.version); put.varint(this.starts.length); @@ -346,9 +488,14 @@ GetBlocks.prototype.serialize = function() { return put.buffer(); }; -module.exports.GetBlocks = GetBlocks; +module.exports.GetBlocks = Message.COMMANDS['getblocks'] = GetBlocks; -// ====== GETHEADERS MESSAGE ====== +/** + * Getheaders Message + * + * @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'; this.version = PROTOCOL_VERSION; @@ -357,4 +504,21 @@ function GetHeaders(starts, stop) { } util.inherits(GetHeaders, GetBlocks); -module.exports.GetHeaders = GetHeaders; +module.exports.GetHeaders = Message.COMMANDS['getheaders'] = GetHeaders; + + +// 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; +}; diff --git a/lib/transport/peer2.js b/lib/transport/peer2.js index 525e106..3b38bc1 100644 --- a/lib/transport/peer2.js +++ b/lib/transport/peer2.js @@ -1,16 +1,14 @@ 'use strict'; -var util = require('util'); +var Buffers = require('buffers'); +var EventEmitter = require('events').EventEmitter; var Net = require('net'); var Put = require('bufferput'); -var Buffers = require('buffers'); -var buffertools = require('buffertools'); var Socks5Client = require('socks5-client'); -var EventEmitter = require('events').EventEmitter; +var util = require('util'); var Networks = require('../networks'); -var Hash = require('../crypto/hash'); -var Message = require('./message'); +var Messages = require('./message'); var MAX_RECEIVE_BUFFER = 10000000; @@ -143,114 +141,38 @@ Peer.prototype.disconnect = function() { }; /** - * Internal function that tries to read a message from the data buffer + * Send a Message to the remote peer. */ -Peer.prototype._readMessage = function() { - if (this.dataBuffer.length < 20) return; - var magic = this.network.networkMagic; - - // Search the next magic number - if (!this._discardUntilNextMessage()) return; - - var PAYLOAD_START = 16; - var payloadLen = (this.dataBuffer.get(PAYLOAD_START)) + - (this.dataBuffer.get(PAYLOAD_START + 1) << 8) + - (this.dataBuffer.get(PAYLOAD_START + 2) << 16) + - (this.dataBuffer.get(PAYLOAD_START + 3) << 24); - - var messageLength = 24 + payloadLen; - if (this.dataBuffer.length < messageLength) return; - - var command = this.dataBuffer.slice(4, 16).toString('ascii').replace(/\0+$/, ''); - var payload = this.dataBuffer.slice(24, messageLength); - var checksum = this.dataBuffer.slice(20, 24); - - var checksumConfirm = Hash.sha256sha256(payload).slice(0, 4); - if (buffertools.compare(checksumConfirm, checksum) !== 0) { - this.dataBuffer.skip(messageLength); - return; - } - - console.log('we have a message:', command); - var message = Message.buildMessage(command, payload); - if (message) this.emit(command, message); - console.log('Emiting message', command, message); - - this.dataBuffer.skip(messageLength); - this._readMessage(); +Peer.prototype.sendMessage = function(message) { + this.socket.write(message.serialize(this.network)); }; -/** - * Internal function that discards data until founds the next message. - */ -Peer.prototype._discardUntilNextMessage = function() { - var magicNumber = this.network.networkMagic; - - var i = 0; - for (;;) { - // check if it's the beginning of a new message - var packageNumber = this.dataBuffer.slice(0, 4); - if (buffertools.compare(packageNumber, magicNumber) == 0) { - this.dataBuffer.skip(i); - return true; - } - - // did we reach the end of the buffer? - if (i > (this.dataBuffer.length - 4)) { - this.dataBuffer.skip(i); - return false; - } - - i++; // continue scanning - } -} - /** * Internal function that sends VERSION message to the remote peer. */ Peer.prototype._sendVersion = function() { - var message = new Message.Version(); - this._sendMessage(message.command, message.serialize()); -}; - -/** - * Send a PING message to the remote peer. - */ -Peer.prototype.sendPing = function(nonce) { - var message = new Message.Pong(nonce); - this._sendMessage(message.command, message.serialize()); + var message = new Messages.Version(); + this.sendMessage(message); }; /** * Send a PONG message to the remote peer. */ -Peer.prototype.sendPong = function(nonce) { - var message = new Message.Pong(nonce); - this._sendMessage(message.command, message.serialize()); +Peer.prototype._sendPong = function(nonce) { + var message = new Messages.Pong(nonce); + this.sendMessage(message); }; /** - * Internal function that sends a message to the remote peer. + * Internal function that tries to read a message from the data buffer */ -Peer.prototype._sendMessage = function(command, payload) { - var magic = this.network.networkMagic; - var commandBuf = new Buffer(command, 'ascii'); - if (commandBuf.length > 12) throw 'Command name too long'; +Peer.prototype._readMessage = function() { + var message = Messages.parseMessage(this.network, this.dataBuffer); - var checksum = Hash.sha256sha256(payload).slice(0, 4); - - // -- HEADER -- - var message = new Put(); - message.put(magic); // magic bytes - message.put(commandBuf); // command name - message.pad(12 - commandBuf.length); // zero-padded - message.word32le(payload.length); // payload length - message.put(checksum); // checksum - - // -- BODY -- - message.put(payload); // payload data - - this.socket.write(message.buffer()); + if (message) { + this.emit(message.command, message); + this._readMessage(); + } }; /** @@ -266,21 +188,4 @@ Peer.prototype._getSocket = function() { return new Net.Socket(); }; - -// 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; -}; - module.exports = Peer; From 30740be5df0b0e7e2bc1344e18dd9dc212b7468a Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 9 Dec 2014 18:03:47 -0300 Subject: [PATCH 08/21] Remove old files --- lib/transport/connection.js | 576 ----------------------------------- lib/transport/peer.js | 56 ---- lib/transport/peermanager.js | 313 ------------------- 3 files changed, 945 deletions(-) delete mode 100644 lib/transport/connection.js delete mode 100644 lib/transport/peer.js delete mode 100644 lib/transport/peermanager.js diff --git a/lib/transport/connection.js b/lib/transport/connection.js deleted file mode 100644 index a7ecd32..0000000 --- a/lib/transport/connection.js +++ /dev/null @@ -1,576 +0,0 @@ -'use strict'; - -var MAX_RECEIVE_BUFFER = 10000000; -var PROTOCOL_VERSION = 70000; - -var util = require('util'); -var Put = require('bufferput'); -var Buffers = require('buffers'); -var buffertools = require('buffertools'); - -// PATCH TODO: Remove (yemel) -Buffers.prototype.skip = function (i) { - if (i == 0) { - return; - } else 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; -}; - -var networks = require('../networks'); -var Block = require('../block'); -var Transaction = require('../transaction'); -var BufferUtil = require('../util/buffer'); -var BufferReader = require('../encoding/bufferreader'); -var Hash = require('../crypto/hash'); -var Random = require('../crypto/random'); -var EventEmitter = require('events').EventEmitter; - -var nonce = Random.getPseudoRandomBuffer(8); - -var BIP0031_VERSION = 60000; - -function Connection(socket, peer, opts) { - this.config = opts || {}; - - this.network = this.config.network || networks.livenet; - this.socket = socket; - this.peer = peer; - - // check for socks5 proxy options and construct a proxied socket - if (this.config.proxy) { - var Socks5Client = require('socks5-client'); - this.socket = new Socks5Client(this.config.proxy.host, this.config.proxy.port); - } - - // A connection is considered "active" once we have received verack - this.active = false; - // The version incoming packages are interpreted as - this.recvVer = 0; - // The version outgoing packages are sent as - this.sendVer = 0; - // The (claimed) height of the remote peer's block chain - this.bestHeight = 0; - // Is this an inbound connection? - this.inbound = !!this.socket.server; - // Have we sent a getaddr on this connection? - this.getaddr = false; - - // Receive buffer - this.buffers = new Buffers(); - - // Starting 20 Feb 2012, Version 0.2 is obsolete - // This is the same behavior as the official client - if (new Date().getTime() > 1329696000000) { - this.recvVer = 209; - this.sendVer = 209; - } - - this.setupHandlers(); -} -util.inherits(Connection, EventEmitter); - -Connection.prototype.open = function(callback) { - if (typeof callback === 'function') this.once('connect', callback); - this.socket.connect(this.peer.port, this.peer.host); - return this; -}; - -Connection.prototype.setupHandlers = function() { - this.socket.addListener('connect', this.handleConnect.bind(this)); - this.socket.addListener('error', this.handleError.bind(this)); - this.socket.addListener('end', this.handleDisconnect.bind(this)); - this.socket.addListener('data', (function(data) { - var dumpLen = 35; - console.debug('[' + this.peer + '] ' + - 'Recieved ' + data.length + ' bytes of data:'); - console.debug('... ' + buffertools.toHex(data.slice(0, dumpLen > data.length ? - data.length : dumpLen)) + - (data.length > dumpLen ? '...' : '')); - }).bind(this)); - this.socket.addListener('data', this.handleData.bind(this)); -}; - -Connection.prototype.handleConnect = function() { - if (!this.inbound) { - this.sendVersion(); - } - this.emit('connect', { - conn: this, - socket: this.socket, - peer: this.peer - }); -}; - -Connection.prototype.handleError = function(err) { - if (err.errno == 110 || err.errno == 'ETIMEDOUT') { - console.info('connection timed out for ' + this.peer); - } else if (err.errno == 111 || err.errno == 'ECONNREFUSED') { - console.info('connection refused for ' + this.peer); - } else { - console.warn('connection with ' + this.peer + ' ' + err.toString()); - } - this.emit('error', { - conn: this, - socket: this.socket, - peer: this.peer, - err: err - }); -}; - -Connection.prototype.handleDisconnect = function() { - this.emit('disconnect', { - conn: this, - socket: this.socket, - peer: this.peer - }); -}; - -Connection.prototype.handleMessage = function(message) { - if (!message) { - // Parser was unable to make sense of the message, drop it - return; - } - - try { - switch (message.command) { - case 'version': - // Did we connect to ourself? - if (buffertools.compare(nonce, message.nonce) === 0) { - this.socket.end(); - return; - } - - if (this.inbound) { - this.sendVersion(); - } - - if (message.version >= 209) { - this.sendMessage('verack', new Buffer([])); - } - this.sendVer = Math.min(message.version, PROTOCOL_VERSION); - if (message.version < 209) { - this.recvVer = Math.min(message.version, PROTOCOL_VERSION); - } else { - // We won't start expecting a checksum until after we've received - // the 'verack' message. - this.once('verack', (function() { - this.recvVer = message.version; - }).bind(this)); - } - this.bestHeight = message.start_height; - break; - - case 'verack': - this.recvVer = Math.min(message.version, PROTOCOL_VERSION); - this.active = true; - break; - - case 'ping': - if ('object' === typeof message.nonce) { - this.sendPong(message.nonce); - } - break; - } - } catch (e) { - console.err('Error while handling "' + message.command + '" message from ' + - this.peer + ':\n' + - (e.stack ? e.stack : e.toString())); - return; - } - this.emit(message.command, { - conn: this, - socket: this.socket, - peer: this.peer, - message: message - }); -}; - -Connection.prototype.sendPong = function(nonce) { - this.sendMessage('pong', nonce); -}; - -Connection.prototype.sendVersion = function() { - var subversion = '/BitcoinX:0.1/'; - - var put = new Put(); - put.word32le(PROTOCOL_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(nonce); - put.varint(subversion.length); - put.put(new Buffer(subversion, 'ascii')); - put.word32le(0); - - this.sendMessage('version', put.buffer()); -}; - -Connection.prototype.sendGetBlocks = function(starts, stop, wantHeaders) { - // Default value for stop is 0 to get as many blocks as possible (500) - stop = stop || BufferUtil.NULL_HASH; - - var put = new Put(); - - // https://en.bitcoin.it/wiki/Protocol_specification#getblocks - put.word32le(this.sendVer); - put.varint(starts.length); - - for (var i = 0; i < starts.length; i++) { - if (starts[i].length != 32) { - throw new Error('Invalid hash length'); - } - - put.put(starts[i]); - } - - var stopBuffer = new Buffer(stop, 'binary'); - if (stopBuffer.length != 32) { - throw new Error('Invalid hash length'); - } - - put.put(stopBuffer); - - var command = 'getblocks'; - if (wantHeaders) - command = 'getheaders'; - this.sendMessage(command, put.buffer()); -}; - -Connection.prototype.sendGetHeaders = function(starts, stop) { - this.sendGetBlocks(starts, stop, true); -}; - -Connection.prototype.sendGetData = function(invs) { - var put = new Put(); - put.varint(invs.length); - for (var i = 0; i < invs.length; i++) { - put.word32le(invs[i].type); - put.put(invs[i].hash); - } - this.sendMessage('getdata', put.buffer()); -}; - -Connection.prototype.sendGetAddr = function(invs) { - var put = new Put(); - this.sendMessage('getaddr', put.buffer()); -}; - -Connection.prototype.sendInv = function(data) { - if (!Array.isArray(data)) data = [data]; - var put = new Put(); - put.varint(data.length); - data.forEach(function(value) { - if (value instanceof Block) { - // Block - put.word32le(2); // MSG_BLOCK - } else { - // Transaction - put.word32le(1); // MSG_TX - } - put.put(value.getHash()); - }); - this.sendMessage('inv', put.buffer()); -}; - -Connection.prototype.sendHeaders = function(headers) { - var put = new Put(); - put.varint(headers.length); - headers.forEach(function(header) { - put.put(header); - - // Indicate 0 transactions - put.word8(0); - }); - this.sendMessage('headers', put.buffer()); -}; - -Connection.prototype.sendTx = function(tx) { - this.sendMessage('tx', tx.serialize()); -}; - -Connection.prototype.sendBlock = function(block, txs) { - var put = new Put(); - - // Block header - put.put(block.getHeader()); - - // List of transactions - put.varint(txs.length); - txs.forEach(function(tx) { - put.put(tx.serialize()); - }); - - this.sendMessage('block', put.buffer()); -}; - -Connection.prototype.sendMessage = function(command, payload) { - try { - var magic = this.network.magic; - var commandBuf = new Buffer(command, 'ascii'); - if (commandBuf.length > 12) throw 'Command name too long'; - - var checksum; - if (this.sendVer >= 209) { - checksum = Hash.sha256sha256(payload).slice(0, 4); - } else { - checksum = new Buffer([]); - } - - var message = new Put(); // -- HEADER -- - message.put(magic); // magic bytes - message.put(commandBuf); // command name - message.pad(12 - commandBuf.length); // zero-padded - message.word32le(payload.length); // payload length - message.put(checksum); // checksum - // -- BODY -- - message.put(payload); // payload data - - var buffer = message.buffer(); - - console.debug('[' + this.peer + '] ' + - 'Sending message ' + command + ' (' + payload.length + ' bytes)'); - - this.socket.write(buffer); - } catch (err) { - // TODO: We should catch this error one level higher in order to better - // determine how to react to it. For now though, ignoring it will do. - console.err('Error while sending message to peer ' + this.peer + ': ' + - (err.stack ? err.stack : err.toString())); - } -}; - -Connection.prototype.handleData = function(data) { - this.buffers.push(data); - - if (this.buffers.length > MAX_RECEIVE_BUFFER) { - console.err('Peer ' + this.peer + ' exceeded maxreceivebuffer, disconnecting.' + - (err.stack ? err.stack : err.toString())); - this.socket.destroy(); - return; - } - - this.processData(); -}; - -Connection.prototype.processData = function() { - // If there are less than 20 bytes there can't be a message yet. - if (this.buffers.length < 20) return; - - var magic = this.network.magic; - var i = 0; - for (;;) { - if (this.buffers.get(i) === magic[0] && - this.buffers.get(i + 1) === magic[1] && - this.buffers.get(i + 2) === magic[2] && - this.buffers.get(i + 3) === magic[3]) { - if (i !== 0) { - console.debug('[' + this.peer + '] ' + - 'Received ' + i + - ' bytes of inter-message garbage: '); - console.debug('... ' + this.buffers.slice(0, i)); - - this.buffers.skip(i); - } - break; - } - - if (i > (this.buffers.length - 4)) { - this.buffers.skip(i); - return; - } - i++; - } - - var payloadLen = (this.buffers.get(16)) + - (this.buffers.get(17) << 8) + - (this.buffers.get(18) << 16) + - (this.buffers.get(19) << 24); - - var startPos = (this.recvVer >= 209) ? 24 : 20; - var endPos = startPos + payloadLen; - - if (this.buffers.length < endPos) return; - - var command = this.buffers.slice(4, 16).toString('ascii').replace(/\0+$/, ''); - var payload = this.buffers.slice(startPos, endPos); - var checksum = (this.recvVer >= 209) ? this.buffers.slice(20, 24) : null; - - console.debug('[' + this.peer + '] ' + - 'Received message ' + command + - ' (' + payloadLen + ' bytes)'); - - if (checksum !== null) { - var checksumConfirm = Hash.sha256sha256(payload).slice(0, 4); - if (buffertools.compare(checksumConfirm, checksum) !== 0) { - console.err('[' + this.peer + '] ' + - 'Checksum failed', { - cmd: command, - expected: checksumConfirm.toString('hex'), - actual: checksum.toString('hex') - }); - return; - } - } - - var message; - try { - message = this.parseMessage(command, payload); - } catch (e) { - console.err('Error while parsing message ' + command + ' from ' + - this.peer + ':\n' + - (e.stack ? e.stack : e.toString())); - } - - if (message) { - this.handleMessage(message); - } - - this.buffers.skip(endPos); - this.processData(); -}; - -Connection.prototype.parseMessage = function(command, payload) { - var parser = new BufferReader(payload); - - var data = { - command: command - }; - - var i; - - switch (command) { - case 'version': // https://en.bitcoin.it/wiki/Protocol_specification#version - data.version = parser.readUInt32LE(); - data.services = parser.readUInt64LEBN(); - data.timestamp = parser.readUInt64LEBN(); - data.addr_me = parser.read(26); - data.addr_you = parser.read(26); - data.nonce = parser.read(8); - data.subversion = parser.readVarintBuf(); - data.start_height = parser.readUInt32LE(); - break; - - case 'inv': - case 'getdata': - data.count = parser.readVarintNum(); - - data.invs = []; - for (i = 0; i < data.count; i++) { - data.invs.push({ - type: parser.readUInt32LE(), - hash: parser.read(32) - }); - } - break; - - case 'headers': - data.count = parser.readVarintNum(); - - data.headers = []; - for (i = 0; i < data.count; i++) { - var header = Block.fromBufferReader(parser); - data.headers.push(header); - } - break; - - case 'block': - var block = Block.fromBufferReader(parser); - - data.block = block; - data.version = block.version; - data.prev_hash = block.prev_hash; - data.merkle_root = block.merkle_root; - data.timestamp = block.timestamp; - data.bits = block.bits; - data.nonce = block.nonce; - - data.txs = block.txs; - - data.size = payload.length; - break; - - case 'tx': - var tx = Transaction.fromBufferReader(parser); - return { - command: command, - version: tx.version, - lock_time: tx.lock_time, - ins: tx.ins, - outs: tx.outs, - tx: tx, - }; - - case 'getblocks': - case 'getheaders': - // parse out the version - data.version = parser.readUInt32LE(); - - // TODO: Limit block locator size? - // reference implementation limits to 500 results - var startCount = parser.readVarintNum(); - - data.starts = []; - for (i = 0; i < startCount; i++) { - data.starts.push(parser.read(32)); - } - data.stop = parser.read(32); - break; - - case 'addr': - var addrCount = parser.readVarintNum(); - - // Enforce a maximum number of addresses per message - if (addrCount > 1000) { - addrCount = 1000; - } - - data.addrs = []; - for (i = 0; i < addrCount; i++) { - // TODO: Time actually depends on the version of the other peer (>=31402) - data.addrs.push({ - time: parser.readUInt32LE(), - services: parser.readUInt64LEBN(), - ip: parser.read(16), - port: parser.readUInt16BE() - }); - } - break; - - case 'alert': - data.payload = parser.readVarintBuf(); - data.signature = parser.readVarintBuf(); - break; - - case 'ping': - if (this.recvVer > BIP0031_VERSION) { - data.nonce = parser.read(8); - } - break; - - case 'getaddr': - case 'verack': - case 'reject': - // Empty message, nothing to parse - break; - - default: - console.err('Connection.parseMessage(): Command not implemented', { - cmd: command - }); - - // This tells the calling function not to issue an event - return null; - } - - return data; -}; - -module.exports = Connection; \ No newline at end of file diff --git a/lib/transport/peer.js b/lib/transport/peer.js deleted file mode 100644 index 6547c77..0000000 --- a/lib/transport/peer.js +++ /dev/null @@ -1,56 +0,0 @@ -var Net = require('net'); -var Put = require('bufferput'); -var buffertools = require('buffertools'); - -function Peer(host, port, services) { - if ("string" === typeof host) { - if (host.indexOf(':') && !port) { - var parts = host.split(':'); - host = parts[0]; - port = parts[1]; - } - this.host = host; - this.port = +port || 8333; - } else if (host instanceof Peer) { - this.host = host.host; - this.port = host.port; - } else if (Buffer.isBuffer(host)) { - if (buffertools.compare(Peer.IPV6_IPV4_PADDING, host.slice(0, 12)) != 0) { - throw new Error('IPV6 not supported yet! Cannot instantiate host.'); - } - this.host = Array.prototype.slice.apply(host.slice(12)).join('.'); - this.port = +port || 8333; - } else { - throw new Error('Could not instantiate peer, invalid parameter type: ' + - typeof host); - } - - this.services = (services) ? services : null; - this.lastSeen = 0; -}; - -Peer.IPV6_IPV4_PADDING = new Buffer([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255]); - -Peer.prototype.createConnection = function() { - this.connection = Net.createConnection(this.port, this.host); - return this.connection; -}; - -Peer.prototype.getHostAsBuffer = function() { - return new Buffer(this.host.split('.')); -}; - -Peer.prototype.toString = function() { - return this.host + ":" + this.port; -}; - -Peer.prototype.toBuffer = function() { - var put = new Put(); - put.word32le(this.lastSeen); - put.word64le(this.services); - put.put(this.getHostAsBuffer()); - put.word16be(this.port); - return put.buffer(); -}; - -module.exports = Peer; \ No newline at end of file diff --git a/lib/transport/peermanager.js b/lib/transport/peermanager.js deleted file mode 100644 index fc3e403..0000000 --- a/lib/transport/peermanager.js +++ /dev/null @@ -1,313 +0,0 @@ -'use strict'; - -var dns = require('dns'); -var util = require('util'); -var async = require('async'); - -var Connection = require('./connection'); -var Peer = require('./peer'); -var networks = require('../networks'); - -var GetAdjustedTime = function() { - // TODO: Implement actual adjustment - return Math.floor(new Date().getTime() / 1000); -}; - -function PeerManager(config) { - // extend defaults with config - this.config = config || {}; - this.config.network = this.config.network || networks.livenet; - - this.active = false; - this.timer = null; - - this.peers = []; - this.pool = []; - this.connections = []; - this.isConnected = false; - this.peerDiscovery = false; - - // Move these to the Node's settings object - this.interval = 5000; - this.minConnections = 8; - this.minKnownPeers = 10; - - // keep track of tried seeds and results - this.seeds = { - resolved: [], - failed: [] - }; -} - -var EventEmitter = require('events').EventEmitter; -util.inherits(PeerManager, EventEmitter); -PeerManager.Connection = Connection; - -PeerManager.prototype.start = function() { - this.active = true; - if (!this.timer) { - this.timer = setInterval(this.checkStatus.bind(this), this.interval); - } -}; - -PeerManager.prototype.stop = function() { - this.active = false; - if (this.timer) { - clearInterval(this.timer); - this.timer = null; - } - for (var i = 0; i < this.connections.length; i++) { - this.connections[i].socket.end(); - }; -}; - -PeerManager.prototype.addPeer = function(peer, port) { - if (peer instanceof Peer) { - this.peers.push(peer); - } else if ("string" == typeof peer) { - this.addPeer(new Peer(peer, port)); - } else { - console.error('Node.addPeer(): Invalid value provided for peer', { - val: peer - }); - throw 'Node.addPeer(): Invalid value provided for peer.'; - } -}; - -PeerManager.prototype.removePeer = function(peer) { - var index = this.peers.indexOf(peer); - var exists = !!~index; - if (exists) this.peers.splice(index, 1); - return exists; -}; - -PeerManager.prototype.checkStatus = function checkStatus() { - // Make sure we are connected to all forcePeers - if (this.peers.length) { - var peerIndex = {}; - this.peers.forEach(function(peer) { - peerIndex[peer.toString()] = peer; - }); - - // Ignore the ones we're already connected to - this.connections.forEach(function(conn) { - var peerName = conn.peer.toString(); - if ("undefined" !== peerIndex[peerName]) { - delete peerIndex[peerName]; - } - }); - - // for debug purposes, print how many of our peers are actually connected - var connected = 0 - this.peers.forEach(function(p) { - if (p.connection && !p.connection._connecting) connected++ - }); - console.debug(connected + ' of ' + this.peers.length + ' peers connected'); - - Object.keys(peerIndex).forEach(function(i) { - this.connectTo(peerIndex[i]); - }.bind(this)); - } -}; - -PeerManager.prototype.connectTo = function(peer) { - console.info('connecting to ' + peer); - try { - return this.addConnection(peer.createConnection(), peer); - } catch (e) { - console.error('creating connection', e); - return null; - } -}; - -PeerManager.prototype.addConnection = function(socketConn, peer) { - var conn = new Connection(socketConn, peer, this.config); - this.connections.push(conn); - this.emit('connection', conn); - - conn.addListener('version', this.handleVersion.bind(this)); - conn.addListener('verack', this.handleReady.bind(this)); - conn.addListener('addr', this.handleAddr.bind(this)); - conn.addListener('getaddr', this.handleGetAddr.bind(this)); - conn.addListener('error', this.handleError.bind(this)); - conn.addListener('disconnect', this.handleDisconnect.bind(this)); - - return conn; -}; - -PeerManager.prototype.handleVersion = function(e) { - e.peer.version = e.message.version; - e.peer.start_height = e.message.start_height; - - if (!e.conn.inbound) { - // TODO: Advertise our address (if listening) - } - // Get recent addresses - if (this.peerDiscovery && - (e.message.version >= 31402 || this.peers.length < 1000)) { - e.conn.sendGetAddr(); - e.conn.getaddr = true; - } -}; - -PeerManager.prototype.handleReady = function(e) { - console.info('connected to ' + e.conn.peer.host + ':' + e.conn.peer.port); - this.emit('connect', { - pm: this, - conn: e.conn, - socket: e.socket, - peer: e.peer - }); - - if (this.isConnected == false) { - this.emit('netConnected', e); - this.isConnected = true; - } -}; - -PeerManager.prototype.handleAddr = function(e) { - if (!this.peerDiscovery) return; - - var now = GetAdjustedTime(); - e.message.addrs.forEach(function(addr) { - try { - // 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; - } - var peer = new Peer(addr.ip, addr.port, addr.services); - peer.lastSeen = addr.time; - - // TODO: Handle duplicate peers - this.peers.push(peer); - - // TODO: Handle addr relay - } catch (e) { - console.warn("Invalid addr received: " + e.message); - } - }.bind(this)); - if (e.message.addrs.length < 1000) { - e.conn.getaddr = false; - } -}; - -PeerManager.prototype.handleGetAddr = function(e) { - // TODO: Reply with addr message. -}; - -PeerManager.prototype.handleError = function(e) { - console.error('unkown error with peer ' + e.peer + ' (disconnecting): ' + e.err); - this.handleDisconnect.apply(this, [].slice.call(arguments)); -}; - -PeerManager.prototype.handleDisconnect = function(e) { - console.info('disconnected from peer ' + e.peer); - var i = this.connections.indexOf(e.conn); - if (i != -1) this.connections.splice(i, 1); - - this.removePeer(e.peer); - if (this.pool.length) { - console.info('replacing peer using the pool of ' + this.pool.length + ' seeds'); - this.addPeer(this.pool.pop()); - } - - if (!this.connections.length) { - this.emit('netDisconnected'); - this.isConnected = false; - } -}; - -PeerManager.prototype.getActiveConnection = function() { - var activeConnections = this.connections.filter(function(conn) { - return conn.active; - }); - - if (activeConnections.length) { - var randomIndex = Math.floor(Math.random() * activeConnections.length); - var candidate = activeConnections[randomIndex]; - if (candidate.socket.writable) { - return candidate; - } else { - // Socket is not writable, remove it from active connections - activeConnections.splice(randomIndex, 1); - - // Then try again - // TODO: This causes an infinite recursion when all connections are dead, - // although it shouldn't. - return this.getActiveConnection(); - } - } else { - return null; - } -}; - -PeerManager.prototype.getActiveConnections = function() { - return this.connections.slice(0); -}; - -PeerManager.prototype.discover = function(options, callback) { - var self = this; - var seeds = this.config.network.dnsSeeds; - - self.limit = options.limit || 12; - - var dnsExecutor = seeds.map(function(seed) { - return function(done) { - // have we already resolved this seed? - if (~self.seeds.resolved.indexOf(seed)) { - // if so, just pass back cached peer list - return done(null, self.seeds.results[seed]); - } - - // has this seed failed to resolve? - if (~self.seeds.failed.indexOf(seed)) { - // if so, pass back empty results - return done(null, []); - } - - console.info('resolving dns seed ' + seed); - - dns.resolve(seed, function(err, peers) { - if (err) { - console.error('failed to resolve dns seed ' + seed, err); - self.seeds.failed.push(seed); - return done(null, []); - } - - console.info('found ' + peers.length + ' peers from ' + seed); - self.seeds.resolved.push(seed); - - // transform that list into a list of Peer instances - peers = peers.map(function(ip) { - return new Peer(ip, self.config.network.port); - }); - - peers.forEach(function(p) { - if (self.peers.length < self.limit) self.addPeer(p); - else self.pool.push(p); - }); - - self.emit('peers', peers); - - return done(null, peers); - }); - - }; - }); - - // try resolving all seeds - async.parallel(dnsExecutor, function(err, results) { - var peers = []; - - // consolidate all resolved peers into one list - results.forEach(function(peerlist) { - peers = peers.concat(peerlist); - }); - - if (typeof callback === 'function') callback(null, peers); - }); - - return self; -}; - -module.exports = PeerManager; \ No newline at end of file From 9ce450c142087dc35f144a3bb20065db8329c01a Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 9 Dec 2014 18:25:49 -0300 Subject: [PATCH 09/21] Remove old tests --- index.js | 4 +- lib/transport/{message.js => messages.js} | 1 + lib/transport/{peer2.js => peer.js} | 3 +- test/transport/connection.js | 38 ----------------- test/transport/peer.js | 52 ----------------------- test/transport/peermanager.js | 42 ------------------ 6 files changed, 3 insertions(+), 137 deletions(-) rename lib/transport/{message.js => messages.js} (99%) rename lib/transport/{peer2.js => peer.js} (98%) delete mode 100644 test/transport/connection.js delete mode 100644 test/transport/peer.js delete mode 100644 test/transport/peermanager.js diff --git a/index.js b/index.js index 03a492e..b071aff 100644 --- a/index.js +++ b/index.js @@ -26,10 +26,8 @@ bitcore.util.js = require('./lib/util/js'); // transport bitcore.transport = {}; -bitcore.transport.Connection = require('./lib/transport/connection'); bitcore.transport.Peer = require('./lib/transport/peer'); -bitcore.transport.Peer2 = require('./lib/transport/peer2'); -bitcore.transport.PeerManager = require('./lib/transport/peermanager'); +bitcore.transport.Messages = require('./lib/transport/messages'); // errors thrown by the library bitcore.errors = require('./lib/errors'); diff --git a/lib/transport/message.js b/lib/transport/messages.js similarity index 99% rename from lib/transport/message.js rename to lib/transport/messages.js index 4aaec14..26bb39e 100644 --- a/lib/transport/message.js +++ b/lib/transport/messages.js @@ -10,6 +10,7 @@ var BufferReader = require('../encoding/bufferreader'); var BufferUtil = require('../util/buffer'); var Hash = require('../crypto/hash'); var Random = require('../crypto/random'); +var Transaction = require('../transaction'); var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8); var PROTOCOL_VERSION = 70000; diff --git a/lib/transport/peer2.js b/lib/transport/peer.js similarity index 98% rename from lib/transport/peer2.js rename to lib/transport/peer.js index 3b38bc1..bdb03a2 100644 --- a/lib/transport/peer2.js +++ b/lib/transport/peer.js @@ -3,12 +3,11 @@ var Buffers = require('buffers'); var EventEmitter = require('events').EventEmitter; var Net = require('net'); -var Put = require('bufferput'); var Socks5Client = require('socks5-client'); var util = require('util'); var Networks = require('../networks'); -var Messages = require('./message'); +var Messages = require('./messages'); var MAX_RECEIVE_BUFFER = 10000000; diff --git a/test/transport/connection.js b/test/transport/connection.js deleted file mode 100644 index 9826fa0..0000000 --- a/test/transport/connection.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -var chai = require('chai'); -var bitcore = require('../..'); - -var should = chai.should(); - -var ConnectionModule = bitcore.transport.Connection; -var Connection; -var nop = function() {}; - -describe('Connection', function() { - it('should initialze the main object', function() { - should.exist(ConnectionModule); - }); - - it('should be able to create class', function() { - Connection = ConnectionModule; - should.exist(Connection); - }); - - it('should be able to create instance', function() { - var mSocket = {server: null, addListener: nop}, - mPeer; - var c = new Connection(mSocket, mPeer); - should.exist(c); - }); - - if (typeof process !== 'undefined' && process.versions) { //node-only tests - it('should create a proxied socket if instructed', function() { - var mPeer; - var c = new Connection(null, mPeer, { - proxy: { host: 'localhost', port: 9050 } - }); - should.exist(c.socket); - }); - }; -}); diff --git a/test/transport/peer.js b/test/transport/peer.js deleted file mode 100644 index b23c63b..0000000 --- a/test/transport/peer.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -var chai = require('chai'); -var bitcore = require('../..'); - -var should = chai.should(); - -var PeerModule = bitcore.transport.Peer; -var Peer; - -describe('Peer', function() { - it('should initialze the main object', function() { - should.exist(PeerModule); - }); - - it('should be able to create class', function() { - Peer = PeerModule; - should.exist(Peer); - }); - - it('should be able to create instance', function() { - var p = new Peer('localhost', 8333); - should.exist(p); - }); - - it('should be able to create instance', function() { - var p = new Peer('localhost:8333'); - should.exist(p); - }); - - it('should be able to create instance', function() { - var p = new Peer('localhost:8333'); - var p2 = new Peer(p); - should.exist(p2); - }); - - it('should not be able to create instance', function() { - should.throw(function() { - new Peer(8333); - }); - }); - - it('should be able to create instance', function() { - var p = new Peer('localhost', 8333); - p.toString().should.equal('localhost:8333'); - }); - - it('check host as buffer', function() { - var p = new Peer('127.0.0.1', 8333); - p.getHostAsBuffer().toString('hex').should.equal('7f000001'); - }); -}); diff --git a/test/transport/peermanager.js b/test/transport/peermanager.js deleted file mode 100644 index 60a13ee..0000000 --- a/test/transport/peermanager.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -var chai = require('chai'); -var bitcore = require('../..'); - -var should = chai.should(); - -var PeerManager = bitcore.transport.PeerManager; - -describe('PeerManager', function() { - it('should be able to create class', function() { - should.exist(PeerManager); - }); - - it('should be able to create instance', function() { - var pm = new PeerManager(); - should.exist(pm); - }); - - it('should be able to start instance', function() { - var pm = new PeerManager(); - pm.start.bind(pm).should.not.throw(); - }); - - it('should be able to stop instance', function() { - var pm = new PeerManager(); - pm.start(); - pm.stop.bind(pm).should.not.throw(); - }); - - it('should extend default config with passed config', function() { - var pm = new PeerManager({ - proxy: { - host: 'localhost', - port: 9050 - } - }); - - should.exist(pm.config.network); - should.exist(pm.config.proxy); - }); -}); From 44eca35af91c47a5d2709346da211683a90c3933 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 9 Dec 2014 18:26:54 -0300 Subject: [PATCH 10/21] rename peer tests --- test/transport/{peer2.js => peer.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/transport/{peer2.js => peer.js} (100%) diff --git a/test/transport/peer2.js b/test/transport/peer.js similarity index 100% rename from test/transport/peer2.js rename to test/transport/peer.js From 56907c1a3be5a40e730d2e9967b89c791c48a570 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Wed, 10 Dec 2014 10:13:07 -0300 Subject: [PATCH 11/21] Add missing argument --- lib/transport/messages.js | 14 +++++++------- test/transport/peer.js | 10 ++-------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/lib/transport/messages.js b/lib/transport/messages.js index 26bb39e..ff1ca14 100644 --- a/lib/transport/messages.js +++ b/lib/transport/messages.js @@ -153,7 +153,7 @@ function Version(subversion, nonce) { this.version = PROTOCOL_VERSION; this.subversion = subversion || '/BitcoinX:0.1/'; this.nonce = nonce || CONNECTION_NONCE; -}; +} util.inherits(Version, Message); Version.prototype.fromBuffer = function(payload) { @@ -352,7 +352,7 @@ function Alert() { } util.inherits(Alert, Message); -Alert.prototype.fromBuffer = function() { +Alert.prototype.fromBuffer = function(payload) { var parser = new BufferReader(payload); this.payload = parser.readVarintBuf(); // TODO: Use current format this.signature = parser.readVarintBuf(); @@ -376,12 +376,12 @@ function Headers(blockheaders) { } util.inherits(Headers, Message); -Headers.prototype.fromBuffer = function() { +Headers.prototype.fromBuffer = function(payload) { var parser = new BufferReader(payload); var count = parser.readVarintNum(); this.headers = []; - for (i = 0; i < count; i++) { + for (var i = 0; i < count; i++) { var header = Block().fromBufferReader(parser); this.headers.push(header); } @@ -406,7 +406,7 @@ function Block(block) { } util.inherits(Block, Message); -Block.prototype.fromBuffer = function() { +Block.prototype.fromBuffer = function(payload) { var parser = new BufferReader(payload); this.block = Block().fromBufferReader(parser); return this; @@ -429,7 +429,7 @@ function Transaction(transaction) { } util.inherits(Transaction, Message); -Transaction.prototype.fromBuffer = function() { +Transaction.prototype.fromBuffer = function(payload) { var parser = new BufferReader(payload); this.transaction = Transaction().fromBufferReader(parser); return this; @@ -455,7 +455,7 @@ function GetBlocks(starts, stop) { } util.inherits(GetBlocks, Message); -GetBlocks.prototype.fromBuffer = function() { +GetBlocks.prototype.fromBuffer = function(payload) { var parser = new BufferReader(payload); this.version = parser.readUInt32LE(); diff --git a/test/transport/peer.js b/test/transport/peer.js index 2f23362..8a832c0 100644 --- a/test/transport/peer.js +++ b/test/transport/peer.js @@ -8,10 +8,10 @@ var should = chai.should(); var expect = chai.expect; var bitcore = require('../..'); -var Peer = bitcore.transport.Peer2; +var Peer = bitcore.transport.Peer; var Networks = bitcore.Networks; -describe.only('Peer2', function() { +describe('Peer', function() { it('should be able to create instance', function() { var peer = new Peer('localhost'); @@ -64,10 +64,4 @@ describe.only('Peer2', function() { peer.should.equal(peer2); }); - - it('should create connection', function() { - var peer = new Peer('localhost').connect(); - }); - - }); From 2d4b26a6a141a50e3362b2022e32cf3d1f039131 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Wed, 10 Dec 2014 11:28:27 -0300 Subject: [PATCH 12/21] Add missing serializations --- lib/transport/messages.js | 50 +++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/lib/transport/messages.js b/lib/transport/messages.js index ff1ca14..1998cce 100644 --- a/lib/transport/messages.js +++ b/lib/transport/messages.js @@ -5,12 +5,13 @@ var buffertools = require('buffertools'); var Put = require('bufferput'); var util = require('util'); -var Block = require('../block'); +var BlockHeaderModel = require('../blockheader'); +var BlockModel = require('../block'); var BufferReader = require('../encoding/bufferreader'); var BufferUtil = require('../util/buffer'); var Hash = require('../crypto/hash'); var Random = require('../crypto/random'); -var Transaction = require('../transaction'); +var TransactionModel = require('../transaction'); var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8); var PROTOCOL_VERSION = 70000; @@ -303,7 +304,17 @@ Addresses.prototype.fromBuffer = function(payload) { }; Addresses.prototype.getPayload = function() { - return BufferUtil.EMPTY_BUFFER; // TODO + 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; @@ -360,7 +371,14 @@ Alert.prototype.fromBuffer = function(payload) { }; Alert.prototype.getPayload = function() { - return BufferUtil.EMPTY_BUFFER; // TODO: Serialize + 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; @@ -382,7 +400,7 @@ Headers.prototype.fromBuffer = function(payload) { this.headers = []; for (var i = 0; i < count; i++) { - var header = Block().fromBufferReader(parser); + var header = BlockHeaderModel._fromBufferReader(parser); this.headers.push(header); } @@ -390,7 +408,15 @@ Headers.prototype.fromBuffer = function(payload) { }; Headers.prototype.getPayload = function() { - return BufferUtil.EMPTY_BUFFER; // TODO: Serialize + 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; @@ -407,13 +433,12 @@ function Block(block) { util.inherits(Block, Message); Block.prototype.fromBuffer = function(payload) { - var parser = new BufferReader(payload); - this.block = Block().fromBufferReader(parser); + this.block = BlockModel(payload); return this; }; Block.prototype.getPayload = function() { - return BufferUtil.EMPTY_BUFFER; // TODO: Serialize + return this.block.toBuffer(); }; module.exports.Block = Message.COMMANDS['block'] = Block; @@ -430,13 +455,12 @@ function Transaction(transaction) { util.inherits(Transaction, Message); Transaction.prototype.fromBuffer = function(payload) { - var parser = new BufferReader(payload); - this.transaction = Transaction().fromBufferReader(parser); + this.transaction = TransactionModel(payload); return this; }; Transaction.prototype.getPayload = function() { - return BufferUtil.EMPTY_BUFFER; // TODO: Serialize + return this.transaction.toBuffer(); }; module.exports.Transaction = Message.COMMANDS['tx'] = Transaction; @@ -474,7 +498,7 @@ GetBlocks.prototype.getPayload = function() { put.word32le(this.version); put.varint(this.starts.length); - for (var i = 0; i < starts.length; i++) { + for (var i = 0; i < this.starts.length; i++) { if (this.starts[i].length != 32) { throw new Error('Invalid hash length'); } From 228e7e214a0ee87724d6a8758b5a81dc11d7871b Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Wed, 10 Dec 2014 17:43:56 -0300 Subject: [PATCH 13/21] Add tests --- lib/transport/messages.js | 15 +- test/transport/messages.js | 380 +++++++++++++++++++++++++++++++++++++ 2 files changed, 390 insertions(+), 5 deletions(-) create mode 100644 test/transport/messages.js diff --git a/lib/transport/messages.js b/lib/transport/messages.js index 1998cce..dbb3f12 100644 --- a/lib/transport/messages.js +++ b/lib/transport/messages.js @@ -48,6 +48,9 @@ var parseMessage = function(network, dataBuffer) { return; } + console.log(command, 'FULL MESSAGE', dataBuffer.slice(0, messageLength).toString('hex')); + console.log(command, 'PAYLOAD MESSAGE', payload.toString('hex')); + dataBuffer.skip(messageLength); return Message.buildMessage(command, payload); }; @@ -165,7 +168,7 @@ Version.prototype.fromBuffer = function(payload) { this.addr_me = parser.read(26); this.addr_you = parser.read(26); this.nonce = parser.read(8); - this.subversion = parser.readVarintBuf(); + this.subversion = parser.readVarintBuf().toString(); this.start_height = parser.readUInt32LE(); return this; @@ -217,8 +220,8 @@ Inventory.prototype.getPayload = function() { put.varint(this.inventory.length); this.inventory.forEach(function(value) { - value instanceof Block ? put.word32le(2) : put.word32le(1); - put.put(value.getHash()); + put.word32le(value.type); + put.put(value.hash); }); return put.buffer(); @@ -272,7 +275,7 @@ function Pong(nonce) { } util.inherits(Pong, Ping); -module.exports.Pong = Pong; +module.exports.Pong = Message.COMMANDS['pong'] = Pong; /** * Addr Message @@ -358,8 +361,10 @@ module.exports.Reject = Message.COMMANDS['reject'] = Reject; * Alert Message * */ -function Alert() { +function Alert(payload, signature) { this.command = 'alert'; + this.payload = payload || new Buffer(32); + this.signature = signature || new Buffer(32); } util.inherits(Alert, Message); diff --git a/test/transport/messages.js b/test/transport/messages.js new file mode 100644 index 0000000..50fbced --- /dev/null +++ b/test/transport/messages.js @@ -0,0 +1,380 @@ +'use strict'; + +var chai = require('chai'); +var Net = require('net'); +var Socks5Client = require('socks5-client'); + +var should = chai.should(); +var expect = chai.expect; + +var bitcore = require('../..'); +var Messages = bitcore.transport.Messages; +var Networks = bitcore.Networks; + +var VERSION = { + message: 'f9beb4d976657273696f6e000000000065000000fc970f17721101000100000000000000ba62885400000000010000000000000000000000000000000000ffffba8886dceab0010000000000000000000000000000000000ffff05095522208de7e1c1ef80a1cea70f2f5361746f7368693a302e392e312fa317050001', + payload: '721101000100000000000000ba62885400000000010000000000000000000000000000000000ffffba8886dceab0010000000000000000000000000000000000ffff05095522208de7e1c1ef80a1cea70f2f5361746f7368693a302e392e312fa317050001' +}; + +var VERACK = { + message: 'f9beb4d976657261636b000000000000000000005df6e0e2', + payload: '' +}; + +var INV = { + message: 'f9beb4d9696e76000000000000000000890200006e4f18431201000000f97347795bf7490ddeba98c129086f54e06633936a49a2a398defb49e5edbb00010000009cb17394db9280b2d5e7d9f01456358384e7453300d48138ca1590e8cd86632f010000000f810b6071b9808117c4b82619b4ae0a7994ed5a0f2c050e50fd5742c3b4e90901000000962798bce9cbfcee9a8c6b5df4cf8bed944eb31ec975e8c1be79b7ab010a1cb501000000baa905bcfc9d2641ecd759724f796a7614e91c7492507b418ba8e077a6e57054010000009a5ac6dae146392edf8b6e96c30951ed7752f9095fab61728207db0f135b18a30100000050e5f153e3f0d89ea432d9a26f088a89b3ca38dbf3f0247ddf15f80779915da801000000c255841510a284b88ba696ed9edfeb4cdb34966271c30bc3bce9fd881106850f0100000000e45af8455894f72cccc4053e1f5e6076a673982b1f4ca5b2abc7b6496823ab01000000c7e6dc049e5b0fdc0f8fac8934e84e9b2f0c3036c7c02638cc05ee2575d6be1701000000aef6435c03c99ee83702aaaf106dc853dee5bcb6025f3af67d1ec72202c437ad010000005369fc3dc81403fe52f766ef585245af908404be1b46e2ef67c93b748ef467e3010000002400f16a63c411ae5e336175afc26515e548c7267c0debdb6aa46830399ba35f01000000ceb4fa6a8ca2713baee2214422b73da47a20934c83d25017bd80053e65ef3091010000000d899968e591703ddd6fd6ce073837588209f8b3b245ecf1bf93e77b9806a6c501000000bddeb56581ad7882d8f2abc78e5a48b3de02d923cd1cfc7525d2dfe80470248a01000000f286188c0947b023f0ba5dd1ea596751d50f67fbe31e64ead39dd711d3585b5801000000e17ad100ebffd2d5a630d37fd2496b2f5ab6ae5c8812da3c642fb6b8dd37f5fd', + payload: '1201000000f97347795bf7490ddeba98c129086f54e06633936a49a2a398defb49e5edbb00010000009cb17394db9280b2d5e7d9f01456358384e7453300d48138ca1590e8cd86632f010000000f810b6071b9808117c4b82619b4ae0a7994ed5a0f2c050e50fd5742c3b4e90901000000962798bce9cbfcee9a8c6b5df4cf8bed944eb31ec975e8c1be79b7ab010a1cb501000000baa905bcfc9d2641ecd759724f796a7614e91c7492507b418ba8e077a6e57054010000009a5ac6dae146392edf8b6e96c30951ed7752f9095fab61728207db0f135b18a30100000050e5f153e3f0d89ea432d9a26f088a89b3ca38dbf3f0247ddf15f80779915da801000000c255841510a284b88ba696ed9edfeb4cdb34966271c30bc3bce9fd881106850f0100000000e45af8455894f72cccc4053e1f5e6076a673982b1f4ca5b2abc7b6496823ab01000000c7e6dc049e5b0fdc0f8fac8934e84e9b2f0c3036c7c02638cc05ee2575d6be1701000000aef6435c03c99ee83702aaaf106dc853dee5bcb6025f3af67d1ec72202c437ad010000005369fc3dc81403fe52f766ef585245af908404be1b46e2ef67c93b748ef467e3010000002400f16a63c411ae5e336175afc26515e548c7267c0debdb6aa46830399ba35f01000000ceb4fa6a8ca2713baee2214422b73da47a20934c83d25017bd80053e65ef3091010000000d899968e591703ddd6fd6ce073837588209f8b3b245ecf1bf93e77b9806a6c501000000bddeb56581ad7882d8f2abc78e5a48b3de02d923cd1cfc7525d2dfe80470248a01000000f286188c0947b023f0ba5dd1ea596751d50f67fbe31e64ead39dd711d3585b5801000000e17ad100ebffd2d5a630d37fd2496b2f5ab6ae5c8812da3c642fb6b8dd37f5fd' +}; + +var ADDR = { + message: 'f9beb4d9616464720000000000000000b93a0000480bab8afdf5016816fa53010000000000000000000000000000000000ffff51403eea208ddb2a8854010000000000000000000000000000000000ffff707c60d9208d31413a54010000000000000000000000000000000000ffff5ed5fd13208d182c8854010000000000000000000000000000000000ffff505f3f81208d65ecb853010000000000000000000000000000000000ffff54d72104208dcca94054010000000000000000000000000000000000ffff40bbe1f2208d73238854010000000000000000000000000000000000ffff55d683cd208d5d258854010000000000000000000000000000000000ffffd523a66d208dcbea87540100000000000000200100005ef579fd2c9d47be4b56d671208db3e58754010000000000000000000000000000000000ffffae373156208d11e58754810000000000000000000000000000000000ffff51071276208d38f48754010000000000000000000000000000000000ffff6d4372e0208d4f448854010000000000000000000000000000000000ffffc556cbea208deb208854010000000000000000000000000000000000ffff450eb059208d97336054010000000000000000000000000000000000ffff4d60444a208dad608754010000000000000000000000000000000000ffff01abd3c3208d2cbe8754010000000000000000000000000000000000ffff7963d8b8208d9dfa8754010000000000000000000000000000000000ffffcebe862c208da7f66554010000000000000000000000000000000000ffff62f2a82e208d430d8854010000000000000000000000000000000000ffff6deb6956208d08148854010000000000000000000000000000000000ffff4b9804ef208d950e8854010000000000000000000000000000000000ffff6b8dd2e1208d1cfc8754010000000000000000000000000000000000ffffb2fe1dab208de7458754010000000000000000000000000000000000ffff32cccb82208d12625654010000000000000000000000000000000000ffff55e6f35a208dd7908354010000000000000000000000000000000000ffff4c642e6f208d17398854010000000000000000000000000000000000ffff45ea14df208dc72d8854010000000000000000000000000000000000ffffdaa48b96208d57fb8654010000000000000000000000000000000000ffff5f0080a9208d4e9a6d54010000000000000000000000000000000000ffff6d94e741208d7a418854010000000000000000000000000000000000ffff6d43b0c1208d5c098854010000000000000000000000000000000000ffff9c11e7ea208d442d8854010000000000000000000000000000000000ffffd0424482208db142fb53010000000000000000000000000000000000ffff54344a4a208d70158854010000000000000000000000000000000000ffff538f8201208d39288854010000000000000000000000000000000000ffff57daab71208d262f8854010000000000000000000000000000000000ffff57e045b1208d91430654010000000000000000000000000000000000ffff4212def5208d7c468854010000000000000000000000000000000000ffff62a4f53b208d9c3e8854010000000000000000000000000000000000ffff5b736bcc208d4d074f54010000000000000000000000000000000000ffff43af9d9b208deaca8754010000000000000000000000000000000000ffff4a8084a0208d74f68754010000000000000000000000000000000000ffff46304541208d5d098854010000000000000000000000000000000000ffff6eabb626208d08228854010000000000000000000000000000000000ffff1f07b030208d959a8254010000000000000000000000000000000000ffff4fa996b3208d4bac8754010000000000000000000000000000000000ffff43a0605b208de4038854010000000000000000000000000000000000ffff4cb41a5b208ddc308854010000000000000000000000000000000000ffffb72e6072208deac86f54010000000000000000000000000000000000ffffd5b815e2208d1b118854010000000000000000000000000000000000ffff3ff8d0a4208db1258854010000000000000000000000000000000000ffff5edd3a6a208d20268754010000000000000000000000000000000000ffff59ed3cfc208d83ed8754010000000000000000000000000000000000ffff4460baea208d71278854010000000000000000000000000000000000ffff4583b547208da7968754010000000000000000000000000000000000ffffd1c34463208d9d428854010000000000000000000000000000000000ffff43c15bc5208d33448854010000000000000000000000000000000000ffff2e0d8a5c208dfa2e7154010000000000000000000000000000000000ffffceff2bca208d47288854010000000000000000000000000000000000ffff5b8d0190208d0e847954010000000000000000000000000000000000ffff5501b8c2208d552f8854010000000000000000000000000000000000ffff5cf3b604208d8c2e8854010000000000000000000000000000000000ffff58666b0d208dcbdd8754010000000000000000000000000000000000ffff59a85cf9208d04208854010000000000000000000000000000000000ffff5f60abe0208dba238854010000000000000000000000000000000000ffff2e77ce67208d62c68754010000000000000000000000000000000000ffffcbae5658208d41108854010000000000000000000000000000000000ffff49329ec8208ddf1d8854010000000000000000000000000000000000ffff603ada12208df90c8854010000000000000000000000000000000000ffff51e0c21c208d5dff8754010000000000000000000000000000000000ffff904ca539208de0118854010000000000000000000000000000000000ffff5c0cecf7208d1b2d88540100000000000000200100005ef579fb18f317b2aba4edfc208dfaa27f54010000000000000000000000000000000000ffff54c65a7c208dc2db8754010000000000000000000000000000000000ffffad0b7981208d88a58754010000000000000000000000000000000000ffff188a1995208de0d78754010000000000000000000000000000000000ffff62a7a6bd208d18f66b54010000000000000000000000000000000000ffffdb4fbe80208dfcf78754010000000000000000000000000000000000ffff67145102208d3d3a8854010000000000000000000000000000000000ffff64259bd6208dcaa98654010000000000000000000000000000000000ffff32c737e1208d3a6a5c54010000000000000000000000000000000000ffff6d793f75208d22d38754010000000000000000000000000000000000ffff0595cc0d208da9ea8754010000000000000000000000000000000000ffff62ca37d3208d440e8854010000000000000000000000000000000000ffff123e1c11208dab428854010000000000000000000000000000000000ffff461df51f208d0f0b8854010000000000000000000000000000000000ffff59d36752208d631f8854010000000000000000000000000000000000ffff5ef8c640208dbc1d8854010000000000000000000000000000000000ffff5f5dc582208d04bf8754010000000000000000000000000000000000ffff0e6797e3208db8028854010000000000000000000000000000000000ffff3cf0c4a1208d6a0c8854010000000000000000000000000000000000ffff58bb5c55208d4f438854010000000000000000000000000000000000ffff5d326005208df6068854010000000000000000000000000000000000ffff5d5a5850208d40eb8754010000000000000000000000000000000000ffff5751ed86208dbad18754010000000000000000000000000000000000ffff63e51608208d99108854010000000000000000000000000000000000ffff36e299cd208d9a1e88540100000000000000200100005ef579fd38a43785a5d441b7208d13258854010000000000000000000000000000000000ffffb90de279208db3778754010000000000000000000000000000000000ffff67fcc80c208d3d358854010000000000000000000000000000000000ffffd00c40fc208dfd0d8854010000000000000000000000000000000000ffff5e89c2f3208dd6428854010000000000000000000000000000000000ffff1863e14a208d89e78754010000000000000000000000000000000000ffff43a11d76208d89ce8754010000000000000000000000000000000000ffff3edb6281208d03298854010000000000000000000000000000000000ffff6d5b26dd208d16df6e54010000000000000000000000000000000000ffffd9c3d176208d7c028854010000000000000000000000000000000000ffffbcc2db1f208d67fc8754010000000000000000000000000000000000ffff59910667208d86b18754010000000000000000000000000000000000ffff6c1482b9208d790b8854010000000000000000000000000000000000ffff0263150d208d89c44554010000000000000000000000000000000000ffff72fcd6b8208d331c8854010000000000000000000000000000000000ffff4c7dcfa8208d9fe98754010000000000000000000000000000000000ffffad08fd05208d0ecd8754010000000000000000000000000000000000ffff7c0fec89208d4af98754010000000000000000000000000000000000ffff2ea6a167208d270b8854010000000000000000000000000000000000ffff59d40fca208d11cf4054010000000000000000000000000000000000ffffb52ac058208d63358254010000000000000000000000000000000000ffff6bd9a130208dbde48754010000000000000000000000000000000000ffff4f915dc7208dece78754010000000000000000000000000000000000ffff5f8423ef208dba308854010000000000000000000000000000000000ffff904cba8c208d953d8854010000000000000000000000000000000000ffff8d1421e5208d5f188854010000000000000000000000000000000000ffff4e1bbfb6208d36fc8754010000000000000000000000000000000000ffff3205cfb6208d5e458854010000000000000000000000000000000000ffff493092c2208dd0878754010000000000000000000000000000000000ffff97caacde208dd99d8754010000000000000000000000000000000000ffffc05f3c38208da7018854010000000000000000000000000000000000ffffdcf5f82d208df0f42254010000000000000000000000000000000000ffff58bf997f208d94228854010000000000000000000000000000000000ffffbc8ee72d208de3448854010000000000000000000000000000000000ffff6d780f99208d32448854010000000000000000000000000000000000ffffbc64c76e208d0b3d8854010000000000000000000000000000000000ffff6c414312208d49138854010000000000000000000000000000000000ffff407e52c6208d2d128854010000000000000000000000000000000000ffff6dc987d8208d4acf8754010000000000000000000000000000000000ffffae151a25208de9dd8754010000000000000000000000000000000000ffff522d2d10208d17df8754010000000000000000000000000000000000ffff4d294737208ddbd48754010000000000000000000000000000000000ffff443703fd208d79108854010000000000000000000000000000000000ffff4bb5a154208d1df28753010000000000000000000000000000000000ffff4c44fc51208d5d496a53010000000000000000000000000000000000ffff530924b0208d180b8854010000000000000000000000000000000000ffffc15fced3208d53128854010000000000000000000000000000000000ffff5bc5b9e4208de21f8854010000000000000000000000000000000000ffff2e05fe1f208d31ab6554010000000000000000000000000000000000ffffae865915208d09248854010000000000000000000000000000000000ffff7d71a4b0208d120b8854010000000000000000000000000000000000ffff93e40150208d25d98754010000000000000000000000000000000000ffffc19f7a86208d99e78754010000000000000000000000000000000000ffff4d675993208d2b218854010000000000000000000000000000000000ffff5144776c208d5a208854010000000000000000000000000000000000ffff2e1cce5820f69e8f8154010000000000000000000000000000000000ffff58718848208d9edd6554010000000000000000000000000000000000ffff2ea758c5208d28c98754010000000000000000000000000000000000ffffc60bd694208d181d8854010000000000000000000000000000000000ffffba6b689e208d77868754010000000000000000000000000000000000ffff182016aa208de1698054010000000000000000000000000000000000ffff5cf5daa9208d823d8854010000000000000000000000000000000000ffffbe4d61c8208d2c3b8854010000000000000000000000000000000000ffff71647df2208da2248854010000000000000000000000000000000000ffff4ff359ca208da2dd8754010000000000000000000000000000000000ffff59f1e52b208da6e60d54010000000000000000000000000000000000ffff579596231159ad218854010000000000000000000000000000000000ffff5895f42d208d5ba78754010000000000000000000000000000000000ffff458ae8b0208ddc9b8754010000000000000000000000000000000000ffff18b507a5208d20eb5754010000000000000000000000000000000000ffff5181a537208daf1d8854010000000000000000000000000000000000ffffd5f51eba208d07f58754010000000000000000000000000000000000ffff6dec54bc208d4b3b8854010000000000000000000000000000000000ffff5c3e1991208d13b54054010000000000000000000000000000000000ffffb273845a208d4f338854010000000000000000000000000000000000ffffcfac797b208d4d898754010000000000000000000000000000000000ffffadaf880d208de72f8854010000000000000000000000000000000000ffffd956ef58208dc1e98754010000000000000000000000000000000000ffff980711cf208dfd8f7154010000000000000000000000000000000000ffff50aba98f208d59fe8754010000000000000000000000000000000000ffff5f2ae582208d3ffa8754010000000000000000000000000000000000ffffbca836db208d389b0054010000000000000000000000000000000000ffffded10925208deb154354010000000000000000000000000000000000ffff717532da208dadde7654010000000000000000000000000000000000ffff93afd0e5208d40458854010000000000000000000000000000000000ffff54ee8cb0208d08fa8754010000000000000000000000000000000000ffff3658eba9208d0df58754010000000000000000000000000000000000ffff72c6876e208d5d408854010000000000000000000000000000000000ffffa7582d7c208de8338854010000000000000000000000000000000000ffffad40d7ad208d6c2a8854010000000000000000000000000000000000ffffbc1833a3208d0fc28754010000000000000000000000000000000000ffffcbceb676208d98448854010000000000000000000000000000000000ffff0121ce6e208d77b58754010000000000000000000000000000000000ffff4b40d938208d853b8854010000000000000000000000000000000000ffff5648d643208d1c9c7854010000000000000000000000000000000000ffff6c3230db208d4e028754010000000000000000000000000000000000ffffd8e33c5d208daf738754010000000000000000000000000000000000ffffad4e9d56208dca438854010000000000000000000000000000000000ffff4f8a0353208d7b408854010000000000000000000000000000000000ffff0599e92a208dc62f8854010000000000000000000000000000000000ffff46236049208dc4d58754010000000000000000000000000000000000ffff4400384d208d0ddb4c54010000000000000000000000000000000000ffff71653b5a208d53388854010000000000000000000000000000000000ffff43bddf66208d7c388854010000000000000000000000000000000000ffff47edca96208d8c068854010000000000000000000000000000000000ffffdded3c2c208d89f60554010000000000000000000000000000000000ffff4e60d8d6208da5ff1154010000000000000000000000000000000000ffff55983c68208d72f58754010000000000000000000000000000000000ffffce47f56a208d7e2d8854010000000000000000000000000000000000ffff17e25c12208da7ca8754010000000000000000000000000000000000ffff051d0baa208d19af8754010000000000000000000000000000000000ffffad42d883208dffd68754010000000000000000000000000000000000ffff5418ff88208d87bf8754010000000000000000000000000000000000ffff4c1fe0dc208deef18754010000000000000000000000000000000000ffff4c71d406208d25868754010000000000000000000000000000000000ffffda67a475208d4d348854010000000000000000000000000000000000ffff5d561269208da5918754010000000000000000000000000000000000ffff42b163b3208dbfac3754010000000000000000000000000000000000ffffad4e01eb208d5c408854010000000000000000000000000000000000ffffcc0bb91e208d4a928754010000000000000000000000000000000000ffff531dba30208db9fd8754010000000000000000000000000000000000ffffa3f72b33208dade58754010000000000000000000000000000000000ffffd8b93aeb208db75d4d54010000000000000000000000000000000000ffffae1d5595208de8d18754010000000000000000000000000000000000ffff4405665c208d88e78754010000000000000000000000000000000000ffff6b96180a208dae667354010000000000000000000000000000000000ffff531f490a208d92f98754010000000000000000000000000000000000ffffd447e857208db1428854010000000000000000000000000000000000ffff36c6b4bb208dd42c8854010000000000000000000000000000000000ffff4b4aff14208df71acd53010000000000000000000000000000000000ffff62d17930208da0b88754010000000000000000000000000000000000ffff974bf913208d5faa7953010000000000000000000000000000000000ffff7419d269208d6c1e8854010000000000000000000000000000000000ffff48a72343208d9c2b8854010000000000000000000000000000000000ffff64431e87208de8908754010000000000000000000000000000000000ffff44660d45208d410b7d54010000000000000000000000000000000000ffff43a3340d208d5b298854010000000000000000000000000000000000ffff9ffda678208de7238854010000000000000000000000000000000000ffff189a3759208da5745354010000000000000000000000000000000000ffff57026e31208df23c885401000000000000002a010488006710000523fbe100000001208dddf48754010000000000000000000000000000000000ffffc1531c5b208d61888754010000000000000000000000000000000000ffff42cd8bc1208df92e8854010000000000000000000000000000000000ffff36e1e970208d0cf75754010000000000000000000000000000000000ffffad4e2420208d0f028854010000000000000000000000000000000000ffff6ec61b02208de6228854010000000000000000000000000000000000ffffdcaa807c208d1cac8754010000000000000000000000000000000000ffff6179416c208db6068854010000000000000000000000000000000000ffffd46121e4208d7ea58754010000000000000000000000000000000000ffff80a41679208d91216254010000000000000000000000000000000000ffff69ece091208d0bf78754010000000000000000000000000000000000ffff45320c0b208dd03c8854010000000000000000000000000000000000ffff4859a2a5208d8e308854010000000000000000000000000000000000ffff2e1ce15a208d30f36354010000000000000000000000000000000000ffff4b61ee15208d7aa05553010000000000000000000000000000000000ffffb129186b208dbb238854010000000000000000000000000000000000ffff581a7f6f208d41248754010000000000000000000000000000000000ffffb009055b208dee218854010000000000000000000000000000000000ffff2e7771f9208db4e78754010000000000000000000000000000000000ffff411bf6ee208db5178854010000000000000000000000000000000000ffffacff001b208dbcf487540100000000000000200100009d386ab83061cfabfe3513a3208d76048854010000000000000000000000000000000000ffff2e35dbc2208d83d28754010000000000000000000000000000000000ffffd91bb1d5208d5c3d8854010000000000000000000000000000000000ffff32a47969208df4bd875401000000000000002a0104f8012032250000000000000002208d1bfa8754010000000000000000000000000000000000ffff53a2f4b6208d141c4554010000000000000000000000000000000000ffffb27b8281208d580c7e54010000000000000000000000000000000000ffffc9ea1340208df7228854010000000000000000000000000000000000ffffd90a268d208d41e18754010000000000000000000000000000000000ffff555b8809208d66358854010000000000000000000000000000000000ffff5280ff23208deecd8754010000000000000000000000000000000000ffff84c6a102208df9bc8754010000000000000000000000000000000000ffffbbc96dae208d9d0a8854010000000000000000000000000000000000ffffb2a2d18a208da9458854010000000000000000000000000000000000ffffca16c30e208d2ca88754010000000000000000000000000000000000ffff6baab63e208d923b8854010000000000000000000000000000000000ffffd5b87bee208d12258854010000000000000000000000000000000000ffff59ee408b208d980e8854010000000000000000000000000000000000ffff2edfb0c4208d33378854010000000000000000000000000000000000ffff6caa7b42208d41176d53010000000000000000000000000000000000ffff1fa2df68208dd1c57f54010000000000000000000000000000000000ffffbeab679a208d17c78754010000000000000000000000000000000000ffff3f983f51208dbb278854010000000000000000000000000000000000ffff568dbc28208d5d5ad953010000000000000000000000000000000000ffff0ec112b7208da1b78754010000000000000000000000000000000000ffffbc287698208d99348854010000000000000000000000000000000000ffff5ff1a302208d23ef8454010000000000000000000000000000000000ffff7ba55d22208d90e98754010000000000000000000000000000000000ffff5ae14503208d58f48754010000000000000000000000000000000000ffffd45a3cae208d530c8854010000000000000000000000000000000000ffff18fb80c4208dc6138854010000000000000000000000000000000000ffff4e2fd6eb208d97208854010000000000000000000000000000000000ffff55ddd5b8208d74f88654010000000000000000000000000000000000ffff1809ad86208dc0278854010000000000000000000000000000000000ffff6ef2dfb6208d84338854010000000000000000000000000000000000ffff58d8114e208dcae78754010000000000000000000000000000000000ffffc21c474c208d61e78754010000000000000000000000000000000000ffffd5b3fc7a208d7a078854010000000000000000000000000000000000ffff32740193208de7547b54010000000000000000000000000000000000ffff8046aac3208db5328854010000000000000000000000000000000000ffff5b0c5366208d5f208854010000000000000000000000000000000000ffff6caa054a208d1d682854010000000000000000000000000000000000ffff69e3eb9d208d179e6b54010000000000000000000000000000000000ffff54aac628208ded2e8854010000000000000000000000000000000000ffff4166d226208d8e148854010000000000000000000000000000000000ffff54305af6208d1ed88754010000000000000000000000000000000000ffff3ed2b2a4208d78ec8554010000000000000000000000000000000000ffff6dd2e575208ddd3e7854010000000000000000000000000000000000ffff1b83a102208d421e8854010000000000000000000000000000000000ffff2eb51bf1208dc01f8854010000000000000000000000000000000000ffff58c66033208d08ce8754010000000000000000000000000000000000ffff5d57b84c208df22e8854010000000000000000000000000000000000ffffbf213e3d208d89cb8754010000000000000000000000000000000000ffff53d7ece1208da9d68754010000000000000000000000000000000000ffff71bef412208d38268854010000000000000000000000000000000000ffff84fc8a6f208d4f107254010000000000000000000000000000000000ffff57a072d9208d990f88540100000000000000200100009d3890d71029db21863020f9208df3c36854010000000000000000000000000000000000ffffbba6a6c4208d3ea71f54010000000000000000000000000000000000ffff3e61237b208d60a18754010000000000000000000000000000000000ffff53a56b4b208db2328854010000000000000000000000000000000000ffff44eef265208d2c0f8854010000000000000000000000000000000000ffff566553c0208dec268854010000000000000000000000000000000000ffff57a69ecd208d93148854010000000000000000000000000000000000ffff9b8f4433208d741b8854010000000000000000000000000000000000ffffae17c74c208ddab94f54010000000000000000000000000000000000ffffab19c63a208d5e886f54010000000000000000000000000000000000ffffbc67a037208d5c3ccc53010000000000000000000000000000000000ffff0e77c86e208d26fb8754010000000000000000000000000000000000ffffc1eae172208d61278854010000000000000000000000000000000000ffff5ce15439208d60df8754010000000000000000000000000000000000ffff2599fb3b208d52418854010000000000000000000000000000000000ffff028710e6208dae448854010000000000000000000000000000000000ffff18152eee208d6ff18754010000000000000000000000000000000000ffff542e396d208d8c248854010000000000000000000000000000000000ffff5519d6d8208d01e33e54010000000000000000000000000000000000ffff5027d563208d05cd8754010000000000000000000000000000000000ffff45a5a9c5208d7c1e8854010000000000000000000000000000000000ffff68c813c8208d38a48754010000000000000000000000000000000000ffff3a60a934208d73ec8754010000000000000000000000000000000000ffffae3db8f3208d096c8754010000000000000000000000000000000000ffff4cb88802208d55d38754010000000000000000000000000000000000ffffd31f082e208d0d8a8754010000000000000000000000000000000000ffff6c22c9bb208d81ba7754010000000000000000000000000000000000ffffca5f88da208d5df88754010000000000000000000000000000000000ffffc31abc05208db1d58754010000000000000000000000000000000000ffff5cf614c8208d2b257c54010000000000000000000000000000000000ffff45f9baa0208d38d78754010000000000000000000000000000000000ffff490e0608208d27e88754010000000000000000000000000000000000ffff17f1ccd0208d501c8854010000000000000000000000000000000000ffff5be834a7208d860b8854010000000000000000000000000000000000ffff640173bc208deada8754010000000000000000000000000000000000ffffdc98f8db208d983d3c54010000000000000000000000000000000000ffff021ab398208d99f28754010000000000000000000000000000000000ffff59a9e417208d19468854010000000000000000000000000000000000ffff91ff01a1208ddbf68754010000000000000000000000000000000000ffff50048949208d89118854010000000000000000000000000000000000ffffc8366923208dea3b8854010000000000000000000000000000000000ffffbcbbb5a9208d364a8854010000000000000000000000000000000000ffffc654bd58208dd52ef953010000000000000000000000000000000000ffff7cabb7bc208db086cc53010000000000000000000000000000000000ffff6c4004ea208d0ff48754010000000000000000000000000000000000ffff0536a3cb208d1c0b8854010000000000000000000000000000000000ffffd90bfef3208dbf188854010000000000000000000000000000000000ffff0252431f208dd1c78754010000000000000000000000000000000000ffff25732b19208dd6fa3954010000000000000000000000000000000000ffff6c1131dc208da6108854010000000000000000000000000000000000ffff1fb9b748208daf368854010000000000000000000000000000000000ffff1f2a2981208d9e2f8854010000000000000000000000000000000000ffffbc658482208d6a458854010000000000000000000000000000000000ffff568b843a208dcd428854010000000000000000000000000000000000ffff5d820794208d38258854010000000000000000000000000000000000ffff6440c630208d1cda8754010000000000000000000000000000000000ffff6e9f7be5208ddd388854010000000000000000000000000000000000ffff9f080244208d2e2d8854010000000000000000000000000000000000ffffbb70cf3d208d3e0c8854010000000000000000000000000000000000ffff3ec27a16208d89098854010000000000000000000000000000000000ffffb55fb2fb208d13468854010000000000000000000000000000000000ffff180ab1e5208dfc2a8854010000000000000000000000000000000000ffffd58a5c0e208db9188854010000000000000000000000000000000000ffff81ce808d208d03d06b54010000000000000000000000000000000000ffff4b496858208d319e8454010000000000000000000000000000000000ffff531fac9f208dc8d98754010000000000000000000000000000000000ffffc113e4ea208d06395754010000000000000000000000000000000000ffff7cabf0d6208d722b8854010000000000000000000000000000000000ffff545db4cd208dd91f8854010000000000000000000000000000000000ffff5f12a597208dfbd57054010000000000000000000000000000000000ffff3cf654e6208dd7358854010000000000000000000000000000000000ffffb4998e79208dd7018854010000000000000000000000000000000000ffffb45cc220208d86df5554010000000000000000000000000000000000ffff7660d462208decd28754010000000000000000000000000000000000ffff5b7bdfe2208dafe28754010000000000000000000000000000000000ffff2e76899e208d0c318854010000000000000000000000000000000000ffffc7bcb183208d35258854010000000000000000000000000000000000ffff3ab2d076208db5c38754010000000000000000000000000000000000ffff48b24d30208ddfd18754010000000000000000000000000000000000ffff904cb00c208d40478854010000000000000000000000000000000000ffff02a3b787208deb318854010000000000000000000000000000000000ffffd2564026208de63f8854010000000000000000000000000000000000ffff615d16c0208dbf1d8854010000000000000000000000000000000000ffffc6fff6f0208d4aee8754010000000000000000000000000000000000ffff56c74bc8208d68bc8754010000000000000000000000000000000000ffff31b581f9208ddcca8754010000000000000000000000000000000000ffff0e97397a208db2318854010000000000000000000000000000000000ffffc766348d208de1bd8754010000000000000000000000000000000000ffffd5927a3d208d2aff8754010000000000000000000000000000000000ffff5152d0a5208da7feb753010000000000000000000000000000000000ffff53fe1694208de7418854010000000000000000000000000000000000ffff189f3d99208de7b38754010000000000000000000000000000000000ffff64004655208dacabb653010000000000000000000000000000000000ffff55413231208d43b88754010000000000000000000000000000000000ffff796e1029208d03788754010000000000000000000000000000000000ffff49b90ff6208d04be8754010000000000000000000000000000000000ffffc327ce1c208d33648254010000000000000000000000000000000000ffff2e896478208d9e308854010000000000000000000000000000000000ffffd395d9bf208df13e8854010000000000000000000000000000000000ffff51bb88ed208dec8c5653010000000000000000000000000000000000ffff557f146e208df4538854010000000000000000000000000000000000ffff48d05928208d41bd8154010000000000000000000000000000000000ffff47384162208df6428854010000000000000000000000000000000000ffffc1566314208d753e8854010000000000000000000000000000000000ffff440c9b4f208d2f078854010000000000000000000000000000000000ffff56021a19208d7b0f4754010000000000000000000000000000000000ffff71653a84208dcee88754010000000000000000000000000000000000ffff5401a332208df4db8754010000000000000000000000000000000000ffff5a9db0e3208df8e38754010000000000000000000000000000000000ffff5d5208fa208d2e298854010000000000000000000000000000000000ffff4c61c0bc208d42ea8754010000000000000000000000000000000000ffff4c405ae7208deade8754010000000000000000000000000000000000ffffc009c823208d99358854010000000000000000000000000000000000ffff6cc2b493208df3fd8754010000000000000000000000000000000000ffff904c6007208d1fde8754010000000000000000000000000000000000ffff92732ae8208d666e8354010000000000000000000000000000000000ffff6daa9d16208d18f78754010000000000000000000000000000000000ffff4d6d8d8a208ddea9ce53010000000000000000000000000000000000ffff17e26f7d208d1ae48754010000000000000000000000000000000000ffffc32ebb89208dee258854010000000000000000000000000000000000ffff4a0f10e1208d290a8854010000000000000000000000000000000000ffff42731593208d7cb33354010000000000000000000000000000000000ffff532e8b97208dfd1e8854010000000000000000000000000000000000ffff6ee91680208d480a1354010000000000000000000000000000000000ffff60f220f6208d55838754010000000000000000000000000000000000ffff1f36d48f208d247e8754010000000000000000000000000000000000ffff44b5a49a208d0efa4954010000000000000000000000000000000000ffff63890597208d862e8854010000000000000000000000000000000000ffffc14d8751208d443c8854010000000000000000000000000000000000ffff70d16dba208d72fe8754010000000000000000000000000000000000ffff55983dd3208d236b6854010000000000000000000000000000000000ffff7aea27f9208d67e08754010000000000000000000000000000000000ffff1b20750d208df10a7154010000000000000000000000000000000000ffff7b9fe56e208db32d8854010000000000000000000000000000000000ffff7ba42d54208db3f58754010000000000000000000000000000000000ffff027b4eba208d70d68754010000000000000000000000000000000000ffffbc1b62b9208d97951454010000000000000000000000000000000000ffffb4b6af10208d251288540100000000000000200100009d386ab800773149c4d7e673208d7b168854010000000000000000000000000000000000ffff58969392208d91d18754010000000000000000000000000000000000ffff9ffd6d4e208d19008854010000000000000000000000000000000000ffff4e926cb8208dd1c98754010000000000000000000000000000000000ffff546cdb77208d6a0a8854010000000000000000000000000000000000ffff6caa8c15208d3a3a8854010000000000000000000000000000000000ffff62e28d6a208d3fe48754010000000000000000000000000000000000ffffd9850be7208dcb438854010000000000000000000000000000000000ffff6c2444b3208d78338854010000000000000000000000000000000000ffffb2df574a208d3b2d3054010000000000000000000000000000000000ffff4b529e67208d5b988754010000000000000000000000000000000000ffff36e152e6208d01ab8754010000000000000000000000000000000000ffff58b531bf208d7b038854010000000000000000000000000000000000ffff54717a6f208db0388854010000000000000000000000000000000000ffff4a694e98208d65418854010000000000000000000000000000000000ffffcef84b25208dcf218854010000000000000000000000000000000000ffff5364fed9208db6c68754010000000000000000000000000000000000ffff4d03decd208dc6158854010000000000000000000000000000000000ffffb9044c79208d65418854010000000000000000000000000000000000ffff56b2192f208d23308754010000000000000000000000000000000000ffffd973f7a4208d05158854010000000000000000000000000000000000ffff1b6d9975208d52cd8754010000000000000000000000000000000000ffffdf5b92ad208d3a128854010000000000000000000000000000000000ffff6d93787e208d351e8854010000000000000000000000000000000000ffffb6a4336f208d9bd58754010000000000000000000000000000000000ffff3ba7f521208dc3288854010000000000000000000000000000000000ffff6bbf20cc208d68998754010000000000000000000000000000000000ffff63b30032208d86848754010000000000000000000000000000000000ffffb52ef15c208dff108854010000000000000000000000000000000000ffff4d06718b208d226f0754010000000000000000000000000000000000ffff44248ba1208da3af7354010000000000000000000000000000000000ffff86f9d72c208d9fd68754010000000000000000000000000000000000ffff521f2444208dae428854010000000000000000000000000000000000ffffd445b025208dfafc8754010000000000000000000000000000000000ffffd447fa84208dbb358854010000000000000000000000000000000000ffff3ee2d196208da01a8854010000000000000000000000000000000000ffff45a615b2208d32edb053010000000000000000000000000000000000ffff6d6494da208d77ed8754010000000000000000000000000000000000ffff555dcc44208d09608754010000000000000000000000000000000000ffff45a5d222208d84558754010000000000000000000000000000000000ffffb01c30ab208d71fe8754010000000000000000000000000000000000ffffae5f68c2208de0acdf53010000000000000000000000000000000000ffffae737936208d29e18754010000000000000000000000000000000000ffffb00a63cb208d220b8854010000000000000000000000000000000000ffff9ec4d12f208db4408854010000000000000000000000000000000000ffff4c6d9bcc208d65a58754010000000000000000000000000000000000ffff5962c453208d2d628854010000000000000000000000000000000000ffff257191d50000', + payload: 'fdf5016816fa53010000000000000000000000000000000000ffff51403eea208ddb2a8854010000000000000000000000000000000000ffff707c60d9208d31413a54010000000000000000000000000000000000ffff5ed5fd13208d182c8854010000000000000000000000000000000000ffff505f3f81208d65ecb853010000000000000000000000000000000000ffff54d72104208dcca94054010000000000000000000000000000000000ffff40bbe1f2208d73238854010000000000000000000000000000000000ffff55d683cd208d5d258854010000000000000000000000000000000000ffffd523a66d208dcbea87540100000000000000200100005ef579fd2c9d47be4b56d671208db3e58754010000000000000000000000000000000000ffffae373156208d11e58754810000000000000000000000000000000000ffff51071276208d38f48754010000000000000000000000000000000000ffff6d4372e0208d4f448854010000000000000000000000000000000000ffffc556cbea208deb208854010000000000000000000000000000000000ffff450eb059208d97336054010000000000000000000000000000000000ffff4d60444a208dad608754010000000000000000000000000000000000ffff01abd3c3208d2cbe8754010000000000000000000000000000000000ffff7963d8b8208d9dfa8754010000000000000000000000000000000000ffffcebe862c208da7f66554010000000000000000000000000000000000ffff62f2a82e208d430d8854010000000000000000000000000000000000ffff6deb6956208d08148854010000000000000000000000000000000000ffff4b9804ef208d950e8854010000000000000000000000000000000000ffff6b8dd2e1208d1cfc8754010000000000000000000000000000000000ffffb2fe1dab208de7458754010000000000000000000000000000000000ffff32cccb82208d12625654010000000000000000000000000000000000ffff55e6f35a208dd7908354010000000000000000000000000000000000ffff4c642e6f208d17398854010000000000000000000000000000000000ffff45ea14df208dc72d8854010000000000000000000000000000000000ffffdaa48b96208d57fb8654010000000000000000000000000000000000ffff5f0080a9208d4e9a6d54010000000000000000000000000000000000ffff6d94e741208d7a418854010000000000000000000000000000000000ffff6d43b0c1208d5c098854010000000000000000000000000000000000ffff9c11e7ea208d442d8854010000000000000000000000000000000000ffffd0424482208db142fb53010000000000000000000000000000000000ffff54344a4a208d70158854010000000000000000000000000000000000ffff538f8201208d39288854010000000000000000000000000000000000ffff57daab71208d262f8854010000000000000000000000000000000000ffff57e045b1208d91430654010000000000000000000000000000000000ffff4212def5208d7c468854010000000000000000000000000000000000ffff62a4f53b208d9c3e8854010000000000000000000000000000000000ffff5b736bcc208d4d074f54010000000000000000000000000000000000ffff43af9d9b208deaca8754010000000000000000000000000000000000ffff4a8084a0208d74f68754010000000000000000000000000000000000ffff46304541208d5d098854010000000000000000000000000000000000ffff6eabb626208d08228854010000000000000000000000000000000000ffff1f07b030208d959a8254010000000000000000000000000000000000ffff4fa996b3208d4bac8754010000000000000000000000000000000000ffff43a0605b208de4038854010000000000000000000000000000000000ffff4cb41a5b208ddc308854010000000000000000000000000000000000ffffb72e6072208deac86f54010000000000000000000000000000000000ffffd5b815e2208d1b118854010000000000000000000000000000000000ffff3ff8d0a4208db1258854010000000000000000000000000000000000ffff5edd3a6a208d20268754010000000000000000000000000000000000ffff59ed3cfc208d83ed8754010000000000000000000000000000000000ffff4460baea208d71278854010000000000000000000000000000000000ffff4583b547208da7968754010000000000000000000000000000000000ffffd1c34463208d9d428854010000000000000000000000000000000000ffff43c15bc5208d33448854010000000000000000000000000000000000ffff2e0d8a5c208dfa2e7154010000000000000000000000000000000000ffffceff2bca208d47288854010000000000000000000000000000000000ffff5b8d0190208d0e847954010000000000000000000000000000000000ffff5501b8c2208d552f8854010000000000000000000000000000000000ffff5cf3b604208d8c2e8854010000000000000000000000000000000000ffff58666b0d208dcbdd8754010000000000000000000000000000000000ffff59a85cf9208d04208854010000000000000000000000000000000000ffff5f60abe0208dba238854010000000000000000000000000000000000ffff2e77ce67208d62c68754010000000000000000000000000000000000ffffcbae5658208d41108854010000000000000000000000000000000000ffff49329ec8208ddf1d8854010000000000000000000000000000000000ffff603ada12208df90c8854010000000000000000000000000000000000ffff51e0c21c208d5dff8754010000000000000000000000000000000000ffff904ca539208de0118854010000000000000000000000000000000000ffff5c0cecf7208d1b2d88540100000000000000200100005ef579fb18f317b2aba4edfc208dfaa27f54010000000000000000000000000000000000ffff54c65a7c208dc2db8754010000000000000000000000000000000000ffffad0b7981208d88a58754010000000000000000000000000000000000ffff188a1995208de0d78754010000000000000000000000000000000000ffff62a7a6bd208d18f66b54010000000000000000000000000000000000ffffdb4fbe80208dfcf78754010000000000000000000000000000000000ffff67145102208d3d3a8854010000000000000000000000000000000000ffff64259bd6208dcaa98654010000000000000000000000000000000000ffff32c737e1208d3a6a5c54010000000000000000000000000000000000ffff6d793f75208d22d38754010000000000000000000000000000000000ffff0595cc0d208da9ea8754010000000000000000000000000000000000ffff62ca37d3208d440e8854010000000000000000000000000000000000ffff123e1c11208dab428854010000000000000000000000000000000000ffff461df51f208d0f0b8854010000000000000000000000000000000000ffff59d36752208d631f8854010000000000000000000000000000000000ffff5ef8c640208dbc1d8854010000000000000000000000000000000000ffff5f5dc582208d04bf8754010000000000000000000000000000000000ffff0e6797e3208db8028854010000000000000000000000000000000000ffff3cf0c4a1208d6a0c8854010000000000000000000000000000000000ffff58bb5c55208d4f438854010000000000000000000000000000000000ffff5d326005208df6068854010000000000000000000000000000000000ffff5d5a5850208d40eb8754010000000000000000000000000000000000ffff5751ed86208dbad18754010000000000000000000000000000000000ffff63e51608208d99108854010000000000000000000000000000000000ffff36e299cd208d9a1e88540100000000000000200100005ef579fd38a43785a5d441b7208d13258854010000000000000000000000000000000000ffffb90de279208db3778754010000000000000000000000000000000000ffff67fcc80c208d3d358854010000000000000000000000000000000000ffffd00c40fc208dfd0d8854010000000000000000000000000000000000ffff5e89c2f3208dd6428854010000000000000000000000000000000000ffff1863e14a208d89e78754010000000000000000000000000000000000ffff43a11d76208d89ce8754010000000000000000000000000000000000ffff3edb6281208d03298854010000000000000000000000000000000000ffff6d5b26dd208d16df6e54010000000000000000000000000000000000ffffd9c3d176208d7c028854010000000000000000000000000000000000ffffbcc2db1f208d67fc8754010000000000000000000000000000000000ffff59910667208d86b18754010000000000000000000000000000000000ffff6c1482b9208d790b8854010000000000000000000000000000000000ffff0263150d208d89c44554010000000000000000000000000000000000ffff72fcd6b8208d331c8854010000000000000000000000000000000000ffff4c7dcfa8208d9fe98754010000000000000000000000000000000000ffffad08fd05208d0ecd8754010000000000000000000000000000000000ffff7c0fec89208d4af98754010000000000000000000000000000000000ffff2ea6a167208d270b8854010000000000000000000000000000000000ffff59d40fca208d11cf4054010000000000000000000000000000000000ffffb52ac058208d63358254010000000000000000000000000000000000ffff6bd9a130208dbde48754010000000000000000000000000000000000ffff4f915dc7208dece78754010000000000000000000000000000000000ffff5f8423ef208dba308854010000000000000000000000000000000000ffff904cba8c208d953d8854010000000000000000000000000000000000ffff8d1421e5208d5f188854010000000000000000000000000000000000ffff4e1bbfb6208d36fc8754010000000000000000000000000000000000ffff3205cfb6208d5e458854010000000000000000000000000000000000ffff493092c2208dd0878754010000000000000000000000000000000000ffff97caacde208dd99d8754010000000000000000000000000000000000ffffc05f3c38208da7018854010000000000000000000000000000000000ffffdcf5f82d208df0f42254010000000000000000000000000000000000ffff58bf997f208d94228854010000000000000000000000000000000000ffffbc8ee72d208de3448854010000000000000000000000000000000000ffff6d780f99208d32448854010000000000000000000000000000000000ffffbc64c76e208d0b3d8854010000000000000000000000000000000000ffff6c414312208d49138854010000000000000000000000000000000000ffff407e52c6208d2d128854010000000000000000000000000000000000ffff6dc987d8208d4acf8754010000000000000000000000000000000000ffffae151a25208de9dd8754010000000000000000000000000000000000ffff522d2d10208d17df8754010000000000000000000000000000000000ffff4d294737208ddbd48754010000000000000000000000000000000000ffff443703fd208d79108854010000000000000000000000000000000000ffff4bb5a154208d1df28753010000000000000000000000000000000000ffff4c44fc51208d5d496a53010000000000000000000000000000000000ffff530924b0208d180b8854010000000000000000000000000000000000ffffc15fced3208d53128854010000000000000000000000000000000000ffff5bc5b9e4208de21f8854010000000000000000000000000000000000ffff2e05fe1f208d31ab6554010000000000000000000000000000000000ffffae865915208d09248854010000000000000000000000000000000000ffff7d71a4b0208d120b8854010000000000000000000000000000000000ffff93e40150208d25d98754010000000000000000000000000000000000ffffc19f7a86208d99e78754010000000000000000000000000000000000ffff4d675993208d2b218854010000000000000000000000000000000000ffff5144776c208d5a208854010000000000000000000000000000000000ffff2e1cce5820f69e8f8154010000000000000000000000000000000000ffff58718848208d9edd6554010000000000000000000000000000000000ffff2ea758c5208d28c98754010000000000000000000000000000000000ffffc60bd694208d181d8854010000000000000000000000000000000000ffffba6b689e208d77868754010000000000000000000000000000000000ffff182016aa208de1698054010000000000000000000000000000000000ffff5cf5daa9208d823d8854010000000000000000000000000000000000ffffbe4d61c8208d2c3b8854010000000000000000000000000000000000ffff71647df2208da2248854010000000000000000000000000000000000ffff4ff359ca208da2dd8754010000000000000000000000000000000000ffff59f1e52b208da6e60d54010000000000000000000000000000000000ffff579596231159ad218854010000000000000000000000000000000000ffff5895f42d208d5ba78754010000000000000000000000000000000000ffff458ae8b0208ddc9b8754010000000000000000000000000000000000ffff18b507a5208d20eb5754010000000000000000000000000000000000ffff5181a537208daf1d8854010000000000000000000000000000000000ffffd5f51eba208d07f58754010000000000000000000000000000000000ffff6dec54bc208d4b3b8854010000000000000000000000000000000000ffff5c3e1991208d13b54054010000000000000000000000000000000000ffffb273845a208d4f338854010000000000000000000000000000000000ffffcfac797b208d4d898754010000000000000000000000000000000000ffffadaf880d208de72f8854010000000000000000000000000000000000ffffd956ef58208dc1e98754010000000000000000000000000000000000ffff980711cf208dfd8f7154010000000000000000000000000000000000ffff50aba98f208d59fe8754010000000000000000000000000000000000ffff5f2ae582208d3ffa8754010000000000000000000000000000000000ffffbca836db208d389b0054010000000000000000000000000000000000ffffded10925208deb154354010000000000000000000000000000000000ffff717532da208dadde7654010000000000000000000000000000000000ffff93afd0e5208d40458854010000000000000000000000000000000000ffff54ee8cb0208d08fa8754010000000000000000000000000000000000ffff3658eba9208d0df58754010000000000000000000000000000000000ffff72c6876e208d5d408854010000000000000000000000000000000000ffffa7582d7c208de8338854010000000000000000000000000000000000ffffad40d7ad208d6c2a8854010000000000000000000000000000000000ffffbc1833a3208d0fc28754010000000000000000000000000000000000ffffcbceb676208d98448854010000000000000000000000000000000000ffff0121ce6e208d77b58754010000000000000000000000000000000000ffff4b40d938208d853b8854010000000000000000000000000000000000ffff5648d643208d1c9c7854010000000000000000000000000000000000ffff6c3230db208d4e028754010000000000000000000000000000000000ffffd8e33c5d208daf738754010000000000000000000000000000000000ffffad4e9d56208dca438854010000000000000000000000000000000000ffff4f8a0353208d7b408854010000000000000000000000000000000000ffff0599e92a208dc62f8854010000000000000000000000000000000000ffff46236049208dc4d58754010000000000000000000000000000000000ffff4400384d208d0ddb4c54010000000000000000000000000000000000ffff71653b5a208d53388854010000000000000000000000000000000000ffff43bddf66208d7c388854010000000000000000000000000000000000ffff47edca96208d8c068854010000000000000000000000000000000000ffffdded3c2c208d89f60554010000000000000000000000000000000000ffff4e60d8d6208da5ff1154010000000000000000000000000000000000ffff55983c68208d72f58754010000000000000000000000000000000000ffffce47f56a208d7e2d8854010000000000000000000000000000000000ffff17e25c12208da7ca8754010000000000000000000000000000000000ffff051d0baa208d19af8754010000000000000000000000000000000000ffffad42d883208dffd68754010000000000000000000000000000000000ffff5418ff88208d87bf8754010000000000000000000000000000000000ffff4c1fe0dc208deef18754010000000000000000000000000000000000ffff4c71d406208d25868754010000000000000000000000000000000000ffffda67a475208d4d348854010000000000000000000000000000000000ffff5d561269208da5918754010000000000000000000000000000000000ffff42b163b3208dbfac3754010000000000000000000000000000000000ffffad4e01eb208d5c408854010000000000000000000000000000000000ffffcc0bb91e208d4a928754010000000000000000000000000000000000ffff531dba30208db9fd8754010000000000000000000000000000000000ffffa3f72b33208dade58754010000000000000000000000000000000000ffffd8b93aeb208db75d4d54010000000000000000000000000000000000ffffae1d5595208de8d18754010000000000000000000000000000000000ffff4405665c208d88e78754010000000000000000000000000000000000ffff6b96180a208dae667354010000000000000000000000000000000000ffff531f490a208d92f98754010000000000000000000000000000000000ffffd447e857208db1428854010000000000000000000000000000000000ffff36c6b4bb208dd42c8854010000000000000000000000000000000000ffff4b4aff14208df71acd53010000000000000000000000000000000000ffff62d17930208da0b88754010000000000000000000000000000000000ffff974bf913208d5faa7953010000000000000000000000000000000000ffff7419d269208d6c1e8854010000000000000000000000000000000000ffff48a72343208d9c2b8854010000000000000000000000000000000000ffff64431e87208de8908754010000000000000000000000000000000000ffff44660d45208d410b7d54010000000000000000000000000000000000ffff43a3340d208d5b298854010000000000000000000000000000000000ffff9ffda678208de7238854010000000000000000000000000000000000ffff189a3759208da5745354010000000000000000000000000000000000ffff57026e31208df23c885401000000000000002a010488006710000523fbe100000001208dddf48754010000000000000000000000000000000000ffffc1531c5b208d61888754010000000000000000000000000000000000ffff42cd8bc1208df92e8854010000000000000000000000000000000000ffff36e1e970208d0cf75754010000000000000000000000000000000000ffffad4e2420208d0f028854010000000000000000000000000000000000ffff6ec61b02208de6228854010000000000000000000000000000000000ffffdcaa807c208d1cac8754010000000000000000000000000000000000ffff6179416c208db6068854010000000000000000000000000000000000ffffd46121e4208d7ea58754010000000000000000000000000000000000ffff80a41679208d91216254010000000000000000000000000000000000ffff69ece091208d0bf78754010000000000000000000000000000000000ffff45320c0b208dd03c8854010000000000000000000000000000000000ffff4859a2a5208d8e308854010000000000000000000000000000000000ffff2e1ce15a208d30f36354010000000000000000000000000000000000ffff4b61ee15208d7aa05553010000000000000000000000000000000000ffffb129186b208dbb238854010000000000000000000000000000000000ffff581a7f6f208d41248754010000000000000000000000000000000000ffffb009055b208dee218854010000000000000000000000000000000000ffff2e7771f9208db4e78754010000000000000000000000000000000000ffff411bf6ee208db5178854010000000000000000000000000000000000ffffacff001b208dbcf487540100000000000000200100009d386ab83061cfabfe3513a3208d76048854010000000000000000000000000000000000ffff2e35dbc2208d83d28754010000000000000000000000000000000000ffffd91bb1d5208d5c3d8854010000000000000000000000000000000000ffff32a47969208df4bd875401000000000000002a0104f8012032250000000000000002208d1bfa8754010000000000000000000000000000000000ffff53a2f4b6208d141c4554010000000000000000000000000000000000ffffb27b8281208d580c7e54010000000000000000000000000000000000ffffc9ea1340208df7228854010000000000000000000000000000000000ffffd90a268d208d41e18754010000000000000000000000000000000000ffff555b8809208d66358854010000000000000000000000000000000000ffff5280ff23208deecd8754010000000000000000000000000000000000ffff84c6a102208df9bc8754010000000000000000000000000000000000ffffbbc96dae208d9d0a8854010000000000000000000000000000000000ffffb2a2d18a208da9458854010000000000000000000000000000000000ffffca16c30e208d2ca88754010000000000000000000000000000000000ffff6baab63e208d923b8854010000000000000000000000000000000000ffffd5b87bee208d12258854010000000000000000000000000000000000ffff59ee408b208d980e8854010000000000000000000000000000000000ffff2edfb0c4208d33378854010000000000000000000000000000000000ffff6caa7b42208d41176d53010000000000000000000000000000000000ffff1fa2df68208dd1c57f54010000000000000000000000000000000000ffffbeab679a208d17c78754010000000000000000000000000000000000ffff3f983f51208dbb278854010000000000000000000000000000000000ffff568dbc28208d5d5ad953010000000000000000000000000000000000ffff0ec112b7208da1b78754010000000000000000000000000000000000ffffbc287698208d99348854010000000000000000000000000000000000ffff5ff1a302208d23ef8454010000000000000000000000000000000000ffff7ba55d22208d90e98754010000000000000000000000000000000000ffff5ae14503208d58f48754010000000000000000000000000000000000ffffd45a3cae208d530c8854010000000000000000000000000000000000ffff18fb80c4208dc6138854010000000000000000000000000000000000ffff4e2fd6eb208d97208854010000000000000000000000000000000000ffff55ddd5b8208d74f88654010000000000000000000000000000000000ffff1809ad86208dc0278854010000000000000000000000000000000000ffff6ef2dfb6208d84338854010000000000000000000000000000000000ffff58d8114e208dcae78754010000000000000000000000000000000000ffffc21c474c208d61e78754010000000000000000000000000000000000ffffd5b3fc7a208d7a078854010000000000000000000000000000000000ffff32740193208de7547b54010000000000000000000000000000000000ffff8046aac3208db5328854010000000000000000000000000000000000ffff5b0c5366208d5f208854010000000000000000000000000000000000ffff6caa054a208d1d682854010000000000000000000000000000000000ffff69e3eb9d208d179e6b54010000000000000000000000000000000000ffff54aac628208ded2e8854010000000000000000000000000000000000ffff4166d226208d8e148854010000000000000000000000000000000000ffff54305af6208d1ed88754010000000000000000000000000000000000ffff3ed2b2a4208d78ec8554010000000000000000000000000000000000ffff6dd2e575208ddd3e7854010000000000000000000000000000000000ffff1b83a102208d421e8854010000000000000000000000000000000000ffff2eb51bf1208dc01f8854010000000000000000000000000000000000ffff58c66033208d08ce8754010000000000000000000000000000000000ffff5d57b84c208df22e8854010000000000000000000000000000000000ffffbf213e3d208d89cb8754010000000000000000000000000000000000ffff53d7ece1208da9d68754010000000000000000000000000000000000ffff71bef412208d38268854010000000000000000000000000000000000ffff84fc8a6f208d4f107254010000000000000000000000000000000000ffff57a072d9208d990f88540100000000000000200100009d3890d71029db21863020f9208df3c36854010000000000000000000000000000000000ffffbba6a6c4208d3ea71f54010000000000000000000000000000000000ffff3e61237b208d60a18754010000000000000000000000000000000000ffff53a56b4b208db2328854010000000000000000000000000000000000ffff44eef265208d2c0f8854010000000000000000000000000000000000ffff566553c0208dec268854010000000000000000000000000000000000ffff57a69ecd208d93148854010000000000000000000000000000000000ffff9b8f4433208d741b8854010000000000000000000000000000000000ffffae17c74c208ddab94f54010000000000000000000000000000000000ffffab19c63a208d5e886f54010000000000000000000000000000000000ffffbc67a037208d5c3ccc53010000000000000000000000000000000000ffff0e77c86e208d26fb8754010000000000000000000000000000000000ffffc1eae172208d61278854010000000000000000000000000000000000ffff5ce15439208d60df8754010000000000000000000000000000000000ffff2599fb3b208d52418854010000000000000000000000000000000000ffff028710e6208dae448854010000000000000000000000000000000000ffff18152eee208d6ff18754010000000000000000000000000000000000ffff542e396d208d8c248854010000000000000000000000000000000000ffff5519d6d8208d01e33e54010000000000000000000000000000000000ffff5027d563208d05cd8754010000000000000000000000000000000000ffff45a5a9c5208d7c1e8854010000000000000000000000000000000000ffff68c813c8208d38a48754010000000000000000000000000000000000ffff3a60a934208d73ec8754010000000000000000000000000000000000ffffae3db8f3208d096c8754010000000000000000000000000000000000ffff4cb88802208d55d38754010000000000000000000000000000000000ffffd31f082e208d0d8a8754010000000000000000000000000000000000ffff6c22c9bb208d81ba7754010000000000000000000000000000000000ffffca5f88da208d5df88754010000000000000000000000000000000000ffffc31abc05208db1d58754010000000000000000000000000000000000ffff5cf614c8208d2b257c54010000000000000000000000000000000000ffff45f9baa0208d38d78754010000000000000000000000000000000000ffff490e0608208d27e88754010000000000000000000000000000000000ffff17f1ccd0208d501c8854010000000000000000000000000000000000ffff5be834a7208d860b8854010000000000000000000000000000000000ffff640173bc208deada8754010000000000000000000000000000000000ffffdc98f8db208d983d3c54010000000000000000000000000000000000ffff021ab398208d99f28754010000000000000000000000000000000000ffff59a9e417208d19468854010000000000000000000000000000000000ffff91ff01a1208ddbf68754010000000000000000000000000000000000ffff50048949208d89118854010000000000000000000000000000000000ffffc8366923208dea3b8854010000000000000000000000000000000000ffffbcbbb5a9208d364a8854010000000000000000000000000000000000ffffc654bd58208dd52ef953010000000000000000000000000000000000ffff7cabb7bc208db086cc53010000000000000000000000000000000000ffff6c4004ea208d0ff48754010000000000000000000000000000000000ffff0536a3cb208d1c0b8854010000000000000000000000000000000000ffffd90bfef3208dbf188854010000000000000000000000000000000000ffff0252431f208dd1c78754010000000000000000000000000000000000ffff25732b19208dd6fa3954010000000000000000000000000000000000ffff6c1131dc208da6108854010000000000000000000000000000000000ffff1fb9b748208daf368854010000000000000000000000000000000000ffff1f2a2981208d9e2f8854010000000000000000000000000000000000ffffbc658482208d6a458854010000000000000000000000000000000000ffff568b843a208dcd428854010000000000000000000000000000000000ffff5d820794208d38258854010000000000000000000000000000000000ffff6440c630208d1cda8754010000000000000000000000000000000000ffff6e9f7be5208ddd388854010000000000000000000000000000000000ffff9f080244208d2e2d8854010000000000000000000000000000000000ffffbb70cf3d208d3e0c8854010000000000000000000000000000000000ffff3ec27a16208d89098854010000000000000000000000000000000000ffffb55fb2fb208d13468854010000000000000000000000000000000000ffff180ab1e5208dfc2a8854010000000000000000000000000000000000ffffd58a5c0e208db9188854010000000000000000000000000000000000ffff81ce808d208d03d06b54010000000000000000000000000000000000ffff4b496858208d319e8454010000000000000000000000000000000000ffff531fac9f208dc8d98754010000000000000000000000000000000000ffffc113e4ea208d06395754010000000000000000000000000000000000ffff7cabf0d6208d722b8854010000000000000000000000000000000000ffff545db4cd208dd91f8854010000000000000000000000000000000000ffff5f12a597208dfbd57054010000000000000000000000000000000000ffff3cf654e6208dd7358854010000000000000000000000000000000000ffffb4998e79208dd7018854010000000000000000000000000000000000ffffb45cc220208d86df5554010000000000000000000000000000000000ffff7660d462208decd28754010000000000000000000000000000000000ffff5b7bdfe2208dafe28754010000000000000000000000000000000000ffff2e76899e208d0c318854010000000000000000000000000000000000ffffc7bcb183208d35258854010000000000000000000000000000000000ffff3ab2d076208db5c38754010000000000000000000000000000000000ffff48b24d30208ddfd18754010000000000000000000000000000000000ffff904cb00c208d40478854010000000000000000000000000000000000ffff02a3b787208deb318854010000000000000000000000000000000000ffffd2564026208de63f8854010000000000000000000000000000000000ffff615d16c0208dbf1d8854010000000000000000000000000000000000ffffc6fff6f0208d4aee8754010000000000000000000000000000000000ffff56c74bc8208d68bc8754010000000000000000000000000000000000ffff31b581f9208ddcca8754010000000000000000000000000000000000ffff0e97397a208db2318854010000000000000000000000000000000000ffffc766348d208de1bd8754010000000000000000000000000000000000ffffd5927a3d208d2aff8754010000000000000000000000000000000000ffff5152d0a5208da7feb753010000000000000000000000000000000000ffff53fe1694208de7418854010000000000000000000000000000000000ffff189f3d99208de7b38754010000000000000000000000000000000000ffff64004655208dacabb653010000000000000000000000000000000000ffff55413231208d43b88754010000000000000000000000000000000000ffff796e1029208d03788754010000000000000000000000000000000000ffff49b90ff6208d04be8754010000000000000000000000000000000000ffffc327ce1c208d33648254010000000000000000000000000000000000ffff2e896478208d9e308854010000000000000000000000000000000000ffffd395d9bf208df13e8854010000000000000000000000000000000000ffff51bb88ed208dec8c5653010000000000000000000000000000000000ffff557f146e208df4538854010000000000000000000000000000000000ffff48d05928208d41bd8154010000000000000000000000000000000000ffff47384162208df6428854010000000000000000000000000000000000ffffc1566314208d753e8854010000000000000000000000000000000000ffff440c9b4f208d2f078854010000000000000000000000000000000000ffff56021a19208d7b0f4754010000000000000000000000000000000000ffff71653a84208dcee88754010000000000000000000000000000000000ffff5401a332208df4db8754010000000000000000000000000000000000ffff5a9db0e3208df8e38754010000000000000000000000000000000000ffff5d5208fa208d2e298854010000000000000000000000000000000000ffff4c61c0bc208d42ea8754010000000000000000000000000000000000ffff4c405ae7208deade8754010000000000000000000000000000000000ffffc009c823208d99358854010000000000000000000000000000000000ffff6cc2b493208df3fd8754010000000000000000000000000000000000ffff904c6007208d1fde8754010000000000000000000000000000000000ffff92732ae8208d666e8354010000000000000000000000000000000000ffff6daa9d16208d18f78754010000000000000000000000000000000000ffff4d6d8d8a208ddea9ce53010000000000000000000000000000000000ffff17e26f7d208d1ae48754010000000000000000000000000000000000ffffc32ebb89208dee258854010000000000000000000000000000000000ffff4a0f10e1208d290a8854010000000000000000000000000000000000ffff42731593208d7cb33354010000000000000000000000000000000000ffff532e8b97208dfd1e8854010000000000000000000000000000000000ffff6ee91680208d480a1354010000000000000000000000000000000000ffff60f220f6208d55838754010000000000000000000000000000000000ffff1f36d48f208d247e8754010000000000000000000000000000000000ffff44b5a49a208d0efa4954010000000000000000000000000000000000ffff63890597208d862e8854010000000000000000000000000000000000ffffc14d8751208d443c8854010000000000000000000000000000000000ffff70d16dba208d72fe8754010000000000000000000000000000000000ffff55983dd3208d236b6854010000000000000000000000000000000000ffff7aea27f9208d67e08754010000000000000000000000000000000000ffff1b20750d208df10a7154010000000000000000000000000000000000ffff7b9fe56e208db32d8854010000000000000000000000000000000000ffff7ba42d54208db3f58754010000000000000000000000000000000000ffff027b4eba208d70d68754010000000000000000000000000000000000ffffbc1b62b9208d97951454010000000000000000000000000000000000ffffb4b6af10208d251288540100000000000000200100009d386ab800773149c4d7e673208d7b168854010000000000000000000000000000000000ffff58969392208d91d18754010000000000000000000000000000000000ffff9ffd6d4e208d19008854010000000000000000000000000000000000ffff4e926cb8208dd1c98754010000000000000000000000000000000000ffff546cdb77208d6a0a8854010000000000000000000000000000000000ffff6caa8c15208d3a3a8854010000000000000000000000000000000000ffff62e28d6a208d3fe48754010000000000000000000000000000000000ffffd9850be7208dcb438854010000000000000000000000000000000000ffff6c2444b3208d78338854010000000000000000000000000000000000ffffb2df574a208d3b2d3054010000000000000000000000000000000000ffff4b529e67208d5b988754010000000000000000000000000000000000ffff36e152e6208d01ab8754010000000000000000000000000000000000ffff58b531bf208d7b038854010000000000000000000000000000000000ffff54717a6f208db0388854010000000000000000000000000000000000ffff4a694e98208d65418854010000000000000000000000000000000000ffffcef84b25208dcf218854010000000000000000000000000000000000ffff5364fed9208db6c68754010000000000000000000000000000000000ffff4d03decd208dc6158854010000000000000000000000000000000000ffffb9044c79208d65418854010000000000000000000000000000000000ffff56b2192f208d23308754010000000000000000000000000000000000ffffd973f7a4208d05158854010000000000000000000000000000000000ffff1b6d9975208d52cd8754010000000000000000000000000000000000ffffdf5b92ad208d3a128854010000000000000000000000000000000000ffff6d93787e208d351e8854010000000000000000000000000000000000ffffb6a4336f208d9bd58754010000000000000000000000000000000000ffff3ba7f521208dc3288854010000000000000000000000000000000000ffff6bbf20cc208d68998754010000000000000000000000000000000000ffff63b30032208d86848754010000000000000000000000000000000000ffffb52ef15c208dff108854010000000000000000000000000000000000ffff4d06718b208d226f0754010000000000000000000000000000000000ffff44248ba1208da3af7354010000000000000000000000000000000000ffff86f9d72c208d9fd68754010000000000000000000000000000000000ffff521f2444208dae428854010000000000000000000000000000000000ffffd445b025208dfafc8754010000000000000000000000000000000000ffffd447fa84208dbb358854010000000000000000000000000000000000ffff3ee2d196208da01a8854010000000000000000000000000000000000ffff45a615b2208d32edb053010000000000000000000000000000000000ffff6d6494da208d77ed8754010000000000000000000000000000000000ffff555dcc44208d09608754010000000000000000000000000000000000ffff45a5d222208d84558754010000000000000000000000000000000000ffffb01c30ab208d71fe8754010000000000000000000000000000000000ffffae5f68c2208de0acdf53010000000000000000000000000000000000ffffae737936208d29e18754010000000000000000000000000000000000ffffb00a63cb208d220b8854010000000000000000000000000000000000ffff9ec4d12f208db4408854010000000000000000000000000000000000ffff4c6d9bcc208d65a58754010000000000000000000000000000000000ffff5962c453208d2d628854010000000000000000000000000000000000ffff257191d50000' +}; + +var PING = { + message: 'f9beb4d9706f6e67000000000000000008000000c6466f1e6b86480ae969867c', + payload: '6b86480ae969867c' +}; + +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); + message.subversion.should.equal('/BitcoinX:0.1/'); + 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(VERSION.payload, 'hex'); + var message = 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(VERACK.payload, 'hex'); + var message = 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(INV.payload, 'hex'); + var message = 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(ADDR.payload, 'hex'); + var message = 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(PING.payload, 'hex'); + var message = 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(PING.payload, 'hex'); + var message = 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 = 'f9beb4d91d0100000100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000'; + 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); + }); + }); +}); From 57968674ef07e6a4e99d82c9a23bc7b4dfebafd6 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Wed, 10 Dec 2014 18:00:34 -0300 Subject: [PATCH 14/21] Add a better check on networkMaps --- lib/networks.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/networks.js b/lib/networks.js index 8b6b85f..12a1d4e 100644 --- a/lib/networks.js +++ b/lib/networks.js @@ -60,12 +60,14 @@ _.extend(testnet, { var networkMaps = {}; _.each(_.values(livenet), function(value) { - if (_.isArray(value)) return; - networkMaps[value] = livenet; + if (!_.isObject(value)) { + networkMaps[value] = livenet; + } }); _.each(_.values(testnet), function(value) { - if (_.isArray(value)) return; - networkMaps[value] = testnet; + if (!_.isObject(value)) { + networkMaps[value] = testnet; + } }); /** From 63022783dd0cc99d7d7b1d214b77312f3611f490 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 11 Dec 2014 11:31:52 -0300 Subject: [PATCH 15/21] Add developer guide section --- docs/Peer.md | 92 +++++++++++++++++++++++++++++++++++++++++++ lib/transport/peer.js | 2 + 2 files changed, 94 insertions(+) create mode 100644 docs/Peer.md diff --git a/docs/Peer.md b/docs/Peer.md new file mode 100644 index 0000000..7bcbc39 --- /dev/null +++ b/docs/Peer.md @@ -0,0 +1,92 @@ +# Peer + +Represents a node from the p2p bitcoin network. The Peer class supports connecting directly to other nodes or through a socks5 proxy like Tor. + +The code to create a new peer looks like this: + +```javascript +var bitcore = require('bitcore'); +var Peer = bitcore.transport.Peer; + +// default port +var livenetPeer = new Peer('5.9.85.34'); +var testnetPeer = new Peer('5.9.85.34', bitcore.testnet); + +// custom port +var livenetPeer = new Peer('5.9.85.34', 8334); +var testnetPeer = new Peer('5.9.85.34', 18334, bitcore.testnet); + +// use sock5 proxy (Tor) +var peer = new Peer('5.9.85.34').setProxy('localhost', 9050); +``` + +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. + +You can subscribe to the change of those states as follows: + +```javascript +var bitcore = require('bitcore'); +var Peer = bitcore.transport.Peer; + +var peer = new Peer('5.9.85.34'); + +peer.on('ready', function() { + // peer info + console.log(peer.version, peer.subversion, peer.bestHeight); +}); + +peer.on('disconnect', function() { + console.log('connection closed'); +}); + +peer.connect(); +``` + +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 bitcore = require('bitcore'); +var peer = new bitcore.transport.Peer('5.9.85.34'); + +// handle events +peer.on('inv', function(message) { + // message.inventory[] +}); + +peer.on('tx', function(message) { + // message.transaction +}); + +peer.on('addr', function(message) { + // message.addresses[] +}); + +peer.connect(); +``` + +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 on the `bitcore.transport.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: + +```javascript +var bitcore = require('bitcore'); +var peer = new bitcore.transport.Peer('5.9.85.34'); + +peer.on('ready', function() { + var message = new bitcore.transport.Messages.GetAddresses(); + peer.sendMessage(message); +}); + +peer.on('addr', function(message) { + message.addresses.forEach(function(address) { + // do something + }); +}); + +peer.connect(); +``` diff --git a/lib/transport/peer.js b/lib/transport/peer.js index bdb03a2..724cd2e 100644 --- a/lib/transport/peer.js +++ b/lib/transport/peer.js @@ -141,6 +141,8 @@ 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)); From 3e775bd0f95e605905202e62a763eb33652d9ae8 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 11 Dec 2014 11:42:56 -0300 Subject: [PATCH 16/21] Move messages data to a separate file --- test/data/messages.js | 24 ++++++++++++++++++++++++ test/transport/messages.js | 38 +++++++------------------------------- 2 files changed, 31 insertions(+), 31 deletions(-) create mode 100644 test/data/messages.js diff --git a/test/data/messages.js b/test/data/messages.js new file mode 100644 index 0000000..d7d6e92 --- /dev/null +++ b/test/data/messages.js @@ -0,0 +1,24 @@ +'use strict'; + +module.exports = { + VERSION: { + message: 'f9beb4d976657273696f6e000000000065000000fc970f17721101000100000000000000ba62885400000000010000000000000000000000000000000000ffffba8886dceab0010000000000000000000000000000000000ffff05095522208de7e1c1ef80a1cea70f2f5361746f7368693a302e392e312fa317050001', + payload: '721101000100000000000000ba62885400000000010000000000000000000000000000000000ffffba8886dceab0010000000000000000000000000000000000ffff05095522208de7e1c1ef80a1cea70f2f5361746f7368693a302e392e312fa317050001' + }, + VERACK: { + message: 'f9beb4d976657261636b000000000000000000005df6e0e2', + payload: '' + }, + INV: { + message: 'f9beb4d9696e76000000000000000000890200006e4f18431201000000f97347795bf7490ddeba98c129086f54e06633936a49a2a398defb49e5edbb00010000009cb17394db9280b2d5e7d9f01456358384e7453300d48138ca1590e8cd86632f010000000f810b6071b9808117c4b82619b4ae0a7994ed5a0f2c050e50fd5742c3b4e90901000000962798bce9cbfcee9a8c6b5df4cf8bed944eb31ec975e8c1be79b7ab010a1cb501000000baa905bcfc9d2641ecd759724f796a7614e91c7492507b418ba8e077a6e57054010000009a5ac6dae146392edf8b6e96c30951ed7752f9095fab61728207db0f135b18a30100000050e5f153e3f0d89ea432d9a26f088a89b3ca38dbf3f0247ddf15f80779915da801000000c255841510a284b88ba696ed9edfeb4cdb34966271c30bc3bce9fd881106850f0100000000e45af8455894f72cccc4053e1f5e6076a673982b1f4ca5b2abc7b6496823ab01000000c7e6dc049e5b0fdc0f8fac8934e84e9b2f0c3036c7c02638cc05ee2575d6be1701000000aef6435c03c99ee83702aaaf106dc853dee5bcb6025f3af67d1ec72202c437ad010000005369fc3dc81403fe52f766ef585245af908404be1b46e2ef67c93b748ef467e3010000002400f16a63c411ae5e336175afc26515e548c7267c0debdb6aa46830399ba35f01000000ceb4fa6a8ca2713baee2214422b73da47a20934c83d25017bd80053e65ef3091010000000d899968e591703ddd6fd6ce073837588209f8b3b245ecf1bf93e77b9806a6c501000000bddeb56581ad7882d8f2abc78e5a48b3de02d923cd1cfc7525d2dfe80470248a01000000f286188c0947b023f0ba5dd1ea596751d50f67fbe31e64ead39dd711d3585b5801000000e17ad100ebffd2d5a630d37fd2496b2f5ab6ae5c8812da3c642fb6b8dd37f5fd', + payload: '1201000000f97347795bf7490ddeba98c129086f54e06633936a49a2a398defb49e5edbb00010000009cb17394db9280b2d5e7d9f01456358384e7453300d48138ca1590e8cd86632f010000000f810b6071b9808117c4b82619b4ae0a7994ed5a0f2c050e50fd5742c3b4e90901000000962798bce9cbfcee9a8c6b5df4cf8bed944eb31ec975e8c1be79b7ab010a1cb501000000baa905bcfc9d2641ecd759724f796a7614e91c7492507b418ba8e077a6e57054010000009a5ac6dae146392edf8b6e96c30951ed7752f9095fab61728207db0f135b18a30100000050e5f153e3f0d89ea432d9a26f088a89b3ca38dbf3f0247ddf15f80779915da801000000c255841510a284b88ba696ed9edfeb4cdb34966271c30bc3bce9fd881106850f0100000000e45af8455894f72cccc4053e1f5e6076a673982b1f4ca5b2abc7b6496823ab01000000c7e6dc049e5b0fdc0f8fac8934e84e9b2f0c3036c7c02638cc05ee2575d6be1701000000aef6435c03c99ee83702aaaf106dc853dee5bcb6025f3af67d1ec72202c437ad010000005369fc3dc81403fe52f766ef585245af908404be1b46e2ef67c93b748ef467e3010000002400f16a63c411ae5e336175afc26515e548c7267c0debdb6aa46830399ba35f01000000ceb4fa6a8ca2713baee2214422b73da47a20934c83d25017bd80053e65ef3091010000000d899968e591703ddd6fd6ce073837588209f8b3b245ecf1bf93e77b9806a6c501000000bddeb56581ad7882d8f2abc78e5a48b3de02d923cd1cfc7525d2dfe80470248a01000000f286188c0947b023f0ba5dd1ea596751d50f67fbe31e64ead39dd711d3585b5801000000e17ad100ebffd2d5a630d37fd2496b2f5ab6ae5c8812da3c642fb6b8dd37f5fd' + }, + ADDR: { + message: 'f9beb4d9616464720000000000000000b93a0000480bab8afdf5016816fa53010000000000000000000000000000000000ffff51403eea208ddb2a8854010000000000000000000000000000000000ffff707c60d9208d31413a54010000000000000000000000000000000000ffff5ed5fd13208d182c8854010000000000000000000000000000000000ffff505f3f81208d65ecb853010000000000000000000000000000000000ffff54d72104208dcca94054010000000000000000000000000000000000ffff40bbe1f2208d73238854010000000000000000000000000000000000ffff55d683cd208d5d258854010000000000000000000000000000000000ffffd523a66d208dcbea87540100000000000000200100005ef579fd2c9d47be4b56d671208db3e58754010000000000000000000000000000000000ffffae373156208d11e58754810000000000000000000000000000000000ffff51071276208d38f48754010000000000000000000000000000000000ffff6d4372e0208d4f448854010000000000000000000000000000000000ffffc556cbea208deb208854010000000000000000000000000000000000ffff450eb059208d97336054010000000000000000000000000000000000ffff4d60444a208dad608754010000000000000000000000000000000000ffff01abd3c3208d2cbe8754010000000000000000000000000000000000ffff7963d8b8208d9dfa8754010000000000000000000000000000000000ffffcebe862c208da7f66554010000000000000000000000000000000000ffff62f2a82e208d430d8854010000000000000000000000000000000000ffff6deb6956208d08148854010000000000000000000000000000000000ffff4b9804ef208d950e8854010000000000000000000000000000000000ffff6b8dd2e1208d1cfc8754010000000000000000000000000000000000ffffb2fe1dab208de7458754010000000000000000000000000000000000ffff32cccb82208d12625654010000000000000000000000000000000000ffff55e6f35a208dd7908354010000000000000000000000000000000000ffff4c642e6f208d17398854010000000000000000000000000000000000ffff45ea14df208dc72d8854010000000000000000000000000000000000ffffdaa48b96208d57fb8654010000000000000000000000000000000000ffff5f0080a9208d4e9a6d54010000000000000000000000000000000000ffff6d94e741208d7a418854010000000000000000000000000000000000ffff6d43b0c1208d5c098854010000000000000000000000000000000000ffff9c11e7ea208d442d8854010000000000000000000000000000000000ffffd0424482208db142fb53010000000000000000000000000000000000ffff54344a4a208d70158854010000000000000000000000000000000000ffff538f8201208d39288854010000000000000000000000000000000000ffff57daab71208d262f8854010000000000000000000000000000000000ffff57e045b1208d91430654010000000000000000000000000000000000ffff4212def5208d7c468854010000000000000000000000000000000000ffff62a4f53b208d9c3e8854010000000000000000000000000000000000ffff5b736bcc208d4d074f54010000000000000000000000000000000000ffff43af9d9b208deaca8754010000000000000000000000000000000000ffff4a8084a0208d74f68754010000000000000000000000000000000000ffff46304541208d5d098854010000000000000000000000000000000000ffff6eabb626208d08228854010000000000000000000000000000000000ffff1f07b030208d959a8254010000000000000000000000000000000000ffff4fa996b3208d4bac8754010000000000000000000000000000000000ffff43a0605b208de4038854010000000000000000000000000000000000ffff4cb41a5b208ddc308854010000000000000000000000000000000000ffffb72e6072208deac86f54010000000000000000000000000000000000ffffd5b815e2208d1b118854010000000000000000000000000000000000ffff3ff8d0a4208db1258854010000000000000000000000000000000000ffff5edd3a6a208d20268754010000000000000000000000000000000000ffff59ed3cfc208d83ed8754010000000000000000000000000000000000ffff4460baea208d71278854010000000000000000000000000000000000ffff4583b547208da7968754010000000000000000000000000000000000ffffd1c34463208d9d428854010000000000000000000000000000000000ffff43c15bc5208d33448854010000000000000000000000000000000000ffff2e0d8a5c208dfa2e7154010000000000000000000000000000000000ffffceff2bca208d47288854010000000000000000000000000000000000ffff5b8d0190208d0e847954010000000000000000000000000000000000ffff5501b8c2208d552f8854010000000000000000000000000000000000ffff5cf3b604208d8c2e8854010000000000000000000000000000000000ffff58666b0d208dcbdd8754010000000000000000000000000000000000ffff59a85cf9208d04208854010000000000000000000000000000000000ffff5f60abe0208dba238854010000000000000000000000000000000000ffff2e77ce67208d62c68754010000000000000000000000000000000000ffffcbae5658208d41108854010000000000000000000000000000000000ffff49329ec8208ddf1d8854010000000000000000000000000000000000ffff603ada12208df90c8854010000000000000000000000000000000000ffff51e0c21c208d5dff8754010000000000000000000000000000000000ffff904ca539208de0118854010000000000000000000000000000000000ffff5c0cecf7208d1b2d88540100000000000000200100005ef579fb18f317b2aba4edfc208dfaa27f54010000000000000000000000000000000000ffff54c65a7c208dc2db8754010000000000000000000000000000000000ffffad0b7981208d88a58754010000000000000000000000000000000000ffff188a1995208de0d78754010000000000000000000000000000000000ffff62a7a6bd208d18f66b54010000000000000000000000000000000000ffffdb4fbe80208dfcf78754010000000000000000000000000000000000ffff67145102208d3d3a8854010000000000000000000000000000000000ffff64259bd6208dcaa98654010000000000000000000000000000000000ffff32c737e1208d3a6a5c54010000000000000000000000000000000000ffff6d793f75208d22d38754010000000000000000000000000000000000ffff0595cc0d208da9ea8754010000000000000000000000000000000000ffff62ca37d3208d440e8854010000000000000000000000000000000000ffff123e1c11208dab428854010000000000000000000000000000000000ffff461df51f208d0f0b8854010000000000000000000000000000000000ffff59d36752208d631f8854010000000000000000000000000000000000ffff5ef8c640208dbc1d8854010000000000000000000000000000000000ffff5f5dc582208d04bf8754010000000000000000000000000000000000ffff0e6797e3208db8028854010000000000000000000000000000000000ffff3cf0c4a1208d6a0c8854010000000000000000000000000000000000ffff58bb5c55208d4f438854010000000000000000000000000000000000ffff5d326005208df6068854010000000000000000000000000000000000ffff5d5a5850208d40eb8754010000000000000000000000000000000000ffff5751ed86208dbad18754010000000000000000000000000000000000ffff63e51608208d99108854010000000000000000000000000000000000ffff36e299cd208d9a1e88540100000000000000200100005ef579fd38a43785a5d441b7208d13258854010000000000000000000000000000000000ffffb90de279208db3778754010000000000000000000000000000000000ffff67fcc80c208d3d358854010000000000000000000000000000000000ffffd00c40fc208dfd0d8854010000000000000000000000000000000000ffff5e89c2f3208dd6428854010000000000000000000000000000000000ffff1863e14a208d89e78754010000000000000000000000000000000000ffff43a11d76208d89ce8754010000000000000000000000000000000000ffff3edb6281208d03298854010000000000000000000000000000000000ffff6d5b26dd208d16df6e54010000000000000000000000000000000000ffffd9c3d176208d7c028854010000000000000000000000000000000000ffffbcc2db1f208d67fc8754010000000000000000000000000000000000ffff59910667208d86b18754010000000000000000000000000000000000ffff6c1482b9208d790b8854010000000000000000000000000000000000ffff0263150d208d89c44554010000000000000000000000000000000000ffff72fcd6b8208d331c8854010000000000000000000000000000000000ffff4c7dcfa8208d9fe98754010000000000000000000000000000000000ffffad08fd05208d0ecd8754010000000000000000000000000000000000ffff7c0fec89208d4af98754010000000000000000000000000000000000ffff2ea6a167208d270b8854010000000000000000000000000000000000ffff59d40fca208d11cf4054010000000000000000000000000000000000ffffb52ac058208d63358254010000000000000000000000000000000000ffff6bd9a130208dbde48754010000000000000000000000000000000000ffff4f915dc7208dece78754010000000000000000000000000000000000ffff5f8423ef208dba308854010000000000000000000000000000000000ffff904cba8c208d953d8854010000000000000000000000000000000000ffff8d1421e5208d5f188854010000000000000000000000000000000000ffff4e1bbfb6208d36fc8754010000000000000000000000000000000000ffff3205cfb6208d5e458854010000000000000000000000000000000000ffff493092c2208dd0878754010000000000000000000000000000000000ffff97caacde208dd99d8754010000000000000000000000000000000000ffffc05f3c38208da7018854010000000000000000000000000000000000ffffdcf5f82d208df0f42254010000000000000000000000000000000000ffff58bf997f208d94228854010000000000000000000000000000000000ffffbc8ee72d208de3448854010000000000000000000000000000000000ffff6d780f99208d32448854010000000000000000000000000000000000ffffbc64c76e208d0b3d8854010000000000000000000000000000000000ffff6c414312208d49138854010000000000000000000000000000000000ffff407e52c6208d2d128854010000000000000000000000000000000000ffff6dc987d8208d4acf8754010000000000000000000000000000000000ffffae151a25208de9dd8754010000000000000000000000000000000000ffff522d2d10208d17df8754010000000000000000000000000000000000ffff4d294737208ddbd48754010000000000000000000000000000000000ffff443703fd208d79108854010000000000000000000000000000000000ffff4bb5a154208d1df28753010000000000000000000000000000000000ffff4c44fc51208d5d496a53010000000000000000000000000000000000ffff530924b0208d180b8854010000000000000000000000000000000000ffffc15fced3208d53128854010000000000000000000000000000000000ffff5bc5b9e4208de21f8854010000000000000000000000000000000000ffff2e05fe1f208d31ab6554010000000000000000000000000000000000ffffae865915208d09248854010000000000000000000000000000000000ffff7d71a4b0208d120b8854010000000000000000000000000000000000ffff93e40150208d25d98754010000000000000000000000000000000000ffffc19f7a86208d99e78754010000000000000000000000000000000000ffff4d675993208d2b218854010000000000000000000000000000000000ffff5144776c208d5a208854010000000000000000000000000000000000ffff2e1cce5820f69e8f8154010000000000000000000000000000000000ffff58718848208d9edd6554010000000000000000000000000000000000ffff2ea758c5208d28c98754010000000000000000000000000000000000ffffc60bd694208d181d8854010000000000000000000000000000000000ffffba6b689e208d77868754010000000000000000000000000000000000ffff182016aa208de1698054010000000000000000000000000000000000ffff5cf5daa9208d823d8854010000000000000000000000000000000000ffffbe4d61c8208d2c3b8854010000000000000000000000000000000000ffff71647df2208da2248854010000000000000000000000000000000000ffff4ff359ca208da2dd8754010000000000000000000000000000000000ffff59f1e52b208da6e60d54010000000000000000000000000000000000ffff579596231159ad218854010000000000000000000000000000000000ffff5895f42d208d5ba78754010000000000000000000000000000000000ffff458ae8b0208ddc9b8754010000000000000000000000000000000000ffff18b507a5208d20eb5754010000000000000000000000000000000000ffff5181a537208daf1d8854010000000000000000000000000000000000ffffd5f51eba208d07f58754010000000000000000000000000000000000ffff6dec54bc208d4b3b8854010000000000000000000000000000000000ffff5c3e1991208d13b54054010000000000000000000000000000000000ffffb273845a208d4f338854010000000000000000000000000000000000ffffcfac797b208d4d898754010000000000000000000000000000000000ffffadaf880d208de72f8854010000000000000000000000000000000000ffffd956ef58208dc1e98754010000000000000000000000000000000000ffff980711cf208dfd8f7154010000000000000000000000000000000000ffff50aba98f208d59fe8754010000000000000000000000000000000000ffff5f2ae582208d3ffa8754010000000000000000000000000000000000ffffbca836db208d389b0054010000000000000000000000000000000000ffffded10925208deb154354010000000000000000000000000000000000ffff717532da208dadde7654010000000000000000000000000000000000ffff93afd0e5208d40458854010000000000000000000000000000000000ffff54ee8cb0208d08fa8754010000000000000000000000000000000000ffff3658eba9208d0df58754010000000000000000000000000000000000ffff72c6876e208d5d408854010000000000000000000000000000000000ffffa7582d7c208de8338854010000000000000000000000000000000000ffffad40d7ad208d6c2a8854010000000000000000000000000000000000ffffbc1833a3208d0fc28754010000000000000000000000000000000000ffffcbceb676208d98448854010000000000000000000000000000000000ffff0121ce6e208d77b58754010000000000000000000000000000000000ffff4b40d938208d853b8854010000000000000000000000000000000000ffff5648d643208d1c9c7854010000000000000000000000000000000000ffff6c3230db208d4e028754010000000000000000000000000000000000ffffd8e33c5d208daf738754010000000000000000000000000000000000ffffad4e9d56208dca438854010000000000000000000000000000000000ffff4f8a0353208d7b408854010000000000000000000000000000000000ffff0599e92a208dc62f8854010000000000000000000000000000000000ffff46236049208dc4d58754010000000000000000000000000000000000ffff4400384d208d0ddb4c54010000000000000000000000000000000000ffff71653b5a208d53388854010000000000000000000000000000000000ffff43bddf66208d7c388854010000000000000000000000000000000000ffff47edca96208d8c068854010000000000000000000000000000000000ffffdded3c2c208d89f60554010000000000000000000000000000000000ffff4e60d8d6208da5ff1154010000000000000000000000000000000000ffff55983c68208d72f58754010000000000000000000000000000000000ffffce47f56a208d7e2d8854010000000000000000000000000000000000ffff17e25c12208da7ca8754010000000000000000000000000000000000ffff051d0baa208d19af8754010000000000000000000000000000000000ffffad42d883208dffd68754010000000000000000000000000000000000ffff5418ff88208d87bf8754010000000000000000000000000000000000ffff4c1fe0dc208deef18754010000000000000000000000000000000000ffff4c71d406208d25868754010000000000000000000000000000000000ffffda67a475208d4d348854010000000000000000000000000000000000ffff5d561269208da5918754010000000000000000000000000000000000ffff42b163b3208dbfac3754010000000000000000000000000000000000ffffad4e01eb208d5c408854010000000000000000000000000000000000ffffcc0bb91e208d4a928754010000000000000000000000000000000000ffff531dba30208db9fd8754010000000000000000000000000000000000ffffa3f72b33208dade58754010000000000000000000000000000000000ffffd8b93aeb208db75d4d54010000000000000000000000000000000000ffffae1d5595208de8d18754010000000000000000000000000000000000ffff4405665c208d88e78754010000000000000000000000000000000000ffff6b96180a208dae667354010000000000000000000000000000000000ffff531f490a208d92f98754010000000000000000000000000000000000ffffd447e857208db1428854010000000000000000000000000000000000ffff36c6b4bb208dd42c8854010000000000000000000000000000000000ffff4b4aff14208df71acd53010000000000000000000000000000000000ffff62d17930208da0b88754010000000000000000000000000000000000ffff974bf913208d5faa7953010000000000000000000000000000000000ffff7419d269208d6c1e8854010000000000000000000000000000000000ffff48a72343208d9c2b8854010000000000000000000000000000000000ffff64431e87208de8908754010000000000000000000000000000000000ffff44660d45208d410b7d54010000000000000000000000000000000000ffff43a3340d208d5b298854010000000000000000000000000000000000ffff9ffda678208de7238854010000000000000000000000000000000000ffff189a3759208da5745354010000000000000000000000000000000000ffff57026e31208df23c885401000000000000002a010488006710000523fbe100000001208dddf48754010000000000000000000000000000000000ffffc1531c5b208d61888754010000000000000000000000000000000000ffff42cd8bc1208df92e8854010000000000000000000000000000000000ffff36e1e970208d0cf75754010000000000000000000000000000000000ffffad4e2420208d0f028854010000000000000000000000000000000000ffff6ec61b02208de6228854010000000000000000000000000000000000ffffdcaa807c208d1cac8754010000000000000000000000000000000000ffff6179416c208db6068854010000000000000000000000000000000000ffffd46121e4208d7ea58754010000000000000000000000000000000000ffff80a41679208d91216254010000000000000000000000000000000000ffff69ece091208d0bf78754010000000000000000000000000000000000ffff45320c0b208dd03c8854010000000000000000000000000000000000ffff4859a2a5208d8e308854010000000000000000000000000000000000ffff2e1ce15a208d30f36354010000000000000000000000000000000000ffff4b61ee15208d7aa05553010000000000000000000000000000000000ffffb129186b208dbb238854010000000000000000000000000000000000ffff581a7f6f208d41248754010000000000000000000000000000000000ffffb009055b208dee218854010000000000000000000000000000000000ffff2e7771f9208db4e78754010000000000000000000000000000000000ffff411bf6ee208db5178854010000000000000000000000000000000000ffffacff001b208dbcf487540100000000000000200100009d386ab83061cfabfe3513a3208d76048854010000000000000000000000000000000000ffff2e35dbc2208d83d28754010000000000000000000000000000000000ffffd91bb1d5208d5c3d8854010000000000000000000000000000000000ffff32a47969208df4bd875401000000000000002a0104f8012032250000000000000002208d1bfa8754010000000000000000000000000000000000ffff53a2f4b6208d141c4554010000000000000000000000000000000000ffffb27b8281208d580c7e54010000000000000000000000000000000000ffffc9ea1340208df7228854010000000000000000000000000000000000ffffd90a268d208d41e18754010000000000000000000000000000000000ffff555b8809208d66358854010000000000000000000000000000000000ffff5280ff23208deecd8754010000000000000000000000000000000000ffff84c6a102208df9bc8754010000000000000000000000000000000000ffffbbc96dae208d9d0a8854010000000000000000000000000000000000ffffb2a2d18a208da9458854010000000000000000000000000000000000ffffca16c30e208d2ca88754010000000000000000000000000000000000ffff6baab63e208d923b8854010000000000000000000000000000000000ffffd5b87bee208d12258854010000000000000000000000000000000000ffff59ee408b208d980e8854010000000000000000000000000000000000ffff2edfb0c4208d33378854010000000000000000000000000000000000ffff6caa7b42208d41176d53010000000000000000000000000000000000ffff1fa2df68208dd1c57f54010000000000000000000000000000000000ffffbeab679a208d17c78754010000000000000000000000000000000000ffff3f983f51208dbb278854010000000000000000000000000000000000ffff568dbc28208d5d5ad953010000000000000000000000000000000000ffff0ec112b7208da1b78754010000000000000000000000000000000000ffffbc287698208d99348854010000000000000000000000000000000000ffff5ff1a302208d23ef8454010000000000000000000000000000000000ffff7ba55d22208d90e98754010000000000000000000000000000000000ffff5ae14503208d58f48754010000000000000000000000000000000000ffffd45a3cae208d530c8854010000000000000000000000000000000000ffff18fb80c4208dc6138854010000000000000000000000000000000000ffff4e2fd6eb208d97208854010000000000000000000000000000000000ffff55ddd5b8208d74f88654010000000000000000000000000000000000ffff1809ad86208dc0278854010000000000000000000000000000000000ffff6ef2dfb6208d84338854010000000000000000000000000000000000ffff58d8114e208dcae78754010000000000000000000000000000000000ffffc21c474c208d61e78754010000000000000000000000000000000000ffffd5b3fc7a208d7a078854010000000000000000000000000000000000ffff32740193208de7547b54010000000000000000000000000000000000ffff8046aac3208db5328854010000000000000000000000000000000000ffff5b0c5366208d5f208854010000000000000000000000000000000000ffff6caa054a208d1d682854010000000000000000000000000000000000ffff69e3eb9d208d179e6b54010000000000000000000000000000000000ffff54aac628208ded2e8854010000000000000000000000000000000000ffff4166d226208d8e148854010000000000000000000000000000000000ffff54305af6208d1ed88754010000000000000000000000000000000000ffff3ed2b2a4208d78ec8554010000000000000000000000000000000000ffff6dd2e575208ddd3e7854010000000000000000000000000000000000ffff1b83a102208d421e8854010000000000000000000000000000000000ffff2eb51bf1208dc01f8854010000000000000000000000000000000000ffff58c66033208d08ce8754010000000000000000000000000000000000ffff5d57b84c208df22e8854010000000000000000000000000000000000ffffbf213e3d208d89cb8754010000000000000000000000000000000000ffff53d7ece1208da9d68754010000000000000000000000000000000000ffff71bef412208d38268854010000000000000000000000000000000000ffff84fc8a6f208d4f107254010000000000000000000000000000000000ffff57a072d9208d990f88540100000000000000200100009d3890d71029db21863020f9208df3c36854010000000000000000000000000000000000ffffbba6a6c4208d3ea71f54010000000000000000000000000000000000ffff3e61237b208d60a18754010000000000000000000000000000000000ffff53a56b4b208db2328854010000000000000000000000000000000000ffff44eef265208d2c0f8854010000000000000000000000000000000000ffff566553c0208dec268854010000000000000000000000000000000000ffff57a69ecd208d93148854010000000000000000000000000000000000ffff9b8f4433208d741b8854010000000000000000000000000000000000ffffae17c74c208ddab94f54010000000000000000000000000000000000ffffab19c63a208d5e886f54010000000000000000000000000000000000ffffbc67a037208d5c3ccc53010000000000000000000000000000000000ffff0e77c86e208d26fb8754010000000000000000000000000000000000ffffc1eae172208d61278854010000000000000000000000000000000000ffff5ce15439208d60df8754010000000000000000000000000000000000ffff2599fb3b208d52418854010000000000000000000000000000000000ffff028710e6208dae448854010000000000000000000000000000000000ffff18152eee208d6ff18754010000000000000000000000000000000000ffff542e396d208d8c248854010000000000000000000000000000000000ffff5519d6d8208d01e33e54010000000000000000000000000000000000ffff5027d563208d05cd8754010000000000000000000000000000000000ffff45a5a9c5208d7c1e8854010000000000000000000000000000000000ffff68c813c8208d38a48754010000000000000000000000000000000000ffff3a60a934208d73ec8754010000000000000000000000000000000000ffffae3db8f3208d096c8754010000000000000000000000000000000000ffff4cb88802208d55d38754010000000000000000000000000000000000ffffd31f082e208d0d8a8754010000000000000000000000000000000000ffff6c22c9bb208d81ba7754010000000000000000000000000000000000ffffca5f88da208d5df88754010000000000000000000000000000000000ffffc31abc05208db1d58754010000000000000000000000000000000000ffff5cf614c8208d2b257c54010000000000000000000000000000000000ffff45f9baa0208d38d78754010000000000000000000000000000000000ffff490e0608208d27e88754010000000000000000000000000000000000ffff17f1ccd0208d501c8854010000000000000000000000000000000000ffff5be834a7208d860b8854010000000000000000000000000000000000ffff640173bc208deada8754010000000000000000000000000000000000ffffdc98f8db208d983d3c54010000000000000000000000000000000000ffff021ab398208d99f28754010000000000000000000000000000000000ffff59a9e417208d19468854010000000000000000000000000000000000ffff91ff01a1208ddbf68754010000000000000000000000000000000000ffff50048949208d89118854010000000000000000000000000000000000ffffc8366923208dea3b8854010000000000000000000000000000000000ffffbcbbb5a9208d364a8854010000000000000000000000000000000000ffffc654bd58208dd52ef953010000000000000000000000000000000000ffff7cabb7bc208db086cc53010000000000000000000000000000000000ffff6c4004ea208d0ff48754010000000000000000000000000000000000ffff0536a3cb208d1c0b8854010000000000000000000000000000000000ffffd90bfef3208dbf188854010000000000000000000000000000000000ffff0252431f208dd1c78754010000000000000000000000000000000000ffff25732b19208dd6fa3954010000000000000000000000000000000000ffff6c1131dc208da6108854010000000000000000000000000000000000ffff1fb9b748208daf368854010000000000000000000000000000000000ffff1f2a2981208d9e2f8854010000000000000000000000000000000000ffffbc658482208d6a458854010000000000000000000000000000000000ffff568b843a208dcd428854010000000000000000000000000000000000ffff5d820794208d38258854010000000000000000000000000000000000ffff6440c630208d1cda8754010000000000000000000000000000000000ffff6e9f7be5208ddd388854010000000000000000000000000000000000ffff9f080244208d2e2d8854010000000000000000000000000000000000ffffbb70cf3d208d3e0c8854010000000000000000000000000000000000ffff3ec27a16208d89098854010000000000000000000000000000000000ffffb55fb2fb208d13468854010000000000000000000000000000000000ffff180ab1e5208dfc2a8854010000000000000000000000000000000000ffffd58a5c0e208db9188854010000000000000000000000000000000000ffff81ce808d208d03d06b54010000000000000000000000000000000000ffff4b496858208d319e8454010000000000000000000000000000000000ffff531fac9f208dc8d98754010000000000000000000000000000000000ffffc113e4ea208d06395754010000000000000000000000000000000000ffff7cabf0d6208d722b8854010000000000000000000000000000000000ffff545db4cd208dd91f8854010000000000000000000000000000000000ffff5f12a597208dfbd57054010000000000000000000000000000000000ffff3cf654e6208dd7358854010000000000000000000000000000000000ffffb4998e79208dd7018854010000000000000000000000000000000000ffffb45cc220208d86df5554010000000000000000000000000000000000ffff7660d462208decd28754010000000000000000000000000000000000ffff5b7bdfe2208dafe28754010000000000000000000000000000000000ffff2e76899e208d0c318854010000000000000000000000000000000000ffffc7bcb183208d35258854010000000000000000000000000000000000ffff3ab2d076208db5c38754010000000000000000000000000000000000ffff48b24d30208ddfd18754010000000000000000000000000000000000ffff904cb00c208d40478854010000000000000000000000000000000000ffff02a3b787208deb318854010000000000000000000000000000000000ffffd2564026208de63f8854010000000000000000000000000000000000ffff615d16c0208dbf1d8854010000000000000000000000000000000000ffffc6fff6f0208d4aee8754010000000000000000000000000000000000ffff56c74bc8208d68bc8754010000000000000000000000000000000000ffff31b581f9208ddcca8754010000000000000000000000000000000000ffff0e97397a208db2318854010000000000000000000000000000000000ffffc766348d208de1bd8754010000000000000000000000000000000000ffffd5927a3d208d2aff8754010000000000000000000000000000000000ffff5152d0a5208da7feb753010000000000000000000000000000000000ffff53fe1694208de7418854010000000000000000000000000000000000ffff189f3d99208de7b38754010000000000000000000000000000000000ffff64004655208dacabb653010000000000000000000000000000000000ffff55413231208d43b88754010000000000000000000000000000000000ffff796e1029208d03788754010000000000000000000000000000000000ffff49b90ff6208d04be8754010000000000000000000000000000000000ffffc327ce1c208d33648254010000000000000000000000000000000000ffff2e896478208d9e308854010000000000000000000000000000000000ffffd395d9bf208df13e8854010000000000000000000000000000000000ffff51bb88ed208dec8c5653010000000000000000000000000000000000ffff557f146e208df4538854010000000000000000000000000000000000ffff48d05928208d41bd8154010000000000000000000000000000000000ffff47384162208df6428854010000000000000000000000000000000000ffffc1566314208d753e8854010000000000000000000000000000000000ffff440c9b4f208d2f078854010000000000000000000000000000000000ffff56021a19208d7b0f4754010000000000000000000000000000000000ffff71653a84208dcee88754010000000000000000000000000000000000ffff5401a332208df4db8754010000000000000000000000000000000000ffff5a9db0e3208df8e38754010000000000000000000000000000000000ffff5d5208fa208d2e298854010000000000000000000000000000000000ffff4c61c0bc208d42ea8754010000000000000000000000000000000000ffff4c405ae7208deade8754010000000000000000000000000000000000ffffc009c823208d99358854010000000000000000000000000000000000ffff6cc2b493208df3fd8754010000000000000000000000000000000000ffff904c6007208d1fde8754010000000000000000000000000000000000ffff92732ae8208d666e8354010000000000000000000000000000000000ffff6daa9d16208d18f78754010000000000000000000000000000000000ffff4d6d8d8a208ddea9ce53010000000000000000000000000000000000ffff17e26f7d208d1ae48754010000000000000000000000000000000000ffffc32ebb89208dee258854010000000000000000000000000000000000ffff4a0f10e1208d290a8854010000000000000000000000000000000000ffff42731593208d7cb33354010000000000000000000000000000000000ffff532e8b97208dfd1e8854010000000000000000000000000000000000ffff6ee91680208d480a1354010000000000000000000000000000000000ffff60f220f6208d55838754010000000000000000000000000000000000ffff1f36d48f208d247e8754010000000000000000000000000000000000ffff44b5a49a208d0efa4954010000000000000000000000000000000000ffff63890597208d862e8854010000000000000000000000000000000000ffffc14d8751208d443c8854010000000000000000000000000000000000ffff70d16dba208d72fe8754010000000000000000000000000000000000ffff55983dd3208d236b6854010000000000000000000000000000000000ffff7aea27f9208d67e08754010000000000000000000000000000000000ffff1b20750d208df10a7154010000000000000000000000000000000000ffff7b9fe56e208db32d8854010000000000000000000000000000000000ffff7ba42d54208db3f58754010000000000000000000000000000000000ffff027b4eba208d70d68754010000000000000000000000000000000000ffffbc1b62b9208d97951454010000000000000000000000000000000000ffffb4b6af10208d251288540100000000000000200100009d386ab800773149c4d7e673208d7b168854010000000000000000000000000000000000ffff58969392208d91d18754010000000000000000000000000000000000ffff9ffd6d4e208d19008854010000000000000000000000000000000000ffff4e926cb8208dd1c98754010000000000000000000000000000000000ffff546cdb77208d6a0a8854010000000000000000000000000000000000ffff6caa8c15208d3a3a8854010000000000000000000000000000000000ffff62e28d6a208d3fe48754010000000000000000000000000000000000ffffd9850be7208dcb438854010000000000000000000000000000000000ffff6c2444b3208d78338854010000000000000000000000000000000000ffffb2df574a208d3b2d3054010000000000000000000000000000000000ffff4b529e67208d5b988754010000000000000000000000000000000000ffff36e152e6208d01ab8754010000000000000000000000000000000000ffff58b531bf208d7b038854010000000000000000000000000000000000ffff54717a6f208db0388854010000000000000000000000000000000000ffff4a694e98208d65418854010000000000000000000000000000000000ffffcef84b25208dcf218854010000000000000000000000000000000000ffff5364fed9208db6c68754010000000000000000000000000000000000ffff4d03decd208dc6158854010000000000000000000000000000000000ffffb9044c79208d65418854010000000000000000000000000000000000ffff56b2192f208d23308754010000000000000000000000000000000000ffffd973f7a4208d05158854010000000000000000000000000000000000ffff1b6d9975208d52cd8754010000000000000000000000000000000000ffffdf5b92ad208d3a128854010000000000000000000000000000000000ffff6d93787e208d351e8854010000000000000000000000000000000000ffffb6a4336f208d9bd58754010000000000000000000000000000000000ffff3ba7f521208dc3288854010000000000000000000000000000000000ffff6bbf20cc208d68998754010000000000000000000000000000000000ffff63b30032208d86848754010000000000000000000000000000000000ffffb52ef15c208dff108854010000000000000000000000000000000000ffff4d06718b208d226f0754010000000000000000000000000000000000ffff44248ba1208da3af7354010000000000000000000000000000000000ffff86f9d72c208d9fd68754010000000000000000000000000000000000ffff521f2444208dae428854010000000000000000000000000000000000ffffd445b025208dfafc8754010000000000000000000000000000000000ffffd447fa84208dbb358854010000000000000000000000000000000000ffff3ee2d196208da01a8854010000000000000000000000000000000000ffff45a615b2208d32edb053010000000000000000000000000000000000ffff6d6494da208d77ed8754010000000000000000000000000000000000ffff555dcc44208d09608754010000000000000000000000000000000000ffff45a5d222208d84558754010000000000000000000000000000000000ffffb01c30ab208d71fe8754010000000000000000000000000000000000ffffae5f68c2208de0acdf53010000000000000000000000000000000000ffffae737936208d29e18754010000000000000000000000000000000000ffffb00a63cb208d220b8854010000000000000000000000000000000000ffff9ec4d12f208db4408854010000000000000000000000000000000000ffff4c6d9bcc208d65a58754010000000000000000000000000000000000ffff5962c453208d2d628854010000000000000000000000000000000000ffff257191d50000', + payload: 'fdf5016816fa53010000000000000000000000000000000000ffff51403eea208ddb2a8854010000000000000000000000000000000000ffff707c60d9208d31413a54010000000000000000000000000000000000ffff5ed5fd13208d182c8854010000000000000000000000000000000000ffff505f3f81208d65ecb853010000000000000000000000000000000000ffff54d72104208dcca94054010000000000000000000000000000000000ffff40bbe1f2208d73238854010000000000000000000000000000000000ffff55d683cd208d5d258854010000000000000000000000000000000000ffffd523a66d208dcbea87540100000000000000200100005ef579fd2c9d47be4b56d671208db3e58754010000000000000000000000000000000000ffffae373156208d11e58754810000000000000000000000000000000000ffff51071276208d38f48754010000000000000000000000000000000000ffff6d4372e0208d4f448854010000000000000000000000000000000000ffffc556cbea208deb208854010000000000000000000000000000000000ffff450eb059208d97336054010000000000000000000000000000000000ffff4d60444a208dad608754010000000000000000000000000000000000ffff01abd3c3208d2cbe8754010000000000000000000000000000000000ffff7963d8b8208d9dfa8754010000000000000000000000000000000000ffffcebe862c208da7f66554010000000000000000000000000000000000ffff62f2a82e208d430d8854010000000000000000000000000000000000ffff6deb6956208d08148854010000000000000000000000000000000000ffff4b9804ef208d950e8854010000000000000000000000000000000000ffff6b8dd2e1208d1cfc8754010000000000000000000000000000000000ffffb2fe1dab208de7458754010000000000000000000000000000000000ffff32cccb82208d12625654010000000000000000000000000000000000ffff55e6f35a208dd7908354010000000000000000000000000000000000ffff4c642e6f208d17398854010000000000000000000000000000000000ffff45ea14df208dc72d8854010000000000000000000000000000000000ffffdaa48b96208d57fb8654010000000000000000000000000000000000ffff5f0080a9208d4e9a6d54010000000000000000000000000000000000ffff6d94e741208d7a418854010000000000000000000000000000000000ffff6d43b0c1208d5c098854010000000000000000000000000000000000ffff9c11e7ea208d442d8854010000000000000000000000000000000000ffffd0424482208db142fb53010000000000000000000000000000000000ffff54344a4a208d70158854010000000000000000000000000000000000ffff538f8201208d39288854010000000000000000000000000000000000ffff57daab71208d262f8854010000000000000000000000000000000000ffff57e045b1208d91430654010000000000000000000000000000000000ffff4212def5208d7c468854010000000000000000000000000000000000ffff62a4f53b208d9c3e8854010000000000000000000000000000000000ffff5b736bcc208d4d074f54010000000000000000000000000000000000ffff43af9d9b208deaca8754010000000000000000000000000000000000ffff4a8084a0208d74f68754010000000000000000000000000000000000ffff46304541208d5d098854010000000000000000000000000000000000ffff6eabb626208d08228854010000000000000000000000000000000000ffff1f07b030208d959a8254010000000000000000000000000000000000ffff4fa996b3208d4bac8754010000000000000000000000000000000000ffff43a0605b208de4038854010000000000000000000000000000000000ffff4cb41a5b208ddc308854010000000000000000000000000000000000ffffb72e6072208deac86f54010000000000000000000000000000000000ffffd5b815e2208d1b118854010000000000000000000000000000000000ffff3ff8d0a4208db1258854010000000000000000000000000000000000ffff5edd3a6a208d20268754010000000000000000000000000000000000ffff59ed3cfc208d83ed8754010000000000000000000000000000000000ffff4460baea208d71278854010000000000000000000000000000000000ffff4583b547208da7968754010000000000000000000000000000000000ffffd1c34463208d9d428854010000000000000000000000000000000000ffff43c15bc5208d33448854010000000000000000000000000000000000ffff2e0d8a5c208dfa2e7154010000000000000000000000000000000000ffffceff2bca208d47288854010000000000000000000000000000000000ffff5b8d0190208d0e847954010000000000000000000000000000000000ffff5501b8c2208d552f8854010000000000000000000000000000000000ffff5cf3b604208d8c2e8854010000000000000000000000000000000000ffff58666b0d208dcbdd8754010000000000000000000000000000000000ffff59a85cf9208d04208854010000000000000000000000000000000000ffff5f60abe0208dba238854010000000000000000000000000000000000ffff2e77ce67208d62c68754010000000000000000000000000000000000ffffcbae5658208d41108854010000000000000000000000000000000000ffff49329ec8208ddf1d8854010000000000000000000000000000000000ffff603ada12208df90c8854010000000000000000000000000000000000ffff51e0c21c208d5dff8754010000000000000000000000000000000000ffff904ca539208de0118854010000000000000000000000000000000000ffff5c0cecf7208d1b2d88540100000000000000200100005ef579fb18f317b2aba4edfc208dfaa27f54010000000000000000000000000000000000ffff54c65a7c208dc2db8754010000000000000000000000000000000000ffffad0b7981208d88a58754010000000000000000000000000000000000ffff188a1995208de0d78754010000000000000000000000000000000000ffff62a7a6bd208d18f66b54010000000000000000000000000000000000ffffdb4fbe80208dfcf78754010000000000000000000000000000000000ffff67145102208d3d3a8854010000000000000000000000000000000000ffff64259bd6208dcaa98654010000000000000000000000000000000000ffff32c737e1208d3a6a5c54010000000000000000000000000000000000ffff6d793f75208d22d38754010000000000000000000000000000000000ffff0595cc0d208da9ea8754010000000000000000000000000000000000ffff62ca37d3208d440e8854010000000000000000000000000000000000ffff123e1c11208dab428854010000000000000000000000000000000000ffff461df51f208d0f0b8854010000000000000000000000000000000000ffff59d36752208d631f8854010000000000000000000000000000000000ffff5ef8c640208dbc1d8854010000000000000000000000000000000000ffff5f5dc582208d04bf8754010000000000000000000000000000000000ffff0e6797e3208db8028854010000000000000000000000000000000000ffff3cf0c4a1208d6a0c8854010000000000000000000000000000000000ffff58bb5c55208d4f438854010000000000000000000000000000000000ffff5d326005208df6068854010000000000000000000000000000000000ffff5d5a5850208d40eb8754010000000000000000000000000000000000ffff5751ed86208dbad18754010000000000000000000000000000000000ffff63e51608208d99108854010000000000000000000000000000000000ffff36e299cd208d9a1e88540100000000000000200100005ef579fd38a43785a5d441b7208d13258854010000000000000000000000000000000000ffffb90de279208db3778754010000000000000000000000000000000000ffff67fcc80c208d3d358854010000000000000000000000000000000000ffffd00c40fc208dfd0d8854010000000000000000000000000000000000ffff5e89c2f3208dd6428854010000000000000000000000000000000000ffff1863e14a208d89e78754010000000000000000000000000000000000ffff43a11d76208d89ce8754010000000000000000000000000000000000ffff3edb6281208d03298854010000000000000000000000000000000000ffff6d5b26dd208d16df6e54010000000000000000000000000000000000ffffd9c3d176208d7c028854010000000000000000000000000000000000ffffbcc2db1f208d67fc8754010000000000000000000000000000000000ffff59910667208d86b18754010000000000000000000000000000000000ffff6c1482b9208d790b8854010000000000000000000000000000000000ffff0263150d208d89c44554010000000000000000000000000000000000ffff72fcd6b8208d331c8854010000000000000000000000000000000000ffff4c7dcfa8208d9fe98754010000000000000000000000000000000000ffffad08fd05208d0ecd8754010000000000000000000000000000000000ffff7c0fec89208d4af98754010000000000000000000000000000000000ffff2ea6a167208d270b8854010000000000000000000000000000000000ffff59d40fca208d11cf4054010000000000000000000000000000000000ffffb52ac058208d63358254010000000000000000000000000000000000ffff6bd9a130208dbde48754010000000000000000000000000000000000ffff4f915dc7208dece78754010000000000000000000000000000000000ffff5f8423ef208dba308854010000000000000000000000000000000000ffff904cba8c208d953d8854010000000000000000000000000000000000ffff8d1421e5208d5f188854010000000000000000000000000000000000ffff4e1bbfb6208d36fc8754010000000000000000000000000000000000ffff3205cfb6208d5e458854010000000000000000000000000000000000ffff493092c2208dd0878754010000000000000000000000000000000000ffff97caacde208dd99d8754010000000000000000000000000000000000ffffc05f3c38208da7018854010000000000000000000000000000000000ffffdcf5f82d208df0f42254010000000000000000000000000000000000ffff58bf997f208d94228854010000000000000000000000000000000000ffffbc8ee72d208de3448854010000000000000000000000000000000000ffff6d780f99208d32448854010000000000000000000000000000000000ffffbc64c76e208d0b3d8854010000000000000000000000000000000000ffff6c414312208d49138854010000000000000000000000000000000000ffff407e52c6208d2d128854010000000000000000000000000000000000ffff6dc987d8208d4acf8754010000000000000000000000000000000000ffffae151a25208de9dd8754010000000000000000000000000000000000ffff522d2d10208d17df8754010000000000000000000000000000000000ffff4d294737208ddbd48754010000000000000000000000000000000000ffff443703fd208d79108854010000000000000000000000000000000000ffff4bb5a154208d1df28753010000000000000000000000000000000000ffff4c44fc51208d5d496a53010000000000000000000000000000000000ffff530924b0208d180b8854010000000000000000000000000000000000ffffc15fced3208d53128854010000000000000000000000000000000000ffff5bc5b9e4208de21f8854010000000000000000000000000000000000ffff2e05fe1f208d31ab6554010000000000000000000000000000000000ffffae865915208d09248854010000000000000000000000000000000000ffff7d71a4b0208d120b8854010000000000000000000000000000000000ffff93e40150208d25d98754010000000000000000000000000000000000ffffc19f7a86208d99e78754010000000000000000000000000000000000ffff4d675993208d2b218854010000000000000000000000000000000000ffff5144776c208d5a208854010000000000000000000000000000000000ffff2e1cce5820f69e8f8154010000000000000000000000000000000000ffff58718848208d9edd6554010000000000000000000000000000000000ffff2ea758c5208d28c98754010000000000000000000000000000000000ffffc60bd694208d181d8854010000000000000000000000000000000000ffffba6b689e208d77868754010000000000000000000000000000000000ffff182016aa208de1698054010000000000000000000000000000000000ffff5cf5daa9208d823d8854010000000000000000000000000000000000ffffbe4d61c8208d2c3b8854010000000000000000000000000000000000ffff71647df2208da2248854010000000000000000000000000000000000ffff4ff359ca208da2dd8754010000000000000000000000000000000000ffff59f1e52b208da6e60d54010000000000000000000000000000000000ffff579596231159ad218854010000000000000000000000000000000000ffff5895f42d208d5ba78754010000000000000000000000000000000000ffff458ae8b0208ddc9b8754010000000000000000000000000000000000ffff18b507a5208d20eb5754010000000000000000000000000000000000ffff5181a537208daf1d8854010000000000000000000000000000000000ffffd5f51eba208d07f58754010000000000000000000000000000000000ffff6dec54bc208d4b3b8854010000000000000000000000000000000000ffff5c3e1991208d13b54054010000000000000000000000000000000000ffffb273845a208d4f338854010000000000000000000000000000000000ffffcfac797b208d4d898754010000000000000000000000000000000000ffffadaf880d208de72f8854010000000000000000000000000000000000ffffd956ef58208dc1e98754010000000000000000000000000000000000ffff980711cf208dfd8f7154010000000000000000000000000000000000ffff50aba98f208d59fe8754010000000000000000000000000000000000ffff5f2ae582208d3ffa8754010000000000000000000000000000000000ffffbca836db208d389b0054010000000000000000000000000000000000ffffded10925208deb154354010000000000000000000000000000000000ffff717532da208dadde7654010000000000000000000000000000000000ffff93afd0e5208d40458854010000000000000000000000000000000000ffff54ee8cb0208d08fa8754010000000000000000000000000000000000ffff3658eba9208d0df58754010000000000000000000000000000000000ffff72c6876e208d5d408854010000000000000000000000000000000000ffffa7582d7c208de8338854010000000000000000000000000000000000ffffad40d7ad208d6c2a8854010000000000000000000000000000000000ffffbc1833a3208d0fc28754010000000000000000000000000000000000ffffcbceb676208d98448854010000000000000000000000000000000000ffff0121ce6e208d77b58754010000000000000000000000000000000000ffff4b40d938208d853b8854010000000000000000000000000000000000ffff5648d643208d1c9c7854010000000000000000000000000000000000ffff6c3230db208d4e028754010000000000000000000000000000000000ffffd8e33c5d208daf738754010000000000000000000000000000000000ffffad4e9d56208dca438854010000000000000000000000000000000000ffff4f8a0353208d7b408854010000000000000000000000000000000000ffff0599e92a208dc62f8854010000000000000000000000000000000000ffff46236049208dc4d58754010000000000000000000000000000000000ffff4400384d208d0ddb4c54010000000000000000000000000000000000ffff71653b5a208d53388854010000000000000000000000000000000000ffff43bddf66208d7c388854010000000000000000000000000000000000ffff47edca96208d8c068854010000000000000000000000000000000000ffffdded3c2c208d89f60554010000000000000000000000000000000000ffff4e60d8d6208da5ff1154010000000000000000000000000000000000ffff55983c68208d72f58754010000000000000000000000000000000000ffffce47f56a208d7e2d8854010000000000000000000000000000000000ffff17e25c12208da7ca8754010000000000000000000000000000000000ffff051d0baa208d19af8754010000000000000000000000000000000000ffffad42d883208dffd68754010000000000000000000000000000000000ffff5418ff88208d87bf8754010000000000000000000000000000000000ffff4c1fe0dc208deef18754010000000000000000000000000000000000ffff4c71d406208d25868754010000000000000000000000000000000000ffffda67a475208d4d348854010000000000000000000000000000000000ffff5d561269208da5918754010000000000000000000000000000000000ffff42b163b3208dbfac3754010000000000000000000000000000000000ffffad4e01eb208d5c408854010000000000000000000000000000000000ffffcc0bb91e208d4a928754010000000000000000000000000000000000ffff531dba30208db9fd8754010000000000000000000000000000000000ffffa3f72b33208dade58754010000000000000000000000000000000000ffffd8b93aeb208db75d4d54010000000000000000000000000000000000ffffae1d5595208de8d18754010000000000000000000000000000000000ffff4405665c208d88e78754010000000000000000000000000000000000ffff6b96180a208dae667354010000000000000000000000000000000000ffff531f490a208d92f98754010000000000000000000000000000000000ffffd447e857208db1428854010000000000000000000000000000000000ffff36c6b4bb208dd42c8854010000000000000000000000000000000000ffff4b4aff14208df71acd53010000000000000000000000000000000000ffff62d17930208da0b88754010000000000000000000000000000000000ffff974bf913208d5faa7953010000000000000000000000000000000000ffff7419d269208d6c1e8854010000000000000000000000000000000000ffff48a72343208d9c2b8854010000000000000000000000000000000000ffff64431e87208de8908754010000000000000000000000000000000000ffff44660d45208d410b7d54010000000000000000000000000000000000ffff43a3340d208d5b298854010000000000000000000000000000000000ffff9ffda678208de7238854010000000000000000000000000000000000ffff189a3759208da5745354010000000000000000000000000000000000ffff57026e31208df23c885401000000000000002a010488006710000523fbe100000001208dddf48754010000000000000000000000000000000000ffffc1531c5b208d61888754010000000000000000000000000000000000ffff42cd8bc1208df92e8854010000000000000000000000000000000000ffff36e1e970208d0cf75754010000000000000000000000000000000000ffffad4e2420208d0f028854010000000000000000000000000000000000ffff6ec61b02208de6228854010000000000000000000000000000000000ffffdcaa807c208d1cac8754010000000000000000000000000000000000ffff6179416c208db6068854010000000000000000000000000000000000ffffd46121e4208d7ea58754010000000000000000000000000000000000ffff80a41679208d91216254010000000000000000000000000000000000ffff69ece091208d0bf78754010000000000000000000000000000000000ffff45320c0b208dd03c8854010000000000000000000000000000000000ffff4859a2a5208d8e308854010000000000000000000000000000000000ffff2e1ce15a208d30f36354010000000000000000000000000000000000ffff4b61ee15208d7aa05553010000000000000000000000000000000000ffffb129186b208dbb238854010000000000000000000000000000000000ffff581a7f6f208d41248754010000000000000000000000000000000000ffffb009055b208dee218854010000000000000000000000000000000000ffff2e7771f9208db4e78754010000000000000000000000000000000000ffff411bf6ee208db5178854010000000000000000000000000000000000ffffacff001b208dbcf487540100000000000000200100009d386ab83061cfabfe3513a3208d76048854010000000000000000000000000000000000ffff2e35dbc2208d83d28754010000000000000000000000000000000000ffffd91bb1d5208d5c3d8854010000000000000000000000000000000000ffff32a47969208df4bd875401000000000000002a0104f8012032250000000000000002208d1bfa8754010000000000000000000000000000000000ffff53a2f4b6208d141c4554010000000000000000000000000000000000ffffb27b8281208d580c7e54010000000000000000000000000000000000ffffc9ea1340208df7228854010000000000000000000000000000000000ffffd90a268d208d41e18754010000000000000000000000000000000000ffff555b8809208d66358854010000000000000000000000000000000000ffff5280ff23208deecd8754010000000000000000000000000000000000ffff84c6a102208df9bc8754010000000000000000000000000000000000ffffbbc96dae208d9d0a8854010000000000000000000000000000000000ffffb2a2d18a208da9458854010000000000000000000000000000000000ffffca16c30e208d2ca88754010000000000000000000000000000000000ffff6baab63e208d923b8854010000000000000000000000000000000000ffffd5b87bee208d12258854010000000000000000000000000000000000ffff59ee408b208d980e8854010000000000000000000000000000000000ffff2edfb0c4208d33378854010000000000000000000000000000000000ffff6caa7b42208d41176d53010000000000000000000000000000000000ffff1fa2df68208dd1c57f54010000000000000000000000000000000000ffffbeab679a208d17c78754010000000000000000000000000000000000ffff3f983f51208dbb278854010000000000000000000000000000000000ffff568dbc28208d5d5ad953010000000000000000000000000000000000ffff0ec112b7208da1b78754010000000000000000000000000000000000ffffbc287698208d99348854010000000000000000000000000000000000ffff5ff1a302208d23ef8454010000000000000000000000000000000000ffff7ba55d22208d90e98754010000000000000000000000000000000000ffff5ae14503208d58f48754010000000000000000000000000000000000ffffd45a3cae208d530c8854010000000000000000000000000000000000ffff18fb80c4208dc6138854010000000000000000000000000000000000ffff4e2fd6eb208d97208854010000000000000000000000000000000000ffff55ddd5b8208d74f88654010000000000000000000000000000000000ffff1809ad86208dc0278854010000000000000000000000000000000000ffff6ef2dfb6208d84338854010000000000000000000000000000000000ffff58d8114e208dcae78754010000000000000000000000000000000000ffffc21c474c208d61e78754010000000000000000000000000000000000ffffd5b3fc7a208d7a078854010000000000000000000000000000000000ffff32740193208de7547b54010000000000000000000000000000000000ffff8046aac3208db5328854010000000000000000000000000000000000ffff5b0c5366208d5f208854010000000000000000000000000000000000ffff6caa054a208d1d682854010000000000000000000000000000000000ffff69e3eb9d208d179e6b54010000000000000000000000000000000000ffff54aac628208ded2e8854010000000000000000000000000000000000ffff4166d226208d8e148854010000000000000000000000000000000000ffff54305af6208d1ed88754010000000000000000000000000000000000ffff3ed2b2a4208d78ec8554010000000000000000000000000000000000ffff6dd2e575208ddd3e7854010000000000000000000000000000000000ffff1b83a102208d421e8854010000000000000000000000000000000000ffff2eb51bf1208dc01f8854010000000000000000000000000000000000ffff58c66033208d08ce8754010000000000000000000000000000000000ffff5d57b84c208df22e8854010000000000000000000000000000000000ffffbf213e3d208d89cb8754010000000000000000000000000000000000ffff53d7ece1208da9d68754010000000000000000000000000000000000ffff71bef412208d38268854010000000000000000000000000000000000ffff84fc8a6f208d4f107254010000000000000000000000000000000000ffff57a072d9208d990f88540100000000000000200100009d3890d71029db21863020f9208df3c36854010000000000000000000000000000000000ffffbba6a6c4208d3ea71f54010000000000000000000000000000000000ffff3e61237b208d60a18754010000000000000000000000000000000000ffff53a56b4b208db2328854010000000000000000000000000000000000ffff44eef265208d2c0f8854010000000000000000000000000000000000ffff566553c0208dec268854010000000000000000000000000000000000ffff57a69ecd208d93148854010000000000000000000000000000000000ffff9b8f4433208d741b8854010000000000000000000000000000000000ffffae17c74c208ddab94f54010000000000000000000000000000000000ffffab19c63a208d5e886f54010000000000000000000000000000000000ffffbc67a037208d5c3ccc53010000000000000000000000000000000000ffff0e77c86e208d26fb8754010000000000000000000000000000000000ffffc1eae172208d61278854010000000000000000000000000000000000ffff5ce15439208d60df8754010000000000000000000000000000000000ffff2599fb3b208d52418854010000000000000000000000000000000000ffff028710e6208dae448854010000000000000000000000000000000000ffff18152eee208d6ff18754010000000000000000000000000000000000ffff542e396d208d8c248854010000000000000000000000000000000000ffff5519d6d8208d01e33e54010000000000000000000000000000000000ffff5027d563208d05cd8754010000000000000000000000000000000000ffff45a5a9c5208d7c1e8854010000000000000000000000000000000000ffff68c813c8208d38a48754010000000000000000000000000000000000ffff3a60a934208d73ec8754010000000000000000000000000000000000ffffae3db8f3208d096c8754010000000000000000000000000000000000ffff4cb88802208d55d38754010000000000000000000000000000000000ffffd31f082e208d0d8a8754010000000000000000000000000000000000ffff6c22c9bb208d81ba7754010000000000000000000000000000000000ffffca5f88da208d5df88754010000000000000000000000000000000000ffffc31abc05208db1d58754010000000000000000000000000000000000ffff5cf614c8208d2b257c54010000000000000000000000000000000000ffff45f9baa0208d38d78754010000000000000000000000000000000000ffff490e0608208d27e88754010000000000000000000000000000000000ffff17f1ccd0208d501c8854010000000000000000000000000000000000ffff5be834a7208d860b8854010000000000000000000000000000000000ffff640173bc208deada8754010000000000000000000000000000000000ffffdc98f8db208d983d3c54010000000000000000000000000000000000ffff021ab398208d99f28754010000000000000000000000000000000000ffff59a9e417208d19468854010000000000000000000000000000000000ffff91ff01a1208ddbf68754010000000000000000000000000000000000ffff50048949208d89118854010000000000000000000000000000000000ffffc8366923208dea3b8854010000000000000000000000000000000000ffffbcbbb5a9208d364a8854010000000000000000000000000000000000ffffc654bd58208dd52ef953010000000000000000000000000000000000ffff7cabb7bc208db086cc53010000000000000000000000000000000000ffff6c4004ea208d0ff48754010000000000000000000000000000000000ffff0536a3cb208d1c0b8854010000000000000000000000000000000000ffffd90bfef3208dbf188854010000000000000000000000000000000000ffff0252431f208dd1c78754010000000000000000000000000000000000ffff25732b19208dd6fa3954010000000000000000000000000000000000ffff6c1131dc208da6108854010000000000000000000000000000000000ffff1fb9b748208daf368854010000000000000000000000000000000000ffff1f2a2981208d9e2f8854010000000000000000000000000000000000ffffbc658482208d6a458854010000000000000000000000000000000000ffff568b843a208dcd428854010000000000000000000000000000000000ffff5d820794208d38258854010000000000000000000000000000000000ffff6440c630208d1cda8754010000000000000000000000000000000000ffff6e9f7be5208ddd388854010000000000000000000000000000000000ffff9f080244208d2e2d8854010000000000000000000000000000000000ffffbb70cf3d208d3e0c8854010000000000000000000000000000000000ffff3ec27a16208d89098854010000000000000000000000000000000000ffffb55fb2fb208d13468854010000000000000000000000000000000000ffff180ab1e5208dfc2a8854010000000000000000000000000000000000ffffd58a5c0e208db9188854010000000000000000000000000000000000ffff81ce808d208d03d06b54010000000000000000000000000000000000ffff4b496858208d319e8454010000000000000000000000000000000000ffff531fac9f208dc8d98754010000000000000000000000000000000000ffffc113e4ea208d06395754010000000000000000000000000000000000ffff7cabf0d6208d722b8854010000000000000000000000000000000000ffff545db4cd208dd91f8854010000000000000000000000000000000000ffff5f12a597208dfbd57054010000000000000000000000000000000000ffff3cf654e6208dd7358854010000000000000000000000000000000000ffffb4998e79208dd7018854010000000000000000000000000000000000ffffb45cc220208d86df5554010000000000000000000000000000000000ffff7660d462208decd28754010000000000000000000000000000000000ffff5b7bdfe2208dafe28754010000000000000000000000000000000000ffff2e76899e208d0c318854010000000000000000000000000000000000ffffc7bcb183208d35258854010000000000000000000000000000000000ffff3ab2d076208db5c38754010000000000000000000000000000000000ffff48b24d30208ddfd18754010000000000000000000000000000000000ffff904cb00c208d40478854010000000000000000000000000000000000ffff02a3b787208deb318854010000000000000000000000000000000000ffffd2564026208de63f8854010000000000000000000000000000000000ffff615d16c0208dbf1d8854010000000000000000000000000000000000ffffc6fff6f0208d4aee8754010000000000000000000000000000000000ffff56c74bc8208d68bc8754010000000000000000000000000000000000ffff31b581f9208ddcca8754010000000000000000000000000000000000ffff0e97397a208db2318854010000000000000000000000000000000000ffffc766348d208de1bd8754010000000000000000000000000000000000ffffd5927a3d208d2aff8754010000000000000000000000000000000000ffff5152d0a5208da7feb753010000000000000000000000000000000000ffff53fe1694208de7418854010000000000000000000000000000000000ffff189f3d99208de7b38754010000000000000000000000000000000000ffff64004655208dacabb653010000000000000000000000000000000000ffff55413231208d43b88754010000000000000000000000000000000000ffff796e1029208d03788754010000000000000000000000000000000000ffff49b90ff6208d04be8754010000000000000000000000000000000000ffffc327ce1c208d33648254010000000000000000000000000000000000ffff2e896478208d9e308854010000000000000000000000000000000000ffffd395d9bf208df13e8854010000000000000000000000000000000000ffff51bb88ed208dec8c5653010000000000000000000000000000000000ffff557f146e208df4538854010000000000000000000000000000000000ffff48d05928208d41bd8154010000000000000000000000000000000000ffff47384162208df6428854010000000000000000000000000000000000ffffc1566314208d753e8854010000000000000000000000000000000000ffff440c9b4f208d2f078854010000000000000000000000000000000000ffff56021a19208d7b0f4754010000000000000000000000000000000000ffff71653a84208dcee88754010000000000000000000000000000000000ffff5401a332208df4db8754010000000000000000000000000000000000ffff5a9db0e3208df8e38754010000000000000000000000000000000000ffff5d5208fa208d2e298854010000000000000000000000000000000000ffff4c61c0bc208d42ea8754010000000000000000000000000000000000ffff4c405ae7208deade8754010000000000000000000000000000000000ffffc009c823208d99358854010000000000000000000000000000000000ffff6cc2b493208df3fd8754010000000000000000000000000000000000ffff904c6007208d1fde8754010000000000000000000000000000000000ffff92732ae8208d666e8354010000000000000000000000000000000000ffff6daa9d16208d18f78754010000000000000000000000000000000000ffff4d6d8d8a208ddea9ce53010000000000000000000000000000000000ffff17e26f7d208d1ae48754010000000000000000000000000000000000ffffc32ebb89208dee258854010000000000000000000000000000000000ffff4a0f10e1208d290a8854010000000000000000000000000000000000ffff42731593208d7cb33354010000000000000000000000000000000000ffff532e8b97208dfd1e8854010000000000000000000000000000000000ffff6ee91680208d480a1354010000000000000000000000000000000000ffff60f220f6208d55838754010000000000000000000000000000000000ffff1f36d48f208d247e8754010000000000000000000000000000000000ffff44b5a49a208d0efa4954010000000000000000000000000000000000ffff63890597208d862e8854010000000000000000000000000000000000ffffc14d8751208d443c8854010000000000000000000000000000000000ffff70d16dba208d72fe8754010000000000000000000000000000000000ffff55983dd3208d236b6854010000000000000000000000000000000000ffff7aea27f9208d67e08754010000000000000000000000000000000000ffff1b20750d208df10a7154010000000000000000000000000000000000ffff7b9fe56e208db32d8854010000000000000000000000000000000000ffff7ba42d54208db3f58754010000000000000000000000000000000000ffff027b4eba208d70d68754010000000000000000000000000000000000ffffbc1b62b9208d97951454010000000000000000000000000000000000ffffb4b6af10208d251288540100000000000000200100009d386ab800773149c4d7e673208d7b168854010000000000000000000000000000000000ffff58969392208d91d18754010000000000000000000000000000000000ffff9ffd6d4e208d19008854010000000000000000000000000000000000ffff4e926cb8208dd1c98754010000000000000000000000000000000000ffff546cdb77208d6a0a8854010000000000000000000000000000000000ffff6caa8c15208d3a3a8854010000000000000000000000000000000000ffff62e28d6a208d3fe48754010000000000000000000000000000000000ffffd9850be7208dcb438854010000000000000000000000000000000000ffff6c2444b3208d78338854010000000000000000000000000000000000ffffb2df574a208d3b2d3054010000000000000000000000000000000000ffff4b529e67208d5b988754010000000000000000000000000000000000ffff36e152e6208d01ab8754010000000000000000000000000000000000ffff58b531bf208d7b038854010000000000000000000000000000000000ffff54717a6f208db0388854010000000000000000000000000000000000ffff4a694e98208d65418854010000000000000000000000000000000000ffffcef84b25208dcf218854010000000000000000000000000000000000ffff5364fed9208db6c68754010000000000000000000000000000000000ffff4d03decd208dc6158854010000000000000000000000000000000000ffffb9044c79208d65418854010000000000000000000000000000000000ffff56b2192f208d23308754010000000000000000000000000000000000ffffd973f7a4208d05158854010000000000000000000000000000000000ffff1b6d9975208d52cd8754010000000000000000000000000000000000ffffdf5b92ad208d3a128854010000000000000000000000000000000000ffff6d93787e208d351e8854010000000000000000000000000000000000ffffb6a4336f208d9bd58754010000000000000000000000000000000000ffff3ba7f521208dc3288854010000000000000000000000000000000000ffff6bbf20cc208d68998754010000000000000000000000000000000000ffff63b30032208d86848754010000000000000000000000000000000000ffffb52ef15c208dff108854010000000000000000000000000000000000ffff4d06718b208d226f0754010000000000000000000000000000000000ffff44248ba1208da3af7354010000000000000000000000000000000000ffff86f9d72c208d9fd68754010000000000000000000000000000000000ffff521f2444208dae428854010000000000000000000000000000000000ffffd445b025208dfafc8754010000000000000000000000000000000000ffffd447fa84208dbb358854010000000000000000000000000000000000ffff3ee2d196208da01a8854010000000000000000000000000000000000ffff45a615b2208d32edb053010000000000000000000000000000000000ffff6d6494da208d77ed8754010000000000000000000000000000000000ffff555dcc44208d09608754010000000000000000000000000000000000ffff45a5d222208d84558754010000000000000000000000000000000000ffffb01c30ab208d71fe8754010000000000000000000000000000000000ffffae5f68c2208de0acdf53010000000000000000000000000000000000ffffae737936208d29e18754010000000000000000000000000000000000ffffb00a63cb208d220b8854010000000000000000000000000000000000ffff9ec4d12f208db4408854010000000000000000000000000000000000ffff4c6d9bcc208d65a58754010000000000000000000000000000000000ffff5962c453208d2d628854010000000000000000000000000000000000ffff257191d50000' + }, + PING: { + message: 'f9beb4d9706f6e67000000000000000008000000c6466f1e6b86480ae969867c', + payload: '6b86480ae969867c' + } +}; diff --git a/test/transport/messages.js b/test/transport/messages.js index 50fbced..9e7c851 100644 --- a/test/transport/messages.js +++ b/test/transport/messages.js @@ -8,34 +8,10 @@ var should = chai.should(); var expect = chai.expect; var bitcore = require('../..'); +var Data = require('../data/messages'); var Messages = bitcore.transport.Messages; var Networks = bitcore.Networks; -var VERSION = { - message: 'f9beb4d976657273696f6e000000000065000000fc970f17721101000100000000000000ba62885400000000010000000000000000000000000000000000ffffba8886dceab0010000000000000000000000000000000000ffff05095522208de7e1c1ef80a1cea70f2f5361746f7368693a302e392e312fa317050001', - payload: '721101000100000000000000ba62885400000000010000000000000000000000000000000000ffffba8886dceab0010000000000000000000000000000000000ffff05095522208de7e1c1ef80a1cea70f2f5361746f7368693a302e392e312fa317050001' -}; - -var VERACK = { - message: 'f9beb4d976657261636b000000000000000000005df6e0e2', - payload: '' -}; - -var INV = { - message: 'f9beb4d9696e76000000000000000000890200006e4f18431201000000f97347795bf7490ddeba98c129086f54e06633936a49a2a398defb49e5edbb00010000009cb17394db9280b2d5e7d9f01456358384e7453300d48138ca1590e8cd86632f010000000f810b6071b9808117c4b82619b4ae0a7994ed5a0f2c050e50fd5742c3b4e90901000000962798bce9cbfcee9a8c6b5df4cf8bed944eb31ec975e8c1be79b7ab010a1cb501000000baa905bcfc9d2641ecd759724f796a7614e91c7492507b418ba8e077a6e57054010000009a5ac6dae146392edf8b6e96c30951ed7752f9095fab61728207db0f135b18a30100000050e5f153e3f0d89ea432d9a26f088a89b3ca38dbf3f0247ddf15f80779915da801000000c255841510a284b88ba696ed9edfeb4cdb34966271c30bc3bce9fd881106850f0100000000e45af8455894f72cccc4053e1f5e6076a673982b1f4ca5b2abc7b6496823ab01000000c7e6dc049e5b0fdc0f8fac8934e84e9b2f0c3036c7c02638cc05ee2575d6be1701000000aef6435c03c99ee83702aaaf106dc853dee5bcb6025f3af67d1ec72202c437ad010000005369fc3dc81403fe52f766ef585245af908404be1b46e2ef67c93b748ef467e3010000002400f16a63c411ae5e336175afc26515e548c7267c0debdb6aa46830399ba35f01000000ceb4fa6a8ca2713baee2214422b73da47a20934c83d25017bd80053e65ef3091010000000d899968e591703ddd6fd6ce073837588209f8b3b245ecf1bf93e77b9806a6c501000000bddeb56581ad7882d8f2abc78e5a48b3de02d923cd1cfc7525d2dfe80470248a01000000f286188c0947b023f0ba5dd1ea596751d50f67fbe31e64ead39dd711d3585b5801000000e17ad100ebffd2d5a630d37fd2496b2f5ab6ae5c8812da3c642fb6b8dd37f5fd', - payload: '1201000000f97347795bf7490ddeba98c129086f54e06633936a49a2a398defb49e5edbb00010000009cb17394db9280b2d5e7d9f01456358384e7453300d48138ca1590e8cd86632f010000000f810b6071b9808117c4b82619b4ae0a7994ed5a0f2c050e50fd5742c3b4e90901000000962798bce9cbfcee9a8c6b5df4cf8bed944eb31ec975e8c1be79b7ab010a1cb501000000baa905bcfc9d2641ecd759724f796a7614e91c7492507b418ba8e077a6e57054010000009a5ac6dae146392edf8b6e96c30951ed7752f9095fab61728207db0f135b18a30100000050e5f153e3f0d89ea432d9a26f088a89b3ca38dbf3f0247ddf15f80779915da801000000c255841510a284b88ba696ed9edfeb4cdb34966271c30bc3bce9fd881106850f0100000000e45af8455894f72cccc4053e1f5e6076a673982b1f4ca5b2abc7b6496823ab01000000c7e6dc049e5b0fdc0f8fac8934e84e9b2f0c3036c7c02638cc05ee2575d6be1701000000aef6435c03c99ee83702aaaf106dc853dee5bcb6025f3af67d1ec72202c437ad010000005369fc3dc81403fe52f766ef585245af908404be1b46e2ef67c93b748ef467e3010000002400f16a63c411ae5e336175afc26515e548c7267c0debdb6aa46830399ba35f01000000ceb4fa6a8ca2713baee2214422b73da47a20934c83d25017bd80053e65ef3091010000000d899968e591703ddd6fd6ce073837588209f8b3b245ecf1bf93e77b9806a6c501000000bddeb56581ad7882d8f2abc78e5a48b3de02d923cd1cfc7525d2dfe80470248a01000000f286188c0947b023f0ba5dd1ea596751d50f67fbe31e64ead39dd711d3585b5801000000e17ad100ebffd2d5a630d37fd2496b2f5ab6ae5c8812da3c642fb6b8dd37f5fd' -}; - -var ADDR = { - message: 'f9beb4d9616464720000000000000000b93a0000480bab8afdf5016816fa53010000000000000000000000000000000000ffff51403eea208ddb2a8854010000000000000000000000000000000000ffff707c60d9208d31413a54010000000000000000000000000000000000ffff5ed5fd13208d182c8854010000000000000000000000000000000000ffff505f3f81208d65ecb853010000000000000000000000000000000000ffff54d72104208dcca94054010000000000000000000000000000000000ffff40bbe1f2208d73238854010000000000000000000000000000000000ffff55d683cd208d5d258854010000000000000000000000000000000000ffffd523a66d208dcbea87540100000000000000200100005ef579fd2c9d47be4b56d671208db3e58754010000000000000000000000000000000000ffffae373156208d11e58754810000000000000000000000000000000000ffff51071276208d38f48754010000000000000000000000000000000000ffff6d4372e0208d4f448854010000000000000000000000000000000000ffffc556cbea208deb208854010000000000000000000000000000000000ffff450eb059208d97336054010000000000000000000000000000000000ffff4d60444a208dad608754010000000000000000000000000000000000ffff01abd3c3208d2cbe8754010000000000000000000000000000000000ffff7963d8b8208d9dfa8754010000000000000000000000000000000000ffffcebe862c208da7f66554010000000000000000000000000000000000ffff62f2a82e208d430d8854010000000000000000000000000000000000ffff6deb6956208d08148854010000000000000000000000000000000000ffff4b9804ef208d950e8854010000000000000000000000000000000000ffff6b8dd2e1208d1cfc8754010000000000000000000000000000000000ffffb2fe1dab208de7458754010000000000000000000000000000000000ffff32cccb82208d12625654010000000000000000000000000000000000ffff55e6f35a208dd7908354010000000000000000000000000000000000ffff4c642e6f208d17398854010000000000000000000000000000000000ffff45ea14df208dc72d8854010000000000000000000000000000000000ffffdaa48b96208d57fb8654010000000000000000000000000000000000ffff5f0080a9208d4e9a6d54010000000000000000000000000000000000ffff6d94e741208d7a418854010000000000000000000000000000000000ffff6d43b0c1208d5c098854010000000000000000000000000000000000ffff9c11e7ea208d442d8854010000000000000000000000000000000000ffffd0424482208db142fb53010000000000000000000000000000000000ffff54344a4a208d70158854010000000000000000000000000000000000ffff538f8201208d39288854010000000000000000000000000000000000ffff57daab71208d262f8854010000000000000000000000000000000000ffff57e045b1208d91430654010000000000000000000000000000000000ffff4212def5208d7c468854010000000000000000000000000000000000ffff62a4f53b208d9c3e8854010000000000000000000000000000000000ffff5b736bcc208d4d074f54010000000000000000000000000000000000ffff43af9d9b208deaca8754010000000000000000000000000000000000ffff4a8084a0208d74f68754010000000000000000000000000000000000ffff46304541208d5d098854010000000000000000000000000000000000ffff6eabb626208d08228854010000000000000000000000000000000000ffff1f07b030208d959a8254010000000000000000000000000000000000ffff4fa996b3208d4bac8754010000000000000000000000000000000000ffff43a0605b208de4038854010000000000000000000000000000000000ffff4cb41a5b208ddc308854010000000000000000000000000000000000ffffb72e6072208deac86f54010000000000000000000000000000000000ffffd5b815e2208d1b118854010000000000000000000000000000000000ffff3ff8d0a4208db1258854010000000000000000000000000000000000ffff5edd3a6a208d20268754010000000000000000000000000000000000ffff59ed3cfc208d83ed8754010000000000000000000000000000000000ffff4460baea208d71278854010000000000000000000000000000000000ffff4583b547208da7968754010000000000000000000000000000000000ffffd1c34463208d9d428854010000000000000000000000000000000000ffff43c15bc5208d33448854010000000000000000000000000000000000ffff2e0d8a5c208dfa2e7154010000000000000000000000000000000000ffffceff2bca208d47288854010000000000000000000000000000000000ffff5b8d0190208d0e847954010000000000000000000000000000000000ffff5501b8c2208d552f8854010000000000000000000000000000000000ffff5cf3b604208d8c2e8854010000000000000000000000000000000000ffff58666b0d208dcbdd8754010000000000000000000000000000000000ffff59a85cf9208d04208854010000000000000000000000000000000000ffff5f60abe0208dba238854010000000000000000000000000000000000ffff2e77ce67208d62c68754010000000000000000000000000000000000ffffcbae5658208d41108854010000000000000000000000000000000000ffff49329ec8208ddf1d8854010000000000000000000000000000000000ffff603ada12208df90c8854010000000000000000000000000000000000ffff51e0c21c208d5dff8754010000000000000000000000000000000000ffff904ca539208de0118854010000000000000000000000000000000000ffff5c0cecf7208d1b2d88540100000000000000200100005ef579fb18f317b2aba4edfc208dfaa27f54010000000000000000000000000000000000ffff54c65a7c208dc2db8754010000000000000000000000000000000000ffffad0b7981208d88a58754010000000000000000000000000000000000ffff188a1995208de0d78754010000000000000000000000000000000000ffff62a7a6bd208d18f66b54010000000000000000000000000000000000ffffdb4fbe80208dfcf78754010000000000000000000000000000000000ffff67145102208d3d3a8854010000000000000000000000000000000000ffff64259bd6208dcaa98654010000000000000000000000000000000000ffff32c737e1208d3a6a5c54010000000000000000000000000000000000ffff6d793f75208d22d38754010000000000000000000000000000000000ffff0595cc0d208da9ea8754010000000000000000000000000000000000ffff62ca37d3208d440e8854010000000000000000000000000000000000ffff123e1c11208dab428854010000000000000000000000000000000000ffff461df51f208d0f0b8854010000000000000000000000000000000000ffff59d36752208d631f8854010000000000000000000000000000000000ffff5ef8c640208dbc1d8854010000000000000000000000000000000000ffff5f5dc582208d04bf8754010000000000000000000000000000000000ffff0e6797e3208db8028854010000000000000000000000000000000000ffff3cf0c4a1208d6a0c8854010000000000000000000000000000000000ffff58bb5c55208d4f438854010000000000000000000000000000000000ffff5d326005208df6068854010000000000000000000000000000000000ffff5d5a5850208d40eb8754010000000000000000000000000000000000ffff5751ed86208dbad18754010000000000000000000000000000000000ffff63e51608208d99108854010000000000000000000000000000000000ffff36e299cd208d9a1e88540100000000000000200100005ef579fd38a43785a5d441b7208d13258854010000000000000000000000000000000000ffffb90de279208db3778754010000000000000000000000000000000000ffff67fcc80c208d3d358854010000000000000000000000000000000000ffffd00c40fc208dfd0d8854010000000000000000000000000000000000ffff5e89c2f3208dd6428854010000000000000000000000000000000000ffff1863e14a208d89e78754010000000000000000000000000000000000ffff43a11d76208d89ce8754010000000000000000000000000000000000ffff3edb6281208d03298854010000000000000000000000000000000000ffff6d5b26dd208d16df6e54010000000000000000000000000000000000ffffd9c3d176208d7c028854010000000000000000000000000000000000ffffbcc2db1f208d67fc8754010000000000000000000000000000000000ffff59910667208d86b18754010000000000000000000000000000000000ffff6c1482b9208d790b8854010000000000000000000000000000000000ffff0263150d208d89c44554010000000000000000000000000000000000ffff72fcd6b8208d331c8854010000000000000000000000000000000000ffff4c7dcfa8208d9fe98754010000000000000000000000000000000000ffffad08fd05208d0ecd8754010000000000000000000000000000000000ffff7c0fec89208d4af98754010000000000000000000000000000000000ffff2ea6a167208d270b8854010000000000000000000000000000000000ffff59d40fca208d11cf4054010000000000000000000000000000000000ffffb52ac058208d63358254010000000000000000000000000000000000ffff6bd9a130208dbde48754010000000000000000000000000000000000ffff4f915dc7208dece78754010000000000000000000000000000000000ffff5f8423ef208dba308854010000000000000000000000000000000000ffff904cba8c208d953d8854010000000000000000000000000000000000ffff8d1421e5208d5f188854010000000000000000000000000000000000ffff4e1bbfb6208d36fc8754010000000000000000000000000000000000ffff3205cfb6208d5e458854010000000000000000000000000000000000ffff493092c2208dd0878754010000000000000000000000000000000000ffff97caacde208dd99d8754010000000000000000000000000000000000ffffc05f3c38208da7018854010000000000000000000000000000000000ffffdcf5f82d208df0f42254010000000000000000000000000000000000ffff58bf997f208d94228854010000000000000000000000000000000000ffffbc8ee72d208de3448854010000000000000000000000000000000000ffff6d780f99208d32448854010000000000000000000000000000000000ffffbc64c76e208d0b3d8854010000000000000000000000000000000000ffff6c414312208d49138854010000000000000000000000000000000000ffff407e52c6208d2d128854010000000000000000000000000000000000ffff6dc987d8208d4acf8754010000000000000000000000000000000000ffffae151a25208de9dd8754010000000000000000000000000000000000ffff522d2d10208d17df8754010000000000000000000000000000000000ffff4d294737208ddbd48754010000000000000000000000000000000000ffff443703fd208d79108854010000000000000000000000000000000000ffff4bb5a154208d1df28753010000000000000000000000000000000000ffff4c44fc51208d5d496a53010000000000000000000000000000000000ffff530924b0208d180b8854010000000000000000000000000000000000ffffc15fced3208d53128854010000000000000000000000000000000000ffff5bc5b9e4208de21f8854010000000000000000000000000000000000ffff2e05fe1f208d31ab6554010000000000000000000000000000000000ffffae865915208d09248854010000000000000000000000000000000000ffff7d71a4b0208d120b8854010000000000000000000000000000000000ffff93e40150208d25d98754010000000000000000000000000000000000ffffc19f7a86208d99e78754010000000000000000000000000000000000ffff4d675993208d2b218854010000000000000000000000000000000000ffff5144776c208d5a208854010000000000000000000000000000000000ffff2e1cce5820f69e8f8154010000000000000000000000000000000000ffff58718848208d9edd6554010000000000000000000000000000000000ffff2ea758c5208d28c98754010000000000000000000000000000000000ffffc60bd694208d181d8854010000000000000000000000000000000000ffffba6b689e208d77868754010000000000000000000000000000000000ffff182016aa208de1698054010000000000000000000000000000000000ffff5cf5daa9208d823d8854010000000000000000000000000000000000ffffbe4d61c8208d2c3b8854010000000000000000000000000000000000ffff71647df2208da2248854010000000000000000000000000000000000ffff4ff359ca208da2dd8754010000000000000000000000000000000000ffff59f1e52b208da6e60d54010000000000000000000000000000000000ffff579596231159ad218854010000000000000000000000000000000000ffff5895f42d208d5ba78754010000000000000000000000000000000000ffff458ae8b0208ddc9b8754010000000000000000000000000000000000ffff18b507a5208d20eb5754010000000000000000000000000000000000ffff5181a537208daf1d8854010000000000000000000000000000000000ffffd5f51eba208d07f58754010000000000000000000000000000000000ffff6dec54bc208d4b3b8854010000000000000000000000000000000000ffff5c3e1991208d13b54054010000000000000000000000000000000000ffffb273845a208d4f338854010000000000000000000000000000000000ffffcfac797b208d4d898754010000000000000000000000000000000000ffffadaf880d208de72f8854010000000000000000000000000000000000ffffd956ef58208dc1e98754010000000000000000000000000000000000ffff980711cf208dfd8f7154010000000000000000000000000000000000ffff50aba98f208d59fe8754010000000000000000000000000000000000ffff5f2ae582208d3ffa8754010000000000000000000000000000000000ffffbca836db208d389b0054010000000000000000000000000000000000ffffded10925208deb154354010000000000000000000000000000000000ffff717532da208dadde7654010000000000000000000000000000000000ffff93afd0e5208d40458854010000000000000000000000000000000000ffff54ee8cb0208d08fa8754010000000000000000000000000000000000ffff3658eba9208d0df58754010000000000000000000000000000000000ffff72c6876e208d5d408854010000000000000000000000000000000000ffffa7582d7c208de8338854010000000000000000000000000000000000ffffad40d7ad208d6c2a8854010000000000000000000000000000000000ffffbc1833a3208d0fc28754010000000000000000000000000000000000ffffcbceb676208d98448854010000000000000000000000000000000000ffff0121ce6e208d77b58754010000000000000000000000000000000000ffff4b40d938208d853b8854010000000000000000000000000000000000ffff5648d643208d1c9c7854010000000000000000000000000000000000ffff6c3230db208d4e028754010000000000000000000000000000000000ffffd8e33c5d208daf738754010000000000000000000000000000000000ffffad4e9d56208dca438854010000000000000000000000000000000000ffff4f8a0353208d7b408854010000000000000000000000000000000000ffff0599e92a208dc62f8854010000000000000000000000000000000000ffff46236049208dc4d58754010000000000000000000000000000000000ffff4400384d208d0ddb4c54010000000000000000000000000000000000ffff71653b5a208d53388854010000000000000000000000000000000000ffff43bddf66208d7c388854010000000000000000000000000000000000ffff47edca96208d8c068854010000000000000000000000000000000000ffffdded3c2c208d89f60554010000000000000000000000000000000000ffff4e60d8d6208da5ff1154010000000000000000000000000000000000ffff55983c68208d72f58754010000000000000000000000000000000000ffffce47f56a208d7e2d8854010000000000000000000000000000000000ffff17e25c12208da7ca8754010000000000000000000000000000000000ffff051d0baa208d19af8754010000000000000000000000000000000000ffffad42d883208dffd68754010000000000000000000000000000000000ffff5418ff88208d87bf8754010000000000000000000000000000000000ffff4c1fe0dc208deef18754010000000000000000000000000000000000ffff4c71d406208d25868754010000000000000000000000000000000000ffffda67a475208d4d348854010000000000000000000000000000000000ffff5d561269208da5918754010000000000000000000000000000000000ffff42b163b3208dbfac3754010000000000000000000000000000000000ffffad4e01eb208d5c408854010000000000000000000000000000000000ffffcc0bb91e208d4a928754010000000000000000000000000000000000ffff531dba30208db9fd8754010000000000000000000000000000000000ffffa3f72b33208dade58754010000000000000000000000000000000000ffffd8b93aeb208db75d4d54010000000000000000000000000000000000ffffae1d5595208de8d18754010000000000000000000000000000000000ffff4405665c208d88e78754010000000000000000000000000000000000ffff6b96180a208dae667354010000000000000000000000000000000000ffff531f490a208d92f98754010000000000000000000000000000000000ffffd447e857208db1428854010000000000000000000000000000000000ffff36c6b4bb208dd42c8854010000000000000000000000000000000000ffff4b4aff14208df71acd53010000000000000000000000000000000000ffff62d17930208da0b88754010000000000000000000000000000000000ffff974bf913208d5faa7953010000000000000000000000000000000000ffff7419d269208d6c1e8854010000000000000000000000000000000000ffff48a72343208d9c2b8854010000000000000000000000000000000000ffff64431e87208de8908754010000000000000000000000000000000000ffff44660d45208d410b7d54010000000000000000000000000000000000ffff43a3340d208d5b298854010000000000000000000000000000000000ffff9ffda678208de7238854010000000000000000000000000000000000ffff189a3759208da5745354010000000000000000000000000000000000ffff57026e31208df23c885401000000000000002a010488006710000523fbe100000001208dddf48754010000000000000000000000000000000000ffffc1531c5b208d61888754010000000000000000000000000000000000ffff42cd8bc1208df92e8854010000000000000000000000000000000000ffff36e1e970208d0cf75754010000000000000000000000000000000000ffffad4e2420208d0f028854010000000000000000000000000000000000ffff6ec61b02208de6228854010000000000000000000000000000000000ffffdcaa807c208d1cac8754010000000000000000000000000000000000ffff6179416c208db6068854010000000000000000000000000000000000ffffd46121e4208d7ea58754010000000000000000000000000000000000ffff80a41679208d91216254010000000000000000000000000000000000ffff69ece091208d0bf78754010000000000000000000000000000000000ffff45320c0b208dd03c8854010000000000000000000000000000000000ffff4859a2a5208d8e308854010000000000000000000000000000000000ffff2e1ce15a208d30f36354010000000000000000000000000000000000ffff4b61ee15208d7aa05553010000000000000000000000000000000000ffffb129186b208dbb238854010000000000000000000000000000000000ffff581a7f6f208d41248754010000000000000000000000000000000000ffffb009055b208dee218854010000000000000000000000000000000000ffff2e7771f9208db4e78754010000000000000000000000000000000000ffff411bf6ee208db5178854010000000000000000000000000000000000ffffacff001b208dbcf487540100000000000000200100009d386ab83061cfabfe3513a3208d76048854010000000000000000000000000000000000ffff2e35dbc2208d83d28754010000000000000000000000000000000000ffffd91bb1d5208d5c3d8854010000000000000000000000000000000000ffff32a47969208df4bd875401000000000000002a0104f8012032250000000000000002208d1bfa8754010000000000000000000000000000000000ffff53a2f4b6208d141c4554010000000000000000000000000000000000ffffb27b8281208d580c7e54010000000000000000000000000000000000ffffc9ea1340208df7228854010000000000000000000000000000000000ffffd90a268d208d41e18754010000000000000000000000000000000000ffff555b8809208d66358854010000000000000000000000000000000000ffff5280ff23208deecd8754010000000000000000000000000000000000ffff84c6a102208df9bc8754010000000000000000000000000000000000ffffbbc96dae208d9d0a8854010000000000000000000000000000000000ffffb2a2d18a208da9458854010000000000000000000000000000000000ffffca16c30e208d2ca88754010000000000000000000000000000000000ffff6baab63e208d923b8854010000000000000000000000000000000000ffffd5b87bee208d12258854010000000000000000000000000000000000ffff59ee408b208d980e8854010000000000000000000000000000000000ffff2edfb0c4208d33378854010000000000000000000000000000000000ffff6caa7b42208d41176d53010000000000000000000000000000000000ffff1fa2df68208dd1c57f54010000000000000000000000000000000000ffffbeab679a208d17c78754010000000000000000000000000000000000ffff3f983f51208dbb278854010000000000000000000000000000000000ffff568dbc28208d5d5ad953010000000000000000000000000000000000ffff0ec112b7208da1b78754010000000000000000000000000000000000ffffbc287698208d99348854010000000000000000000000000000000000ffff5ff1a302208d23ef8454010000000000000000000000000000000000ffff7ba55d22208d90e98754010000000000000000000000000000000000ffff5ae14503208d58f48754010000000000000000000000000000000000ffffd45a3cae208d530c8854010000000000000000000000000000000000ffff18fb80c4208dc6138854010000000000000000000000000000000000ffff4e2fd6eb208d97208854010000000000000000000000000000000000ffff55ddd5b8208d74f88654010000000000000000000000000000000000ffff1809ad86208dc0278854010000000000000000000000000000000000ffff6ef2dfb6208d84338854010000000000000000000000000000000000ffff58d8114e208dcae78754010000000000000000000000000000000000ffffc21c474c208d61e78754010000000000000000000000000000000000ffffd5b3fc7a208d7a078854010000000000000000000000000000000000ffff32740193208de7547b54010000000000000000000000000000000000ffff8046aac3208db5328854010000000000000000000000000000000000ffff5b0c5366208d5f208854010000000000000000000000000000000000ffff6caa054a208d1d682854010000000000000000000000000000000000ffff69e3eb9d208d179e6b54010000000000000000000000000000000000ffff54aac628208ded2e8854010000000000000000000000000000000000ffff4166d226208d8e148854010000000000000000000000000000000000ffff54305af6208d1ed88754010000000000000000000000000000000000ffff3ed2b2a4208d78ec8554010000000000000000000000000000000000ffff6dd2e575208ddd3e7854010000000000000000000000000000000000ffff1b83a102208d421e8854010000000000000000000000000000000000ffff2eb51bf1208dc01f8854010000000000000000000000000000000000ffff58c66033208d08ce8754010000000000000000000000000000000000ffff5d57b84c208df22e8854010000000000000000000000000000000000ffffbf213e3d208d89cb8754010000000000000000000000000000000000ffff53d7ece1208da9d68754010000000000000000000000000000000000ffff71bef412208d38268854010000000000000000000000000000000000ffff84fc8a6f208d4f107254010000000000000000000000000000000000ffff57a072d9208d990f88540100000000000000200100009d3890d71029db21863020f9208df3c36854010000000000000000000000000000000000ffffbba6a6c4208d3ea71f54010000000000000000000000000000000000ffff3e61237b208d60a18754010000000000000000000000000000000000ffff53a56b4b208db2328854010000000000000000000000000000000000ffff44eef265208d2c0f8854010000000000000000000000000000000000ffff566553c0208dec268854010000000000000000000000000000000000ffff57a69ecd208d93148854010000000000000000000000000000000000ffff9b8f4433208d741b8854010000000000000000000000000000000000ffffae17c74c208ddab94f54010000000000000000000000000000000000ffffab19c63a208d5e886f54010000000000000000000000000000000000ffffbc67a037208d5c3ccc53010000000000000000000000000000000000ffff0e77c86e208d26fb8754010000000000000000000000000000000000ffffc1eae172208d61278854010000000000000000000000000000000000ffff5ce15439208d60df8754010000000000000000000000000000000000ffff2599fb3b208d52418854010000000000000000000000000000000000ffff028710e6208dae448854010000000000000000000000000000000000ffff18152eee208d6ff18754010000000000000000000000000000000000ffff542e396d208d8c248854010000000000000000000000000000000000ffff5519d6d8208d01e33e54010000000000000000000000000000000000ffff5027d563208d05cd8754010000000000000000000000000000000000ffff45a5a9c5208d7c1e8854010000000000000000000000000000000000ffff68c813c8208d38a48754010000000000000000000000000000000000ffff3a60a934208d73ec8754010000000000000000000000000000000000ffffae3db8f3208d096c8754010000000000000000000000000000000000ffff4cb88802208d55d38754010000000000000000000000000000000000ffffd31f082e208d0d8a8754010000000000000000000000000000000000ffff6c22c9bb208d81ba7754010000000000000000000000000000000000ffffca5f88da208d5df88754010000000000000000000000000000000000ffffc31abc05208db1d58754010000000000000000000000000000000000ffff5cf614c8208d2b257c54010000000000000000000000000000000000ffff45f9baa0208d38d78754010000000000000000000000000000000000ffff490e0608208d27e88754010000000000000000000000000000000000ffff17f1ccd0208d501c8854010000000000000000000000000000000000ffff5be834a7208d860b8854010000000000000000000000000000000000ffff640173bc208deada8754010000000000000000000000000000000000ffffdc98f8db208d983d3c54010000000000000000000000000000000000ffff021ab398208d99f28754010000000000000000000000000000000000ffff59a9e417208d19468854010000000000000000000000000000000000ffff91ff01a1208ddbf68754010000000000000000000000000000000000ffff50048949208d89118854010000000000000000000000000000000000ffffc8366923208dea3b8854010000000000000000000000000000000000ffffbcbbb5a9208d364a8854010000000000000000000000000000000000ffffc654bd58208dd52ef953010000000000000000000000000000000000ffff7cabb7bc208db086cc53010000000000000000000000000000000000ffff6c4004ea208d0ff48754010000000000000000000000000000000000ffff0536a3cb208d1c0b8854010000000000000000000000000000000000ffffd90bfef3208dbf188854010000000000000000000000000000000000ffff0252431f208dd1c78754010000000000000000000000000000000000ffff25732b19208dd6fa3954010000000000000000000000000000000000ffff6c1131dc208da6108854010000000000000000000000000000000000ffff1fb9b748208daf368854010000000000000000000000000000000000ffff1f2a2981208d9e2f8854010000000000000000000000000000000000ffffbc658482208d6a458854010000000000000000000000000000000000ffff568b843a208dcd428854010000000000000000000000000000000000ffff5d820794208d38258854010000000000000000000000000000000000ffff6440c630208d1cda8754010000000000000000000000000000000000ffff6e9f7be5208ddd388854010000000000000000000000000000000000ffff9f080244208d2e2d8854010000000000000000000000000000000000ffffbb70cf3d208d3e0c8854010000000000000000000000000000000000ffff3ec27a16208d89098854010000000000000000000000000000000000ffffb55fb2fb208d13468854010000000000000000000000000000000000ffff180ab1e5208dfc2a8854010000000000000000000000000000000000ffffd58a5c0e208db9188854010000000000000000000000000000000000ffff81ce808d208d03d06b54010000000000000000000000000000000000ffff4b496858208d319e8454010000000000000000000000000000000000ffff531fac9f208dc8d98754010000000000000000000000000000000000ffffc113e4ea208d06395754010000000000000000000000000000000000ffff7cabf0d6208d722b8854010000000000000000000000000000000000ffff545db4cd208dd91f8854010000000000000000000000000000000000ffff5f12a597208dfbd57054010000000000000000000000000000000000ffff3cf654e6208dd7358854010000000000000000000000000000000000ffffb4998e79208dd7018854010000000000000000000000000000000000ffffb45cc220208d86df5554010000000000000000000000000000000000ffff7660d462208decd28754010000000000000000000000000000000000ffff5b7bdfe2208dafe28754010000000000000000000000000000000000ffff2e76899e208d0c318854010000000000000000000000000000000000ffffc7bcb183208d35258854010000000000000000000000000000000000ffff3ab2d076208db5c38754010000000000000000000000000000000000ffff48b24d30208ddfd18754010000000000000000000000000000000000ffff904cb00c208d40478854010000000000000000000000000000000000ffff02a3b787208deb318854010000000000000000000000000000000000ffffd2564026208de63f8854010000000000000000000000000000000000ffff615d16c0208dbf1d8854010000000000000000000000000000000000ffffc6fff6f0208d4aee8754010000000000000000000000000000000000ffff56c74bc8208d68bc8754010000000000000000000000000000000000ffff31b581f9208ddcca8754010000000000000000000000000000000000ffff0e97397a208db2318854010000000000000000000000000000000000ffffc766348d208de1bd8754010000000000000000000000000000000000ffffd5927a3d208d2aff8754010000000000000000000000000000000000ffff5152d0a5208da7feb753010000000000000000000000000000000000ffff53fe1694208de7418854010000000000000000000000000000000000ffff189f3d99208de7b38754010000000000000000000000000000000000ffff64004655208dacabb653010000000000000000000000000000000000ffff55413231208d43b88754010000000000000000000000000000000000ffff796e1029208d03788754010000000000000000000000000000000000ffff49b90ff6208d04be8754010000000000000000000000000000000000ffffc327ce1c208d33648254010000000000000000000000000000000000ffff2e896478208d9e308854010000000000000000000000000000000000ffffd395d9bf208df13e8854010000000000000000000000000000000000ffff51bb88ed208dec8c5653010000000000000000000000000000000000ffff557f146e208df4538854010000000000000000000000000000000000ffff48d05928208d41bd8154010000000000000000000000000000000000ffff47384162208df6428854010000000000000000000000000000000000ffffc1566314208d753e8854010000000000000000000000000000000000ffff440c9b4f208d2f078854010000000000000000000000000000000000ffff56021a19208d7b0f4754010000000000000000000000000000000000ffff71653a84208dcee88754010000000000000000000000000000000000ffff5401a332208df4db8754010000000000000000000000000000000000ffff5a9db0e3208df8e38754010000000000000000000000000000000000ffff5d5208fa208d2e298854010000000000000000000000000000000000ffff4c61c0bc208d42ea8754010000000000000000000000000000000000ffff4c405ae7208deade8754010000000000000000000000000000000000ffffc009c823208d99358854010000000000000000000000000000000000ffff6cc2b493208df3fd8754010000000000000000000000000000000000ffff904c6007208d1fde8754010000000000000000000000000000000000ffff92732ae8208d666e8354010000000000000000000000000000000000ffff6daa9d16208d18f78754010000000000000000000000000000000000ffff4d6d8d8a208ddea9ce53010000000000000000000000000000000000ffff17e26f7d208d1ae48754010000000000000000000000000000000000ffffc32ebb89208dee258854010000000000000000000000000000000000ffff4a0f10e1208d290a8854010000000000000000000000000000000000ffff42731593208d7cb33354010000000000000000000000000000000000ffff532e8b97208dfd1e8854010000000000000000000000000000000000ffff6ee91680208d480a1354010000000000000000000000000000000000ffff60f220f6208d55838754010000000000000000000000000000000000ffff1f36d48f208d247e8754010000000000000000000000000000000000ffff44b5a49a208d0efa4954010000000000000000000000000000000000ffff63890597208d862e8854010000000000000000000000000000000000ffffc14d8751208d443c8854010000000000000000000000000000000000ffff70d16dba208d72fe8754010000000000000000000000000000000000ffff55983dd3208d236b6854010000000000000000000000000000000000ffff7aea27f9208d67e08754010000000000000000000000000000000000ffff1b20750d208df10a7154010000000000000000000000000000000000ffff7b9fe56e208db32d8854010000000000000000000000000000000000ffff7ba42d54208db3f58754010000000000000000000000000000000000ffff027b4eba208d70d68754010000000000000000000000000000000000ffffbc1b62b9208d97951454010000000000000000000000000000000000ffffb4b6af10208d251288540100000000000000200100009d386ab800773149c4d7e673208d7b168854010000000000000000000000000000000000ffff58969392208d91d18754010000000000000000000000000000000000ffff9ffd6d4e208d19008854010000000000000000000000000000000000ffff4e926cb8208dd1c98754010000000000000000000000000000000000ffff546cdb77208d6a0a8854010000000000000000000000000000000000ffff6caa8c15208d3a3a8854010000000000000000000000000000000000ffff62e28d6a208d3fe48754010000000000000000000000000000000000ffffd9850be7208dcb438854010000000000000000000000000000000000ffff6c2444b3208d78338854010000000000000000000000000000000000ffffb2df574a208d3b2d3054010000000000000000000000000000000000ffff4b529e67208d5b988754010000000000000000000000000000000000ffff36e152e6208d01ab8754010000000000000000000000000000000000ffff58b531bf208d7b038854010000000000000000000000000000000000ffff54717a6f208db0388854010000000000000000000000000000000000ffff4a694e98208d65418854010000000000000000000000000000000000ffffcef84b25208dcf218854010000000000000000000000000000000000ffff5364fed9208db6c68754010000000000000000000000000000000000ffff4d03decd208dc6158854010000000000000000000000000000000000ffffb9044c79208d65418854010000000000000000000000000000000000ffff56b2192f208d23308754010000000000000000000000000000000000ffffd973f7a4208d05158854010000000000000000000000000000000000ffff1b6d9975208d52cd8754010000000000000000000000000000000000ffffdf5b92ad208d3a128854010000000000000000000000000000000000ffff6d93787e208d351e8854010000000000000000000000000000000000ffffb6a4336f208d9bd58754010000000000000000000000000000000000ffff3ba7f521208dc3288854010000000000000000000000000000000000ffff6bbf20cc208d68998754010000000000000000000000000000000000ffff63b30032208d86848754010000000000000000000000000000000000ffffb52ef15c208dff108854010000000000000000000000000000000000ffff4d06718b208d226f0754010000000000000000000000000000000000ffff44248ba1208da3af7354010000000000000000000000000000000000ffff86f9d72c208d9fd68754010000000000000000000000000000000000ffff521f2444208dae428854010000000000000000000000000000000000ffffd445b025208dfafc8754010000000000000000000000000000000000ffffd447fa84208dbb358854010000000000000000000000000000000000ffff3ee2d196208da01a8854010000000000000000000000000000000000ffff45a615b2208d32edb053010000000000000000000000000000000000ffff6d6494da208d77ed8754010000000000000000000000000000000000ffff555dcc44208d09608754010000000000000000000000000000000000ffff45a5d222208d84558754010000000000000000000000000000000000ffffb01c30ab208d71fe8754010000000000000000000000000000000000ffffae5f68c2208de0acdf53010000000000000000000000000000000000ffffae737936208d29e18754010000000000000000000000000000000000ffffb00a63cb208d220b8854010000000000000000000000000000000000ffff9ec4d12f208db4408854010000000000000000000000000000000000ffff4c6d9bcc208d65a58754010000000000000000000000000000000000ffff5962c453208d2d628854010000000000000000000000000000000000ffff257191d50000', - payload: 'fdf5016816fa53010000000000000000000000000000000000ffff51403eea208ddb2a8854010000000000000000000000000000000000ffff707c60d9208d31413a54010000000000000000000000000000000000ffff5ed5fd13208d182c8854010000000000000000000000000000000000ffff505f3f81208d65ecb853010000000000000000000000000000000000ffff54d72104208dcca94054010000000000000000000000000000000000ffff40bbe1f2208d73238854010000000000000000000000000000000000ffff55d683cd208d5d258854010000000000000000000000000000000000ffffd523a66d208dcbea87540100000000000000200100005ef579fd2c9d47be4b56d671208db3e58754010000000000000000000000000000000000ffffae373156208d11e58754810000000000000000000000000000000000ffff51071276208d38f48754010000000000000000000000000000000000ffff6d4372e0208d4f448854010000000000000000000000000000000000ffffc556cbea208deb208854010000000000000000000000000000000000ffff450eb059208d97336054010000000000000000000000000000000000ffff4d60444a208dad608754010000000000000000000000000000000000ffff01abd3c3208d2cbe8754010000000000000000000000000000000000ffff7963d8b8208d9dfa8754010000000000000000000000000000000000ffffcebe862c208da7f66554010000000000000000000000000000000000ffff62f2a82e208d430d8854010000000000000000000000000000000000ffff6deb6956208d08148854010000000000000000000000000000000000ffff4b9804ef208d950e8854010000000000000000000000000000000000ffff6b8dd2e1208d1cfc8754010000000000000000000000000000000000ffffb2fe1dab208de7458754010000000000000000000000000000000000ffff32cccb82208d12625654010000000000000000000000000000000000ffff55e6f35a208dd7908354010000000000000000000000000000000000ffff4c642e6f208d17398854010000000000000000000000000000000000ffff45ea14df208dc72d8854010000000000000000000000000000000000ffffdaa48b96208d57fb8654010000000000000000000000000000000000ffff5f0080a9208d4e9a6d54010000000000000000000000000000000000ffff6d94e741208d7a418854010000000000000000000000000000000000ffff6d43b0c1208d5c098854010000000000000000000000000000000000ffff9c11e7ea208d442d8854010000000000000000000000000000000000ffffd0424482208db142fb53010000000000000000000000000000000000ffff54344a4a208d70158854010000000000000000000000000000000000ffff538f8201208d39288854010000000000000000000000000000000000ffff57daab71208d262f8854010000000000000000000000000000000000ffff57e045b1208d91430654010000000000000000000000000000000000ffff4212def5208d7c468854010000000000000000000000000000000000ffff62a4f53b208d9c3e8854010000000000000000000000000000000000ffff5b736bcc208d4d074f54010000000000000000000000000000000000ffff43af9d9b208deaca8754010000000000000000000000000000000000ffff4a8084a0208d74f68754010000000000000000000000000000000000ffff46304541208d5d098854010000000000000000000000000000000000ffff6eabb626208d08228854010000000000000000000000000000000000ffff1f07b030208d959a8254010000000000000000000000000000000000ffff4fa996b3208d4bac8754010000000000000000000000000000000000ffff43a0605b208de4038854010000000000000000000000000000000000ffff4cb41a5b208ddc308854010000000000000000000000000000000000ffffb72e6072208deac86f54010000000000000000000000000000000000ffffd5b815e2208d1b118854010000000000000000000000000000000000ffff3ff8d0a4208db1258854010000000000000000000000000000000000ffff5edd3a6a208d20268754010000000000000000000000000000000000ffff59ed3cfc208d83ed8754010000000000000000000000000000000000ffff4460baea208d71278854010000000000000000000000000000000000ffff4583b547208da7968754010000000000000000000000000000000000ffffd1c34463208d9d428854010000000000000000000000000000000000ffff43c15bc5208d33448854010000000000000000000000000000000000ffff2e0d8a5c208dfa2e7154010000000000000000000000000000000000ffffceff2bca208d47288854010000000000000000000000000000000000ffff5b8d0190208d0e847954010000000000000000000000000000000000ffff5501b8c2208d552f8854010000000000000000000000000000000000ffff5cf3b604208d8c2e8854010000000000000000000000000000000000ffff58666b0d208dcbdd8754010000000000000000000000000000000000ffff59a85cf9208d04208854010000000000000000000000000000000000ffff5f60abe0208dba238854010000000000000000000000000000000000ffff2e77ce67208d62c68754010000000000000000000000000000000000ffffcbae5658208d41108854010000000000000000000000000000000000ffff49329ec8208ddf1d8854010000000000000000000000000000000000ffff603ada12208df90c8854010000000000000000000000000000000000ffff51e0c21c208d5dff8754010000000000000000000000000000000000ffff904ca539208de0118854010000000000000000000000000000000000ffff5c0cecf7208d1b2d88540100000000000000200100005ef579fb18f317b2aba4edfc208dfaa27f54010000000000000000000000000000000000ffff54c65a7c208dc2db8754010000000000000000000000000000000000ffffad0b7981208d88a58754010000000000000000000000000000000000ffff188a1995208de0d78754010000000000000000000000000000000000ffff62a7a6bd208d18f66b54010000000000000000000000000000000000ffffdb4fbe80208dfcf78754010000000000000000000000000000000000ffff67145102208d3d3a8854010000000000000000000000000000000000ffff64259bd6208dcaa98654010000000000000000000000000000000000ffff32c737e1208d3a6a5c54010000000000000000000000000000000000ffff6d793f75208d22d38754010000000000000000000000000000000000ffff0595cc0d208da9ea8754010000000000000000000000000000000000ffff62ca37d3208d440e8854010000000000000000000000000000000000ffff123e1c11208dab428854010000000000000000000000000000000000ffff461df51f208d0f0b8854010000000000000000000000000000000000ffff59d36752208d631f8854010000000000000000000000000000000000ffff5ef8c640208dbc1d8854010000000000000000000000000000000000ffff5f5dc582208d04bf8754010000000000000000000000000000000000ffff0e6797e3208db8028854010000000000000000000000000000000000ffff3cf0c4a1208d6a0c8854010000000000000000000000000000000000ffff58bb5c55208d4f438854010000000000000000000000000000000000ffff5d326005208df6068854010000000000000000000000000000000000ffff5d5a5850208d40eb8754010000000000000000000000000000000000ffff5751ed86208dbad18754010000000000000000000000000000000000ffff63e51608208d99108854010000000000000000000000000000000000ffff36e299cd208d9a1e88540100000000000000200100005ef579fd38a43785a5d441b7208d13258854010000000000000000000000000000000000ffffb90de279208db3778754010000000000000000000000000000000000ffff67fcc80c208d3d358854010000000000000000000000000000000000ffffd00c40fc208dfd0d8854010000000000000000000000000000000000ffff5e89c2f3208dd6428854010000000000000000000000000000000000ffff1863e14a208d89e78754010000000000000000000000000000000000ffff43a11d76208d89ce8754010000000000000000000000000000000000ffff3edb6281208d03298854010000000000000000000000000000000000ffff6d5b26dd208d16df6e54010000000000000000000000000000000000ffffd9c3d176208d7c028854010000000000000000000000000000000000ffffbcc2db1f208d67fc8754010000000000000000000000000000000000ffff59910667208d86b18754010000000000000000000000000000000000ffff6c1482b9208d790b8854010000000000000000000000000000000000ffff0263150d208d89c44554010000000000000000000000000000000000ffff72fcd6b8208d331c8854010000000000000000000000000000000000ffff4c7dcfa8208d9fe98754010000000000000000000000000000000000ffffad08fd05208d0ecd8754010000000000000000000000000000000000ffff7c0fec89208d4af98754010000000000000000000000000000000000ffff2ea6a167208d270b8854010000000000000000000000000000000000ffff59d40fca208d11cf4054010000000000000000000000000000000000ffffb52ac058208d63358254010000000000000000000000000000000000ffff6bd9a130208dbde48754010000000000000000000000000000000000ffff4f915dc7208dece78754010000000000000000000000000000000000ffff5f8423ef208dba308854010000000000000000000000000000000000ffff904cba8c208d953d8854010000000000000000000000000000000000ffff8d1421e5208d5f188854010000000000000000000000000000000000ffff4e1bbfb6208d36fc8754010000000000000000000000000000000000ffff3205cfb6208d5e458854010000000000000000000000000000000000ffff493092c2208dd0878754010000000000000000000000000000000000ffff97caacde208dd99d8754010000000000000000000000000000000000ffffc05f3c38208da7018854010000000000000000000000000000000000ffffdcf5f82d208df0f42254010000000000000000000000000000000000ffff58bf997f208d94228854010000000000000000000000000000000000ffffbc8ee72d208de3448854010000000000000000000000000000000000ffff6d780f99208d32448854010000000000000000000000000000000000ffffbc64c76e208d0b3d8854010000000000000000000000000000000000ffff6c414312208d49138854010000000000000000000000000000000000ffff407e52c6208d2d128854010000000000000000000000000000000000ffff6dc987d8208d4acf8754010000000000000000000000000000000000ffffae151a25208de9dd8754010000000000000000000000000000000000ffff522d2d10208d17df8754010000000000000000000000000000000000ffff4d294737208ddbd48754010000000000000000000000000000000000ffff443703fd208d79108854010000000000000000000000000000000000ffff4bb5a154208d1df28753010000000000000000000000000000000000ffff4c44fc51208d5d496a53010000000000000000000000000000000000ffff530924b0208d180b8854010000000000000000000000000000000000ffffc15fced3208d53128854010000000000000000000000000000000000ffff5bc5b9e4208de21f8854010000000000000000000000000000000000ffff2e05fe1f208d31ab6554010000000000000000000000000000000000ffffae865915208d09248854010000000000000000000000000000000000ffff7d71a4b0208d120b8854010000000000000000000000000000000000ffff93e40150208d25d98754010000000000000000000000000000000000ffffc19f7a86208d99e78754010000000000000000000000000000000000ffff4d675993208d2b218854010000000000000000000000000000000000ffff5144776c208d5a208854010000000000000000000000000000000000ffff2e1cce5820f69e8f8154010000000000000000000000000000000000ffff58718848208d9edd6554010000000000000000000000000000000000ffff2ea758c5208d28c98754010000000000000000000000000000000000ffffc60bd694208d181d8854010000000000000000000000000000000000ffffba6b689e208d77868754010000000000000000000000000000000000ffff182016aa208de1698054010000000000000000000000000000000000ffff5cf5daa9208d823d8854010000000000000000000000000000000000ffffbe4d61c8208d2c3b8854010000000000000000000000000000000000ffff71647df2208da2248854010000000000000000000000000000000000ffff4ff359ca208da2dd8754010000000000000000000000000000000000ffff59f1e52b208da6e60d54010000000000000000000000000000000000ffff579596231159ad218854010000000000000000000000000000000000ffff5895f42d208d5ba78754010000000000000000000000000000000000ffff458ae8b0208ddc9b8754010000000000000000000000000000000000ffff18b507a5208d20eb5754010000000000000000000000000000000000ffff5181a537208daf1d8854010000000000000000000000000000000000ffffd5f51eba208d07f58754010000000000000000000000000000000000ffff6dec54bc208d4b3b8854010000000000000000000000000000000000ffff5c3e1991208d13b54054010000000000000000000000000000000000ffffb273845a208d4f338854010000000000000000000000000000000000ffffcfac797b208d4d898754010000000000000000000000000000000000ffffadaf880d208de72f8854010000000000000000000000000000000000ffffd956ef58208dc1e98754010000000000000000000000000000000000ffff980711cf208dfd8f7154010000000000000000000000000000000000ffff50aba98f208d59fe8754010000000000000000000000000000000000ffff5f2ae582208d3ffa8754010000000000000000000000000000000000ffffbca836db208d389b0054010000000000000000000000000000000000ffffded10925208deb154354010000000000000000000000000000000000ffff717532da208dadde7654010000000000000000000000000000000000ffff93afd0e5208d40458854010000000000000000000000000000000000ffff54ee8cb0208d08fa8754010000000000000000000000000000000000ffff3658eba9208d0df58754010000000000000000000000000000000000ffff72c6876e208d5d408854010000000000000000000000000000000000ffffa7582d7c208de8338854010000000000000000000000000000000000ffffad40d7ad208d6c2a8854010000000000000000000000000000000000ffffbc1833a3208d0fc28754010000000000000000000000000000000000ffffcbceb676208d98448854010000000000000000000000000000000000ffff0121ce6e208d77b58754010000000000000000000000000000000000ffff4b40d938208d853b8854010000000000000000000000000000000000ffff5648d643208d1c9c7854010000000000000000000000000000000000ffff6c3230db208d4e028754010000000000000000000000000000000000ffffd8e33c5d208daf738754010000000000000000000000000000000000ffffad4e9d56208dca438854010000000000000000000000000000000000ffff4f8a0353208d7b408854010000000000000000000000000000000000ffff0599e92a208dc62f8854010000000000000000000000000000000000ffff46236049208dc4d58754010000000000000000000000000000000000ffff4400384d208d0ddb4c54010000000000000000000000000000000000ffff71653b5a208d53388854010000000000000000000000000000000000ffff43bddf66208d7c388854010000000000000000000000000000000000ffff47edca96208d8c068854010000000000000000000000000000000000ffffdded3c2c208d89f60554010000000000000000000000000000000000ffff4e60d8d6208da5ff1154010000000000000000000000000000000000ffff55983c68208d72f58754010000000000000000000000000000000000ffffce47f56a208d7e2d8854010000000000000000000000000000000000ffff17e25c12208da7ca8754010000000000000000000000000000000000ffff051d0baa208d19af8754010000000000000000000000000000000000ffffad42d883208dffd68754010000000000000000000000000000000000ffff5418ff88208d87bf8754010000000000000000000000000000000000ffff4c1fe0dc208deef18754010000000000000000000000000000000000ffff4c71d406208d25868754010000000000000000000000000000000000ffffda67a475208d4d348854010000000000000000000000000000000000ffff5d561269208da5918754010000000000000000000000000000000000ffff42b163b3208dbfac3754010000000000000000000000000000000000ffffad4e01eb208d5c408854010000000000000000000000000000000000ffffcc0bb91e208d4a928754010000000000000000000000000000000000ffff531dba30208db9fd8754010000000000000000000000000000000000ffffa3f72b33208dade58754010000000000000000000000000000000000ffffd8b93aeb208db75d4d54010000000000000000000000000000000000ffffae1d5595208de8d18754010000000000000000000000000000000000ffff4405665c208d88e78754010000000000000000000000000000000000ffff6b96180a208dae667354010000000000000000000000000000000000ffff531f490a208d92f98754010000000000000000000000000000000000ffffd447e857208db1428854010000000000000000000000000000000000ffff36c6b4bb208dd42c8854010000000000000000000000000000000000ffff4b4aff14208df71acd53010000000000000000000000000000000000ffff62d17930208da0b88754010000000000000000000000000000000000ffff974bf913208d5faa7953010000000000000000000000000000000000ffff7419d269208d6c1e8854010000000000000000000000000000000000ffff48a72343208d9c2b8854010000000000000000000000000000000000ffff64431e87208de8908754010000000000000000000000000000000000ffff44660d45208d410b7d54010000000000000000000000000000000000ffff43a3340d208d5b298854010000000000000000000000000000000000ffff9ffda678208de7238854010000000000000000000000000000000000ffff189a3759208da5745354010000000000000000000000000000000000ffff57026e31208df23c885401000000000000002a010488006710000523fbe100000001208dddf48754010000000000000000000000000000000000ffffc1531c5b208d61888754010000000000000000000000000000000000ffff42cd8bc1208df92e8854010000000000000000000000000000000000ffff36e1e970208d0cf75754010000000000000000000000000000000000ffffad4e2420208d0f028854010000000000000000000000000000000000ffff6ec61b02208de6228854010000000000000000000000000000000000ffffdcaa807c208d1cac8754010000000000000000000000000000000000ffff6179416c208db6068854010000000000000000000000000000000000ffffd46121e4208d7ea58754010000000000000000000000000000000000ffff80a41679208d91216254010000000000000000000000000000000000ffff69ece091208d0bf78754010000000000000000000000000000000000ffff45320c0b208dd03c8854010000000000000000000000000000000000ffff4859a2a5208d8e308854010000000000000000000000000000000000ffff2e1ce15a208d30f36354010000000000000000000000000000000000ffff4b61ee15208d7aa05553010000000000000000000000000000000000ffffb129186b208dbb238854010000000000000000000000000000000000ffff581a7f6f208d41248754010000000000000000000000000000000000ffffb009055b208dee218854010000000000000000000000000000000000ffff2e7771f9208db4e78754010000000000000000000000000000000000ffff411bf6ee208db5178854010000000000000000000000000000000000ffffacff001b208dbcf487540100000000000000200100009d386ab83061cfabfe3513a3208d76048854010000000000000000000000000000000000ffff2e35dbc2208d83d28754010000000000000000000000000000000000ffffd91bb1d5208d5c3d8854010000000000000000000000000000000000ffff32a47969208df4bd875401000000000000002a0104f8012032250000000000000002208d1bfa8754010000000000000000000000000000000000ffff53a2f4b6208d141c4554010000000000000000000000000000000000ffffb27b8281208d580c7e54010000000000000000000000000000000000ffffc9ea1340208df7228854010000000000000000000000000000000000ffffd90a268d208d41e18754010000000000000000000000000000000000ffff555b8809208d66358854010000000000000000000000000000000000ffff5280ff23208deecd8754010000000000000000000000000000000000ffff84c6a102208df9bc8754010000000000000000000000000000000000ffffbbc96dae208d9d0a8854010000000000000000000000000000000000ffffb2a2d18a208da9458854010000000000000000000000000000000000ffffca16c30e208d2ca88754010000000000000000000000000000000000ffff6baab63e208d923b8854010000000000000000000000000000000000ffffd5b87bee208d12258854010000000000000000000000000000000000ffff59ee408b208d980e8854010000000000000000000000000000000000ffff2edfb0c4208d33378854010000000000000000000000000000000000ffff6caa7b42208d41176d53010000000000000000000000000000000000ffff1fa2df68208dd1c57f54010000000000000000000000000000000000ffffbeab679a208d17c78754010000000000000000000000000000000000ffff3f983f51208dbb278854010000000000000000000000000000000000ffff568dbc28208d5d5ad953010000000000000000000000000000000000ffff0ec112b7208da1b78754010000000000000000000000000000000000ffffbc287698208d99348854010000000000000000000000000000000000ffff5ff1a302208d23ef8454010000000000000000000000000000000000ffff7ba55d22208d90e98754010000000000000000000000000000000000ffff5ae14503208d58f48754010000000000000000000000000000000000ffffd45a3cae208d530c8854010000000000000000000000000000000000ffff18fb80c4208dc6138854010000000000000000000000000000000000ffff4e2fd6eb208d97208854010000000000000000000000000000000000ffff55ddd5b8208d74f88654010000000000000000000000000000000000ffff1809ad86208dc0278854010000000000000000000000000000000000ffff6ef2dfb6208d84338854010000000000000000000000000000000000ffff58d8114e208dcae78754010000000000000000000000000000000000ffffc21c474c208d61e78754010000000000000000000000000000000000ffffd5b3fc7a208d7a078854010000000000000000000000000000000000ffff32740193208de7547b54010000000000000000000000000000000000ffff8046aac3208db5328854010000000000000000000000000000000000ffff5b0c5366208d5f208854010000000000000000000000000000000000ffff6caa054a208d1d682854010000000000000000000000000000000000ffff69e3eb9d208d179e6b54010000000000000000000000000000000000ffff54aac628208ded2e8854010000000000000000000000000000000000ffff4166d226208d8e148854010000000000000000000000000000000000ffff54305af6208d1ed88754010000000000000000000000000000000000ffff3ed2b2a4208d78ec8554010000000000000000000000000000000000ffff6dd2e575208ddd3e7854010000000000000000000000000000000000ffff1b83a102208d421e8854010000000000000000000000000000000000ffff2eb51bf1208dc01f8854010000000000000000000000000000000000ffff58c66033208d08ce8754010000000000000000000000000000000000ffff5d57b84c208df22e8854010000000000000000000000000000000000ffffbf213e3d208d89cb8754010000000000000000000000000000000000ffff53d7ece1208da9d68754010000000000000000000000000000000000ffff71bef412208d38268854010000000000000000000000000000000000ffff84fc8a6f208d4f107254010000000000000000000000000000000000ffff57a072d9208d990f88540100000000000000200100009d3890d71029db21863020f9208df3c36854010000000000000000000000000000000000ffffbba6a6c4208d3ea71f54010000000000000000000000000000000000ffff3e61237b208d60a18754010000000000000000000000000000000000ffff53a56b4b208db2328854010000000000000000000000000000000000ffff44eef265208d2c0f8854010000000000000000000000000000000000ffff566553c0208dec268854010000000000000000000000000000000000ffff57a69ecd208d93148854010000000000000000000000000000000000ffff9b8f4433208d741b8854010000000000000000000000000000000000ffffae17c74c208ddab94f54010000000000000000000000000000000000ffffab19c63a208d5e886f54010000000000000000000000000000000000ffffbc67a037208d5c3ccc53010000000000000000000000000000000000ffff0e77c86e208d26fb8754010000000000000000000000000000000000ffffc1eae172208d61278854010000000000000000000000000000000000ffff5ce15439208d60df8754010000000000000000000000000000000000ffff2599fb3b208d52418854010000000000000000000000000000000000ffff028710e6208dae448854010000000000000000000000000000000000ffff18152eee208d6ff18754010000000000000000000000000000000000ffff542e396d208d8c248854010000000000000000000000000000000000ffff5519d6d8208d01e33e54010000000000000000000000000000000000ffff5027d563208d05cd8754010000000000000000000000000000000000ffff45a5a9c5208d7c1e8854010000000000000000000000000000000000ffff68c813c8208d38a48754010000000000000000000000000000000000ffff3a60a934208d73ec8754010000000000000000000000000000000000ffffae3db8f3208d096c8754010000000000000000000000000000000000ffff4cb88802208d55d38754010000000000000000000000000000000000ffffd31f082e208d0d8a8754010000000000000000000000000000000000ffff6c22c9bb208d81ba7754010000000000000000000000000000000000ffffca5f88da208d5df88754010000000000000000000000000000000000ffffc31abc05208db1d58754010000000000000000000000000000000000ffff5cf614c8208d2b257c54010000000000000000000000000000000000ffff45f9baa0208d38d78754010000000000000000000000000000000000ffff490e0608208d27e88754010000000000000000000000000000000000ffff17f1ccd0208d501c8854010000000000000000000000000000000000ffff5be834a7208d860b8854010000000000000000000000000000000000ffff640173bc208deada8754010000000000000000000000000000000000ffffdc98f8db208d983d3c54010000000000000000000000000000000000ffff021ab398208d99f28754010000000000000000000000000000000000ffff59a9e417208d19468854010000000000000000000000000000000000ffff91ff01a1208ddbf68754010000000000000000000000000000000000ffff50048949208d89118854010000000000000000000000000000000000ffffc8366923208dea3b8854010000000000000000000000000000000000ffffbcbbb5a9208d364a8854010000000000000000000000000000000000ffffc654bd58208dd52ef953010000000000000000000000000000000000ffff7cabb7bc208db086cc53010000000000000000000000000000000000ffff6c4004ea208d0ff48754010000000000000000000000000000000000ffff0536a3cb208d1c0b8854010000000000000000000000000000000000ffffd90bfef3208dbf188854010000000000000000000000000000000000ffff0252431f208dd1c78754010000000000000000000000000000000000ffff25732b19208dd6fa3954010000000000000000000000000000000000ffff6c1131dc208da6108854010000000000000000000000000000000000ffff1fb9b748208daf368854010000000000000000000000000000000000ffff1f2a2981208d9e2f8854010000000000000000000000000000000000ffffbc658482208d6a458854010000000000000000000000000000000000ffff568b843a208dcd428854010000000000000000000000000000000000ffff5d820794208d38258854010000000000000000000000000000000000ffff6440c630208d1cda8754010000000000000000000000000000000000ffff6e9f7be5208ddd388854010000000000000000000000000000000000ffff9f080244208d2e2d8854010000000000000000000000000000000000ffffbb70cf3d208d3e0c8854010000000000000000000000000000000000ffff3ec27a16208d89098854010000000000000000000000000000000000ffffb55fb2fb208d13468854010000000000000000000000000000000000ffff180ab1e5208dfc2a8854010000000000000000000000000000000000ffffd58a5c0e208db9188854010000000000000000000000000000000000ffff81ce808d208d03d06b54010000000000000000000000000000000000ffff4b496858208d319e8454010000000000000000000000000000000000ffff531fac9f208dc8d98754010000000000000000000000000000000000ffffc113e4ea208d06395754010000000000000000000000000000000000ffff7cabf0d6208d722b8854010000000000000000000000000000000000ffff545db4cd208dd91f8854010000000000000000000000000000000000ffff5f12a597208dfbd57054010000000000000000000000000000000000ffff3cf654e6208dd7358854010000000000000000000000000000000000ffffb4998e79208dd7018854010000000000000000000000000000000000ffffb45cc220208d86df5554010000000000000000000000000000000000ffff7660d462208decd28754010000000000000000000000000000000000ffff5b7bdfe2208dafe28754010000000000000000000000000000000000ffff2e76899e208d0c318854010000000000000000000000000000000000ffffc7bcb183208d35258854010000000000000000000000000000000000ffff3ab2d076208db5c38754010000000000000000000000000000000000ffff48b24d30208ddfd18754010000000000000000000000000000000000ffff904cb00c208d40478854010000000000000000000000000000000000ffff02a3b787208deb318854010000000000000000000000000000000000ffffd2564026208de63f8854010000000000000000000000000000000000ffff615d16c0208dbf1d8854010000000000000000000000000000000000ffffc6fff6f0208d4aee8754010000000000000000000000000000000000ffff56c74bc8208d68bc8754010000000000000000000000000000000000ffff31b581f9208ddcca8754010000000000000000000000000000000000ffff0e97397a208db2318854010000000000000000000000000000000000ffffc766348d208de1bd8754010000000000000000000000000000000000ffffd5927a3d208d2aff8754010000000000000000000000000000000000ffff5152d0a5208da7feb753010000000000000000000000000000000000ffff53fe1694208de7418854010000000000000000000000000000000000ffff189f3d99208de7b38754010000000000000000000000000000000000ffff64004655208dacabb653010000000000000000000000000000000000ffff55413231208d43b88754010000000000000000000000000000000000ffff796e1029208d03788754010000000000000000000000000000000000ffff49b90ff6208d04be8754010000000000000000000000000000000000ffffc327ce1c208d33648254010000000000000000000000000000000000ffff2e896478208d9e308854010000000000000000000000000000000000ffffd395d9bf208df13e8854010000000000000000000000000000000000ffff51bb88ed208dec8c5653010000000000000000000000000000000000ffff557f146e208df4538854010000000000000000000000000000000000ffff48d05928208d41bd8154010000000000000000000000000000000000ffff47384162208df6428854010000000000000000000000000000000000ffffc1566314208d753e8854010000000000000000000000000000000000ffff440c9b4f208d2f078854010000000000000000000000000000000000ffff56021a19208d7b0f4754010000000000000000000000000000000000ffff71653a84208dcee88754010000000000000000000000000000000000ffff5401a332208df4db8754010000000000000000000000000000000000ffff5a9db0e3208df8e38754010000000000000000000000000000000000ffff5d5208fa208d2e298854010000000000000000000000000000000000ffff4c61c0bc208d42ea8754010000000000000000000000000000000000ffff4c405ae7208deade8754010000000000000000000000000000000000ffffc009c823208d99358854010000000000000000000000000000000000ffff6cc2b493208df3fd8754010000000000000000000000000000000000ffff904c6007208d1fde8754010000000000000000000000000000000000ffff92732ae8208d666e8354010000000000000000000000000000000000ffff6daa9d16208d18f78754010000000000000000000000000000000000ffff4d6d8d8a208ddea9ce53010000000000000000000000000000000000ffff17e26f7d208d1ae48754010000000000000000000000000000000000ffffc32ebb89208dee258854010000000000000000000000000000000000ffff4a0f10e1208d290a8854010000000000000000000000000000000000ffff42731593208d7cb33354010000000000000000000000000000000000ffff532e8b97208dfd1e8854010000000000000000000000000000000000ffff6ee91680208d480a1354010000000000000000000000000000000000ffff60f220f6208d55838754010000000000000000000000000000000000ffff1f36d48f208d247e8754010000000000000000000000000000000000ffff44b5a49a208d0efa4954010000000000000000000000000000000000ffff63890597208d862e8854010000000000000000000000000000000000ffffc14d8751208d443c8854010000000000000000000000000000000000ffff70d16dba208d72fe8754010000000000000000000000000000000000ffff55983dd3208d236b6854010000000000000000000000000000000000ffff7aea27f9208d67e08754010000000000000000000000000000000000ffff1b20750d208df10a7154010000000000000000000000000000000000ffff7b9fe56e208db32d8854010000000000000000000000000000000000ffff7ba42d54208db3f58754010000000000000000000000000000000000ffff027b4eba208d70d68754010000000000000000000000000000000000ffffbc1b62b9208d97951454010000000000000000000000000000000000ffffb4b6af10208d251288540100000000000000200100009d386ab800773149c4d7e673208d7b168854010000000000000000000000000000000000ffff58969392208d91d18754010000000000000000000000000000000000ffff9ffd6d4e208d19008854010000000000000000000000000000000000ffff4e926cb8208dd1c98754010000000000000000000000000000000000ffff546cdb77208d6a0a8854010000000000000000000000000000000000ffff6caa8c15208d3a3a8854010000000000000000000000000000000000ffff62e28d6a208d3fe48754010000000000000000000000000000000000ffffd9850be7208dcb438854010000000000000000000000000000000000ffff6c2444b3208d78338854010000000000000000000000000000000000ffffb2df574a208d3b2d3054010000000000000000000000000000000000ffff4b529e67208d5b988754010000000000000000000000000000000000ffff36e152e6208d01ab8754010000000000000000000000000000000000ffff58b531bf208d7b038854010000000000000000000000000000000000ffff54717a6f208db0388854010000000000000000000000000000000000ffff4a694e98208d65418854010000000000000000000000000000000000ffffcef84b25208dcf218854010000000000000000000000000000000000ffff5364fed9208db6c68754010000000000000000000000000000000000ffff4d03decd208dc6158854010000000000000000000000000000000000ffffb9044c79208d65418854010000000000000000000000000000000000ffff56b2192f208d23308754010000000000000000000000000000000000ffffd973f7a4208d05158854010000000000000000000000000000000000ffff1b6d9975208d52cd8754010000000000000000000000000000000000ffffdf5b92ad208d3a128854010000000000000000000000000000000000ffff6d93787e208d351e8854010000000000000000000000000000000000ffffb6a4336f208d9bd58754010000000000000000000000000000000000ffff3ba7f521208dc3288854010000000000000000000000000000000000ffff6bbf20cc208d68998754010000000000000000000000000000000000ffff63b30032208d86848754010000000000000000000000000000000000ffffb52ef15c208dff108854010000000000000000000000000000000000ffff4d06718b208d226f0754010000000000000000000000000000000000ffff44248ba1208da3af7354010000000000000000000000000000000000ffff86f9d72c208d9fd68754010000000000000000000000000000000000ffff521f2444208dae428854010000000000000000000000000000000000ffffd445b025208dfafc8754010000000000000000000000000000000000ffffd447fa84208dbb358854010000000000000000000000000000000000ffff3ee2d196208da01a8854010000000000000000000000000000000000ffff45a615b2208d32edb053010000000000000000000000000000000000ffff6d6494da208d77ed8754010000000000000000000000000000000000ffff555dcc44208d09608754010000000000000000000000000000000000ffff45a5d222208d84558754010000000000000000000000000000000000ffffb01c30ab208d71fe8754010000000000000000000000000000000000ffffae5f68c2208de0acdf53010000000000000000000000000000000000ffffae737936208d29e18754010000000000000000000000000000000000ffffb00a63cb208d220b8854010000000000000000000000000000000000ffff9ec4d12f208db4408854010000000000000000000000000000000000ffff4c6d9bcc208d65a58754010000000000000000000000000000000000ffff5962c453208d2d628854010000000000000000000000000000000000ffff257191d50000' -}; - -var PING = { - message: 'f9beb4d9706f6e67000000000000000008000000c6466f1e6b86480ae969867c', - payload: '6b86480ae969867c' -}; - describe('Messages', function() { describe('Version', function() { @@ -60,7 +36,7 @@ describe('Messages', function() { }); it('should be able to parse payload', function() { - var payload = new Buffer(VERSION.payload, 'hex'); + var payload = new Buffer(Data.VERSION.payload, 'hex'); var message = new Messages.Version().fromBuffer(payload); }); }); @@ -84,7 +60,7 @@ describe('Messages', function() { }); it('should be able to parse payload', function() { - var payload = new Buffer(VERACK.payload, 'hex'); + var payload = new Buffer(Data.VERACK.payload, 'hex'); var message = new Messages.VerAck().fromBuffer(payload); }); }); @@ -108,7 +84,7 @@ describe('Messages', function() { }); it('should be able to parse payload', function() { - var payload = new Buffer(INV.payload, 'hex'); + var payload = new Buffer(Data.INV.payload, 'hex'); var message = new Messages.Inventory().fromBuffer(payload); }); }); @@ -132,7 +108,7 @@ describe('Messages', function() { }); it('should be able to parse payload', function() { - var payload = new Buffer(ADDR.payload, 'hex'); + var payload = new Buffer(Data.ADDR.payload, 'hex'); var message = new Messages.Addresses().fromBuffer(payload); }); }); @@ -156,7 +132,7 @@ describe('Messages', function() { }); it('should be able to parse payload', function() { - var payload = new Buffer(PING.payload, 'hex'); + var payload = new Buffer(Data.PING.payload, 'hex'); var message = new Messages.Ping().fromBuffer(payload); }); }); @@ -180,7 +156,7 @@ describe('Messages', function() { }); it('should be able to parse payload', function() { - var payload = new Buffer(PING.payload, 'hex'); + var payload = new Buffer(Data.PING.payload, 'hex'); var message = new Messages.Pong().fromBuffer(payload); }); }); From c30f656540d246918b20667a64deb12f6b057d6f Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 11 Dec 2014 11:57:14 -0300 Subject: [PATCH 17/21] Remove custom hex function at networks.js --- lib/networks.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/networks.js b/lib/networks.js index 12a1d4e..a6c0cc5 100644 --- a/lib/networks.js +++ b/lib/networks.js @@ -1,6 +1,8 @@ 'use strict'; var _ = require('lodash'); +var BufferUtil = require('./util/buffer'); + /** * A network is merely a map containing values that correspond to version * numbers for each bitcoin network. Currently only supporting "livenet" @@ -9,8 +11,6 @@ var _ = require('lodash'); */ function Network() {} -var hex = function(hex) { return new Buffer(hex, 'hex'); }; - /** * @instance * @member Network#livenet @@ -24,7 +24,7 @@ _.extend(livenet, { scripthash: 0x05, xpubkey: 0x0488b21e, xprivkey: 0x0488ade4, - networkMagic: hex('f9beb4d9'), + networkMagic: BufferUtil.integerAsBuffer(0xf9beb4d9), port: 8333, dnsSeeds: [ 'seed.bitcoin.sipa.be', @@ -49,7 +49,7 @@ _.extend(testnet, { scripthash: 0xc4, xpubkey: 0x043587cf, xprivkey: 0x04358394, - networkMagic: hex('0b110907'), + networkMagic: BufferUtil.integerAsBuffer(0x0b110907), port: 18333, dnsSeeds: [ 'testnet-seed.bitcoin.petertodd.org', From ee169a80af1274a74dc2047906048ed98eb9e7ec Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 11 Dec 2014 12:45:24 -0300 Subject: [PATCH 18/21] Replace buffertools --- lib/transport/messages.js | 5 ++--- lib/util/buffer.js | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/transport/messages.js b/lib/transport/messages.js index dbb3f12..8a55a8d 100644 --- a/lib/transport/messages.js +++ b/lib/transport/messages.js @@ -1,7 +1,6 @@ 'use strict'; var Buffers = require('buffers'); -var buffertools = require('buffertools'); var Put = require('bufferput'); var util = require('util'); @@ -43,7 +42,7 @@ var parseMessage = function(network, dataBuffer) { var checksum = dataBuffer.slice(20, 24); var checksumConfirm = Hash.sha256sha256(payload).slice(0, 4); - if (buffertools.compare(checksumConfirm, checksum) !== 0) { + if (!BufferUtil.equals(checksumConfirm, checksum)) { dataBuffer.skip(messageLength); return; } @@ -67,7 +66,7 @@ function discardUntilNextMessage(network, dataBuffer) { for (;;) { // check if it's the beginning of a new message var packageNumber = dataBuffer.slice(0, 4); - if (buffertools.compare(packageNumber, magicNumber) == 0) { + if (BufferUtil.equals(packageNumber, magicNumber)) { dataBuffer.skip(i); return true; } diff --git a/lib/util/buffer.js b/lib/util/buffer.js index deb9f3a..f7de3cd 100644 --- a/lib/util/buffer.js +++ b/lib/util/buffer.js @@ -2,7 +2,6 @@ var buffer = require('buffer'); var assert = require('assert'); -var buffertools = require('buffertools'); var js = require('./js'); @@ -20,8 +19,20 @@ function equals(a, b) { } module.exports = { - NULL_HASH: buffertools.fill(new Buffer(32), 0), - EMPTY_BUFFER: new Buffer(0), + /** + * Fill a buffer with a value. + * + * @param {Buffer} buffer + * @param {number} value + * @return {Buffer} + */ + fill: function fill(buffer, value) { + var length = buffer.length; + for (var i = 0; i < length; i++) { + buffer[i] = value; + } + return buffer; + }, /** * Returns true if the given argument is an instance of a buffer. Tests for @@ -140,3 +151,6 @@ module.exports = { return new buffer.Buffer(string, 'hex'); } }; + +module.exports.NULL_HASH = module.exports.fill(new Buffer(32), 0); +module.exports.EMPTY_BUFFER = new Buffer(0); From 9b6183b08377b9bf53dc9f64860bdf3f5d087dbc Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 11 Dec 2014 13:11:55 -0300 Subject: [PATCH 19/21] Improving code thanks to jshint --- lib/transport/messages.js | 41 +++++++++++++++++++------------------- test/transport/messages.js | 15 ++++++-------- test/transport/peer.js | 3 ++- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/lib/transport/messages.js b/lib/transport/messages.js index 8a55a8d..5b0fb19 100644 --- a/lib/transport/messages.js +++ b/lib/transport/messages.js @@ -1,4 +1,5 @@ 'use strict'; +/* jshint curly: false */ var Buffers = require('buffers'); var Put = require('bufferput'); @@ -85,7 +86,7 @@ function discardUntilNextMessage(network, dataBuffer) { * Abstract Message that knows how to parse and serialize itself. * Concret subclases should implement {fromBuffer} and {getPayload} methods. */ -function Message() {}; +function Message() {} Message.COMMANDS = {}; @@ -95,7 +96,6 @@ Message.buildMessage = function(command, payload) { return new CommandClass().fromBuffer(payload); } catch (err) { console.log('Error while parsing message', err); - throw err; } }; @@ -106,6 +106,7 @@ Message.buildMessage = function(command, payload) { * @returns{Message} The same message instance */ Message.prototype.fromBuffer = function(payload) { + /* jshint unused: false */ return this; }; @@ -188,7 +189,7 @@ Version.prototype.getPayload = function() { return put.buffer(); }; -module.exports.Version = Message.COMMANDS['version'] = Version; +module.exports.Version = Message.COMMANDS.version = Version; /** * Inv Message @@ -226,7 +227,7 @@ Inventory.prototype.getPayload = function() { return put.buffer(); }; -module.exports.Inventory = Message.COMMANDS['inv'] = Inventory; +module.exports.Inventory = Message.COMMANDS.inv = Inventory; /** * Getdata Message @@ -261,7 +262,7 @@ Ping.prototype.getPayload = function() { return this.nonce; }; -module.exports.Ping = Message.COMMANDS['ping'] = Ping; +module.exports.Ping = Message.COMMANDS.ping = Ping; /** * Pong Message @@ -274,7 +275,7 @@ function Pong(nonce) { } util.inherits(Pong, Ping); -module.exports.Pong = Message.COMMANDS['pong'] = Pong; +module.exports.Pong = Message.COMMANDS.pong = Pong; /** * Addr Message @@ -319,7 +320,7 @@ Addresses.prototype.getPayload = function() { return put.buffer(); }; -module.exports.Addresses = Message.COMMANDS['addr'] = Addresses; +module.exports.Addresses = Message.COMMANDS.addr = Addresses; /** * GetAddr Message @@ -330,7 +331,7 @@ function GetAddresses() { } util.inherits(GetAddresses, Message); -module.exports.GetAddresses = Message.COMMANDS['getaddr'] = GetAddresses; +module.exports.GetAddresses = Message.COMMANDS.getaddr = GetAddresses; /** * Verack Message @@ -341,7 +342,7 @@ function VerAck() { } util.inherits(VerAck, Message); -module.exports.VerAck = Message.COMMANDS['verack'] = VerAck; +module.exports.VerAck = Message.COMMANDS.verack = VerAck; /** * Reject Message @@ -354,7 +355,7 @@ util.inherits(Reject, Message); // TODO: Parse REJECT message -module.exports.Reject = Message.COMMANDS['reject'] = Reject; +module.exports.Reject = Message.COMMANDS.reject = Reject; /** * Alert Message @@ -385,7 +386,7 @@ Alert.prototype.getPayload = function() { return put.buffer(); }; -module.exports.Alert = Message.COMMANDS['alert'] = Alert; +module.exports.Alert = Message.COMMANDS.alert = Alert; /** * Headers Message @@ -423,7 +424,7 @@ Headers.prototype.getPayload = function() { return put.buffer(); }; -module.exports.Headers = Message.COMMANDS['headers'] = Headers; +module.exports.Headers = Message.COMMANDS.headers = Headers; /** * Block Message @@ -445,7 +446,7 @@ Block.prototype.getPayload = function() { return this.block.toBuffer(); }; -module.exports.Block = Message.COMMANDS['block'] = Block; +module.exports.Block = Message.COMMANDS.block = Block; /** * Tx Message @@ -467,7 +468,7 @@ Transaction.prototype.getPayload = function() { return this.transaction.toBuffer(); }; -module.exports.Transaction = Message.COMMANDS['tx'] = Transaction; +module.exports.Transaction = Message.COMMANDS.tx = Transaction; /** * Getblocks Message @@ -503,13 +504,13 @@ GetBlocks.prototype.getPayload = function() { put.varint(this.starts.length); for (var i = 0; i < this.starts.length; i++) { - if (this.starts[i].length != 32) { + if (this.starts[i].length !== 32) { throw new Error('Invalid hash length'); } put.put(this.starts[i]); } - if (this.stop.length != 32) { + if (this.stop.length !== 32) { throw new Error('Invalid hash length'); } put.put(this.stop); @@ -517,7 +518,7 @@ GetBlocks.prototype.getPayload = function() { return put.buffer(); }; -module.exports.GetBlocks = Message.COMMANDS['getblocks'] = GetBlocks; +module.exports.GetBlocks = Message.COMMANDS.getblocks = GetBlocks; /** * Getheaders Message @@ -533,14 +534,14 @@ function GetHeaders(starts, stop) { } util.inherits(GetHeaders, GetBlocks); -module.exports.GetHeaders = Message.COMMANDS['getheaders'] = GetHeaders; +module.exports.GetHeaders = Message.COMMANDS.getheaders = GetHeaders; // TODO: Remove this PATCH (yemel) Buffers.prototype.skip = function (i) { - if (i == 0) return; + if (i === 0) return; - if (i == this.length) { + if (i === this.length) { this.buffers = []; this.length = 0; return; diff --git a/test/transport/messages.js b/test/transport/messages.js index 9e7c851..15325b3 100644 --- a/test/transport/messages.js +++ b/test/transport/messages.js @@ -1,11 +1,8 @@ 'use strict'; var chai = require('chai'); -var Net = require('net'); -var Socks5Client = require('socks5-client'); var should = chai.should(); -var expect = chai.expect; var bitcore = require('../..'); var Data = require('../data/messages'); @@ -37,7 +34,7 @@ describe('Messages', function() { it('should be able to parse payload', function() { var payload = new Buffer(Data.VERSION.payload, 'hex'); - var message = new Messages.Version().fromBuffer(payload); + new Messages.Version().fromBuffer(payload); }); }); @@ -61,7 +58,7 @@ describe('Messages', function() { it('should be able to parse payload', function() { var payload = new Buffer(Data.VERACK.payload, 'hex'); - var message = new Messages.VerAck().fromBuffer(payload); + new Messages.VerAck().fromBuffer(payload); }); }); @@ -85,7 +82,7 @@ describe('Messages', function() { it('should be able to parse payload', function() { var payload = new Buffer(Data.INV.payload, 'hex'); - var message = new Messages.Inventory().fromBuffer(payload); + new Messages.Inventory().fromBuffer(payload); }); }); @@ -109,7 +106,7 @@ describe('Messages', function() { it('should be able to parse payload', function() { var payload = new Buffer(Data.ADDR.payload, 'hex'); - var message = new Messages.Addresses().fromBuffer(payload); + new Messages.Addresses().fromBuffer(payload); }); }); @@ -133,7 +130,7 @@ describe('Messages', function() { it('should be able to parse payload', function() { var payload = new Buffer(Data.PING.payload, 'hex'); - var message = new Messages.Ping().fromBuffer(payload); + new Messages.Ping().fromBuffer(payload); }); }); @@ -157,7 +154,7 @@ describe('Messages', function() { it('should be able to parse payload', function() { var payload = new Buffer(Data.PING.payload, 'hex'); - var message = new Messages.Pong().fromBuffer(payload); + new Messages.Pong().fromBuffer(payload); }); }); diff --git a/test/transport/peer.js b/test/transport/peer.js index 8a832c0..f0e25c2 100644 --- a/test/transport/peer.js +++ b/test/transport/peer.js @@ -4,6 +4,7 @@ var chai = require('chai'); var Net = require('net'); var Socks5Client = require('socks5-client'); +/* jshint unused: false */ var should = chai.should(); var expect = chai.expect; @@ -52,7 +53,7 @@ describe('Peer', function() { var peer, peer2, socket; peer = new Peer('localhost'); - expect(peer.proxy).to.be.undefined; + expect(peer.proxy).to.be.undefined(); socket = peer._getSocket(); socket.should.be.instanceof(Net.Socket); From 751ed3a9f924c682a1ab20a085eb04ec9583560e Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 11 Dec 2014 13:31:09 -0300 Subject: [PATCH 20/21] add buffers dependency --- npm-shrinkwrap.json | 5 +++++ package.json | 5 ++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index cd8ea4c..f3bfced 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -22,6 +22,11 @@ "from": "bs58@2.0.0", "resolved": "https://registry.npmjs.org/bs58/-/bs58-2.0.0.tgz" }, + "buffers": { + "version": "0.1.1", + "from": "buffers@0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz" + }, "elliptic": { "version": "0.15.12", "from": "elliptic@0.15.12", diff --git a/package.json b/package.json index 8af7aab..c5469ec 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "asn1.js": "=0.4.1", "bn.js": "=0.15.2", "bs58": "=2.0.0", + "buffers": "^0.1.1", "elliptic": "=0.15.14", "hash.js": "=0.3.2", "inherits": "=2.0.1", @@ -97,9 +98,7 @@ "run-sequence": "^1.0.2", "karma": "^0.12.28", "karma-firefox-launcher": "^0.1.3", - "karma-mocha": "^0.1.9", - "mocha": "~2.0.1", - "run-sequence": "^1.0.2" + "karma-mocha": "^0.1.9" }, "license": "MIT" } From 3190991941e735d2dd8d8c2edea6d929b86bc20b Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 11 Dec 2014 14:01:06 -0300 Subject: [PATCH 21/21] add dependencies and update tests --- npm-shrinkwrap.json | 10 ++++++++++ package.json | 4 +++- test/transport/peer.js | 3 ++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index f3bfced..1f09939 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -22,6 +22,11 @@ "from": "bs58@2.0.0", "resolved": "https://registry.npmjs.org/bs58/-/bs58-2.0.0.tgz" }, + "bufferput": { + "version": "0.1.2", + "from": "bufferput@0.1.2", + "resolved": "https://registry.npmjs.org/bufferput/-/bufferput-0.1.2.tgz" + }, "buffers": { "version": "0.1.1", "from": "buffers@0.1.1", @@ -83,6 +88,11 @@ "version": "0.0.1", "from": "sha512@=0.0.1", "resolved": "https://registry.npmjs.org/sha512/-/sha512-0.0.1.tgz" + }, + "socks5-client": { + "version": "0.3.6", + "from": "socks5-client@0.3.6", + "resolved": "https://registry.npmjs.org/socks5-client/-/socks5-client-0.3.6.tgz" } } } diff --git a/package.json b/package.json index c5469ec..bfe4649 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "asn1.js": "=0.4.1", "bn.js": "=0.15.2", "bs58": "=2.0.0", + "bufferput": "^0.1.2", "buffers": "^0.1.1", "elliptic": "=0.15.14", "hash.js": "=0.3.2", @@ -81,7 +82,8 @@ "jsrsasign": "=0.0.3", "lodash": "=2.4.1", "protobufjs": "=3.0.0", - "sha512": "=0.0.1" + "sha512": "=0.0.1", + "socks5-client": "^0.3.6" }, "devDependencies": { "brfs": "1.2.0", diff --git a/test/transport/peer.js b/test/transport/peer.js index f0e25c2..9733a22 100644 --- a/test/transport/peer.js +++ b/test/transport/peer.js @@ -49,7 +49,8 @@ describe('Peer', function() { peer.port.should.equal(8111); }); - it('should be able to set a proxy', function() { + // only for node TODO: (yemel) + it.skip('should be able to set a proxy', function() { var peer, peer2, socket; peer = new Peer('localhost');