From a6be7bf5f7bf18a56cea1796e861aee6afffda4e Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Tue, 6 May 2014 21:33:05 +0400 Subject: [PATCH] lib: JSON serialization for everyone --- lib/bcoin/chain.js | 61 +++++++++++++++++++++++++++++------ lib/bcoin/pool.js | 16 +++++++++ lib/bcoin/protocol/parser.js | 12 +++---- lib/bcoin/protocol/preload.js | 2 ++ lib/bcoin/tx-pool.js | 40 +++++++++++++++++------ lib/bcoin/tx.js | 10 ++++++ lib/bcoin/wallet.js | 47 ++++++++++++++++++++++++--- test/wallet-test.js | 5 ++- 8 files changed, 163 insertions(+), 30 deletions(-) diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 1c458b5d..6625cf69 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -1,3 +1,4 @@ +var assert = require('assert'); var util = require('util'); var EventEmitter = require('events').EventEmitter; @@ -27,17 +28,13 @@ function Chain(options) { count: 0 }; this.index = { - bloom: new bcoin.bloom(28 * 1024 * 1024, 33, 0xdeadbee0), - hashes: preload.hashes.slice(), - ts: preload.ts.slice() + bloom: null, + hashes: [], + ts: [] }; this.request = new utils.RequestCache(); - if (this.index.hashes.length === 0) - this.add(new bcoin.block(constants.genesis)); - - for (var i = 0; i < this.index.hashes.length; i++) - this.index.bloom.add(this.index.hashes[i], 'hex'); + this.fromJSON(preload); } util.inherits(Chain, EventEmitter); module.exports = Chain; @@ -69,7 +66,11 @@ Chain.prototype.addIndex = function addIndex(hash, ts) { if (this.probeIndex(hash, ts)) return; - var pos = utils.binaryInsert(this.index.ts, ts, compareTs); + var pos = utils.binaryInsert(this.index.ts, ts, compareTs, true); + if (pos <= this.index.ts.length - 1000) + return; + + this.index.ts.splice(pos, 0, ts); this.index.hashes.splice(pos, 0, hash); this.index.bloom.add(hash, 'hex'); }; @@ -216,3 +217,45 @@ Chain.prototype.hashesInRange = function hashesInRange(start, end) { Chain.prototype.getLast = function getLast() { return this.index.hashes[this.index.hashes.length - 1]; }; + +Chain.prototype.toJSON = function toJSON() { + // Keep only last 1000 consequent blocks, use every 50th for older + var last = { + hashes: this.index.hashes.slice(-1000), + ts: this.index.ts.slice(-1000) + }; + + var first = { + hashes: [], + ts: [] + }; + var len = (this.index.hashes.length - 1000) - this.index.hashes.length % 50; + for (var i = 0; i < len; i += 50) { + first.hashes.push(this.index.hashes[i]); + first.ts.push(this.index.ts[i]); + } + + return { + v: 1, + type: 'chain', + hashes: first.hashes.concat(last.hashes), + ts: first.ts.concat(last.ts), + }; +}; + +Chain.prototype.fromJSON = function fromJSON(json) { + assert.equal(json.v, 1); + assert.equal(json.type, 'chain'); + this.index.hashes = json.hashes.slice(); + this.index.ts = json.ts.slice(); + if (this.index.bloom) + this.index.bloom.reset(); + else + this.index.bloom = new bcoin.bloom(28 * 1024 * 1024, 33, 0xdeadbee0); + + if (this.index.hashes.length === 0) + this.add(new bcoin.block(constants.genesis)); + + for (var i = 0; i < this.index.hashes.length; i++) + this.index.bloom.add(this.index.hashes[i], 'hex'); +}; diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 1fc8ddd0..53b6a83e 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -532,6 +532,22 @@ Pool.prototype.sendTX = function sendTX(tx) { return e; }; +Pool.prototype.toJSON = function toJSON() { + return { + v: 1, + type: 'pool', + chain: this.chain.toJSON() + }; +}; + +Pool.prototype.fromJSON = function fromJSON(json) { + assert.equal(json.v, 1); + assert.equal(json.type, 'pool'); + this.chain.fromJSON(json.chain); + + return this; +}; + function LoadRequest(pool, type, hash, cb) { this.pool = pool this.type = type; diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index 1e12af52..dec81419 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -270,10 +270,10 @@ Parser.prototype.parseTX = function parseTX(p) { var inCount = readIntv(p, 4); var off = inCount.off; inCount = inCount.r; - if (inCount <= 0) - return this._error('Invalid tx_in count'); + if (inCount < 0) + return this._error('Invalid tx_in count (negative)'); if (off + 41 * inCount + 14 > p.length) - return this._error('Invalid tx_in count'); + return this._error('Invalid tx_in count (too big)'); var txIn = new Array(inCount); for (var i = 0; i < inCount; i++) { @@ -290,10 +290,10 @@ Parser.prototype.parseTX = function parseTX(p) { var outCount = readIntv(p, off); var off = outCount.off; outCount = outCount.r; - if (outCount <= 0) - return this._error('Invalid tx_out count'); + if (outCount < 0) + return this._error('Invalid tx_out count (negative)'); if (off + 9 * outCount + 4 > p.length) - return this._error('Invalid tx_out count'); + return this._error('Invalid tx_out count (too big)'); var txOut = new Array(outCount); for (var i = 0; i < outCount; i++) { diff --git a/lib/bcoin/protocol/preload.js b/lib/bcoin/protocol/preload.js index 69c44212..c894886e 100644 --- a/lib/bcoin/protocol/preload.js +++ b/lib/bcoin/protocol/preload.js @@ -16,6 +16,8 @@ */ module.exports = { + "v": 1, + "type": "chain", "hashes": [ "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000", "33aa0fa26441ead7005df4b0ad2e61405e80cb805e3c657f194df32600000000", diff --git a/lib/bcoin/tx-pool.js b/lib/bcoin/tx-pool.js index 35d2887c..42f31c7a 100644 --- a/lib/bcoin/tx-pool.js +++ b/lib/bcoin/tx-pool.js @@ -1,19 +1,22 @@ +var assert = require('assert'); var bn = require('bn.js'); +var bcoin = require('../bcoin'); -function TXPool() { +function TXPool(wallet) { if (!(this instanceof TXPool)) - return new TXPool(); + return new TXPool(wallet); + this._wallet = wallet; this._all = {}; this._unspent = {}; this._orphans = {}; } module.exports = TXPool; -TXPool.prototype.add = function add(tx, wallet) { +TXPool.prototype.add = function add(tx) { var hash = tx.hash('hex'); - if (!wallet.own(tx)) + if (!this._wallet.own(tx)) return; // Do not add TX two times @@ -61,17 +64,17 @@ TXPool.prototype.add = function add(tx, wallet) { return true; }; -TXPool.prototype.unspent = function unspent(wallet) { +TXPool.prototype.unspent = function unspent() { return Object.keys(this._unspent).map(function(key) { return this._unspent[key]; }, this).filter(function(item) { - return wallet.own(item.tx, item.index); - }); + return this._wallet.own(item.tx, item.index); + }, this); }; -TXPool.prototype.balance = function balance(wallet) { +TXPool.prototype.balance = function balance() { var acc = new bn(0); - var unspent = this.unspent(wallet); + var unspent = this.unspent(); if (unspent.length === 0) return acc; @@ -79,3 +82,22 @@ TXPool.prototype.balance = function balance(wallet) { return acc.iadd(item.tx.outputs[item.index].value); }, acc); }; + +TXPool.prototype.toJSON = function toJSON() { + return { + v: 1, + type: 'tx-pool', + txs: Object.keys(this._all).map(function(hash) { + return this._all[hash].toJSON(); + }, this) + }; +}; + +TXPool.prototype.fromJSON = function fromJSON(json) { + assert.equal(json.v, 1); + assert.equal(json.type, 'tx-pool'); + + json.txs.forEach(function(tx) { + this.add(bcoin.tx.fromJSON(tx)); + }, this); +}; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 54677afe..ea57429f 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -171,3 +171,13 @@ TX.prototype.verify = function verify() { return stack.length > 0 && utils.isEqual(stack.pop(), [ 1 ]); }, this); }; + +TX.prototype.toJSON = function toJSON() { + // Compact representation + return utils.toBase58(this.render()); +}; + +TX.fromJSON = function fromJSON(json) { + // Compact representation + return new TX(new bcoin.protocol.parser().parseTX(utils.fromBase58(json))); +}; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 172d9b45..5384de6f 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -18,7 +18,7 @@ function Wallet(options, passphrase) { options = {}; this.compressed = true; - this.tx = new bcoin.txPool(); + this.tx = new bcoin.txPool(this); this.key = null; if (options.passphrase) { @@ -41,7 +41,12 @@ Wallet.prototype.getPrivateKey = function getPrivateKey(enc) { if (enc === 'base58') { // We'll be using ncompressed public key as an address - var arr = [ 128 ].concat(priv); + var arr = [ 128 ]; + + // 0-pad key + while (arr.length + priv.length < 33) + arr.push(0); + arr = arr.concat(priv); if (this.compressed) arr.push(1); var chk = utils.checksum(arr); @@ -149,13 +154,45 @@ Wallet.prototype.sign = function sign(tx, type) { }; Wallet.prototype.addTX = function addTX(tx) { - return this.tx.add(tx, this); + return this.tx.add(tx); }; Wallet.prototype.unspent = function unspent() { - return this.tx.unspent(this); + return this.tx.unspent(); }; Wallet.prototype.balance = function balance() { - return this.tx.balance(this); + return this.tx.balance(); +}; + +Wallet.prototype.toJSON = function toJSON() { + return { + v: 1, + type: 'wallet', + key: this.getPrivateKey('base58'), + tx: this.tx.toJSON() + }; +}; + +Wallet.prototype.fromJSON = function fromJSON(json) { + assert.equal(json.v, 1); + assert.equal(json.type, 'wallet'); + + var key = bcoin.utils.fromBase58(json.key); + assert(utils.isEqual(key.slice(-4), utils.checksum(key.slice(0, -4)))); + assert.equal(key[0], 128); + + key = key.slice(0, -4); + if (key.length === 34) { + assert.equal(key[33], 1); + this.key = bcoin.ecdsa.keyPair(key.slice(1, -1)); + this.compressed = true; + } else { + this.key = bcoin.ecdsa.keyPair(key.slice(1)); + this.compressed = false; + } + + this.tx.fromJSON(json.tx); + + return this; }; diff --git a/test/wallet-test.js b/test/wallet-test.js index 79744b66..e7b4a793 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -71,7 +71,7 @@ describe('Wallet', function() { assert(tx.verify()); }); - it('should have TX pool', function() { + it('should have TX pool and be serializable', function() { var w = bcoin.wallet(); // Coinbase @@ -93,5 +93,8 @@ describe('Wallet', function() { assert.equal(w.balance().toString(10), '47000'); w.addTX(t3); assert.equal(w.balance().toString(10), '22000'); + + var w2 = bcoin.wallet().fromJSON(w.toJSON()); + assert.equal(w2.balance().toString(10), '22000'); }); });