async wallet cwallet.

This commit is contained in:
Christopher Jeffrey 2016-05-28 08:29:27 -07:00
parent 420d72d647
commit 14088e5b0c
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
4 changed files with 724 additions and 198 deletions

View File

@ -52,7 +52,8 @@ function TXDB(db, options) {
if (!options)
options = {};
this.db = db;
this.wdb = db;
this.db = db.db;
this.options = options;
this.network = bcoin.network.get(options.network);
this.busy = false;
@ -568,21 +569,21 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
if (err)
return callback(err);
self.emit('tx', tx, map);
self.wdb.sync(tx, map, function(err) {
if (err)
return callback(err);
if (updated) {
if (tx.ts !== 0) {
self.emit('confirmed', tx, map, function() {
self.emit('updated', tx, map);
return callback(null, true);
});
return;
self.emit('tx', tx, map);
if (updated) {
if (tx.ts !== 0)
self.emit('confirmed', tx, map);
self.emit('updated', tx, map);
}
self.emit('updated', tx, map);
}
return callback(null, true);
return callback(null, true);
});
});
});
});
@ -764,7 +765,11 @@ TXDB.prototype._confirm = function _confirm(tx, map, callback, force) {
if (err)
return callback(err);
self.emit('confirmed', tx, map, function() {
self.wdb.sync(tx, map, function(err) {
if (err)
return callback(err);
self.emit('confirmed');
self.emit('tx', tx, map);
return callback(null, true);

View File

@ -63,10 +63,11 @@ function Wallet(options) {
if (!options.master)
options.master = bcoin.hd.fromMnemonic(null, this.network);
if (!(options.master instanceof bcoin.hd))
if (!(options.master instanceof bcoin.hd) && !(options.master instanceof MasterKey))
options.master = bcoin.hd.fromAny(options.master, this.network);
options.master = MasterKey.fromKey(options.master);
if (!(options.master instanceof MasterKey))
options.master = MasterKey.fromKey(options.master);
this.id = options.id || null;
this.master = options.master || null;
@ -510,9 +511,6 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) {
path = change;
if (path) {
// Map address to path
if (path.indexOf('/') === -1)
return;
data = this.parsePath(path);
} else {
data = {
@ -1257,7 +1255,7 @@ Wallet.prototype.sign = function sign(tx, options, callback) {
return callback(err);
try {
key = self.master.decrypt(options.passphrase);
var master = self.master.decrypt(options.passphrase);
} catch (e) {
return callback(null, 0);
}
@ -1265,12 +1263,17 @@ Wallet.prototype.sign = function sign(tx, options, callback) {
for (i = 0; i < addresses.length; i++) {
address = addresses[i];
if (self.derivation === 'bip44')
key = master;
if (self.derivation === 'bip44') {
key = key.deriveAccount44(self.accountIndex);
else if (self.derivation === 'bip45')
assert.equal(key.xpubkey, self.accountKey.xpubkey);
} else if (self.derivation === 'bip45')
key = key.derivePurpose45();
else
assert(false);
key = key.derive(address.path);
assert(utils.equal(key.getPublicKey(), address.key));
total += address.sign(tx, key, options.index, options.type);
}
@ -1706,3 +1709,537 @@ MasterKey.fromJSON = function fromJSON(json) {
*/
module.exports = Wallet;
/*!
* wallet.js - wallet object for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/indutny/bcoin
*/
var bcoin = require('./env');
var EventEmitter = require('events').EventEmitter;
var utils = require('./utils');
var assert = utils.assert;
var constants = bcoin.protocol.constants;
var BufferWriter = require('./writer');
/**
* HD BIP-44/45 wallet
* @exports Wallet
* @constructor
* @param {Object} options
* @param {WalletDB} options.db
* present, no coins will be available.
* @param {(HDPrivateKey|HDPublicKey)?} options.master - Master HD key. If not
* present, it will be generated.
* @param {Boolean?} options.witness - Whether to use witness programs.
* @param {Number?} options.accountIndex - The BIP44 account index (default=0).
* @param {Number?} options.receiveDepth - The index of the _next_ receiving
* address.
* @param {Number?} options.changeDepth - The index of the _next_ change
* address.
* @param {Boolean?} options.copayBIP45 - Use copay-style BIP45 if bip45
* derivation is used.
* @param {Number?} options.lookahead - Amount of lookahead addresses
* (default=5).
* @param {String?} options.type - Type of wallet (pubkeyhash, multisig)
* (default=pubkeyhash).
* @param {String?} options.derivation - Derivation type (bip44, bip45)
* (default=bip44).
* @param {Boolean?} options.compressed - Whether to use compressed
* public keys (default=true).
* @param {Number?} options.m - `m` value for multisig.
* @param {Number?} options.n - `n` value for multisig.
* @param {String?} options.id - Wallet ID (used for storage)
* (default=account key "address").
*/
function CWallet(id, db) {
var i, key;
if (!(this instanceof CWallet))
return new CWallet(id, db);
EventEmitter.call(this);
this.network = db.network;
this.db = db || bcoin.walletdb({ name: 'wtmp', db: 'memory' });
this.id = id;
}
utils.inherits(CWallet, EventEmitter);
/**
* Open the wallet, wait for the database to load.
* @param {Function} callback
*/
CWallet.prototype.open = function open(callback) {
var self = this;
if (this.loaded)
return utils.nextTick(callback);
if (this._loading)
return this.once('open', callback);
this.db.register(this.id, this);
callback();
};
/**
* Close the wallet, wait for the database to close.
* @method
* @param {Function} callback
*/
CWallet.prototype.close =
CWallet.prototype.destroy = function destroy(callback) {
callback = utils.ensure(callback);
if (!this.db)
return utils.nextTick(callback);
this.db.unregister(this.id, this);
this.db = null;
};
/**
* Add a public account/purpose key to the wallet for multisig.
* @param {HDPublicKey|Base58String} key - Account (bip44) or
* Purpose (bip45) key (can be in base58 form).
* @throws Error on non-hdkey/non-accountkey.
*/
CWallet.prototype.addKey = function addKey(key, callback) {
this.db.addKey(this.id, key, callback);
};
/**
* Remove a public account/purpose key to the wallet for multisig.
* @param {HDPublicKey|Base58String} key - Account (bip44) or Purpose
* (bip45) key (can be in base58 form).
* @throws Error on non-hdkey/non-accountkey.
*/
CWallet.prototype.removeKey = function removeKey(key, callback) {
this.db.removeKey(this.id, key, callback);
};
/**
* Get the wallet ID which is either the passed in `id`
* option, or the account/purpose key converted to an
* address with a prefix of `0x03be04` (`WLT`).
* @returns {Base58String}
*/
CWallet.prototype.getID = function getID() {
return this.id;
};
/**
* Create a new receiving address (increments receiveDepth).
* @returns {KeyRing}
*/
CWallet.prototype.createReceive = function createReceive(callback) {
return this.db.createAddress(this.id, false, callback);
};
/**
* Create a new change address (increments receiveDepth).
* @returns {KeyRing}
*/
CWallet.prototype.createChange = function createChange(callback) {
return this.db.createAddress(this.id, true, callback);
};
/**
* Create a new address (increments depth).
* @param {Boolean} change
* @returns {KeyRing}
*/
CWallet.prototype.createAddress = function createAddress(change, callback) {
return this.db.createAddress(this.id, change, callback);
};
/**
* Test whether the wallet posesses an address.
* @param {Base58Address} address
* @returns {Boolean}
*/
CWallet.prototype.hasAddress = function hasAddress(address, callback) {
this.db.hasAddress(this.id, address, callback);
};
/**
* Fill a transaction with inputs, estimate
* transaction size, calculate fee, and add a change output.
* @see MTX#selectCoins
* @see MTX#fill
* @param {MTX} tx - _Must_ be a mutable transaction.
* @param {Object?} options
* @param {String?} options.selection - Coin selection priority. Can
* be `age`, `random`, or `all`. (default=age).
* @param {Boolean} options.round - Whether to round to the nearest
* kilobyte for fee calculation.
* See {@link TX#getMinFee} vs. {@link TX#getRoundFee}.
* @param {Rate} options.rate - Rate used for fee calculation.
* @param {Boolean} options.confirmed - Select only confirmed coins.
* @param {Boolean} options.free - Do not apply a fee if the
* transaction priority is high enough to be considered free.
* @param {Amount?} options.fee - Use a hard fee rather than calculating one.
* @param {Number|Boolean} options.subtractFee - Whether to subtract the
* fee from existing outputs rather than adding more inputs.
*/
CWallet.prototype.fill = function fill(tx, options, callback) {
this.db.fill(this.id, tx, options, callback);
};
/**
* Fill transaction with coins (accesses db).
* @param {TX} tx
* @param {Function} callback - Returns [Error, {@link TX}].
*/
CWallet.prototype.fillCoins = function fillCoins(tx, callback) {
this.db.fillHistory(this.id, tx, callback);
};
/**
* Get a coin from the wallet (accesses db).
* @param {Hash} hash
* @param {Number} index
* @param {Function} callback - Returns [Error, {@link Coin}].
*/
CWallet.prototype.getCoin = function getCoin(hash, index, callback) {
this.db.getCoin(hash, index, callback);
};
/**
* Get a transaction from the wallet (accesses db).
* @param {Hash} hash
* @param {Function} callback - Returns [Error, {@link TX}].
*/
CWallet.prototype.getTX = function getTX(hash, callback) {
this.db.getTX(hash, callback);
};
/**
* Build a transaction, fill it with outputs and inputs,
* sort the members according to BIP69, set locktime,
* and sign it (accesses db).
* @param {Object} options - See {@link CWallet#fill options}.
* @param {Object[]} outputs - See {@link Script.createOutputScript}.
* @param {Function} callback - Returns [Error, {@link MTX}].
*/
CWallet.prototype.createTX = function createTX(options, outputs, callback) {
this.db.createTX(this.id, options, outputs, callback);
};
/**
* Get path by address.
* @param {Base58Address} address - Base58 address.
*/
CWallet.prototype.getPath = function getPath(address, callback) {
if (!address || typeof address !== 'string')
return callback();
this.db.getPath(this.id, address, callback);
};
/**
* Get a redeem script or witness script by hash.
* @param {Hash} hash - Can be a ripemd160 or a sha256.
* @returns {Script}
*/
CWallet.prototype.getRedeem = function getRedeem(hash, callback) {
var self = this;
var address;
if (typeof hash === 'string')
hash = new Buffer(hash, 'hex');
this.getPath(hash.toString('hex'), function(err, path) {
if (err)
return callback(err);
if (!path)
return callback();
address = self.deriveAddress(path);
if (address.program && hash.length === 20) {
if (utils.equal(hash, address.programHash))
return callback(null, address.program);
}
return callback(null, address.script);
});
};
/**
* Zap stale TXs from wallet (accesses db).
* @param {Number} now - Current time (unix time).
* @param {Number} age - Age threshold (unix time, default=72 hours).
* @param {Function} callback - Returns [Error].
*/
CWallet.prototype.zap = function zap(now, age, callback) {
return this.db.zapWallet(this.id, now, age, callback);
};
/**
* Build input scripts templates for a transaction (does not
* sign, only creates signature slots). Only builds scripts
* for inputs that are redeemable by this wallet.
* @param {MTX} tx
* @param {Number?} index - Index of input. If not present,
* it will attempt to sign all redeemable inputs.
* @returns {Number} Total number of scripts built.
*/
CWallet.prototype.scriptInputs = function scriptInputs(tx, callback) {
this.db.scriptInputs(this.id, tx, callback);
};
/**
* Build input scripts and sign inputs for a transaction. Only attempts
* to build/sign inputs that are redeemable by this wallet.
* @param {MTX} tx
* @param {Number?} index - Index of input. If not present,
* it will attempt to build and sign all redeemable inputs.
* @param {SighashType?} type
* @returns {Number} Total number of inputs scripts built and signed.
*/
CWallet.prototype.sign = function sign(tx, options, callback) {
this.db.sign(this.id, tx, options, callback);
};
/**
* Add a transaction to the wallets TX history (accesses db).
* @param {TX} tx
* @param {Function} callback
*/
CWallet.prototype.addTX = function addTX(tx, callback) {
return this.db.addTX(tx, callback);
};
/**
* Get all transactions in transaction history (accesses db).
* @param {Function} callback - Returns [Error, {@link TX}[]].
*/
CWallet.prototype.getHistory = function getHistory(callback) {
return this.db.getHistory(this.id, callback);
};
/**
* Get all available coins (accesses db).
* @param {Function} callback - Returns [Error, {@link Coin}[]].
*/
CWallet.prototype.getCoins = function getCoins(callback) {
return this.db.getCoins(this.id, callback);
};
/**
* Get all pending/unconfirmed transactions (accesses db).
* @param {Function} callback - Returns [Error, {@link TX}[]].
*/
CWallet.prototype.getUnconfirmed = function getUnconfirmed(callback) {
return this.db.getUnconfirmed(this.id, callback);
};
/**
* Get wallet balance (accesses db).
* @param {Function} callback - Returns [Error, {@link Balance}].
*/
CWallet.prototype.getBalance = function getBalance(callback) {
return this.db.getBalance(this.id, callback);
};
/**
* Get last timestamp and height this wallet was active
* at (accesses db). Useful for resetting the chain
* to a certain height when in SPV mode.
* @param {Function} callback - Returns [Error, Number(ts), Number(height)].
*/
CWallet.prototype.getLastTime = function getLastTime(callback) {
return this.db.getLastTime(this.id, callback);
};
/**
* Get the last N transactions (accesses db).
* @param {Number} limit
* @param {Function} callback - Returns [Error, {@link TX}[]].
*/
CWallet.prototype.getLast = function getLast(limit, callback) {
return this.db.getLast(this.id, limit, callback);
};
/**
* Get a range of transactions between two timestamps (accesses db).
* @param {Object} options
* @param {Number} options.start
* @param {Number} options.end
* @param {Function} callback - Returns [Error, {@link TX}[]].
*/
CWallet.prototype.getTimeRange = function getTimeRange(options, callback) {
return this.db.getTimeRange(this.id, options, callback);
};
CWallet.prototype.getReceiveAddress = function getReceiveAddress(callback) {
return this.db.getReceiveAddress(this.id, callback);
};
/**
* Convert the wallet to a more inspection-friendly object.
* @returns {Object}
*/
CWallet.prototype.inspect = function inspect() {
return {
id: this.id,
type: this.type,
network: this.network.type,
m: this.m,
n: this.n,
keyAddress: this.initialized
? this.keyAddress
: null,
scriptAddress: this.initialized
? this.scriptAddress
: null,
programAddress: this.initialized
? this.programAddress
: null,
witness: this.witness,
derivation: this.derivation,
copayBIP45: this.copayBIP45,
accountIndex: this.accountIndex,
receiveDepth: this.receiveDepth,
changeDepth: this.changeDepth,
master: this.master.toJSON(),
accountKey: this.accountKey.xpubkey,
keys: this.keys.map(function(key) {
return key.xpubkey;
})
};
};
/**
* Convert the wallet to an object suitable for
* serialization. Will automatically encrypt the
* master key based on the `passphrase` option.
* @returns {Object}
*/
CWallet.prototype.toJSON = function toJSON() {
return {
v: 3,
name: 'wallet',
network: this.network.type,
id: this.id,
type: this.type,
m: this.m,
n: this.n,
initialized: this.initialized,
saved: this.saved,
witness: this.witness,
derivation: this.derivation,
copayBIP45: this.copayBIP45,
accountIndex: this.accountIndex,
receiveDepth: this.receiveDepth,
changeDepth: this.changeDepth,
master: this.master.toJSON(),
accountKey: this.accountKey.xpubkey,
keys: this.keys.map(function(key) {
return key.xpubkey;
})
};
};
/**
* Handle a deserialized JSON wallet object.
* @returns {Object} A "naked" wallet (a
* plain javascript object which is suitable
* for passing to the Wallet constructor).
* @param {Object} json
* @param {String?} passphrase
* @returns {Object}
* @throws Error on bad decrypt
*/
CWallet.parseJSON = function parseJSON(json) {
assert.equal(json.v, 3);
assert.equal(json.name, 'wallet');
return {
network: json.network,
id: json.id,
type: json.type,
m: json.m,
n: json.n,
initialized: json.initialized,
saved: json.saved,
witness: json.witness,
derivation: json.derivation,
copayBIP45: json.copayBIP45,
accountIndex: json.accountIndex,
receiveDepth: json.receiveDepth,
changeDepth: json.changeDepth,
master: MasterKey.fromJSON(json.master),
accountKey: bcoin.hd.fromBase58(json.accountKey),
keys: json.keys.map(function(key) {
return bcoin.hd.fromBase58(key);
})
};
};
/**
* Instantiate a Wallet from a
* jsonified wallet object.
* @param {Object} json - The jsonified wallet object.
* @returns {Wallet}
*/
CWallet.fromJSON = function fromJSON(json) {
return new CWallet(CWallet.parseJSON(json));
};
/**
* Test an object to see if it is a CWallet.
* @param {Object} obj
* @returns {Boolean}
*/
CWallet.isCWallet = function isCWallet(obj) {
return obj
&& typeof obj.receiveDepth === 'number'
&& obj.deriveAddress === 'function';
};
/*
* Expose
*/
module.exports = Wallet;
bcoin.cwallet = CWallet;
module.exports.CWallet = CWallet;

View File

@ -108,7 +108,7 @@ WalletDB.prototype._init = function _init() {
writeBufferSize: 4 << 20
});
this.tx = new bcoin.txdb(this.db, {
this.tx = new bcoin.txdb(this, {
network: this.network,
indexExtra: true,
indexAddress: true,
@ -141,19 +141,11 @@ WalletDB.prototype._init = function _init() {
});
});
this.tx.on('confirmed', function(tx, map, callback) {
this.tx.on('confirmed', function(tx, map) {
self.emit('confirmed', tx, map);
map.all.forEach(function(id) {
self.fire(id, 'confirmed', tx);
});
utils.forEachSerial(map.output, function(id, next) {
self.syncOutputDepth(id, tx, next);
}, function(err) {
if (err)
self.emit('error', err);
if (callback)
callback();
});
});
this.tx.on('unconfirmed', function(tx, map) {
@ -197,6 +189,13 @@ WalletDB.prototype._init = function _init() {
});
};
WalletDB.prototype.sync = function sync(tx, map, callback) {
var self = this;
utils.forEachSerial(map.output, function(id, next) {
self.syncOutputDepth(id, tx, next);
}, callback);
};
/**
* Open the walletdb, wait for the database to load.
* @param {Function} callback
@ -221,50 +220,6 @@ WalletDB.prototype.destroy = function destroy(callback) {
this.db.close(callback);
};
/**
* Sync address depths based on a transaction's outputs.
* This is used for deriving new addresses when
* a confirmed transaction is seen.
* @param {WalletID} id
* @param {TX} tx
* @param {Function} callback
*/
WalletDB.prototype.syncOutputDepth = function syncOutputDepth(id, tx, callback) {
var self = this;
callback = utils.ensure(callback);
this.get(id, function(err, wallet) {
if (err)
return callback(err);
if (!wallet)
return callback(new Error('No wallet.'));
wallet.syncOutputDepth(tx, function(err) {
if (err) {
wallet.destroy();
return callback(err);
}
wallet.destroy();
if (!self.providers[id])
return callback();
self.providers[id].forEach(function(provider) {
provider.receiveDepth = wallet.receiveDepth;
provider.changeDepth = wallet.changeDepth;
provider.receiveAddress = wallet.receiveAddress;
provider.changeAddress = wallet.changeAddress;
});
callback();
});
});
};
/**
* Derive an address.
* @param {WalletID} id
@ -272,89 +227,87 @@ WalletDB.prototype.syncOutputDepth = function syncOutputDepth(id, tx, callback)
* @param {Function} callback
*/
WalletDB.prototype.createAddress = function createAddress(id, change, callback) {
WalletDB.prototype.rpc = function rpc(id, callback, method) {
var self = this;
var address;
callback = utils.ensure(callback);
this.get(id, function(err, wallet) {
this.get(id, function(err, _, wallet) {
if (err)
return callback(err);
if (!wallet)
return callback(new Error('No wallet.'));
wallet.createAddress(change, function(err) {
if (err) {
wallet.destroy();
return callback(err);
}
wallet.destroy();
if (!self.providers[id])
return callback();
self.providers[id].forEach(function(provider) {
provider.receiveDepth = wallet.receiveDepth;
provider.changeDepth = wallet.changeDepth;
provider.receiveAddress = wallet.receiveAddress;
provider.changeAddress = wallet.changeAddress;
});
callback();
});
method(wallet);
});
};
/**
* Add a public account/purpose key to the wallet for multisig.
* @param {WalletID} id
* @param {HDPublicKey|Base58String} key - Account (bip44) or
* Purpose (bip45) key (can be in base58 form).
* @param {Function} callback
*/
WalletDB.prototype.syncOutputDepth = function syncOutputDepth(id, tx, callback) {
this.rpc(id, callback, function(wallet) {
wallet.syncOutputDepth(tx, callback);
});
};
WalletDB.prototype.modifyKey = function modifyKey(id, key, remove, callback) {
var self = this;
WalletDB.prototype.createAddress = function createAddress(id, change, callback) {
this.rpc(id, callback, function(wallet) {
wallet.createAddress(change, callback);
});
};
callback = utils.ensure(callback);
WalletDB.prototype.getReceiveAddress = function getReceiveAddress(id, callback) {
this.rpc(id, callback, function(wallet) {
callback(null, wallet.receiveAddress);
});
};
this.get(id, function(err, json) {
if (err)
return callback(err);
WalletDB.prototype.getChangeAddress = function getChangeAddress(id, callback) {
this.rpc(id, callback, function(wallet) {
callback(null, wallet.changeAddress);
});
};
if (!wallet)
return callback(new Error('No wallet.'));
WalletDB.prototype.fill = function fill(id, tx, options, callback) {
this.rpc(id, callback, function(wallet) {
wallet.fill(tx, options, callback);
});
};
function done(err) {
if (err) {
wallet.destroy();
return callback(err);
}
WalletDB.prototype.scriptInputs = function scriptInputs(id, tx, callback) {
this.rpc(id, callback, function(wallet) {
wallet.scriptInputs(tx, callback);
});
};
wallet.destroy();
WalletDB.prototype.sign = function sign(id, tx, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
if (!self.providers[id])
return callback();
if (typeof options === 'string' || Buffer.isBuffer(options))
options = { passphrase: options };
self.providers[id].forEach(function(provider) {
provider.keys = wallet.keys.slice();
provider.initialized = wallet.initialized;
});
this.rpc(id, callback, function(wallet) {
wallet.sign(tx, options, callback);
});
};
callback();
}
WalletDB.prototype.createTX = function createTX(id, options, outputs, callback) {
this.rpc(id, callback, function(wallet) {
wallet.createTX(options, outputs, callback);
});
};
try {
if (!remove)
wallet.addKey(key, done);
else
wallet.removeKey(key, done);
} catch (e) {
return done(e);
}
WalletDB.prototype.addKey = function addKey(id, key, callback) {
this.rpc(id, callback, function(wallet) {
wallet.addKey(key, callback);
});
};
WalletDB.prototype.removeKey = function removeKey(id, key, callback) {
this.rpc(id, callback, function(wallet) {
wallet.removeKey(key, callback);
});
};
@ -454,7 +407,7 @@ WalletDB.prototype.get = function get(id, callback) {
if (err)
return callback(err);
return callback(null, wallet);
return callback(null, new bcoin.cwallet(wallet.id, self), wallet);
});
});
};
@ -510,6 +463,7 @@ WalletDB.prototype.create = function create(options, callback) {
return callback(
new Error('`' + options.id + '` already exists.'),
null,
null,
json);
}
@ -524,7 +478,7 @@ WalletDB.prototype.create = function create(options, callback) {
if (err)
return callback(err);
return callback(null, wallet);
return callback(null, new bcoin.cwallet(wallet.id, self), wallet);
});
}
@ -543,12 +497,12 @@ WalletDB.prototype.create = function create(options, callback) {
WalletDB.prototype.ensure = function ensure(options, callback) {
var self = this;
return this.create(options, function(err, wallet, json) {
return this.create(options, function(err, cwallet, wallet, json) {
if (err && !json)
return callback(err);
if (wallet)
return callback(null, wallet);
if (cwallet)
return callback(null, cwallet);
assert(json);
@ -564,7 +518,7 @@ WalletDB.prototype.ensure = function ensure(options, callback) {
if (err)
return callback(err);
return callback(null, wallet);
return callback(null, new bcoin.cwallet(wallet.id, self), wallet);
});
});
};

View File

@ -82,37 +82,40 @@ describe('Wallet', function() {
if (witness)
flags |= bcoin.protocol.constants.flags.VERIFY_WITNESS;
wdb.create({ witness: witness }, function(err, w) {
wdb.create({ witness: witness }, function(err, w, w2) {
assert.ifError(err);
if (witness)
assert(bcoin.address.parseBase58(w.getAddress()).type === 'witnesspubkeyhash');
else
assert(bcoin.address.parseBase58(w.getAddress()).type === 'pubkeyhash');
w.getReceiveAddress(function(err, a) {
if (witness)
assert(bcoin.address.parseBase58(a.getAddress()).type === 'witnesspubkeyhash');
else
assert(bcoin.address.parseBase58(a.getAddress()).type === 'pubkeyhash');
// Input transcation
var src = bcoin.mtx({
outputs: [{
value: 5460 * 2,
address: bullshitNesting
? w.getProgramAddress()
: w.getAddress()
}, {
value: 5460 * 2,
address: bcoin.address.fromData(new Buffer([])).toBase58()
}]
});
// Input transcation
var src = bcoin.mtx({
outputs: [{
value: 5460 * 2,
address: bullshitNesting
? a.getProgramAddress()
: a.getAddress()
}, {
value: 5460 * 2,
address: bcoin.address.fromData(new Buffer([])).toBase58()
}]
});
src.addInput(dummyInput);
src.addInput(dummyInput);
var tx = bcoin.mtx()
.addInput(src, 0)
.addOutput(w.getAddress(), 5460);
var tx = bcoin.mtx()
.addInput(src, 0)
.addOutput(a.getAddress(), 5460);
w.sign(tx, function(err) {
assert.ifError(err);
assert(tx.verify(null, true, flags));
cb();
w.sign(tx, function(err) {
assert.ifError(err);
utils.print(tx);
assert(tx.verify(null, true, flags));
cb();
});
});
});
}
@ -171,21 +174,25 @@ describe('Wallet', function() {
var dw, di;
it('should have TX pool and be serializable', function(cb) {
wdb.create({}, function(err, w) {
assert.ifError(err);
w.getReceiveAddress(function(err, aw) {
assert.ifError(err);
wdb.create({}, function(err, f) {
assert.ifError(err);
f.getReceiveAddress(function(err, af) {
assert.ifError(err);
dw = w;
// Coinbase
var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 1000);
var t1 = bcoin.mtx().addOutput(aw, 50000).addOutput(aw, 1000);
t1.addInput(dummyInput);
// balance: 51000
// w.sign(t1);
w.sign(t1, function(err) {
assert.ifError(err);
var t2 = bcoin.mtx().addInput(t1, 0) // 50000
.addOutput(w, 24000)
.addOutput(w, 24000);
.addOutput(aw, 24000)
.addOutput(aw, 24000);
di = t2.inputs[0];
// balance: 49000
// w.sign(t2);
@ -193,27 +200,27 @@ describe('Wallet', function() {
assert.ifError(err);
var t3 = bcoin.mtx().addInput(t1, 1) // 1000
.addInput(t2, 0) // 24000
.addOutput(w, 23000);
.addOutput(aw, 23000);
// balance: 47000
// w.sign(t3);
w.sign(t3, function(err) {
assert.ifError(err);
var t4 = bcoin.mtx().addInput(t2, 1) // 24000
.addInput(t3, 0) // 23000
.addOutput(w, 11000)
.addOutput(w, 11000);
.addOutput(aw, 11000)
.addOutput(aw, 11000);
// balance: 22000
// w.sign(t4);
w.sign(t4, function(err) {
assert.ifError(err);
var f1 = bcoin.mtx().addInput(t4, 1) // 11000
.addOutput(f, 10000);
.addOutput(af, 10000);
// balance: 11000
// w.sign(f1);
w.sign(f1, function(err) {
assert.ifError(err);
var fake = bcoin.mtx().addInput(t1, 1) // 1000 (already redeemed)
.addOutput(w, 500);
.addOutput(aw, 500);
// Script inputs but do not sign
// w.scriptInputs(fake);
w.scriptInputs(fake, function(err) {
@ -262,7 +269,7 @@ describe('Wallet', function() {
return tx.hash('hex') === f1.hash('hex');
}));
var w2 = bcoin.wallet.fromJSON(w.toJSON());
//var w2 = bcoin.wallet.fromJSON(w.toJSON());
// assert.equal(w2.getBalance(), 11000);
// assert(w2.getHistory().some(function(tx) {
// return tx.hash('hex') === f1.hash('hex');
@ -278,6 +285,8 @@ describe('Wallet', function() {
});
});
});
});
});
});
});
});
@ -305,16 +314,20 @@ describe('Wallet', function() {
it('should fill tx with inputs', function(cb) {
wdb.create({}, function(err, w1) {
assert.ifError(err);
w1.getReceiveAddress(function(err, aw1) {
assert.ifError(err);
wdb.create({}, function(err, w2) {
assert.ifError(err);
w2.getReceiveAddress(function(err, aw2) {
assert.ifError(err);
// Coinbase
var t1 = bcoin.mtx()
.addOutput(w1, 5460)
.addOutput(w1, 5460)
.addOutput(w1, 5460)
.addOutput(w1, 5460);
.addOutput(aw1, 5460)
.addOutput(aw1, 5460)
.addOutput(aw1, 5460)
.addOutput(aw1, 5460);
t1.addInput(dummyInput);
@ -323,7 +336,7 @@ describe('Wallet', function() {
assert.ifError(err);
// Create new transaction
var t2 = bcoin.mtx().addOutput(w2, 5460);
var t2 = bcoin.mtx().addOutput(aw2, 5460);
w1.fill(t2, { rate: 10000, round: true }, function(err) {
assert.ifError(err);
w1.sign(t2, function(err) {
@ -338,7 +351,7 @@ describe('Wallet', function() {
assert.equal(t2.getFee(), 10920);
// Create new transaction
var t3 = bcoin.mtx().addOutput(w2, 15000);
var t3 = bcoin.mtx().addOutput(aw2, 15000);
w1.fill(t3, { rate: 10000, round: true }, function(err) {
assert(err);
assert.equal(err.requiredFunds, 25000);
@ -347,22 +360,28 @@ describe('Wallet', function() {
});
});
});
});
});
});
});
});
it('should fill tx with inputs with accurate fee', function(cb) {
wdb.create({ master: KEY1 }, function(err, w1) {
assert.ifError(err);
w1.getReceiveAddress(function(err, aw1) {
assert.ifError(err);
wdb.create({ master: KEY2 }, function(err, w2) {
assert.ifError(err);
w2.getReceiveAddress(function(err, aw2) {
assert.ifError(err);
// Coinbase
var t1 = bcoin.mtx()
.addOutput(w1, 5460)
.addOutput(w1, 5460)
.addOutput(w1, 5460)
.addOutput(w1, 5460);
.addOutput(aw1, 5460)
.addOutput(aw1, 5460)
.addOutput(aw1, 5460)
.addOutput(aw1, 5460);
t1.addInput(dummyInput);
@ -371,7 +390,7 @@ describe('Wallet', function() {
assert.ifError(err);
// Create new transaction
var t2 = bcoin.mtx().addOutput(w2, 5460);
var t2 = bcoin.mtx().addOutput(aw2, 5460);
w1.fill(t2, { rate: 10000 }, function(err) {
assert.ifError(err);
w1.sign(t2, function(err) {
@ -393,7 +412,7 @@ describe('Wallet', function() {
// Create new transaction
wdb.addTX(t2, function(err) {
assert.ifError(err);
var t3 = bcoin.mtx().addOutput(w2, 15000);
var t3 = bcoin.mtx().addOutput(aw2, 15000);
w1.fill(t3, { rate: 10000 }, function(err) {
assert(err);
cb();
@ -404,32 +423,40 @@ describe('Wallet', function() {
});
});
});
});
});
});
it('should sign multiple inputs using different keys', function(cb) {
wdb.create({}, function(err, w1) {
assert.ifError(err);
w1.getReceiveAddress(function(err, aw1) {
assert.ifError(err);
wdb.create({}, function(err, w2) {
assert.ifError(err);
w2.getReceiveAddress(function(err, aw2) {
assert.ifError(err);
wdb.create({}, function(err, to) {
assert.ifError(err);
to.getReceiveAddress(function(err, ato) {
assert.ifError(err);
// Coinbase
var t1 = bcoin.mtx()
.addOutput(w1, 5460)
.addOutput(w1, 5460)
.addOutput(w1, 5460)
.addOutput(w1, 5460);
.addOutput(aw1, 5460)
.addOutput(aw1, 5460)
.addOutput(aw1, 5460)
.addOutput(aw1, 5460);
t1.addInput(dummyInput);
// Fake TX should temporarly change output
// Coinbase
var t2 = bcoin.mtx()
.addOutput(w2, 5460)
.addOutput(w2, 5460)
.addOutput(w2, 5460)
.addOutput(w2, 5460);
.addOutput(aw2, 5460)
.addOutput(aw2, 5460)
.addOutput(aw2, 5460)
.addOutput(aw2, 5460);
t2.addInput(dummyInput);
// Fake TX should temporarly change output
@ -441,7 +468,7 @@ describe('Wallet', function() {
// Create our tx with an output
var tx = bcoin.mtx();
tx.addOutput(to, 5460);
tx.addOutput(ato, 5460);
var cost = tx.getOutputValue();
var total = cost * constants.tx.MIN_FEE;
@ -452,7 +479,7 @@ describe('Wallet', function() {
assert.ifError(err);
// Add dummy output (for `left`) to calculate maximum TX size
tx.addOutput(w1, 0);
tx.addOutput(aw1, 0);
// Add our unspent inputs to sign
tx.addInput(coins1[0]);
@ -507,6 +534,9 @@ describe('Wallet', function() {
});
});
});
});
});
});
});
function multisig(witness, bullshitNesting, cb) {