-
Notifications
You must be signed in to change notification settings - Fork 52
/
hooked-web3-provider.es6
156 lines (131 loc) · 5.46 KB
/
hooked-web3-provider.es6
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
var factory = function(Web3) {
class HookedWeb3Provider extends Web3.providers.HttpProvider {
constructor({host, transaction_signer}) {
super(host);
this.transaction_signer = transaction_signer;
// Cache of the most up to date transaction counts (nonces) for each address
// encountered by the web3 provider that's managed by the transaction signer.
this.global_nonces = {};
}
// We can't support *all* synchronous methods because we have to call out to
// a transaction signer. So removing the ability to serve any.
send(payload, callback) {
var requests = payload;
if (!(requests instanceof Array)) {
requests = [requests];
}
for (var request of requests) {
if (request.method == "eth_sendTransaction") {
throw new Error("HookedWeb3Provider does not support synchronous transactions. Please provide a callback.")
}
}
var finishedWithRewrite = () => {
return super.send(payload, callback);
};
return this.rewritePayloads(0, requests, {}, finishedWithRewrite);
}
// Catch the requests at the sendAsync level, rewriting all sendTransaction
// methods to sendRawTransaction, calling out to the transaction_signer to
// get the data for sendRawTransaction.
sendAsync(payload, callback) {
var finishedWithRewrite = () => {
super.sendAsync(payload, callback);
};
var requests = payload;
if (!(payload instanceof Array)) {
requests = [payload];
}
this.rewritePayloads(0, requests, {}, finishedWithRewrite);
}
// Rewrite all eth_sendTransaction payloads in the requests array.
// This takes care of batch requests, and updates the nonces accordingly.
rewritePayloads(index, requests, session_nonces, finished) {
if (index >= requests.length) {
return finished();
}
var payload = requests[index];
// Function to remove code duplication for going to the next payload
var next = (err) => {
if (err != null) {
return finished(err);
}
return this.rewritePayloads(index + 1, requests, session_nonces, finished);
};
// If this isn't a transaction we can modify, ignore it.
if (payload.method != "eth_sendTransaction") {
return next();
}
var tx_params = payload.params[0];
var sender = tx_params.from;
this.transaction_signer.hasAddress(sender, (err, has_address) => {
if (err != null || has_address == false) {
return next(err);
}
// Get the nonce, requesting from web3 if we haven't already requested it in this session.
// Remember: "session_nonces" is the nonces we know about for this batch of rewriting (this "session").
// Having this cache makes it so we only need to call getTransactionCount once per batch.
// "global_nonces" is nonces across the life of this provider.
var getNonce = (done) => {
// If a nonce is specified in our nonce list, use that nonce.
var nonce = session_nonces[sender];
if (nonce != null) {
done(null, nonce);
} else {
// Include pending transactions, so the nonce is set accordingly.
// Note: "pending" doesn't seem to take effect for some Ethereum clients (geth),
// hence the need for global_nonces.
// We call directly to our own sendAsync method, because the web3 provider
// is not guaranteed to be set.
this.sendAsync({
jsonrpc: '2.0',
method: 'eth_getTransactionCount',
params: [sender, "pending"],
id: (new Date()).getTime()
}, function(err, result) {
if (err != null) {
done(err);
} else {
var new_nonce = result.result;
done(null, Web3.prototype.toDecimal(new_nonce));
}
});
}
};
// Get the nonce, requesting from web3 if we need to.
// We then store the nonce and update it so we don't have to
// to request from web3 again.
getNonce((err, nonce) => {
if (err != null) {
return finished(err);
}
// Set the expected nonce, and update our caches of nonces.
// Note that if our session nonce is lower than what we have cached
// across all transactions (and not just this batch) use our cached
// version instead, even if
var final_nonce = Math.max(nonce, this.global_nonces[sender] || 0);
// Update the transaction parameters.
tx_params.nonce = Web3.prototype.toHex(final_nonce);
// Update caches.
session_nonces[sender] = final_nonce + 1;
this.global_nonces[sender] = final_nonce + 1;
// If our transaction signer does represent the address,
// sign the transaction ourself and rewrite the payload.
this.transaction_signer.signTransaction(tx_params, function(err, raw_tx) {
if (err != null) {
return next(err);
}
payload.method = "eth_sendRawTransaction";
payload.params = [raw_tx];
return next();
});
});
});
}
}
return HookedWeb3Provider;
};
if (typeof module !== 'undefined') {
module.exports = factory(require("web3"));
} else {
window.HookedWeb3Provider = factory(Web3);
}