bcoin: classify everything else.

This commit is contained in:
Christopher Jeffrey 2017-11-16 20:32:26 -08:00
parent bad24a6f31
commit 73664efcd0
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
5 changed files with 1607 additions and 1603 deletions

1518
bin/cli

File diff suppressed because it is too large Load Diff

View File

@ -12,157 +12,163 @@ const bsock = require('bsock');
const hash256 = require('bcrypto/lib/hash256');
const bio = require('bufio');
function ProxySocket(uri) {
if (!(this instanceof ProxySocket))
return new ProxySocket(uri);
class ProxySocket extends EventEmitter {
constructor(uri) {
super();
EventEmitter.call(this);
this.info = null;
this.info = null;
this.socket = bsock.connect(uri);
this.sendBuffer = [];
this.recvBuffer = [];
this.paused = false;
this.snonce = null;
this.bytesWritten = 0;
this.bytesRead = 0;
this.remoteAddress = null;
this.remotePort = 0;
this.socket = bsock.connect(uri);
this.sendBuffer = [];
this.recvBuffer = [];
this.paused = false;
this.snonce = null;
this.bytesWritten = 0;
this.bytesRead = 0;
this.remoteAddress = null;
this.remotePort = 0;
this.closed = false;
this.closed = false;
this.init();
}
this.init();
}
init() {
this.socket.bind('info', (info) => {
if (this.closed)
return;
Object.setPrototypeOf(ProxySocket.prototype, EventEmitter.prototype);
this.info = info;
ProxySocket.prototype.init = function init() {
this.socket.bind('info', (info) => {
if (this.closed)
return;
if (info.pow) {
this.snonce = Buffer.from(info.snonce, 'hex');
this.target = Buffer.from(info.target, 'hex');
}
this.info = info;
this.emit('info', info);
});
if (info.pow) {
this.snonce = Buffer.from(info.snonce, 'hex');
this.target = Buffer.from(info.target, 'hex');
}
this.socket.on('error', (err) => {
console.error(err);
});
this.emit('info', info);
});
this.socket.bind('tcp connect', (addr, port) => {
if (this.closed)
return;
this.remoteAddress = addr;
this.remotePort = port;
this.emit('connect');
});
this.socket.on('error', (err) => {
console.error(err);
});
this.socket.bind('tcp data', (data) => {
data = Buffer.from(data, 'hex');
if (this.paused) {
this.recvBuffer.push(data);
return;
}
this.bytesRead += data.length;
this.emit('data', data);
});
this.socket.bind('tcp connect', (addr, port) => {
if (this.closed)
return;
this.remoteAddress = addr;
this.socket.bind('tcp close', (data) => {
if (this.closed)
return;
this.closed = true;
this.emit('close');
});
this.socket.bind('tcp error', (e) => {
const err = new Error(e.message);
err.code = e.code;
this.emit('error', err);
});
this.socket.bind('tcp timeout', () => {
this.emit('timeout');
});
this.socket.bind('disconnect', () => {
if (this.closed)
return;
this.closed = true;
this.emit('close');
});
}
connect(port, host) {
this.remoteAddress = host;
this.remotePort = port;
this.emit('connect');
});
this.socket.bind('tcp data', (data) => {
data = Buffer.from(data, 'hex');
if (this.paused) {
this.recvBuffer.push(data);
if (this.closed) {
this.sendBuffer.length = 0;
return;
}
this.bytesRead += data.length;
this.emit('data', data);
});
this.socket.bind('tcp close', (data) => {
if (this.closed)
if (!this.info) {
this.once('info', connect.bind(this, port, host));
return;
this.closed = true;
this.emit('close');
});
}
this.socket.bind('tcp error', (e) => {
const err = new Error(e.message);
err.code = e.code;
this.emit('error', err);
});
let nonce = 0;
this.socket.bind('tcp timeout', () => {
this.emit('timeout');
});
if (this.info.pow) {
const bw = bio.write();
this.socket.bind('disconnect', () => {
if (this.closed)
return;
this.closed = true;
this.emit('close');
});
};
bw.writeU32(nonce);
bw.writeBytes(this.snonce);
bw.writeU32(port);
bw.writeString(host, 'ascii');
ProxySocket.prototype.connect = function connect(port, host) {
this.remoteAddress = host;
this.remotePort = port;
const pow = bw.render();
console.log(
'Solving proof of work to create socket (%d, %s) -- please wait.',
port, host);
do {
nonce += 1;
assert(nonce <= 0xffffffff, 'Could not create socket.');
pow.writeUInt32LE(nonce, 0, true);
} while (hash256.digest(pow).compare(this.target) > 0);
console.log('Solved proof of work: %d', nonce);
}
this.socket.fire('tcp connect', port, host, nonce);
for (const chunk of this.sendBuffer)
this.write(chunk);
if (this.closed) {
this.sendBuffer.length = 0;
return;
}
if (!this.info) {
this.once('info', connect.bind(this, port, host));
return;
setKeepAlive(enable, delay) {
this.socket.fire('tcp keep alive', enable, delay);
}
let nonce = 0;
if (this.info.pow) {
const bw = bio.write();
bw.writeU32(nonce);
bw.writeBytes(this.snonce);
bw.writeU32(port);
bw.writeString(host, 'ascii');
const pow = bw.render();
console.log(
'Solving proof of work to create socket (%d, %s) -- please wait.',
port, host);
do {
nonce += 1;
assert(nonce <= 0xffffffff, 'Could not create socket.');
pow.writeUInt32LE(nonce, 0, true);
} while (hash256.digest(pow).compare(this.target) > 0);
console.log('Solved proof of work: %d', nonce);
setNoDelay(enable) {
this.socket.fire('tcp no delay', enable);
}
this.socket.fire('tcp connect', port, host, nonce);
setTimeout(timeout, callback) {
this.socket.fire('tcp set timeout', timeout);
if (callback)
this.on('timeout', callback);
}
for (const chunk of this.sendBuffer)
this.write(chunk);
write(data, callback) {
if (!this.info) {
this.sendBuffer.push(data);
this.sendBuffer.length = 0;
};
if (callback)
callback();
ProxySocket.prototype.setKeepAlive = function setKeepAlive(enable, delay) {
this.socket.fire('tcp keep alive', enable, delay);
};
return true;
}
ProxySocket.prototype.setNoDelay = function setNoDelay(enable) {
this.socket.fire('tcp no delay', enable);
};
this.bytesWritten += data.length;
ProxySocket.prototype.setTimeout = function setTimeout(timeout, callback) {
this.socket.fire('tcp set timeout', timeout);
if (callback)
this.on('timeout', callback);
};
ProxySocket.prototype.write = function write(data, callback) {
if (!this.info) {
this.sendBuffer.push(data);
this.socket.fire('tcp data', data.toString('hex'));
if (callback)
callback();
@ -170,43 +176,34 @@ ProxySocket.prototype.write = function write(data, callback) {
return true;
}
this.bytesWritten += data.length;
this.socket.fire('tcp data', data.toString('hex'));
if (callback)
callback();
return true;
};
ProxySocket.prototype.pause = function pause() {
this.paused = true;
};
ProxySocket.prototype.resume = function resume() {
const recv = this.recvBuffer;
this.paused = false;
this.recvBuffer = [];
for (const data of recv) {
this.bytesRead += data.length;
this.emit('data', data);
pause() {
this.paused = true;
}
};
ProxySocket.prototype.destroy = function destroy() {
if (this.closed)
return;
this.closed = true;
this.socket.destroy();
};
resume() {
const recv = this.recvBuffer;
ProxySocket.connect = function connect(uri, port, host) {
const socket = new ProxySocket(uri);
socket.connect(port, host);
return socket;
};
this.paused = false;
this.recvBuffer = [];
for (const data of recv) {
this.bytesRead += data.length;
this.emit('data', data);
}
}
destroy() {
if (this.closed)
return;
this.closed = true;
this.socket.destroy();
}
static connect(uri, port, host) {
const socket = new this(uri);
socket.connect(port, host);
return socket;
}
}
module.exports = ProxySocket;

View File

@ -12,240 +12,239 @@ const TARGET = Buffer.from(
'0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'hex');
function WSProxy(options) {
if (!(this instanceof WSProxy))
return new WSProxy(options);
class WSProxy extends EventEmitter {
constructor(options) {
super();
EventEmitter.call(this);
if (!options)
options = {};
if (!options)
options = {};
this.options = options;
this.target = options.target || TARGET;
this.pow = options.pow === true;
this.ports = new Set();
this.io = bsock.server();
this.sockets = new WeakMap();
this.options = options;
this.target = options.target || TARGET;
this.pow = options.pow === true;
this.ports = new Set();
this.io = bsock.server();
this.sockets = new WeakMap();
if (options.ports) {
for (const port of options.ports)
this.ports.add(port);
}
if (options.ports) {
for (const port of options.ports)
this.ports.add(port);
this.init();
}
this.init();
}
init() {
this.io.on('error', (err) => {
this.emit('error', err);
});
Object.setPrototypeOf(WSProxy.prototype, EventEmitter.prototype);
WSProxy.prototype.init = function init() {
this.io.on('error', (err) => {
this.emit('error', err);
});
this.io.on('socket', (ws) => {
this.handleSocket(ws);
});
};
WSProxy.prototype.handleSocket = function handleSocket(ws) {
const state = new SocketState(this, ws);
// Use a weak map to avoid
// mutating the websocket object.
this.sockets.set(ws, state);
ws.fire('info', state.toInfo());
ws.on('error', (err) => {
this.emit('error', err);
});
ws.bind('tcp connect', (port, host, nonce) => {
this.handleConnect(ws, port, host, nonce);
});
};
WSProxy.prototype.handleConnect = function handleConnect(ws, port, host, nonce) {
const state = this.sockets.get(ws);
assert(state);
if (state.socket) {
this.log('Client is trying to reconnect (%s).', state.host);
return;
this.io.on('socket', (ws) => {
this.handleSocket(ws);
});
}
if ((port & 0xffff) !== port
|| typeof host !== 'string'
|| host.length === 0) {
this.log('Client gave bad arguments (%s).', state.host);
ws.fire('tcp close');
ws.destroy();
return;
handleSocket(ws) {
const state = new SocketState(this, ws);
// Use a weak map to avoid
// mutating the websocket object.
this.sockets.set(ws, state);
ws.fire('info', state.toInfo());
ws.on('error', (err) => {
this.emit('error', err);
});
ws.bind('tcp connect', (port, host, nonce) => {
this.handleConnect(ws, port, host, nonce);
});
}
if (this.pow) {
if ((nonce >>> 0) !== nonce) {
this.log('Client did not solve proof of work (%s).', state.host);
handleConnect(ws, port, host, nonce) {
const state = this.sockets.get(ws);
assert(state);
if (state.socket) {
this.log('Client is trying to reconnect (%s).', state.host);
return;
}
if ((port & 0xffff) !== port
|| typeof host !== 'string'
|| host.length === 0) {
this.log('Client gave bad arguments (%s).', state.host);
ws.fire('tcp close');
ws.destroy();
return;
}
const bw = bio.write();
bw.writeU32(nonce);
bw.writeBytes(state.snonce);
bw.writeU32(port);
bw.writeString(host, 'ascii');
if (this.pow) {
if ((nonce >>> 0) !== nonce) {
this.log('Client did not solve proof of work (%s).', state.host);
ws.fire('tcp close');
ws.destroy();
return;
}
const pow = bw.render();
const bw = bio.write();
bw.writeU32(nonce);
bw.writeBytes(state.snonce);
bw.writeU32(port);
bw.writeString(host, 'ascii');
if (hash256.digest(pow).compare(this.target) > 0) {
this.log('Client did not solve proof of work (%s).', state.host);
ws.fire('tcp close');
const pow = bw.render();
if (hash256.digest(pow).compare(this.target) > 0) {
this.log('Client did not solve proof of work (%s).', state.host);
ws.fire('tcp close');
ws.destroy();
return;
}
}
let raw, addr;
try {
raw = IP.toBuffer(host);
addr = IP.toString(raw);
} catch (e) {
this.log('Client gave a bad host: %s (%s).', host, state.host);
ws.fire('tcp error', {
message: 'EHOSTUNREACH',
code: 'EHOSTUNREACH'
});
ws.destroy();
return;
}
}
let raw, addr;
try {
raw = IP.toBuffer(host);
addr = IP.toString(raw);
} catch (e) {
this.log('Client gave a bad host: %s (%s).', host, state.host);
ws.fire('tcp error', {
message: 'EHOSTUNREACH',
code: 'EHOSTUNREACH'
});
ws.destroy();
return;
}
if (!IP.isRoutable(raw) || IP.isOnion(raw)) {
this.log(
'Client is trying to connect to a bad ip: %s (%s).',
addr, state.host);
ws.fire('tcp error', {
message: 'ENETUNREACH',
code: 'ENETUNREACH'
});
ws.destroy();
return;
}
if (!this.ports.has(port)) {
this.log('Client is connecting to non-whitelist port (%s).', state.host);
ws.fire('tcp error', {
message: 'ENETUNREACH',
code: 'ENETUNREACH'
});
ws.destroy();
return;
}
let socket;
try {
socket = state.connect(port, addr);
this.log('Connecting to %s (%s).', state.remoteHost, state.host);
} catch (e) {
this.log(e.message);
this.log('Closing %s (%s).', state.remoteHost, state.host);
ws.fire('tcp error', {
message: 'ENETUNREACH',
code: 'ENETUNREACH'
});
ws.destroy();
return;
}
socket.on('connect', () => {
ws.fire('tcp connect', socket.remoteAddress, socket.remotePort);
});
socket.on('data', (data) => {
ws.fire('tcp data', data.toString('hex'));
});
socket.on('error', (err) => {
ws.fire('tcp error', {
message: err.message,
code: err.code || null
});
});
socket.on('timeout', () => {
ws.fire('tcp timeout');
});
socket.on('close', () => {
this.log('Closing %s (%s).', state.remoteHost, state.host);
ws.fire('tcp close');
ws.destroy();
});
ws.bind('tcp data', (data) => {
if (typeof data !== 'string')
if (!IP.isRoutable(raw) || IP.isOnion(raw)) {
this.log(
'Client is trying to connect to a bad ip: %s (%s).',
addr, state.host);
ws.fire('tcp error', {
message: 'ENETUNREACH',
code: 'ENETUNREACH'
});
ws.destroy();
return;
socket.write(Buffer.from(data, 'hex'));
});
}
ws.bind('tcp keep alive', (enable, delay) => {
socket.setKeepAlive(enable, delay);
});
if (!this.ports.has(port)) {
this.log('Client is connecting to non-whitelist port (%s).', state.host);
ws.fire('tcp error', {
message: 'ENETUNREACH',
code: 'ENETUNREACH'
});
ws.destroy();
return;
}
ws.bind('tcp no delay', (enable) => {
socket.setNoDelay(enable);
});
let socket;
try {
socket = state.connect(port, addr);
this.log('Connecting to %s (%s).', state.remoteHost, state.host);
} catch (e) {
this.log(e.message);
this.log('Closing %s (%s).', state.remoteHost, state.host);
ws.fire('tcp error', {
message: 'ENETUNREACH',
code: 'ENETUNREACH'
});
ws.destroy();
return;
}
ws.bind('tcp set timeout', (timeout) => {
socket.setTimeout(timeout);
});
socket.on('connect', () => {
ws.fire('tcp connect', socket.remoteAddress, socket.remotePort);
});
ws.bind('tcp pause', () => {
socket.pause();
});
socket.on('data', (data) => {
ws.fire('tcp data', data.toString('hex'));
});
ws.bind('tcp resume', () => {
socket.resume();
});
socket.on('error', (err) => {
ws.fire('tcp error', {
message: err.message,
code: err.code || null
});
});
ws.bind('disconnect', () => {
socket.destroy();
});
};
socket.on('timeout', () => {
ws.fire('tcp timeout');
});
WSProxy.prototype.log = function log(...args) {
process.stdout.write('wsproxy: ');
console.log(...args);
};
socket.on('close', () => {
this.log('Closing %s (%s).', state.remoteHost, state.host);
ws.fire('tcp close');
ws.destroy();
});
WSProxy.prototype.attach = function attach(server) {
this.io.attach(server);
};
ws.bind('tcp data', (data) => {
if (typeof data !== 'string')
return;
socket.write(Buffer.from(data, 'hex'));
});
function SocketState(server, socket) {
this.pow = server.pow;
this.target = server.target;
this.snonce = nonce();
this.socket = null;
this.host = socket.host;
this.remoteHost = null;
ws.bind('tcp keep alive', (enable, delay) => {
socket.setKeepAlive(enable, delay);
});
ws.bind('tcp no delay', (enable) => {
socket.setNoDelay(enable);
});
ws.bind('tcp set timeout', (timeout) => {
socket.setTimeout(timeout);
});
ws.bind('tcp pause', () => {
socket.pause();
});
ws.bind('tcp resume', () => {
socket.resume();
});
ws.bind('disconnect', () => {
socket.destroy();
});
}
log(...args) {
process.stdout.write('wsproxy: ');
console.log(...args);
}
attach(server) {
this.io.attach(server);
}
}
SocketState.prototype.toInfo = function toInfo() {
return {
pow: this.pow,
target: this.target.toString('hex'),
snonce: this.snonce.toString('hex')
};
};
class SocketState {
constructor(server, socket) {
this.pow = server.pow;
this.target = server.target;
this.snonce = nonce();
this.socket = null;
this.host = socket.host;
this.remoteHost = null;
}
SocketState.prototype.connect = function connect(port, host) {
this.socket = net.connect(port, host);
this.remoteHost = IP.toHostname(host, port);
return this.socket;
};
toInfo() {
return {
pow: this.pow,
target: this.target.toString('hex'),
snonce: this.snonce.toString('hex')
};
}
connect(port, host) {
this.socket = net.connect(port, host);
this.remoteHost = IP.toHostname(host, port);
return this.socket;
}
}
function nonce() {
const buf = Buffer.allocUnsafe(8);

View File

@ -15,412 +15,418 @@ const KeyRing = require('../../lib/primitives/keyring');
const Outpoint = require('../../lib/primitives/outpoint');
const Coin = require('../../lib/primitives/coin');
function MemWallet(options) {
if (!(this instanceof MemWallet))
return new MemWallet(options);
class MemWallet {
constructor(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 = new Set();
this.coins = new Map();
this.spent = new Map();
this.paths = new Map();
this.balance = 0;
this.txs = 0;
this.filter = BloomFilter.fromRate(1000000, 0.001, -1);
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 = new Set();
this.coins = new Map();
this.spent = new Map();
this.paths = new Map();
this.balance = 0;
this.txs = 0;
this.filter = BloomFilter.fromRate(1000000, 0.001, -1);
if (options)
this.fromOptions(options);
if (options)
this.fromOptions(options);
this.init();
}
this.init();
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;
}
init() {
let i;
if (!this.master)
this.master = HD.PrivateKey.generate();
if (!this.key) {
const type = this.network.keyPrefix.coinType;
this.key = this.master.deriveAccount(44, type, this.account);
}
i = this.receiveDepth;
while (i--)
this.createReceive();
i = this.changeDepth;
while (i--)
this.createChange();
}
createReceive() {
const index = this.receiveDepth++;
const key = this.deriveReceive(index);
const hash = key.getHash('hex');
this.filter.add(hash, 'hex');
this.paths.set(hash, new Path(hash, 0, index));
this.receive = key;
return key;
}
createChange() {
const index = this.changeDepth++;
const key = this.deriveChange(index);
const hash = key.getHash('hex');
this.filter.add(hash, 'hex');
this.paths.set(hash, new Path(hash, 1, index));
this.change = key;
return key;
}
deriveReceive(index) {
return this.deriveKey(0, index);
}
deriveChange(index) {
return this.deriveKey(1, index);
}
derivePath(path) {
return this.deriveKey(path.branch, path.index);
}
deriveKey(branch, index) {
const type = this.network.keyPrefix.coinType;
let key = this.master.deriveAccount(44, type, this.account);
key = key.derive(branch).derive(index);
const ring = new KeyRing({
network: this.network,
privateKey: key.privateKey,
witness: this.witness
});
ring.witness = this.witness;
return ring;
}
getKey(hash) {
const path = this.paths.get(hash);
if (!path)
return null;
return this.derivePath(path);
}
getPath(hash) {
return this.paths.get(hash);
}
getCoin(key) {
return this.coins.get(key);
}
getUndo(key) {
return this.spent.get(key);
}
addCoin(coin) {
const op = new Outpoint(coin.hash, coin.index);
const key = op.toKey();
this.filter.add(op.toRaw());
this.spent.delete(key);
this.coins.set(key, coin);
this.balance += coin.value;
}
removeCoin(key) {
const coin = this.coins.get(key);
if (!coin)
return;
this.spent.set(key, coin);
this.balance -= coin.value;
this.coins.delete(key);
}
getAddress() {
return this.receive.getAddress();
}
getReceive() {
return this.receive.getAddress();
}
getChange() {
return this.change.getAddress();
}
getCoins() {
const coins = [];
for (const coin of this.coins.values())
coins.push(coin);
return coins;
}
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;
}
}
addBlock(entry, txs) {
for (let i = 0; i < txs.length; i++) {
const tx = txs[i];
this.addTX(tx, entry.height);
}
}
removeBlock(entry, txs) {
for (let i = txs.length - 1; i >= 0; i--) {
const tx = txs[i];
this.removeTX(tx, entry.height);
}
}
addTX(tx, height) {
const hash = tx.hash('hex');
let result = false;
if (height == null)
height = -1;
if (this.map.has(hash))
return true;
for (let i = 0; i < tx.inputs.length; i++) {
const input = tx.inputs[i];
const op = input.prevout.toKey();
const coin = this.getCoin(op);
if (!coin)
continue;
result = true;
this.removeCoin(op);
}
for (let i = 0; i < tx.outputs.length; i++) {
const output = tx.outputs[i];
const addr = output.getHash('hex');
if (!addr)
continue;
const path = this.getPath(addr);
if (!path)
continue;
result = true;
const coin = Coin.fromTX(tx, i, height);
this.addCoin(coin);
this.syncKey(path);
}
if (result) {
this.txs += 1;
this.map.add(hash);
}
return result;
}
removeTX(tx, height) {
const hash = tx.hash('hex');
let result = false;
if (!this.map.has(hash))
return false;
for (let i = 0; i < tx.outputs.length; i++) {
const op = new Outpoint(hash, i).toKey();
const coin = this.getCoin(op);
if (!coin)
continue;
result = true;
this.removeCoin(op);
}
for (let i = 0; i < tx.inputs.length; i++) {
const input = tx.inputs[i];
const op = input.prevout.toKey();
const coin = this.getUndo(op);
if (!coin)
continue;
result = true;
this.addCoin(coin);
}
if (result)
this.txs -= 1;
this.map.delete(hash);
return result;
}
deriveInputs(mtx) {
const keys = [];
for (let i = 0; i < mtx.inputs.length; i++) {
const input = mtx.inputs[i];
const coin = mtx.view.getOutputFor(input);
if (!coin)
continue;
const addr = coin.getHash('hex');
if (!addr)
continue;
const path = this.getPath(addr);
if (!path)
continue;
const key = this.derivePath(path);
keys.push(key);
}
return keys;
}
fund(mtx, options) {
const 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
});
}
template(mtx) {
const keys = this.deriveInputs(mtx);
mtx.template(keys);
}
sign(mtx) {
const keys = this.deriveInputs(mtx);
mtx.template(keys);
mtx.sign(keys);
}
async create(options) {
const mtx = new MTX(options);
await 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;
}
async send(options) {
const mtx = await this.create(options);
this.addTX(mtx.toTX());
return mtx;
}
}
MemWallet.prototype.fromOptions = function fromOptions(options) {
if (options.network != null) {
assert(options.network);
this.network = Network.get(options.network);
class Path {
constructor(hash, branch, index) {
this.hash = hash;
this.branch = branch;
this.index = index;
}
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() {
let i;
if (!this.master)
this.master = HD.PrivateKey.generate();
if (!this.key) {
const type = this.network.keyPrefix.coinType;
this.key = this.master.deriveAccount(44, type, this.account);
}
i = this.receiveDepth;
while (i--)
this.createReceive();
i = this.changeDepth;
while (i--)
this.createChange();
};
MemWallet.prototype.createReceive = function createReceive() {
const index = this.receiveDepth++;
const key = this.deriveReceive(index);
const hash = key.getHash('hex');
this.filter.add(hash, 'hex');
this.paths.set(hash, new Path(hash, 0, index));
this.receive = key;
return key;
};
MemWallet.prototype.createChange = function createChange() {
const index = this.changeDepth++;
const key = this.deriveChange(index);
const hash = key.getHash('hex');
this.filter.add(hash, 'hex');
this.paths.set(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) {
const type = this.network.keyPrefix.coinType;
let key = this.master.deriveAccount(44, type, this.account);
key = key.derive(branch).derive(index);
const ring = new KeyRing({
network: this.network,
privateKey: key.privateKey,
witness: this.witness
});
ring.witness = this.witness;
return ring;
};
MemWallet.prototype.getKey = function getKey(hash) {
const path = this.paths.get(hash);
if (!path)
return null;
return this.derivePath(path);
};
MemWallet.prototype.getPath = function getPath(hash) {
return this.paths.get(hash);
};
MemWallet.prototype.getCoin = function getCoin(key) {
return this.coins.get(key);
};
MemWallet.prototype.getUndo = function getUndo(key) {
return this.spent.get(key);
};
MemWallet.prototype.addCoin = function addCoin(coin) {
const op = new Outpoint(coin.hash, coin.index);
const key = op.toKey();
this.filter.add(op.toRaw());
this.spent.delete(key);
this.coins.set(key, coin);
this.balance += coin.value;
};
MemWallet.prototype.removeCoin = function removeCoin(key) {
const coin = this.coins.get(key);
if (!coin)
return;
this.spent.set(key, coin);
this.balance -= coin.value;
this.coins.delete(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() {
const coins = [];
for (const coin of this.coins.values())
coins.push(coin);
return 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) {
for (let i = 0; i < txs.length; i++) {
const tx = txs[i];
this.addTX(tx, entry.height);
}
};
MemWallet.prototype.removeBlock = function removeBlock(entry, txs) {
for (let i = txs.length - 1; i >= 0; i--) {
const tx = txs[i];
this.removeTX(tx, entry.height);
}
};
MemWallet.prototype.addTX = function addTX(tx, height) {
const hash = tx.hash('hex');
let result = false;
if (height == null)
height = -1;
if (this.map.has(hash))
return true;
for (let i = 0; i < tx.inputs.length; i++) {
const input = tx.inputs[i];
const op = input.prevout.toKey();
const coin = this.getCoin(op);
if (!coin)
continue;
result = true;
this.removeCoin(op);
}
for (let i = 0; i < tx.outputs.length; i++) {
const output = tx.outputs[i];
const addr = output.getHash('hex');
if (!addr)
continue;
const path = this.getPath(addr);
if (!path)
continue;
result = true;
const coin = Coin.fromTX(tx, i, height);
this.addCoin(coin);
this.syncKey(path);
}
if (result) {
this.txs++;
this.map.add(hash);
}
return result;
};
MemWallet.prototype.removeTX = function removeTX(tx, height) {
const hash = tx.hash('hex');
let result = false;
if (!this.map.has(hash))
return false;
for (let i = 0; i < tx.outputs.length; i++) {
const op = new Outpoint(hash, i).toKey();
const coin = this.getCoin(op);
if (!coin)
continue;
result = true;
this.removeCoin(op);
}
for (let i = 0; i < tx.inputs.length; i++) {
const input = tx.inputs[i];
const op = input.prevout.toKey();
const coin = this.getUndo(op);
if (!coin)
continue;
result = true;
this.addCoin(coin);
}
if (result)
this.txs--;
this.map.delete(hash);
return result;
};
MemWallet.prototype.deriveInputs = function deriveInputs(mtx) {
const keys = [];
for (let i = 0; i < mtx.inputs.length; i++) {
const input = mtx.inputs[i];
const coin = mtx.view.getOutputFor(input);
if (!coin)
continue;
const addr = coin.getHash('hex');
if (!addr)
continue;
const path = this.getPath(addr);
if (!path)
continue;
const key = this.derivePath(path);
keys.push(key);
}
return keys;
};
MemWallet.prototype.fund = function fund(mtx, options) {
const 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) {
const keys = this.deriveInputs(mtx);
mtx.template(keys);
};
MemWallet.prototype.sign = function sign(mtx) {
const keys = this.deriveInputs(mtx);
mtx.template(keys);
mtx.sign(keys);
};
MemWallet.prototype.create = async function create(options) {
const mtx = new MTX(options);
await 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 = async function send(options) {
const mtx = await 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;

View File

@ -5,119 +5,119 @@ const FullNode = require('../../lib/node/fullnode');
const Network = require('../../lib/protocol/network');
const Logger = require('blgr');
function NodeContext(network, size) {
if (!(this instanceof NodeContext))
return new NodeContext(network, size);
class NodeContext {
constructor(network, size) {
this.network = Network.get(network);
this.size = size || 4;
this.nodes = [];
this.network = Network.get(network);
this.size = size || 4;
this.nodes = [];
this.init();
};
NodeContext.prototype.init = function init() {
for (let i = 0; i < this.size; i++) {
const port = this.network.port + i;
let last = port - 1;
if (last < this.network.port)
last = port;
const node = new FullNode({
network: this.network,
db: 'memory',
logger: new Logger({
level: 'debug',
file: false,
console: false
}),
listen: true,
publicHost: '127.0.0.1',
publicPort: port,
httpPort: port + 100,
host: '127.0.0.1',
port: port,
seeds: [
`127.0.0.1:${last}`
]
});
node.on('error', (err) => {
node.logger.error(err);
});
this.nodes.push(node);
this.init();
}
};
NodeContext.prototype.open = function open() {
const jobs = [];
init() {
for (let i = 0; i < this.size; i++) {
const port = this.network.port + i;
for (const node of this.nodes)
jobs.push(node.open());
let last = port - 1;
return Promise.all(jobs);
};
if (last < this.network.port)
last = port;
NodeContext.prototype.close = function close() {
const jobs = [];
const node = new FullNode({
network: this.network,
db: 'memory',
logger: new Logger({
level: 'debug',
file: false,
console: false
}),
listen: true,
publicHost: '127.0.0.1',
publicPort: port,
httpPort: port + 100,
host: '127.0.0.1',
port: port,
seeds: [
`127.0.0.1:${last}`
]
});
for (const node of this.nodes)
jobs.push(node.close());
node.on('error', (err) => {
node.logger.error(err);
});
return Promise.all(jobs);
};
NodeContext.prototype.connect = async function connect() {
for (const node of this.nodes) {
await node.connect();
await new Promise(r => setTimeout(r, 1000));
this.nodes.push(node);
}
}
};
NodeContext.prototype.disconnect = async function disconnect() {
for (let i = this.nodes.length - 1; i >= 0; i--) {
const node = this.nodes[i];
await node.disconnect();
await new Promise(r => setTimeout(r, 1000));
open() {
const jobs = [];
for (const node of this.nodes)
jobs.push(node.open());
return Promise.all(jobs);
}
};
NodeContext.prototype.startSync = function startSync() {
for (const node of this.nodes) {
node.chain.synced = true;
node.chain.emit('full');
node.startSync();
close() {
const jobs = [];
for (const node of this.nodes)
jobs.push(node.close());
return Promise.all(jobs);
}
};
NodeContext.prototype.stopSync = function stopSync() {
for (const node of this.nodes)
node.stopSync();
};
NodeContext.prototype.generate = async function generate(index, blocks) {
const node = this.nodes[index];
assert(node);
for (let i = 0; i < blocks; i++) {
const block = await node.miner.mineBlock();
await node.chain.add(block);
async connect() {
for (const node of this.nodes) {
await node.connect();
await new Promise(r => setTimeout(r, 1000));
}
}
};
NodeContext.prototype.height = function height(index) {
const node = this.nodes[index];
async disconnect() {
for (let i = this.nodes.length - 1; i >= 0; i--) {
const node = this.nodes[i];
await node.disconnect();
await new Promise(r => setTimeout(r, 1000));
}
}
assert(node);
startSync() {
for (const node of this.nodes) {
node.chain.synced = true;
node.chain.emit('full');
node.startSync();
}
}
return node.chain.height;
};
stopSync() {
for (const node of this.nodes)
node.stopSync();
}
NodeContext.prototype.sync = async function sync() {
return new Promise(r => setTimeout(r, 3000));
};
async generate(index, blocks) {
const node = this.nodes[index];
assert(node);
for (let i = 0; i < blocks; i++) {
const block = await node.miner.mineBlock();
await node.chain.add(block);
}
}
height(index) {
const node = this.nodes[index];
assert(node);
return node.chain.height;
}
async sync() {
return new Promise(r => setTimeout(r, 3000));
}
}
module.exports = NodeContext;