Skip to content

Commit f867cc1

Browse files
refactor(proxy): proxy blockchain accounts so that they are available in the Dapp
1 parent eb44a7d commit f867cc1

File tree

9 files changed

+278
-45
lines changed

9 files changed

+278
-45
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
"multihashes": "0.4.14",
132132
"neo-blessed": "0.2.0",
133133
"netcat": "1.3.5",
134+
"node-http-proxy-json": "0.1.6",
134135
"node-ipc": "9.1.1",
135136
"node-sass": "4.9.3",
136137
"npmlog": "4.1.2",

src/lib/modules/blockchain_process/blockchain.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,17 @@ Blockchain.prototype.initProxy = function () {
147147
};
148148

149149
Blockchain.prototype.setupProxy = async function () {
150+
const AccountParser = require('../../utils/accountParser');
150151
if (!this.proxyIpc) this.proxyIpc = new Ipc({ipcRole: 'client'});
151152

153+
const addresses = AccountParser.parseAccountsConfig(this.userConfig.accounts, false, this.logger);
154+
152155
let wsProxy;
153156
if (this.config.wsRPC) {
154-
wsProxy = proxy.serve(this.proxyIpc, this.config.wsHost, this.config.wsPort, true, this.config.wsOrigins, this.certOptions);
157+
wsProxy = proxy.serve(this.proxyIpc, this.config.wsHost, this.config.wsPort, true, this.config.wsOrigins, addresses, this.certOptions);
155158
}
156159

157-
[this.rpcProxy, this.wsProxy] = await Promise.all([proxy.serve(this.proxyIpc, this.config.rpcHost, this.config.rpcPort, false, undefined, this.certOptions), wsProxy]);
160+
[this.rpcProxy, this.wsProxy] = await Promise.all([proxy.serve(this.proxyIpc, this.config.rpcHost, this.config.rpcPort, false, null, addresses, this.certOptions), wsProxy]);
158161
};
159162

160163
Blockchain.prototype.shutdownProxy = function () {
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/* global require */
2+
3+
const http = require('http');
4+
const https = require('https');
5+
const httpProxyWsIncoming = require('http-proxy/lib/http-proxy/passes/ws-incoming');
6+
const common = require('http-proxy/lib/http-proxy/common');
7+
8+
const CRLF = '\r\n';
9+
10+
httpProxyWsIncoming.stream = (req, socket, options, head, server, cb) => {
11+
const createHttpHeader = function(line, headers) {
12+
return Object.keys(headers).reduce(function (head, key) {
13+
const value = headers[key];
14+
if (!Array.isArray(value)) {
15+
head.push(`${key}: ${value}`);
16+
return head;
17+
}
18+
for (let i = 0; i < value.length; i++) {
19+
head.push(`${key}: ${value[i]}`);
20+
}
21+
return head;
22+
}, [line])
23+
.join(CRLF) + `${CRLF}${CRLF}`;
24+
};
25+
26+
common.setupSocket(socket);
27+
28+
if (head && head.length) socket.unshift(head);
29+
30+
const protocol = common.isSSL.test(options.target.protocol) ? https : http;
31+
32+
const proxyReq = protocol.request(
33+
common.setupOutgoing(options.ssl || {}, options, req)
34+
);
35+
36+
// Enable developers to modify the proxyReq before headers are sent
37+
if (server) {
38+
server.emit('proxyReqWs', proxyReq, req, socket, options, head);
39+
}
40+
41+
// Error Handler
42+
proxyReq.on('error', onOutgoingError);
43+
proxyReq.on('response', function (res) {
44+
// if upgrade event isn't going to happen, close the socket
45+
if (!res.upgrade) {
46+
const {httpVersion, statusCode, statusMessage, headers} = res;
47+
socket.write(createHttpHeader(
48+
`HTTP/${httpVersion} ${statusCode} ${statusMessage}`,
49+
headers
50+
));
51+
res.pipe(socket);
52+
}
53+
});
54+
55+
proxyReq.on('upgrade', function(proxyRes, proxySocket, proxyHead) {
56+
proxySocket.on('error', onOutgoingError);
57+
58+
// Allow us to listen when the websocket has completed
59+
proxySocket.on('end', function () {
60+
server.emit('close', proxyRes, proxySocket, proxyHead);
61+
});
62+
63+
// The pipe below will end proxySocket if socket closes cleanly, but not
64+
// if it errors (eg, vanishes from the net and starts returning
65+
// EHOSTUNREACH). We need to do that explicitly.
66+
socket.on('error', function () {
67+
proxySocket.end();
68+
});
69+
70+
common.setupSocket(proxySocket);
71+
72+
if (proxyHead && proxyHead.length) proxySocket.unshift(proxyHead);
73+
74+
// Remark: Handle writing the headers to the socket when switching protocols
75+
// Also handles when a header is an array
76+
socket.write(createHttpHeader(
77+
'HTTP/1.1 101 Switching Protocols',
78+
proxyRes.headers
79+
));
80+
81+
let proxyStream = proxySocket;
82+
83+
if (options.createWsServerTransformStream) {
84+
const wsServerTransformStream = options.createWsServerTransformStream(
85+
req,
86+
proxyReq,
87+
proxyRes,
88+
);
89+
90+
wsServerTransformStream.on('error', onOutgoingError);
91+
proxyStream = proxyStream.pipe(wsServerTransformStream);
92+
}
93+
94+
proxyStream = proxyStream.pipe(socket);
95+
96+
if (options.createWsClientTransformStream) {
97+
const wsClientTransformStream = options.createWsClientTransformStream(
98+
req,
99+
proxyReq,
100+
proxyRes,
101+
);
102+
103+
wsClientTransformStream.on('error', onOutgoingError);
104+
proxyStream = proxyStream.pipe(wsClientTransformStream);
105+
}
106+
107+
proxyStream.pipe(proxySocket);
108+
109+
server.emit('open', proxySocket);
110+
server.emit('proxySocket', proxySocket); //DEPRECATED.
111+
});
112+
113+
return proxyReq.end(); // XXX: CHECK IF THIS IS THIS CORRECT
114+
115+
function onOutgoingError(err) {
116+
if (cb) {
117+
cb(err, req, socket);
118+
} else {
119+
server.emit('error', err, req, socket);
120+
}
121+
socket.end();
122+
}
123+
};

src/lib/modules/blockchain_process/proxy.js

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,30 @@
11
/* global Buffer __ exports require */
22

3+
require('./httpProxyOverride');
34
const Asm = require('stream-json/Assembler');
45
const {canonicalHost, defaultHost} = require('../../utils/host');
56
const constants = require('../../constants.json');
7+
const {Duplex} = require('stream');
68
const http = require('http');
79
const httpProxy = require('http-proxy');
810
const {parser: jsonParser} = require('stream-json');
911
const pump = require('pump');
1012
const utils = require('../../utils/utils');
1113
const WsParser = require('simples/lib/parsers/ws');
14+
const WsWrapper = require('simples/lib/ws/wrapper');
15+
const modifyResponse = require('node-http-proxy-json');
16+
17+
const METHODS_TO_MODIFY = {accounts: 'eth_accounts'};
18+
19+
function modifyPayload(toModifyPayloads, body, accounts) {
20+
switch (toModifyPayloads[body.id]) {
21+
case METHODS_TO_MODIFY.accounts:
22+
body.result = body.result.concat(accounts);
23+
break;
24+
default:
25+
}
26+
return body;
27+
}
1228

1329
const hex = (n) => {
1430
let _n = n.toString(16);
@@ -32,14 +48,18 @@ const parseJsonMaybe = (string) => {
3248
return object;
3349
};
3450

35-
exports.serve = async (ipc, host, port, ws, origin, certOptions={}) => {
51+
exports.serve = async (ipc, host, port, ws, origin, accounts, certOptions={}) => {
3652
const commList = {};
3753
const receipts = {};
3854
const transactions = {};
55+
const toModifyPayloads = {};
3956

4057
const trackRequest = (req) => {
4158
if (!req) return;
4259
try {
60+
if (Object.values(METHODS_TO_MODIFY).includes(req.method)) {
61+
toModifyPayloads[req.id] = req.method;
62+
}
4363
if (req.method === 'eth_sendTransaction') {
4464
commList[req.id] = {
4565
type: 'contract-log',
@@ -121,7 +141,32 @@ exports.serve = async (ipc, host, port, ws, origin, certOptions={}) => {
121141
host: canonicalHost(host),
122142
port: port
123143
},
124-
ws: ws
144+
ws: ws,
145+
createWsServerTransformStream: (_req, _proxyReq, _proxyRes) => {
146+
const parser = new WsParser(0, true);
147+
parser.on('frame', ({data: buffer}) => {
148+
let object = parseJsonMaybe(buffer.toString());
149+
if (object) {
150+
object = modifyPayload(toModifyPayloads, object, accounts);
151+
// track the modified response
152+
trackResponse(object);
153+
// send the modified response
154+
WsWrapper.wrap(
155+
{connection: dupl, masked: 0},
156+
Buffer.from(JSON.stringify(object)),
157+
() => {}
158+
);
159+
}
160+
});
161+
const dupl = new Duplex({
162+
read(_size) {},
163+
write(chunk, encoding, callback) {
164+
parser.write(chunk);
165+
callback();
166+
}
167+
});
168+
return dupl;
169+
}
125170
});
126171

127172
proxy.on('error', (err) => {
@@ -131,15 +176,14 @@ exports.serve = async (ipc, host, port, ws, origin, certOptions={}) => {
131176
);
132177
});
133178

134-
proxy.on('proxyRes', (proxyRes, req, _res) => {
135-
if (req.method === 'POST') {
136-
// messages FROM the target
137-
Asm.connectTo(
138-
pump(proxyRes, jsonParser())
139-
).on('done', ({current: object}) => {
140-
trackResponse(object);
141-
});
142-
}
179+
proxy.on('proxyRes', (proxyRes, req, res) => {
180+
modifyResponse(res, proxyRes, (body) => {
181+
if (body) {
182+
body = modifyPayload(toModifyPayloads, body, accounts);
183+
trackResponse(body);
184+
}
185+
return body;
186+
});
143187
});
144188

145189
const server = http.createServer((req, res) => {
@@ -162,13 +206,7 @@ exports.serve = async (ipc, host, port, ws, origin, certOptions={}) => {
162206
proxy.ws(msg, socket, head);
163207
});
164208

165-
proxy.on('open', (proxySocket) => {
166-
// messages FROM the target
167-
pump(proxySocket, new WsParser(0, true)).on('frame', ({data: buffer}) => {
168-
const object = parseJsonMaybe(buffer.toString());
169-
trackResponse(object);
170-
});
171-
});
209+
proxy.on('open', (_proxySocket) => { /* messages FROM the target */ });
172210

173211
proxy.on('proxyReqWs', (_proxyReq, _req, socket) => {
174212
// messages TO the target

src/lib/modules/blockchain_process/simulator.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class Simulator {
6767
}
6868

6969
runCommand(cmds, useProxy, host, port) {
70-
const ganache_main = require.resolve('ganache-cli', {paths: fs.embarkPath('node_modules')});
70+
const ganache_main = require.resolve('ganache-cli', {paths: [fs.embarkPath('node_modules')]});
7171
const ganache_json = pkgUp.sync(path.dirname(ganache_main));
7272
const ganache_root = path.dirname(ganache_json);
7373
const ganache_bin = require(ganache_json).bin;

src/lib/modules/code_generator/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ class CodeGenerator {
290290
function getWeb3Location(next) {
291291
self.events.request("version:get:web3", function(web3Version) {
292292
if (web3Version === "1.0.0-beta") {
293-
return next(null, require.resolve("web3", {paths: fs.embarkPath("node_modules")}));
293+
return next(null, require.resolve("web3", {paths: [fs.embarkPath("node_modules")]}));
294294
}
295295
self.events.request("version:getPackageLocation", "web3", web3Version, function(err, location) {
296296
return next(null, fs.dappPath(location));
@@ -356,7 +356,7 @@ class CodeGenerator {
356356
function getWeb3Location(next) {
357357
self.events.request("version:get:web3", function(web3Version) {
358358
if (web3Version === "1.0.0-beta") {
359-
return next(null, require.resolve("web3", {paths: fs.embarkPath("node_modules")}));
359+
return next(null, require.resolve("web3", {paths: [fs.embarkPath("node_modules")]}));
360360
}
361361
self.events.request("version:getPackageLocation", "web3", web3Version, function(err, location) {
362362
return next(null, fs.dappPath(location));

src/lib/utils/accountParser.js

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class AccountParser {
1111
let accounts = [];
1212
if (accountsConfig && accountsConfig.length) {
1313
accountsConfig.forEach(accountConfig => {
14-
const account = AccountParser.getAccount(accountConfig, web3, logger, nodeAccounts);
14+
let account = AccountParser.getAccount(accountConfig, web3, logger, nodeAccounts);
1515
if (!account) {
1616
return;
1717
}
@@ -25,23 +25,30 @@ class AccountParser {
2525
return accounts;
2626
}
2727

28+
/*eslint complexity: ["error", 30]*/
2829
static getAccount(accountConfig, web3, logger = console, nodeAccounts) {
30+
const {utils} = require('web3');
31+
const returnAddress = web3 === false;
2932
let hexBalance = null;
30-
if (accountConfig.balance) {
33+
if (accountConfig.balance && web3) {
3134
hexBalance = getHexBalanceFromString(accountConfig.balance, web3);
3235
}
3336

3437
if (accountConfig.privateKey === 'random') {
38+
if (!web3) {
39+
logger.warn('Cannot use random in this context');
40+
return null;
41+
}
3542
let randomAccount = web3.eth.accounts.create();
3643
accountConfig.privateKey = randomAccount.privateKey;
3744
}
3845

3946
if (accountConfig.nodeAccounts) {
40-
if (!nodeAccounts) {
47+
if (!nodeAccounts && !returnAddress) {
4148
logger.warn('Cannot use nodeAccounts in this context');
4249
return null;
4350
}
44-
if (!nodeAccounts.length) {
51+
if (!nodeAccounts || !nodeAccounts.length) {
4552
return null;
4653
}
4754

@@ -54,10 +61,13 @@ class AccountParser {
5461
if (!accountConfig.privateKey.startsWith('0x')) {
5562
accountConfig.privateKey = '0x' + accountConfig.privateKey;
5663
}
57-
if (!web3.utils.isHexStrict(accountConfig.privateKey)) {
64+
if (!utils.isHexStrict(accountConfig.privateKey)) {
5865
logger.warn(`Private key ending with ${accountConfig.privateKey.substr(accountConfig.privateKey.length - 5)} is not a HEX string`);
5966
return null;
6067
}
68+
if (returnAddress) {
69+
return ethereumjsWallet.fromPrivateKey(accountConfig.privateKey).getChecksumAddressString();
70+
}
6171
return Object.assign(web3.eth.accounts.privateKeyToAccount(accountConfig.privateKey), {hexBalance});
6272
}
6373

@@ -73,6 +83,9 @@ class AccountParser {
7383
}
7484
const wallet = ethereumjsWallet['fromV' + fileContent.version](fileContent, accountConfig.password);
7585

86+
if (returnAddress) {
87+
return wallet.getChecksumAddressString();
88+
}
7689
return Object.assign(web3.eth.accounts.privateKeyToAccount('0x' + wallet.getPrivateKey().toString('hex')), {hexBalance});
7790
} catch (e) {
7891
logger.error('Private key file is not a keystore JSON file but a password was provided');
@@ -86,10 +99,13 @@ class AccountParser {
8699
if (!key.startsWith('0x')) {
87100
key = '0x' + key;
88101
}
89-
if (!web3.utils.isHexStrict(key)) {
102+
if (!utils.isHexStrict(key)) {
90103
logger.warn(`Private key is not a HEX string in file ${accountConfig.privateKeyFile} at index ${index}`);
91104
return null;
92105
}
106+
if (returnAddress) {
107+
return ethereumjsWallet.fromPrivateKey(key).getChecksumAddressString();
108+
}
93109
return Object.assign(web3.eth.accounts.privateKeyToAccount(key), {hexBalance});
94110
});
95111
}
@@ -104,7 +120,11 @@ class AccountParser {
104120
const accounts = [];
105121
for (let i = addressIndex; i < addressIndex + numAddresses; i++) {
106122
const wallet = hdwallet.derivePath(wallet_hdpath + i).getWallet();
107-
accounts.push(Object.assign(web3.eth.accounts.privateKeyToAccount('0x' + wallet.getPrivateKey().toString('hex')), {hexBalance}));
123+
if (returnAddress) {
124+
accounts.push(wallet.getAddressString());
125+
} else {
126+
accounts.push(Object.assign(web3.eth.accounts.privateKeyToAccount('0x' + wallet.getPrivateKey().toString('hex')), {hexBalance}));
127+
}
108128
}
109129
return accounts;
110130
}

0 commit comments

Comments
 (0)