fcoin/lib/wallet/records.js
2017-02-03 22:47:26 -08:00

759 lines
14 KiB
JavaScript

/*!
* records.js - walletdb records
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
/**
* @module wallet/records
*/
var assert = require('assert');
var util = require('../utils/util');
var encoding = require('../utils/encoding');
var BufferReader = require('../utils/reader');
var StaticWriter = require('../utils/staticwriter');
var TX = require('../primitives/tx');
/**
* Chain State
* @constructor
*/
function ChainState() {
if (!(this instanceof ChainState))
return new ChainState();
this.startHeight = -1;
this.startHash = encoding.NULL_HASH;
this.height = -1;
this.marked = false;
}
/**
* Clone the state.
* @returns {ChainState}
*/
ChainState.prototype.clone = function clone() {
var state = new ChainState();
state.startHeight = this.startHeight;
state.startHash = this.startHash;
state.height = this.height;
state.marked = this.marked;
return state;
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
*/
ChainState.prototype.fromRaw = function fromRaw(data) {
var br = new BufferReader(data);
this.startHeight = br.readU32();
this.startHash = br.readHash('hex');
this.height = br.readU32();
this.marked = true;
if (br.left() > 0)
this.marked = br.readU8() === 1;
return this;
};
/**
* Instantiate chain state from serialized data.
* @param {Hash} hash
* @param {Buffer} data
* @returns {ChainState}
*/
ChainState.fromRaw = function fromRaw(data) {
return new ChainState().fromRaw(data);
};
/**
* Serialize the chain state.
* @returns {Buffer}
*/
ChainState.prototype.toRaw = function toRaw() {
var bw = new StaticWriter(41);
bw.writeU32(this.startHeight);
bw.writeHash(this.startHash);
bw.writeU32(this.height);
bw.writeU8(this.marked ? 1 : 0);
return bw.render();
};
/**
* Block Meta
* @constructor
* @param {Hash} hash
* @param {Number} height
* @param {Number} ts
*/
function BlockMeta(hash, height, ts) {
if (!(this instanceof BlockMeta))
return new BlockMeta(hash, height, ts);
this.hash = hash || encoding.NULL_HASH;
this.height = height != null ? height : -1;
this.ts = ts || 0;
}
/**
* Clone the block.
* @returns {BlockMeta}
*/
BlockMeta.prototype.clone = function clone() {
return new BlockMeta(this.hash, this.height, this.ts);
};
/**
* Get block meta hash as a buffer.
* @returns {Buffer}
*/
BlockMeta.prototype.toHash = function toHash() {
return new Buffer(this.hash, 'hex');
};
/**
* Instantiate block meta from chain entry.
* @private
* @param {ChainEntry} entry
*/
BlockMeta.prototype.fromEntry = function fromEntry(entry) {
this.hash = entry.hash;
this.height = entry.height;
this.ts = entry.ts;
return this;
};
/**
* Instantiate block meta from json object.
* @private
* @param {Object} json
*/
BlockMeta.prototype.fromJSON = function fromJSON(json) {
this.hash = util.revHex(json.hash);
this.height = json.height;
this.ts = json.ts;
return this;
};
/**
* Instantiate block meta from serialized tip data.
* @private
* @param {Buffer} data
*/
BlockMeta.prototype.fromRaw = function fromRaw(data) {
var br = new BufferReader(data);
this.hash = br.readHash('hex');
this.height = br.readU32();
this.ts = br.readU32();
return this;
};
/**
* Instantiate block meta from chain entry.
* @param {ChainEntry} entry
* @returns {BlockMeta}
*/
BlockMeta.fromEntry = function fromEntry(entry) {
return new BlockMeta().fromEntry(entry);
};
/**
* Instantiate block meta from json object.
* @param {Object} json
* @returns {BlockMeta}
*/
BlockMeta.fromJSON = function fromJSON(json) {
return new BlockMeta().fromJSON(json);
};
/**
* Instantiate block meta from serialized data.
* @param {Hash} hash
* @param {Buffer} data
* @returns {BlockMeta}
*/
BlockMeta.fromRaw = function fromRaw(data) {
return new BlockMeta().fromRaw(data);
};
/**
* Serialize the block meta.
* @returns {Buffer}
*/
BlockMeta.prototype.toRaw = function toRaw() {
var bw = new StaticWriter(42);
bw.writeHash(this.hash);
bw.writeU32(this.height);
bw.writeU32(this.ts);
return bw.render();
};
/**
* Convert the block meta to a more json-friendly object.
* @returns {Object}
*/
BlockMeta.prototype.toJSON = function toJSON() {
return {
hash: util.revHex(this.hash),
height: this.height,
ts: this.ts
};
};
/**
* Wallet Block
* @constructor
* @param {Hash} hash
* @param {Number} height
*/
function BlockMapRecord(height) {
if (!(this instanceof BlockMapRecord))
return new BlockMapRecord(height);
this.height = height != null ? height : -1;
this.txs = [];
this.index = {};
}
/**
* Instantiate wallet block from serialized data.
* @private
* @param {Hash} hash
* @param {Buffer} data
*/
BlockMapRecord.prototype.fromRaw = function fromRaw(data) {
var br = new BufferReader(data);
var count = br.readU32();
var i, hash, tx;
for (i = 0; i < count; i++) {
hash = br.readHash('hex');
tx = TXMapRecord.fromReader(hash, br);
this.txs.push(tx);
this.index[tx.hash] = tx;
}
return this;
};
/**
* Instantiate wallet block from serialized data.
* @param {Hash} hash
* @param {Buffer} data
* @returns {BlockMapRecord}
*/
BlockMapRecord.fromRaw = function fromRaw(height, data) {
return new BlockMapRecord(height).fromRaw(data);
};
/**
* Calculate serialization size.
* @returns {Number}
*/
BlockMapRecord.prototype.getSize = function getSize() {
var size = 0;
var i, tx;
size += 4;
for (i = 0; i < this.txs.length; i++) {
tx = this.txs[i];
size += 32;
size += tx.getSize();
}
return size;
};
/**
* Serialize the wallet block as a block.
* Contains matching transaction hashes.
* @returns {Buffer}
*/
BlockMapRecord.prototype.toRaw = function toRaw() {
var size = this.getSize();
var bw = new StaticWriter(size);
var i, tx;
bw.writeU32(this.txs.length);
for (i = 0; i < this.txs.length; i++) {
tx = this.txs[i];
bw.writeHash(tx.hash);
tx.toWriter(bw);
}
return bw.render();
};
/**
* Add a hash and wid pair to the block.
* @param {Hash} hash
* @param {WalletID} wid
* @returns {Boolean}
*/
BlockMapRecord.prototype.add = function add(hash, wid) {
var tx = this.index[hash];
if (!tx) {
tx = new TXMapRecord(hash);
tx.wids.push(wid);
this.txs.push(tx);
this.index[tx.hash] = tx;
return true;
}
return tx.add(wid);
};
/**
* Remove a hash and wid pair from the block.
* @param {Hash} hash
* @param {WalletID} wid
* @returns {Boolean}
*/
BlockMapRecord.prototype.remove = function remove(hash, wid) {
var tx = this.index[hash];
var result;
if (!tx)
return false;
if (!tx.remove(wid))
return false;
if (tx.wids.length === 0) {
result = util.binaryRemove(this.txs, tx, cmpid);
assert(result);
delete this.index[tx.hash];
}
return true;
};
/**
* TX Hash
* @constructor
*/
function TXMapRecord(hash, wids) {
this.hash = hash || encoding.NULL_HASH;
this.wids = wids || [];
this.id = TXMapRecord.id++;
}
TXMapRecord.id = 0;
TXMapRecord.prototype.add = function add(wid) {
return util.binaryInsert(this.wids, wid, cmp, true) !== -1;
};
TXMapRecord.prototype.remove = function remove(wid) {
return util.binaryRemove(this.wids, wid, cmp);
};
TXMapRecord.prototype.toWriter = function toWriter(bw) {
return serializeWallets(bw, this.wids);
};
TXMapRecord.prototype.getSize = function getSize() {
return sizeWallets(this.wids);
};
TXMapRecord.prototype.toRaw = function toRaw() {
var size = this.getSize();
return this.toWriter(new StaticWriter(size)).render();
};
TXMapRecord.prototype.fromReader = function fromReader(br) {
this.wids = parseWallets(br);
return this;
};
TXMapRecord.prototype.fromRaw = function fromRaw(data) {
return this.fromReader(new BufferReader(data));
};
TXMapRecord.fromReader = function fromReader(hash, br) {
return new TXMapRecord(hash).fromReader(br);
};
TXMapRecord.fromRaw = function fromRaw(hash, data) {
return new TXMapRecord(hash).fromRaw(data);
};
/**
* Outpoint Map
* @constructor
*/
function OutpointMapRecord(hash, index, wids) {
this.hash = hash || encoding.NULL_HASH;
this.index = index != null ? index : -1;
this.wids = wids || [];
}
OutpointMapRecord.prototype.add = function add(wid) {
return util.binaryInsert(this.wids, wid, cmp, true) !== -1;
};
OutpointMapRecord.prototype.remove = function remove(wid) {
return util.binaryRemove(this.wids, wid, cmp);
};
OutpointMapRecord.prototype.toWriter = function toWriter(bw) {
return serializeWallets(bw, this.wids);
};
OutpointMapRecord.prototype.getSize = function getSize() {
return sizeWallets(this.wids);
};
OutpointMapRecord.prototype.toRaw = function toRaw() {
var size = this.getSize();
return this.toWriter(new StaticWriter(size)).render();
};
OutpointMapRecord.prototype.fromReader = function fromReader(br) {
this.wids = parseWallets(br);
return this;
};
OutpointMapRecord.prototype.fromRaw = function fromRaw(data) {
return this.fromReader(new BufferReader(data));
};
OutpointMapRecord.fromReader = function fromReader(hash, index, br) {
return new OutpointMapRecord(hash, index).fromReader(br);
};
OutpointMapRecord.fromRaw = function fromRaw(hash, index, data) {
return new OutpointMapRecord(hash, index).fromRaw(data);
};
/**
* Path Record
* @constructor
*/
function PathMapRecord(hash, wids) {
this.hash = hash || encoding.NULL_HASH;
this.wids = wids || [];
}
PathMapRecord.prototype.add = function add(wid) {
return util.binaryInsert(this.wids, wid, cmp, true) !== -1;
};
PathMapRecord.prototype.remove = function remove(wid) {
return util.binaryRemove(this.wids, wid, cmp);
};
PathMapRecord.prototype.toWriter = function toWriter(bw) {
return serializeWallets(bw, this.wids);
};
PathMapRecord.prototype.getSize = function getSize() {
return sizeWallets(this.wids);
};
PathMapRecord.prototype.toRaw = function toRaw() {
var size = this.getSize();
return this.toWriter(new StaticWriter(size)).render();
};
PathMapRecord.prototype.fromReader = function fromReader(br) {
this.wids = parseWallets(br);
return this;
};
PathMapRecord.prototype.fromRaw = function fromRaw(data) {
return this.fromReader(new BufferReader(data));
};
PathMapRecord.fromReader = function fromReader(hash, br) {
return new PathMapRecord(hash).fromReader(br);
};
PathMapRecord.fromRaw = function fromRaw(hash, data) {
return new PathMapRecord(hash).fromRaw(data);
};
/**
* TXRecord
* @constructor
* @param {TX} tx
* @param {BlockMeta?} block
*/
function TXRecord(tx, block) {
if (!(this instanceof TXRecord))
return new TXRecord(tx, block);
this.tx = null;
this.hash = null;
this.ps = util.now();
this.height = -1;
this.block = null;
this.index = -1;
this.ts = 0;
if (tx)
this.fromTX(tx, block);
}
/**
* Inject properties from tx and block.
* @private
* @param {TX} tx
* @param {Block?} block
* @returns {TXRecord}
*/
TXRecord.prototype.fromTX = function fromTX(tx, block) {
this.tx = tx;
this.hash = tx.hash('hex');
if (block)
this.setBlock(block);
return this;
};
/**
* Instantiate tx record from tx and block.
* @param {TX} tx
* @param {Block?} block
* @returns {TXRecord}
*/
TXRecord.fromTX = function fromTX(tx, block) {
return new TXRecord().fromTX(tx, block);
};
/**
* Set block data (confirm).
* @param {BlockMeta} block
*/
TXRecord.prototype.setBlock = function setBlock(block) {
this.height = block.height;
this.block = block.hash;
this.ts = block.ts;
};
/**
* Unset block (unconfirm).
*/
TXRecord.prototype.unsetBlock = function unsetBlock() {
this.height = -1;
this.block = null;
this.ts = 0;
};
/**
* Convert tx record to a block meta.
* @returns {BlockMeta}
*/
TXRecord.prototype.getBlock = function getBlock() {
if (this.height === -1)
return;
return new BlockMeta(this.block, this.height, this.ts);
};
/**
* Calculate current number of transaction confirmations.
* @param {Number} height - Current chain height.
* @returns {Number} confirmations
*/
TXRecord.prototype.getDepth = function getDepth(height) {
assert(typeof height === 'number', 'Must pass in height.');
if (this.height === -1)
return 0;
if (height < this.height)
return 0;
return height - this.height + 1;
};
/**
* Get serialization size.
* @returns {Number}
*/
TXRecord.prototype.getSize = function getSize() {
var size = 0;
size += this.tx.getSize();
size += 4;
if (this.block) {
size += 1;
size += 32;
size += 4 * 3;
} else {
size += 1;
}
return size;
};
/**
* Serialize a transaction to "extended format".
* @returns {Buffer}
*/
TXRecord.prototype.toRaw = function toRaw() {
var size = this.getSize();
var bw = new StaticWriter(size);
var index = this.index;
this.tx.toWriter(bw);
bw.writeU32(this.ps);
if (this.block) {
if (index === -1)
index = 0x7fffffff;
bw.writeU8(1);
bw.writeHash(this.block);
bw.writeU32(this.height);
bw.writeU32(this.ts);
bw.writeU32(index);
} else {
bw.writeU8(0);
}
return bw.render();
};
/**
* Inject properties from "extended" format.
* @private
* @param {Buffer} data
*/
TXRecord.prototype.fromRaw = function fromRaw(data) {
var br = new BufferReader(data);
this.tx = new TX();
this.tx.fromReader(br);
this.hash = this.tx.hash('hex');
this.ps = br.readU32();
if (br.readU8() === 1) {
this.block = br.readHash('hex');
this.height = br.readU32();
this.ts = br.readU32();
this.index = br.readU32();
if (this.index === 0x7fffffff)
this.index = -1;
}
return this;
};
/**
* Instantiate a transaction from a buffer
* in "extended" serialization format.
* @param {Buffer} data
* @returns {TX}
*/
TXRecord.fromRaw = function fromRaw(data) {
return new TXRecord().fromRaw(data);
};
/*
* Helpers
*/
function cmp(a, b) {
return a - b;
}
function cmpid(a, b) {
return a.id - b.id;
}
function parseWallets(br) {
var count = br.readU32();
var wids = [];
var i;
for (i = 0; i < count; i++)
wids.push(br.readU32());
return wids;
}
function sizeWallets(wids) {
return 4 + wids.length * 4;
}
function serializeWallets(bw, wids) {
var i, wid;
bw.writeU32(wids.length);
for (i = 0; i < wids.length; i++) {
wid = wids[i];
bw.writeU32(wid);
}
return bw;
}
/*
* Expose
*/
exports.ChainState = ChainState;
exports.BlockMeta = BlockMeta;
exports.BlockMapRecord = BlockMapRecord;
exports.TXMapRecord = TXMapRecord;
exports.OutpointMapRecord = OutpointMapRecord;
exports.PathMapRecord = PathMapRecord;
exports.TXRecord = TXRecord;
module.exports = exports;