bcoin: classify everything else.
This commit is contained in:
parent
bad24a6f31
commit
73664efcd0
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user