fcoin/test/util/memwallet.js
2017-03-14 06:10:35 -07:00

427 lines
8.8 KiB
JavaScript

/*!
* memwallet.js - in-memory wallet object for bcoin
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var assert = require('assert');
var Network = require('../../lib/protocol/network');
var util = require('../../lib/utils/util');
var MTX = require('../../lib/primitives/mtx');
var HD = require('../../lib/hd/hd');
var Bloom = require('../../lib/utils/bloom');
var KeyRing = require('../../lib/primitives/keyring');
var Outpoint = require('../../lib/primitives/outpoint');
var Coin = require('../../lib/primitives/coin');
var co = require('../../lib/utils/co');
function MemWallet(options) {
if (!(this instanceof MemWallet))
return new MemWallet(options);
this.network = Network.primary;
this.master = null;
this.key = null;
this.witness = false;
this.account = 0;
this.receiveDepth = 1;
this.changeDepth = 1;
this.receive = null;
this.change = null;
this.map = {};
this.coins = {};
this.spent = {};
this.paths = {};
this.balance = 0;
this.txs = 0;
this.filter = Bloom.fromRate(1000000, 0.001, -1);
if (options)
this.fromOptions(options);
this.init();
}
MemWallet.prototype.fromOptions = function fromOptions(options) {
if (options.network != null) {
assert(options.network);
this.network = Network.get(options.network);
}
if (options.master != null) {
assert(options.master);
this.master = HD.PrivateKey.fromOptions(options.master, this.network);
}
if (options.key != null) {
assert(HD.isPrivate(options.key));
this.key = options.key;
}
if (options.witness != null) {
assert(typeof options.witness === 'boolean');
this.witness = options.witness;
}
if (options.account != null) {
assert(typeof options.account === 'number');
this.account = options.account;
}
if (options.receiveDepth != null) {
assert(typeof options.receiveDepth === 'number');
this.receiveDepth = options.receiveDepth;
}
if (options.changeDepth != null) {
assert(typeof options.changeDepth === 'number');
this.changeDepth = options.changeDepth;
}
return this;
};
MemWallet.prototype.init = function init() {
var i;
if (!this.master)
this.master = HD.PrivateKey.generate();
if (!this.key)
this.key = this.master.deriveAccount44(this.account);
i = this.receiveDepth;
while (i--)
this.createReceive();
i = this.changeDepth;
while (i--)
this.createChange();
};
MemWallet.prototype.createReceive = function createReceive() {
var index = this.receiveDepth++;
var key = this.deriveReceive(index);
var hash = key.getHash('hex');
this.filter.add(hash, 'hex');
this.paths[hash] = new Path(hash, 0, index);
this.receive = key;
return key;
};
MemWallet.prototype.createChange = function createChange() {
var index = this.changeDepth++;
var key = this.deriveChange(index);
var hash = key.getHash('hex');
this.filter.add(hash, 'hex');
this.paths[hash] = new Path(hash, 1, index);
this.change = key;
return key;
};
MemWallet.prototype.deriveReceive = function deriveReceive(index) {
return this.deriveKey(0, index);
};
MemWallet.prototype.deriveChange = function deriveChange(index) {
return this.deriveKey(1, index);
};
MemWallet.prototype.derivePath = function derivePath(path) {
return this.deriveKey(path.branch, path.index);
};
MemWallet.prototype.deriveKey = function deriveKey(branch, index) {
var key = this.master.deriveAccount44(this.account);
key = key.derive(branch).derive(index);
key = new KeyRing({
network: this.network,
privateKey: key.privateKey,
witness: this.witness
});
key.witness = this.witness;
return key;
};
MemWallet.prototype.getKey = function getKey(hash) {
var path = this.paths[hash];
if (!path)
return;
return this.derivePath(path);
};
MemWallet.prototype.getPath = function getPath(hash) {
return this.paths[hash];
};
MemWallet.prototype.getCoin = function getCoin(key) {
return this.coins[key];
};
MemWallet.prototype.getUndo = function getUndo(key) {
return this.spent[key];
};
MemWallet.prototype.addCoin = function addCoin(coin) {
var op = Outpoint(coin.hash, coin.index);
var key = op.toKey();
this.filter.add(op.toRaw());
delete this.spent[key];
this.coins[key] = coin;
this.balance += coin.value;
};
MemWallet.prototype.removeCoin = function removeCoin(key) {
var coin = this.coins[key];
if (!coin)
return;
this.spent[key] = coin;
this.balance -= coin.value;
delete this.coins[key];
};
MemWallet.prototype.getAddress = function getAddress() {
return this.receive.getAddress();
};
MemWallet.prototype.getReceive = function getReceive() {
return this.receive.getAddress();
};
MemWallet.prototype.getChange = function getChange() {
return this.change.getAddress();
};
MemWallet.prototype.getCoins = function getCoins() {
return util.values(this.coins);
};
MemWallet.prototype.syncKey = function syncKey(path) {
switch (path.branch) {
case 0:
if (path.index === this.receiveDepth - 1)
this.createReceive();
break;
case 1:
if (path.index === this.changeDepth - 1)
this.createChange();
break;
default:
assert(false);
break;
}
};
MemWallet.prototype.addBlock = function addBlock(entry, txs) {
var i, tx;
for (i = 0; i < txs.length; i++) {
tx = txs[i];
this.addTX(tx, entry.height);
}
};
MemWallet.prototype.removeBlock = function removeBlock(entry, txs) {
var i, tx;
for (i = txs.length - 1; i >= 0; i--) {
tx = txs[i];
this.removeTX(tx, entry.height);
}
};
MemWallet.prototype.addTX = function addTX(tx, height) {
var hash = tx.hash('hex');
var result = false;
var i, op, path, addr, coin, input, output;
if (height == null)
height = -1;
if (this.map[hash])
return true;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
op = input.prevout.toKey();
coin = this.getCoin(op);
if (!coin)
continue;
result = true;
this.removeCoin(op);
}
for (i = 0; i < tx.outputs.length; i++) {
output = tx.outputs[i];
addr = output.getHash('hex');
if (!addr)
continue;
path = this.getPath(addr);
if (!path)
continue;
result = true;
coin = Coin.fromTX(tx, i, height);
this.addCoin(coin);
this.syncKey(path);
}
if (result) {
this.txs++;
this.map[hash] = true;
}
return result;
};
MemWallet.prototype.removeTX = function removeTX(tx, height) {
var hash = tx.hash('hex');
var result = false;
var i, op, coin, input, output;
if (!this.map[hash])
return false;
for (i = 0; i < tx.outputs.length; i++) {
output = tx.outputs[i];
op = Outpoint(hash, i).toKey();
coin = this.getCoin(op);
if (!coin)
continue;
result = true;
this.removeCoin(op);
}
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
op = input.prevout.toKey();
coin = this.getUndo(op);
if (!coin)
continue;
result = true;
this.addCoin(coin);
}
if (result)
this.txs--;
delete this.map[hash];
return result;
};
MemWallet.prototype.deriveInputs = function deriveInputs(mtx) {
var keys = [];
var i, input, coin, addr, path, key;
for (i = 0; i < mtx.inputs.length; i++) {
input = mtx.inputs[i];
coin = mtx.view.getOutput(input);
if (!coin)
continue;
addr = coin.getHash('hex');
if (!addr)
continue;
path = this.getPath(addr);
if (!path)
continue;
key = this.derivePath(path);
keys.push(key);
}
return keys;
};
MemWallet.prototype.fund = function fund(mtx, options) {
var coins = this.getCoins();
if (!options)
options = {};
return mtx.fund(coins, {
selection: options.selection || 'age',
round: options.round,
depth: options.depth,
hardFee: options.hardFee,
subtractFee: options.subtractFee,
changeAddress: this.getChange(),
height: -1,
rate: options.rate,
maxFee: options.maxFee
});
};
MemWallet.prototype.template = function template(mtx) {
var keys = this.deriveInputs(mtx);
mtx.template(keys);
};
MemWallet.prototype.sign = function sign(mtx) {
var keys = this.deriveInputs(mtx);
mtx.template(keys);
mtx.sign(keys);
};
MemWallet.prototype.create = co(function* create(options) {
var mtx = new MTX(options);
var tx;
yield this.fund(mtx, options);
assert(mtx.getFee() <= MTX.Selector.MAX_FEE, 'TX exceeds MAX_FEE.');
mtx.sortMembers();
if (options.locktime != null)
mtx.setLocktime(options.locktime);
this.sign(mtx);
if (!mtx.isSigned())
throw new Error('Cannot sign tx.');
return mtx;
});
MemWallet.prototype.send = co(function* send(options) {
var mtx = yield this.create(options);
this.addTX(mtx.toTX());
return mtx;
});
function Path(hash, branch, index) {
this.hash = hash;
this.branch = branch;
this.index = index;
}
module.exports = MemWallet;