-
Notifications
You must be signed in to change notification settings - Fork 493
/
provider.js
189 lines (164 loc) · 6.74 KB
/
provider.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import { __ } from 'embark-i18n';
const async = require('async');
const { AccountParser, dappPath } = require('embark-utils');
const fundAccount = require('./fundAccount');
const constants = require('embark-core/constants');
const Transaction = require('ethereumjs-tx');
const ethUtil = require('ethereumjs-util');
class Provider {
constructor(options) {
this.web3 = options.web3;
this.accountsConfig = options.accountsConfig;
this.blockchainConfig = options.blockchainConfig;
this.type = options.type;
this.web3Endpoint = options.web3Endpoint;
this.logger = options.logger;
this.isDev = options.isDev;
this.events = options.events;
this.nonceCache = {};
this.events.setCommandHandler("blockchain:provider:contract:accounts:get", cb => {
const accounts = this.accounts.map(a => a.address || a);
cb(null, accounts);
});
this.events.setCommandHandler("blockchain:provider:contract:accounts:getAll", (cb) => {
cb(null, this.accounts);
});
}
getNonce(address, callback) {
this.web3.eth.getTransactionCount(address, (_error, transactionCount) => {
if(this.nonceCache[address] === undefined) {
this.nonceCache[address] = -1;
}
if (transactionCount > this.nonceCache[address]) {
this.nonceCache[address] = transactionCount;
return callback(this.nonceCache[address]);
}
this.nonceCache[address]++;
callback(this.nonceCache[address]);
});
}
startWeb3Provider(callback) {
const self = this;
if (this.type === 'rpc') {
self.provider = new this.web3.providers.HttpProvider(self.web3Endpoint);
} else if (this.type === 'ws') {
// Note: don't pass to the provider things like {headers: {Origin: "embark"}}. Origin header is for browser to fill
// to protect user, it has no meaning if it is used server-side. See here for more details: https://github.com/ethereum/go-ethereum/issues/16608
// Moreover, Parity reject origins that are not urls so if you try to connect with Origin: "embark" it gives the following error:
// << Blocked connection to WebSockets server from untrusted origin: Some("embark") >>
// The best choice is to use void origin, BUT Geth rejects void origin, so to keep both clients happy we can use http://embark
self.provider = new this.web3.providers.WebsocketProvider(self.web3Endpoint, {
headers: {Origin: constants.embarkResourceOrigin},
// TODO remove this when Geth fixes this: https://github.com/ethereum/go-ethereum/issues/16846
clientConfig: {
fragmentationThreshold: 81920
}
});
self.provider.on('error', () => self.logger.error('Websocket Error'));
self.provider.on('end', () => self.logger.error('Websocket connection ended'));
} else {
return callback(__("contracts config error: unknown deployment type %s", this.type));
}
self.web3.setProvider(self.provider);
self.web3.eth.getAccounts((err, accounts = []) => {
if (err) {
self.logger.warn('Error while getting the node\'s accounts.', err.message || err);
}
self.blockchainAccounts = AccountParser.parseAccountsConfig(self.blockchainConfig.accounts, self.web3, dappPath(), self.logger, accounts);
accounts = accounts.concat(self.blockchainAccounts);
self.accounts = AccountParser.parseAccountsConfig(self.accountsConfig, self.web3, dappPath(), self.logger, accounts);
if (!self.accounts.length) {
self.accounts = accounts;
}
self.addresses = [];
self.accounts.forEach(account => {
self.addresses.push(account.address || account);
if (account.privateKey) {
self.web3.eth.accounts.wallet.add(account);
}
});
self.addresses = [...new Set(self.addresses)]; // Remove duplicates
if (self.accounts.length) {
self.web3.eth.defaultAccount = self.addresses[0];
}
const realSend = self.provider.send.bind(self.provider);
// Allow to run transaction in parallel by resolving
// the nonce manually.
// For each transaction, resolve the nonce by taking the
// max of current transaction count and the cache we keep
// locally.
// Deconstruct the transaction and update the nonce.
// Before updating the transaction, it must be signed.
self.runTransaction = async.queue(({payload}, callback) => {
const rawTx = payload.params[0];
const rawData = Buffer.from(ethUtil.stripHexPrefix(rawTx), 'hex');
const tx = new Transaction(rawData, 'hex');
const address = '0x' + tx.getSenderAddress().toString('hex').toLowerCase();
self.getNonce(address, (newNonce) => {
tx.nonce = newNonce;
const key = ethUtil.stripHexPrefix(self.web3.eth.accounts.wallet[address].privateKey);
const privKey = Buffer.from(key, 'hex');
tx.sign(privKey);
payload.params[0] = '0x' + tx.serialize().toString('hex');
return realSend(payload, (error, result) => {
self.web3.eth.getTransaction(result.result, () => {
callback(error, result);
});
});
});
}, 1);
self.provider.send = function(payload, cb) {
if (payload.method === 'eth_accounts') {
return realSend(payload, function(err, result) {
if (err) {
return cb(err);
}
if (self.accounts.length) {
result.result = self.addresses;
}
cb(null, result);
});
} else if (payload.method === constants.blockchain.transactionMethods.eth_sendRawTransaction) {
return self.runTransaction.push({payload}, cb);
}
realSend(payload, cb);
};
callback();
});
}
connected() {
if (this.type === 'rpc') {
return !!this.provider;
} else if (this.type === 'ws') {
return this.provider && this.provider.connection._connection && this.provider.connection._connection.connected;
}
return false;
}
stop() {
if (this.provider && this.provider.removeAllListeners) {
this.provider.removeAllListeners('connect');
this.provider.removeAllListeners('error');
this.provider.removeAllListeners('end');
this.provider.removeAllListeners('data');
this.provider.responseCallbacks = {};
}
this.provider = null;
this.web3.setProvider(null);
}
fundAccounts(callback) {
const self = this;
if (!self.accounts.length) {
return callback();
}
if (!self.isDev) {
return callback();
}
async.eachLimit(self.accounts, 1, (account, eachCb) => {
if (!account.address) {
return eachCb();
}
fundAccount(self.web3, account.address, account.hexBalance, eachCb);
}, callback);
}
}
module.exports = Provider;