lib: JSON serialization for everyone

This commit is contained in:
Fedor Indutny 2014-05-06 21:33:05 +04:00
parent b98be4b388
commit a6be7bf5f7
8 changed files with 163 additions and 30 deletions

View File

@ -1,3 +1,4 @@
var assert = require('assert');
var util = require('util'); var util = require('util');
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
@ -27,17 +28,13 @@ function Chain(options) {
count: 0 count: 0
}; };
this.index = { this.index = {
bloom: new bcoin.bloom(28 * 1024 * 1024, 33, 0xdeadbee0), bloom: null,
hashes: preload.hashes.slice(), hashes: [],
ts: preload.ts.slice() ts: []
}; };
this.request = new utils.RequestCache(); this.request = new utils.RequestCache();
if (this.index.hashes.length === 0) this.fromJSON(preload);
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');
} }
util.inherits(Chain, EventEmitter); util.inherits(Chain, EventEmitter);
module.exports = Chain; module.exports = Chain;
@ -69,7 +66,11 @@ Chain.prototype.addIndex = function addIndex(hash, ts) {
if (this.probeIndex(hash, ts)) if (this.probeIndex(hash, ts))
return; 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.hashes.splice(pos, 0, hash);
this.index.bloom.add(hash, 'hex'); this.index.bloom.add(hash, 'hex');
}; };
@ -216,3 +217,45 @@ Chain.prototype.hashesInRange = function hashesInRange(start, end) {
Chain.prototype.getLast = function getLast() { Chain.prototype.getLast = function getLast() {
return this.index.hashes[this.index.hashes.length - 1]; 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');
};

View File

@ -532,6 +532,22 @@ Pool.prototype.sendTX = function sendTX(tx) {
return e; 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) { function LoadRequest(pool, type, hash, cb) {
this.pool = pool this.pool = pool
this.type = type; this.type = type;

View File

@ -270,10 +270,10 @@ Parser.prototype.parseTX = function parseTX(p) {
var inCount = readIntv(p, 4); var inCount = readIntv(p, 4);
var off = inCount.off; var off = inCount.off;
inCount = inCount.r; inCount = inCount.r;
if (inCount <= 0) if (inCount < 0)
return this._error('Invalid tx_in count'); return this._error('Invalid tx_in count (negative)');
if (off + 41 * inCount + 14 > p.length) 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); var txIn = new Array(inCount);
for (var i = 0; i < inCount; i++) { for (var i = 0; i < inCount; i++) {
@ -290,10 +290,10 @@ Parser.prototype.parseTX = function parseTX(p) {
var outCount = readIntv(p, off); var outCount = readIntv(p, off);
var off = outCount.off; var off = outCount.off;
outCount = outCount.r; outCount = outCount.r;
if (outCount <= 0) if (outCount < 0)
return this._error('Invalid tx_out count'); return this._error('Invalid tx_out count (negative)');
if (off + 9 * outCount + 4 > p.length) 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); var txOut = new Array(outCount);
for (var i = 0; i < outCount; i++) { for (var i = 0; i < outCount; i++) {

View File

@ -16,6 +16,8 @@
*/ */
module.exports = { module.exports = {
"v": 1,
"type": "chain",
"hashes": [ "hashes": [
"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000", "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000",
"33aa0fa26441ead7005df4b0ad2e61405e80cb805e3c657f194df32600000000", "33aa0fa26441ead7005df4b0ad2e61405e80cb805e3c657f194df32600000000",

View File

@ -1,19 +1,22 @@
var assert = require('assert');
var bn = require('bn.js'); var bn = require('bn.js');
var bcoin = require('../bcoin');
function TXPool() { function TXPool(wallet) {
if (!(this instanceof TXPool)) if (!(this instanceof TXPool))
return new TXPool(); return new TXPool(wallet);
this._wallet = wallet;
this._all = {}; this._all = {};
this._unspent = {}; this._unspent = {};
this._orphans = {}; this._orphans = {};
} }
module.exports = TXPool; module.exports = TXPool;
TXPool.prototype.add = function add(tx, wallet) { TXPool.prototype.add = function add(tx) {
var hash = tx.hash('hex'); var hash = tx.hash('hex');
if (!wallet.own(tx)) if (!this._wallet.own(tx))
return; return;
// Do not add TX two times // Do not add TX two times
@ -61,17 +64,17 @@ TXPool.prototype.add = function add(tx, wallet) {
return true; return true;
}; };
TXPool.prototype.unspent = function unspent(wallet) { TXPool.prototype.unspent = function unspent() {
return Object.keys(this._unspent).map(function(key) { return Object.keys(this._unspent).map(function(key) {
return this._unspent[key]; return this._unspent[key];
}, this).filter(function(item) { }, 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 acc = new bn(0);
var unspent = this.unspent(wallet); var unspent = this.unspent();
if (unspent.length === 0) if (unspent.length === 0)
return acc; return acc;
@ -79,3 +82,22 @@ TXPool.prototype.balance = function balance(wallet) {
return acc.iadd(item.tx.outputs[item.index].value); return acc.iadd(item.tx.outputs[item.index].value);
}, acc); }, 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);
};

View File

@ -171,3 +171,13 @@ TX.prototype.verify = function verify() {
return stack.length > 0 && utils.isEqual(stack.pop(), [ 1 ]); return stack.length > 0 && utils.isEqual(stack.pop(), [ 1 ]);
}, this); }, 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)));
};

View File

@ -18,7 +18,7 @@ function Wallet(options, passphrase) {
options = {}; options = {};
this.compressed = true; this.compressed = true;
this.tx = new bcoin.txPool(); this.tx = new bcoin.txPool(this);
this.key = null; this.key = null;
if (options.passphrase) { if (options.passphrase) {
@ -41,7 +41,12 @@ Wallet.prototype.getPrivateKey = function getPrivateKey(enc) {
if (enc === 'base58') { if (enc === 'base58') {
// We'll be using ncompressed public key as an address // 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) if (this.compressed)
arr.push(1); arr.push(1);
var chk = utils.checksum(arr); var chk = utils.checksum(arr);
@ -149,13 +154,45 @@ Wallet.prototype.sign = function sign(tx, type) {
}; };
Wallet.prototype.addTX = function addTX(tx) { Wallet.prototype.addTX = function addTX(tx) {
return this.tx.add(tx, this); return this.tx.add(tx);
}; };
Wallet.prototype.unspent = function unspent() { Wallet.prototype.unspent = function unspent() {
return this.tx.unspent(this); return this.tx.unspent();
}; };
Wallet.prototype.balance = function balance() { 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;
}; };

View File

@ -71,7 +71,7 @@ describe('Wallet', function() {
assert(tx.verify()); assert(tx.verify());
}); });
it('should have TX pool', function() { it('should have TX pool and be serializable', function() {
var w = bcoin.wallet(); var w = bcoin.wallet();
// Coinbase // Coinbase
@ -93,5 +93,8 @@ describe('Wallet', function() {
assert.equal(w.balance().toString(10), '47000'); assert.equal(w.balance().toString(10), '47000');
w.addTX(t3); w.addTX(t3);
assert.equal(w.balance().toString(10), '22000'); assert.equal(w.balance().toString(10), '22000');
var w2 = bcoin.wallet().fromJSON(w.toJSON());
assert.equal(w2.balance().toString(10), '22000');
}); });
}); });