-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathjson-rpc.ss
603 lines (528 loc) · 23.7 KB
/
json-rpc.ss
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
;; Ethereum JSON-RPC API
;;
;; The reference documentation for the Ethereum JSON RPC protocol is now at:
;; https://eth.wiki/json-rpc/API
;; We support all non-deprecated methods in the standard protocol as of 2020-07-05,
;;
;; Geth extensions are documented here:
;; https://geth.ethereum.org/docs/rpc/server
;; We only support a few of the Geth extensions.
;;
;; The OpenEthereum implementation by Parity has its JSON RPC documentation here:
;; https://openethereum.wiki/JSONRPC/
;;
;; Infura documents its API at:
;; https://infura.io/docs/ethereum/json-rpc/eth-chainId
;;
;; TODO: compare with https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/etclabscore/ethereum-json-rpc-specification/master/openrpc.json&uiSchema%5BappBar%5D%5Bui:input%5D=false
;; https://raw.githubusercontent.com/etclabscore/ethereum-json-rpc-specification/master/openrpc.json
;;
;; TODO:
;; - Resolve all the dark spots.
;; - Systematically lift all the Geth extensions.
;; - Add plenty of tests everywhere.
;; - Support multiple eth-like networks from a same client.
(export #t)
(import
:gerbil/gambit
(for-syntax :std/format :std/stxutil)
:std/format :std/lazy :std/net/json-rpc :std/sugar
:clan/base :clan/concurrency :clan/io :clan/json :clan/logger :clan/failure :clan/hash
:clan/maybe :clan/option :clan/string :clan/syntax
:clan/poo/object :clan/poo/brace :clan/poo/io
:clan/crypto/secp256k1
./types ./ethereum ./network-config ./logger)
;; We use a mutex for access to the ethereum node, not to overload it and get timeouts.
;; TODO: Have a pool of a small number of connections to the node rather than just one.
(def ethereum-mutex (make-mutex 'ethereum))
(def (ethereum-json-rpc method-name result-decoder param-encoder
log: (log eth-log) url: (url (ethereum-url))
params)
(with-lock ethereum-mutex
(cut json-rpc url method-name params
result-decoder: result-decoder
param-encoder: param-encoder
headers: '(("Content-Type" . "application/json-rpc"))
log: log)))
(defsyntax (define-ethereum-api stx)
(syntax-case stx (<-)
((_ namespace method result-type <- argument-type ...)
(let*-values (((method-name method-formals args-vector)
(syntax-case #'method ()
((name . formals)
(values #'name #'formals (call<-formals #'(vector) #'formals)))
(name
(let* ((n (length (syntax->datum #'(argument-type ...))))
(vars (formals<-nat n)))
(values #'name vars (cons 'vector vars))))))
((fun-id) (stx-identifier (stx-car stx) #'namespace "_" method-name))
((method-string) (as-string (syntax->datum fun-id))))
(with-syntax (((formals ...) method-formals)
(args-vector args-vector)
(method-string method-string)
(fun-id fun-id))
#'(begin
(def params-type (Tuple argument-type ...))
(def (fun-id log: (log eth-log) url: (url (ethereum-url)) formals ...)
(ethereum-json-rpc method-string
(.@ result-type .<-json)
(.@ params-type .json<-) args-vector
log: log url: url))))))))
(define-ethereum-api web3 clientVersion
String <-)
(define-ethereum-api web3 sha3
Bytes32 <- Bytes) ;; keccak256
(define-ethereum-api net version
String <-) ;; a decimal number in a String
(define-ethereum-api net listening
Bool <-)
(define-ethereum-api net peerCount
Quantity <-)
(define-ethereum-api eth protocolVersion
String <-) ;; a decimal number
(define-type SyncingStatus
(Record startingBlock: [Quantity]
currentBlock: [Quantity]
highestBlock: [Quantity]))
(define-ethereum-api eth syncing
(OrFalse SyncingStatus) <-)
(define-ethereum-api eth coinbase
Address <-)
(define-ethereum-api eth mining
Bool <-)
(define-ethereum-api eth hashrate
Quantity <-)
(define-type BlockParameter
(Union
Quantity ;; block number as 0x string. In practice, should fit 32-bit
(Enum latest earliest pending)))
(define-type TransactionCondition
(Union
(Record block: [Quantity]) ;; block number as 0x string.
(Record time: [Quantity]) ;; time in seconds-since-epoch as 0x string
Unit)) ;; JSON null, isomorphic to unit, but its own thing for faithful FFI purposes. (void) in Gerbil.
;; TODO: Implement Record inheritance, and have it be just Transaction plus a condition
(define-type TransactionParameters
(Record
from: [Address]
to: [(Maybe Address) optional: #t default: (void)]
gas: [(Maybe Quantity) optional: #t default: (void)] ; in gas
gasPrice: [(Maybe Quantity) optional: #t default: (void)] ; in wei/gas
value: [(Maybe Quantity) optional: #t default: (void)] ; in wei
data: [(Maybe Bytes) optional: #t default: (void)]
nonce: [(Maybe Quantity) optional: #t default: (void)]
condition: [(Maybe TransactionCondition) optional: #t default: (void)]))
(def (TransactionParameters<-PreTransaction tx)
(def-slots (from to data value nonce gas gasPrice) tx)
{from to data value nonce gas gasPrice condition: (void)})
(define-type TransactionInformation
(Record
hash: [Digest]
nonce: [Quantity]
blockHash: [(Maybe Digest) optional: #t default: (void)]
blockNumber: [(Maybe Quantity) optional: #t default: (void)]
transactionIndex: [(Maybe Quantity) optional: #t default: (void)]
from: [(Maybe Address) optional: #t default: (void)]
to: [(Maybe Address) optional: #t default: (void)]
value: [Quantity]
gasPrice: [Quantity]
gas: [Quantity]
input: [Bytes] ;; TODO: make sure *we* are always translating input: to/from data: where needed
v: [(Maybe Quantity) optional: #t default: (void)]
standard-v: [(Maybe Quantity) optional: #t default: (void)]
r: [(Maybe UInt256) optional: #t default: (void)]
s: [(Maybe UInt256) optional: #t default: (void)]
raw: [(Maybe Data) optional: #t default: (void)]
publicKey: [(Maybe PublicKey) optional: #t default: (void)]
networkID: [(Maybe Quantity) optional: #t default: (void)]
creates: [(Maybe Digest) optional: #t default: (void)]
condition: [Json optional: #t default: (void)])) ;; is this any JSON, or a TransactionCondition above??
(define-type SignTransactionResult
(Record
data: [Bytes]
signed: [TransactionInformation]))
(define-type Bloom Bytes256)
(define-type LogObject
(Record
removed: [(Maybe Bool) optional: #t default: (void)] ;; not present on Mantis (at least outside eth_newFilter)
logIndex: [(Maybe Quantity) optional: #t default: (void)]
transactionIndex: [(Maybe Quantity) optional: #t default: (void)]
transactionHash: [(Maybe Digest) optional: #t default: (void)]
blockNumber: [(Maybe Quantity) optional: #t default: (void)]
blockHash: [(Maybe Digest) optional: #t default: (void)]
address: [Address]
data: [Bytes]
topics: [(List Bytes32)]))
(define-type LogObjectList (List LogObject))
(define-type TransactionReceipt
(Record
blockHash: [Digest]
blockNumber: [Quantity]
contractAddress: [(Maybe Address) optional: #t default: (void)]
cumulativeGasUsed: [Quantity]
from: [(Maybe Address) optional: #t default: (void)] ;; in geth, not in mantis
to: [(Maybe Address) optional: #t default: (void)] ;; in geth, not in mantis
gasUsed: [Quantity]
logs: [LogObjectList]
logsBloom: [(Maybe Bloom) optional: #t default: (void)] ;; in geth, not mantis
status: [(Maybe Quantity) optional: #t default: (void)] ;; in geth, not mantis: 1 success, 0 failure
statusCode: [(Maybe Quantity) optional: #t default: (void)] ;; in mantis, not geth: 0 success, >0 error code
transactionHash: [Digest]
transactionIndex: [Quantity]
returnData: [(Maybe Data) optional: #t default: (void)])) ;; in mantis, not geth.
;; Mantis statusCode decoding:
;; 0x00: success
;; 0x01: function does not exist
;; 0x02: function has wrong signature
;; 0x03: function does not exist on empty account
;; 0x04: execution of instructions led to failure
;; 0x05: out of gas
;; 0x06: deploying to an account that already exists
;; 0x07: insufficient balance to transfer
;; 0x08: negative balance or gas limit or call depth exceeded
;; 0x09: contract being uploaded to blockchain is not well formed
(def (Confirmation<-TransactionReceipt tr)
(def-slots (transactionHash transactionIndex blockNumber blockHash status) tr)
(if (zero? status)
(error "receipt indicates transaction failed" transactionHash)
{transactionHash transactionIndex blockNumber blockHash})) ;; Confirmation
;; Returns a list of address owned by the client
(define-ethereum-api eth accounts
(List Address) <-)
;; same as Transaction, but without nonce
(define-type CallParameters
(Record
from: [Address]
to: [(Maybe Address) optional: #t default: (void)]
gas: [(Maybe Quantity) optional: #t default: (void)]
gasPrice: [(Maybe Quantity) optional: #t default: (void)]
value: [(Maybe Quantity) optional: #t default: (void)]
data: [(Maybe Bytes) optional: #t default: (void)]))
(define-type StateOverrideSet ;; contract data to override before executing the call
(Record
balance: [Quantity optional: #t]
nonce: [Quantity optional: #t]
code: [Bytes optional: #t]
state: [(Map Bytes32 <- Quantity) optional: #t] ;; keys are 0x quantity
stateDiff: [(Map Bytes32 <- Quantity) optional: #t])) ;; override individual slots in account storage
;; TODO: Geth has an optional third parameter StateOverrideSet
;; TODO: Make sure this function works both on geth vs mantis, or provide two functions?
(define-ethereum-api eth (call params (block 'latest) (state-override-set (void)))
Data <- CallParameters BlockParameter (Maybe StateOverrideSet))
(define-ethereum-api eth chainId
(Maybe UInt256) <-)
;; SignedTransactionData + {hash}
(define-type SignedTx
(Record
nonce: [Quantity]
gasPrice: [Quantity]
gas: [Quantity]
to: [(Maybe Address) optional: #t default: (void)]
value: [Quantity]
input: [Bytes]
v: [UInt256] ;; why did an earlier version of our code indicate v, r, s as optional???
r: [UInt256]
s: [UInt256]
hash: [Digest]))
(define-type SignedTransaction
(Record
raw: [Bytes]
tx: [SignedTx]))
;; Returns estimate of gas needed for transaction
(define-ethereum-api eth estimateGas
Quantity <- TransactionParameters)
;; Get the current gas price in wei
(define-ethereum-api eth gasPrice
Quantity <-)
;; Returns the balance of the account of given address (and block)
(define-ethereum-api eth (getBalance address (block 'latest))
Quantity <- Address BlockParameter)
;; Returns the content of storage in given contract at given memory position, given block
(define-ethereum-api eth (getStorageAt address position (block 'latest))
Bytes32 <- Address Quantity BlockParameter)
;; Returns the code of given address (and block)
(define-ethereum-api eth (getCode contract-address (block 'latest))
Bytes <- Address BlockParameter)
;; Returns a transaction by the hash code
(define-ethereum-api eth getTransactionByHash
(Maybe TransactionInformation) <- Digest)
;; Returns a transaction by block hash and transaction index position
(define-ethereum-api eth getTransactionByBlockHashAndIndex
TransactionInformation <- Digest Quantity)
;; Returns a transaction by block height and transaction index position
(define-ethereum-api eth getTransactionByBlockNumberAndIndex
TransactionInformation <- BlockParameter Quantity)
;; Returns the number of transaction at address (and transaction)
(define-ethereum-api eth (getTransactionCount address (block 'latest))
Quantity <- Address BlockParameter)
;; Returns the number of transactions in a block found by its hash code
(define-ethereum-api eth getTransactionCountByHash
Quantity <- Digest)
;; Returns the number of transactions in a block found by its height
(define-ethereum-api eth getTransactionCountByNumber
Quantity <- BlockParameter)
;; Returns the number of uncles in a block found by its hash
(define-ethereum-api eth getUncleCountByBlockHash
Quantity <- Digest)
;; Returns the number of uncles in a block found by its height
(define-ethereum-api eth getUncleCountByNumber
Quantity <- BlockParameter)
;; Returns uncle information
(define-ethereum-api eth getUncleByBlockHashAndIndex
BlockInformation <- Digest Quantity)
(define-ethereum-api eth getUncleByBlockNumberAndIndex
BlockInformation <- BlockParameter Quantity)
;; Returns a receipt of transaction by transaction hash (not available if transaction still pending)
(define-ethereum-api eth getTransactionReceipt
(Maybe TransactionReceipt) <- Digest)
;; Create new message call transaction or a contract creation for signed transaction
(define-ethereum-api eth sendRawTransaction
Digest <- Data)
;; NB: Not to be used in our code, it's too flaky wrt various attacks.
;; Creates new message call transaction or a contract creation if the datafield contains code.
(define-ethereum-api eth sendTransaction
Digest <- TransactionParameters)
;; Computes an eth signature of (eth-sign-prefix message)
(define-ethereum-api eth sign
Data <- Address Data)
;; : Data <- Data
(def (eth-sign-prefix message)
(u8vector-append (string->bytes "\x19;Ethereum Signed Message:\n")
(string->bytes (number->string (u8vector-length message)))
message))
;; This is the thing specified (and used?) by Geth:
(define-ethereum-api eth signTransaction
Bytes <- TransactionParameters)
;; However, parity's OpenEthereum documents this richer return type:
;;(define-ethereum-api eth signTransaction SignTransactionResult <- TransactionParameters)
;; And it's not supported by Mantis.
(define-type BlockInformation
(Record number: [(Maybe Quantity)]
hash: [(or Digest (void))]
parentHash: [Digest]
nonce: [(Maybe Bytes8)]
sha3Uncles: [Digest]
logsBloom: [Bloom]
transactionsRoot: [Digest]
stateRoot: [Digest]
receiptsRoot: [Digest]
miner: [Address]
difficulty: [Quantity]
totalDifficulty: [Quantity]
extraData: [Bytes]
size: [Quantity]
gasLimit: [Quantity]
gasUsed: [Quantity]
timestamp: [Quantity] ;; unix timestamp
transactions: [(Or (List TransactionInformation) (List Digest))]
gasUsed: [Quantity]
uncles: [(List Digest)]))
;; boolean: true for full tx objects, false for txhashes only
(define-ethereum-api eth getBlockByHash
(Maybe BlockInformation) <- Digest Bool)
(define-ethereum-api eth getBlockByNumber
(Maybe BlockInformation) <- BlockParameter Bool)
(define-ethereum-api eth blockNumber
Quantity <-)
(define-type newFilterOptions ;; for newFilter
(Record fromBlock: [BlockParameter optional: #t default: 'latest]
toBlock: [BlockParameter optional: #t default: 'latest]
address: [(Or Address (List Address) Unit) optional: #t default: (void)]
topics: [(Maybe (List (Maybe (Or Bytes32 (List Bytes32))))) optional: #t default: (void)]))
(define-type getLogsFilterOptions ;; for getLogs
(Record fromBlock: [(Maybe BlockParameter) optional: #t default: 'latest]
toBlock: [(Maybe BlockParameter) optional: #t default: 'latest]
address: [(Or Address (List Address) Unit) optional: #t default: (void)]
topics: [(Maybe (List (Or Bytes32 Unit (List Bytes32)))) optional: #t default: (void)]
blockhash: [(Maybe Digest) optional: #t default: (void)]))
(define-ethereum-api eth newFilter
Quantity <- newFilterOptions)
(define-ethereum-api eth newBlockFilter
Quantity <-)
(define-ethereum-api eth newPendingTransactionFilter
Quantity <-)
(define-ethereum-api eth uninstallFilter
Bool <- Quantity)
(define-ethereum-api eth getFilterChanges
(Or (List Digest) ;; for newBlockFilter (block hashes), newPendingTransactionFilter (tx hashes)
LogObjectList) ;; for newFilter
<- Quantity)
(define-ethereum-api eth getFilterLogs
(Or (List Digest) ;; for newBlockFilter (block hashes), newPendingTransactionFilter (tx hashes)
LogObjectList) ;; for newFilter
<- Quantity)
;; TODO: Check that it is coherent
;; Get a list of matchings blocks
(define-ethereum-api eth getLogs
LogObjectList <- getLogsFilterOptions)
;; returns: 1. current block header pow-hash, 2. seed hash used for the DAG,
;; 3. boundary condition (“target”), 2^256 / difficulty.
(define-ethereum-api eth getWork
(Tuple Bytes32 Bytes32 Bytes32) <-)
(define-ethereum-api eth submitWork
Bool <- Bytes32 Bytes32 Bytes32)
(define-ethereum-api shh version
String <-)
(define-type ShhMessageSent
(Record
from: [(Maybe Bytes60)]
to: [(Maybe Bytes60)]
topics: [(List Bytes)]
payload: [Bytes]
priority: [Quantity]
ttl: [Quantity])) ;; time to live in seconds.
(define-ethereum-api shh post
Bool <- ShhMessageSent)
(define-ethereum-api shh newIdentity
Bytes60 <-)
(define-ethereum-api shh hasIdentity
Bool <- Bytes60)
(define-ethereum-api shh newGroup
Bytes60 <-)
(define-ethereum-api shh addToGroup
Bool <- Bytes60)
(define-type ShhFilter
(Record
to: [(Maybe Bytes60)]
topics: [(List (Or Bytes Unit (List Bytes)))]))
(define-ethereum-api shh newFilter
Quantity <- ShhFilter)
(define-ethereum-api shh uninstallFilter
Bool <- Quantity)
(define-type ShhMessageReceived
(Record
hash: [Digest]
from: [(Maybe Bytes60)]
to: [(Maybe Bytes60)]
expiry: [Quantity] ;; Integer of the time in seconds when this message should expire (?).
ttl: [Quantity] ;; Integer of the time the message should float in the system in seconds (?).
sent: [Quantity] ;; Integer of the unix timestamp when the message was sent.
topics: [(List Bytes)] ;; Array of DATA topics the message contained.
payload: [Bytes] ;; The payload of the message.
workProved: [Quantity])) ;; Integer of the work this message required before it was send (?).
(define-ethereum-api shh getFilterChanges
(List ShhMessageReceived) <- Quantity)
(define-ethereum-api shh getMessages
(List ShhMessageReceived) <- Quantity)
;;;; Geth extensions, Personal Namespace https://geth.ethereum.org/docs/rpc/ns-personal
;; Not present in Mantis. Is it present in Parity, though I haven't looked for discrepancies.
;; Arguments: (1) SecretKey as hex string, no 0x prefix, (2) passphrase.
(define-ethereum-api personal importRawKey
Address <- String String)
(define-ethereum-api personal listAccounts
(List Address) <-)
(define-ethereum-api personal lockAccount
Bool <- Address) ;; returns true if account found (?)
(define-ethereum-api personal newAccount
Address <- String) ;; argument is passphrase
(define-ethereum-api personal unlockAccount
Bool <- ;; returns true if found?
Address
String ;; passphrase
(Maybe JsInt)) ;; duration in seconds (default 300)
(define-ethereum-api personal sendTransaction
Digest <- TransactionParameters String) ;; passphrase
;;; TODO: translate the 0x Bytes into a Signature.
;;; If so, some translation is required.
;;; The sign method calculates an Ethereum specific signature of:
;;; (keccak256<-bytes (ethereum-sign-message-wrapper message))
(define-ethereum-api personal sign
Bytes <- String Address String) ;; message address passphrase
;;; Looking at the code in go-ethereum, the length that matters is the length in bytes.
;;; However, the JSON RPC API passes the string as JSON, which will be UTF-8 encoded,
;;; so it might be "interesting" to try to sign arbitrary bytes that are not valid JSON string.
(def ethereum-sign-message-prefix
(u8vector-append #u8(19) (string->bytes "Ethereum Signed Message:")))
(def (ethereum-sign-message-wrapper/bytes message)
(call-with-output-u8vector
(lambda (p)
(write-u8vector ethereum-sign-message-prefix p)
(write-u8vector (string->bytes (number->string (u8vector-length message))) p)
(write-u8vector* message p)
(write-u8 10 p))))
(def ethereum-sign-message-wrapper
(compose ethereum-sign-message-wrapper/bytes string->bytes))
;;; Recover the signer of a message signed with personal_sign
(define-ethereum-api personal ecRecover
Address <- String Signature) ;; message signature
;; https://github.com/ethereum/go-ethereum/pull/15971/files
(define-ethereum-api personal signTransaction
SignedTransaction <- TransactionParameters String)
;; txpool namespace https://geth.ethereum.org/docs/rpc/ns-txpool
(define-type TxPoolEntry
(Record
blockHash: [Digest]
blockNumber: [(Maybe Quantity)]
from: [Address]
gas: [Quantity]
gasPrice: [Quantity]
hash: [Digest]
input: [Bytes]
nonce: [Quantity]
to: [Address]
transactionIndex: [(Maybe Quantity)]
value: [Quantity]))
#;
(define-type TxPoolContent
(Record
pending: [(Hash Address -> (Hash Decimal -> TxPoolEntry))] ; Decimal is a Nonce in decimal
queued: [(Hash Address -> (Hash Decimal -> TxPoolEntry))]))
;; https://github.com/ethereum/go-ethereum/wiki/Management-APIs#txpool-content
#;(define-ethereum-api txpool content TxPoolContent <-)
;; https://geth.ethereum.org/docs/rpc/pubsub -- we need use websocket for that.
;; eth_subscribe, eth_unsubscribe
;; Poll the ethereum node until it's ready
(def (poll-for-ethereum-node
url
message: (message ".")
retry-window: (retry-window 0.05)
max-window: (max-window 1.0)
max-retries: (max-retries 10))
(retry retry-window: retry-window max-window: max-window max-retries: max-retries
(lambda () (display message) (eth_blockNumber url: url))))
;; Set of allowed environment variables in URL substitutions
;; (Table '#t <- String)
(def allowed-url-variables (hashset<-list '("INFURA_API_KEY")))
;; String <- String
(def (substitute-url-variable v)
(unless (hash-get allowed-url-variables v)
(error "Variable not allowed in connection URL configuration" v))
(or (getenv v #f)
(error "Environment variable undefined in connection URL configuration" v)))
;; String <- EthereumNetworkConfig
(def (ethereum-url<-config config)
(string-interpolate (car (.@ config rpc)) substitute-url-variable))
(def (current-ethereum-connection-for? name)
(match (current-ethereum-network)
((ethereum-network (? object? config) (? object? connection))
(equal? name (.@ config name)))
(_ #f)))
(def (ensure-ethereum-connection name poll: (poll #t))
(unless (current-ethereum-connection-for? name)
(init-ethereum-connection name poll: poll)))
(def (init-ethereum-connection name poll: poll)
(def network (ensure-ethereum-network name))
(def config (ethereum-network-config network))
(def url (ethereum-url<-config config))
(when poll
(poll-for-ethereum-node
url message: (format "Connecting to the ~a at ~a ...\n" (.@ config name) url)))
(def client-version (web3_clientVersion url: url))
(def mantis? (string-prefix? "mantis/" client-version))
(def network-id (.@ config networkId))
(def server-network-id
(with-catch (lambda _ (eth-log "The node doesn't support net_version. Assuming 0.") 0)
(cut <-string JsInt (net_version url: url))))
(eth-log (if (equal? server-network-id network-id)
"The server and configuration agree on networkId. Good."
"The server and configuration disagree on networkId. BAD. Trusting configuration."))
(def chain-id (.@ config chainId))
(def server-chain-id
(with-catch (lambda _ (eth-log "The node doesn't support eth_chainId. Assuming 0 (no EIP-155).") 0)
(cut validate JsInt (eth_chainId url: url))))
(eth-log (if (equal? server-chain-id chain-id)
"The server and configuration agree on chainId. Good."
"The server and configuration disagree on chainId. BAD. Trusting configuration."))
(def connection {url client-version network-id server-network-id chain-id server-chain-id mantis?})
(eth-log ["EthereumNetworkConnection" (list->hash-table (.alist connection))])
(set! (ethereum-network-connection network) connection))