landscape: installs without crashing (but threads still fail)

This commit is contained in:
Ted Blackman 2021-07-19 14:02:50 +03:00
parent 6cb0afa15d
commit efb1c547eb
7 changed files with 1732 additions and 6 deletions

View File

@ -35,6 +35,7 @@
|_ =bowl:gall
+* this .
def ~(. (default-agent this %|) bowl)
desk q.byk.bowl
::
++ on-init
|^ ^- (quip card _this)
@ -134,6 +135,7 @@
~|('we avoid infinite loops' !!)
?: (~(has in vals) observer)
~|('duplicate observer' !!)
~& 'OBSERVE_WATCH'^app.observer
:_ this(observers (~(put by observers) (sham eny.bowl) observer))
:_ ~
:* %pass
@ -164,7 +166,7 @@
~|('cannot warm up cache that is already warm' !!)
:_ this(warm-cache %.y)
=/ =rave:clay [%sing [%t da+now.bowl /mar]]
[%pass /warm-cache %arvo %c %warp our.bowl %home `rave]~
[%pass /warm-cache %arvo %c %warp our.bowl desk `rave]~
::
++ cool-cache-all
?. warm-cache
@ -184,6 +186,7 @@
::
++ on-observer
?> ?=([%observer @ ~] wire)
~& 'ON_OBSERVER'^i.t.wire^-.sign
?+ -.sign (on-agent:def wire sign)
%watch-ack
?~ p.sign [~ this]
@ -300,9 +303,9 @@
=* riot p.sign-arvo
?~ riot
=/ =rave:clay [%next [%t da+now.bowl /mar]]
[%pass /warm-cache %arvo %c %warp our.bowl %home `rave]~
[%pass /warm-cache %arvo %c %warp our.bowl desk `rave]~
:- =/ =rave:clay [%next [%t q.p.u.riot /mar]]
[%pass /warm-cache %arvo %c %warp our.bowl %home `rave]
[%pass /warm-cache %arvo %c %warp our.bowl desk `rave]
%+ turn !<((list path) q.r.u.riot)
|= pax=path
^- card
@ -314,7 +317,7 @@
term
:((cury cat 3) mark '-' term)
=/ =rave:clay [%sing %b da+now.bowl /[mark]]
[%pass [%mar mark ~] %arvo %c %warp our.bowl %home `rave]
[%pass [%mar mark ~] %arvo %c %warp our.bowl desk `rave]
::
[%mar ^]
?. warm-cache
@ -325,7 +328,7 @@
?~ riot
~
=/ =rave:clay [%next %b q.p.u.riot mark]
[%pass wire %arvo %c %warp our.bowl %home `rave]~
[%pass wire %arvo %c %warp our.bowl desk `rave]~
==
::
++ on-watch on-watch:def

View File

@ -32,7 +32,6 @@
%invite-hook
%invite-store
%invite-view
%landscape
%launch
%metadata-hook
%metadata-pull-hook

View File

@ -0,0 +1,454 @@
:: azimuth: constants and utilities
::
/+ ethereum
::
=> => [azimuth-types ethereum-types .]
|%
+$ complete-ship
$: state=point
history=(list diff-point) ::TODO maybe block/event nr? :: newest first
keys=(map life pass)
==
::
++ fleet (map @p complete-ship)
::
++ eth-type
|%
++ point
:~ [%bytes-n 32] :: encryptionKey
[%bytes-n 32] :: authenticationKey
%bool :: hasSponsor
%bool :: active
%bool :: escapeRequested
%uint :: sponsor
%uint :: escapeRequestedTo
%uint :: cryptoSuiteVersion
%uint :: keyRevisionNumber
%uint :: continuityNumber
==
++ deed
:~ %address :: owner
%address :: managementProxy
%address :: spawnProxy
%address :: votingProxy
%address :: transferProxy
==
--
::
++ eth-noun
|%
+$ point
$: encryption-key=octs
authentication-key=octs
has-sponsor=?
active=?
escape-requested=?
sponsor=@ud
escape-to=@ud
crypto-suite=@ud
key-revision=@ud
continuity-number=@ud
==
+$ deed
$: owner=address
management-proxy=address
spawn-proxy=address
voting-proxy=address
transfer-proxy=address
==
--
::
++ function
|%
++ azimuth
$% [%points who=@p]
[%rights who=@p]
[%get-spawned who=@p]
[%dns-domains ind=@ud]
==
--
::
:: # diffs
::
++ update
$% [%full ships=(map ship point) dns=dnses heard=events]
[%difs dis=(list (pair event-id diff-azimuth))]
==
::
:: # constants
::
:: contract addresses
++ contracts mainnet-contracts
++ mainnet-contracts
|%
:: azimuth: data contract
::
++ azimuth
0x223c.067f.8cf2.8ae1.73ee.5caf.ea60.ca44.c335.fecb
::
++ ecliptic
0x6ac0.7b7c.4601.b5ce.11de.8dfe.6335.b871.c7c4.dd4d
::
++ linear-star-release
0x86cd.9cd0.992f.0423.1751.e376.1de4.5cec.ea5d.1801
::
++ conditional-star-release
0x8c24.1098.c3d3.498f.e126.1421.633f.d579.86d7.4aea
::
++ delegated-sending
0xf790.8ab1.f1e3.52f8.3c5e.bc75.051c.0565.aeae.a5fb
::
:: launch: block number of azimuth deploy
::
++ launch 6.784.800
::
:: public: block number of azimuth becoming independent
::
++ public 7.033.765
--
::
:: Testnet contract addresses
::
++ ropsten-contracts
|%
++ azimuth
0x308a.b6a6.024c.f198.b57e.008d.0ac9.ad02.1988.6579
::
++ ecliptic
0x8b9f.86a2.8921.d9c7.05b3.113a.755f.b979.e1bd.1bce
::
++ linear-star-release
0x1f8e.dd03.1ee4.1474.0aed.b39b.84fb.8f2f.66ca.422f
::
++ conditional-star-release
0x0
::
++ delegated-sending
0x3e8c.a510.354b.c2fd.bbd6.1502.52d9.3105.c9c2.7bbe
::
++ launch 4.601.630
++ public launch
--
::
:: Local contract addresses
::
:: These addresses are only reproducible if you use the deploy
:: script in bridge
::
++ local-contracts
|%
++ ecliptic
0x56db.68f2.9203.ff44.a803.faa2.404a.44ec.bb7a.7480
++ azimuth
0x863d.9c2e.5c4c.1335.96cf.ac29.d552.55f0.d0f8.6381
++ delegated-sending
0xb71c.0b6c.ee1b.cae5.6dfe.95cd.9d3e.41dd.d7ea.fc43
++ linear-star-release
0x3c3.dc12.be65.8158.d1d7.f9e6.6e08.ec40.99c5.68e4
++ conditional-star-release
0x35eb.3b10.2d9c.1b69.ac14.69c1.b1fe.1799.850c.d3eb
++ launch 0
++ public 0
--
::
:: ++ azimuth 0x863d.9c2e.5c4c.1335.96cf.ac29.d552.55f0.d0f8.6381 :: local bridge
:: hashes of ship event signatures
++ azimuth-events
|%
::
:: OwnerChanged(uint32,address)
++ owner-changed
0x16d0.f539.d49c.6cad.822b.767a.9445.bfb1.
cf7e.a6f2.a6c2.b120.a7ea.4cc7.660d.8fda
::
:: Activated(uint32)
++ activated
0xe74c.0380.9d07.69e1.b1f7.06cc.8414.258c.
d1f3.b6fe.020c.d15d.0165.c210.ba50.3a0f
::
:: Spawned(uint32,uint32)
++ spawned
0xb2d3.a6e7.a339.f5c8.ff96.265e.2f03.a010.
a854.1070.f374.4a24.7090.9644.1508.1546
::
:: EscapeRequested(uint32,uint32)
++ escape-requested
0xb4d4.850b.8f21.8218.141c.5665.cba3.79e5.
3e9b.b015.b51e.8d93.4be7.0210.aead.874a
::
:: EscapeCanceled(uint32,uint32)
++ escape-canceled
0xd653.bb0e.0bb7.ce83.93e6.24d9.8fbf.17cd.
a590.2c83.28ed.0cd0.9988.f368.90d9.932a
::
:: EscapeAccepted(uint32,uint32)
++ escape-accepted
0x7e44.7c9b.1bda.4b17.4b07.96e1.00bf.7f34.
ebf3.6dbb.7fe6.6549.0b1b.fce6.246a.9da5
::
:: LostSponsor(uint32,uint32)
++ lost-sponsor
0xd770.4f9a.2519.3dbd.0b0c.b4a8.09fe.ffff.
a7f1.9d1a.ae88.17a7.1346.c194.4482.10d5
::
:: ChangedKeys(uint32,bytes32,bytes32,uint32,uint32)
++ changed-keys
0xaa10.e7a0.117d.4323.f1d9.9d63.0ec1.69be.
bb3a.988e.8957.70e3.5198.7e01.ff54.23d5
::
:: BrokeContinuity(uint32,uint32)
++ broke-continuity
0x2929.4799.f1c2.1a37.ef83.8e15.f79d.d91b.
cee2.df99.d63c.d1c1.8ac9.68b1.2951.4e6e
::
:: ChangedSpawnProxy(uint32,address)
++ changed-spawn-proxy
0x9027.36af.7b3c.efe1.0d9e.840a.ed0d.687e.
35c8.4095.122b.2505.1a20.ead8.866f.006d
::
:: ChangedTransferProxy(uint32,address)
++ changed-transfer-proxy
0xcfe3.69b7.197e.7f0c.f067.93ae.2472.a9b1.
3583.fecb.ed2f.78df.a14d.1f10.796b.847c
::
:: ChangedManagementProxy(uint32,address)
++ changed-management-proxy
0xab9c.9327.cffd.2acc.168f.afed.be06.139f.
5f55.cb84.c761.df05.e051.1c25.1e2e.e9bf
::
:: ChangedVotingProxy(uint32,address)
++ changed-voting-proxy
0xcbd6.269e.c714.57f2.c7b1.a227.74f2.46f6.
c5a2.eae3.795e.d730.0db5.1768.0c61.c805
::
:: ChangedDns(string,string,string)
++ changed-dns
0xfafd.04ad.e1da.ae2e.1fdb.0fc1.cc6a.899f.
d424.063e.d5c9.2120.e67e.0730.53b9.4898
--
--
::
:: logic
::
|%
++ pass-from-eth
|= [enc=octs aut=octs sut=@ud]
^- pass
%^ cat 3 'b'
?. &(=(1 sut) =(p.enc 32) =(p.aut 32))
(cat 8 0 0)
(cat 8 q.aut q.enc)
::
++ point-from-eth
|= [who=@p point:eth-noun deed:eth-noun]
^- point
::
:: ownership
::
:+ :* owner
management-proxy
voting-proxy
transfer-proxy
==
::
:: network state
::
?. active ~
:- ~
:* key-revision
::
(pass-from-eth encryption-key authentication-key crypto-suite)
::
continuity-number
::
[has-sponsor `@p`sponsor]
::
?. escape-requested ~
``@p`escape-to
==
::
:: spawn state
::
?. ?=(?(%czar %king) (clan:title who)) ~
:- ~
:* spawn-proxy
~ ::TODO call getSpawned to fill this
==
::
++ event-log-to-point-diff
=, azimuth-events
=, abi:ethereum
|= log=event-log:rpc:ethereum
^- (unit (pair ship diff-point))
~? ?=(~ mined.log) %processing-unmined-event
::
?: =(i.topics.log owner-changed)
=/ [who=@ wer=address]
(decode-topics t.topics.log ~[%uint %address])
`[who %owner wer]
::
?: =(i.topics.log activated)
=/ who=@
(decode-topics t.topics.log ~[%uint])
`[who %activated who]
::
?: =(i.topics.log spawned)
=/ [pre=@ who=@]
(decode-topics t.topics.log ~[%uint %uint])
`[pre %spawned who]
::
?: =(i.topics.log escape-requested)
=/ [who=@ wer=@]
(decode-topics t.topics.log ~[%uint %uint])
`[who %escape `wer]
::
?: =(i.topics.log escape-canceled)
=/ who=@ (decode-topics t.topics.log ~[%uint])
`[who %escape ~]
::
?: =(i.topics.log escape-accepted)
=/ [who=@ wer=@]
(decode-topics t.topics.log ~[%uint %uint])
`[who %sponsor & wer]
::
?: =(i.topics.log lost-sponsor)
=/ [who=@ pos=@]
(decode-topics t.topics.log ~[%uint %uint])
`[who %sponsor | pos]
::
?: =(i.topics.log changed-keys)
=/ who=@ (decode-topics t.topics.log ~[%uint])
=/ [enc=octs aut=octs sut=@ud rev=@ud]
%+ decode-results data.log
~[[%bytes-n 32] [%bytes-n 32] %uint %uint]
`[who %keys rev (pass-from-eth enc aut sut)]
::
?: =(i.topics.log broke-continuity)
=/ who=@ (decode-topics t.topics.log ~[%uint])
=/ num=@ (decode-results data.log ~[%uint])
`[who %continuity num]
::
?: =(i.topics.log changed-management-proxy)
=/ [who=@ sox=address]
(decode-topics t.topics.log ~[%uint %address])
`[who %management-proxy sox]
::
?: =(i.topics.log changed-voting-proxy)
=/ [who=@ tox=address]
(decode-topics t.topics.log ~[%uint %address])
`[who %voting-proxy tox]
::
?: =(i.topics.log changed-spawn-proxy)
=/ [who=@ sox=address]
(decode-topics t.topics.log ~[%uint %address])
`[who %spawn-proxy sox]
::
?: =(i.topics.log changed-transfer-proxy)
=/ [who=@ tox=address]
(decode-topics t.topics.log ~[%uint %address])
`[who %transfer-proxy tox]
::
:: warn about unimplemented events, but ignore
:: the ones we know are harmless.
~? ?! .= i.topics.log
:: OwnershipTransferred(address,address)
0x8be0.079c.5316.5914.1344.cd1f.d0a4.f284.
1949.7f97.22a3.daaf.e3b4.186f.6b64.57e0
[%unimplemented-event i.topics.log]
~
::
++ apply-point-diff
|= [pot=point dif=diff-point]
^- point
?- -.dif
%full new.dif
::
%activated
%_ pot
net `[0 0 0 &^(^sein:title who.dif) ~]
kid ?. ?=(?(%czar %king) (clan:title who.dif)) ~
`[0x0 ~]
==
::
:: ownership
::
%owner pot(owner.own new.dif)
%transfer-proxy pot(transfer-proxy.own new.dif)
%management-proxy pot(management-proxy.own new.dif)
%voting-proxy pot(voting-proxy.own new.dif)
::
:: networking
::
?(%keys %continuity %sponsor %escape)
?> ?=(^ net.pot)
?- -.dif
%keys
pot(life.u.net life.dif, pass.u.net pass.dif)
::
%sponsor
%= pot
sponsor.u.net new.dif
escape.u.net ?:(has.new.dif ~ escape.u.net.pot)
==
::
%continuity pot(continuity-number.u.net new.dif)
%escape pot(escape.u.net new.dif)
==
::
:: spawning
::
?(%spawned %spawn-proxy)
?> ?=(^ kid.pot)
?- -.dif
%spawned
=- pot(spawned.u.kid -)
(~(put in spawned.u.kid.pot) who.dif)
::
%spawn-proxy pot(spawn-proxy.u.kid new.dif)
==
==
::
++ parse-id
|= id=@t
^- azimuth:function
|^
~| id
%+ rash id
;~ pose
(function %points 'points' shipname)
(function %get-spawned 'getSpawned' shipname)
(function %dns-domains 'dnsDomains' dem:ag)
==
::
++ function
|* [tag=@tas fun=@t rul=rule]
;~(plug (cold tag (jest fun)) (ifix [pal par] rul))
::
++ shipname
;~(pfix sig fed:ag)
--
::
++ function-to-call
|%
++ azimuth
|= cal=azimuth:function
^- [id=@t dat=call-data:rpc:ethereum]
?- -.cal
%points
:- (crip "points({(scow %p who.cal)})")
['points(uint32)' ~[uint+`@`who.cal]]
::
%rights
:- (crip "rights({(scow %p who.cal)})")
['rights(uint32)' ~[uint+`@`who.cal]]
::
%get-spawned
:- (crip "getSpawned({(scow %p who.cal)})")
['getSpawned(uint32)' ~[uint+`@`who.cal]]
::
%dns-domains
:- (crip "dnsDomains({(scow %ud ind.cal)})")
['dnsDomains(uint256)' ~[uint+ind.cal]]
==
--
--

View File

@ -0,0 +1,146 @@
/- rpc=json-rpc
/+ ethereum, azimuth, strandio
=, strand=strand:strandio
=, jael
|%
++ tract azimuth:contracts:azimuth
++ fetch-point
|= [url=@ta who=ship]
=/ m (strand ,point:azimuth)
^- form:m
=/ =request:rpc:ethereum
:+ %eth-call
=- [from=~ to=tract gas=~ price=~ value=~ data=-]
(encode-call:rpc:ethereum 'points(uint32)' [%uint `@`who]~)
[%label %latest]
;< jon=json bind:m (request-rpc url `'point' request)
=/ res=cord (so:dejs:format jon)
=/ =point:eth-noun:azimuth
(decode-results:abi:ethereum res point:eth-type:azimuth)
::
=/ =request:rpc:ethereum
:+ %eth-call
=- [from=~ to=tract gas=~ price=~ value=~ data=-]
(encode-call:rpc:ethereum 'rights(uint32)' [%uint `@`who]~)
[%label %latest]
;< jon=json bind:m (request-rpc url `'deed' request)
=/ res=cord (so:dejs:format jon)
=/ =deed:eth-noun:azimuth
(decode-results:abi:ethereum res deed:eth-type:azimuth)
::
(pure:m (point-from-eth:azimuth who point deed))
::
++ request-rpc
|= [url=@ta id=(unit @t) req=request:rpc:ethereum]
=/ m (strand ,json)
^- form:m
%+ (retry json) `10
=/ m (strand ,(unit json))
^- form:m
|^
=/ =request:http
:* method=%'POST'
url=url
header-list=['Content-Type'^'application/json' ~]
^= body
%- some %- as-octt:mimes:html
%- en-json:html
(request-to-json:rpc:ethereum id req)
==
;< ~ bind:m (send-request:strandio request)
;< rep=(unit client-response:iris) bind:m
take-maybe-response:strandio
?~ rep
(pure:m ~)
(parse-response u.rep)
::
++ parse-response
|= =client-response:iris
=/ m (strand ,(unit json))
^- form:m
?> ?=(%finished -.client-response)
?~ full-file.client-response
(pure:m ~)
=/ body=@t q.data.u.full-file.client-response
=/ jon=(unit json) (de-json:html body)
?~ jon
(pure:m ~)
=, dejs-soft:format
=/ array=(unit (list response:rpc))
((ar parse-one-response) u.jon)
?~ array
=/ res=(unit response:rpc) (parse-one-response u.jon)
?~ res
(strand-fail:strandio %request-rpc-parse-error >id< ~)
?: ?=(%error -.u.res)
(strand-fail:strandio %request-rpc-error >id< >+.res< ~)
?. ?=(%result -.u.res)
(strand-fail:strandio %request-rpc-fail >u.res< ~)
(pure:m `res.u.res)
(strand-fail:strandio %request-rpc-batch >%not-implemented< ~)
:: (pure:m `[%batch u.array])
::
++ parse-one-response
|= =json
^- (unit response:rpc)
=/ res=(unit [@t ^json])
%. json
=, dejs-soft:format
(ot id+so result+some ~)
?^ res `[%result u.res]
~| parse-one-response=json
:+ ~ %error %- need
%. json
=, dejs-soft:format
(ot id+so error+(ot code+no message+so ~) ~)
--
::
++ retry
|* result=mold
|= [crash-after=(unit @ud) computation=_*form:(strand (unit result))]
=/ m (strand ,result)
=| try=@ud
|- ^- form:m
=* loop $
?: =(crash-after `try)
(strand-fail:strandio %retry-too-many ~)
;< ~ bind:m (backoff:strandio try ~m1)
;< res=(unit result) bind:m computation
?^ res
(pure:m u.res)
loop(try +(try))
::
++ get-latest-block
|= url=@ta
=/ m (strand ,block)
^- form:m
;< =json bind:m (request-rpc url `'block number' %eth-block-number ~)
(get-block-by-number url (parse-eth-block-number:rpc:ethereum json))
::
++ get-block-by-number
|= [url=@ta =number:block]
=/ m (strand ,block)
^- form:m
|^
;< =json bind:m
(request-rpc url `'block by number' %eth-get-block-by-number number |)
=/ =block (parse-block json)
?. =(number number.id.block)
(strand-fail:strandio %reorg-detected >number< >block< ~)
(pure:m block)
::
++ parse-block
|= =json
^- block
=< [[&1 &2] |2]
^- [@ @ @]
~| json
%. json
=, dejs:format
%- ot
:~ hash+parse-hex-result:rpc:ethereum
number+parse-hex-result:rpc:ethereum
'parentHash'^parse-hex-result:rpc:ethereum
==
--
--

View File

@ -1,3 +1,4 @@
::
/+ skeleton
|* [agent=* help=*]
?: ?=(%& help)

View File

@ -0,0 +1,866 @@
:: ethereum: utilities
::
=, ethereum-types
|%
:: deriving and using ethereum keys
::
++ key
|%
++ address-from-pub
=, keccak:crypto
|= pub=@
%+ end [3 20]
%+ keccak-256 64
(rev 3 64 pub)
::
++ address-from-prv
(cork pub-from-prv address-from-pub)
::
++ pub-from-prv
=, secp256k1:secp:crypto
|= prv=@
%- serialize-point
(priv-to-pub prv)
::
++ sign-transaction
=, crypto
|= [tx=transaction:rpc pk=@]
^- @ux
:: hash the raw transaction data
=/ hash=@
=/ dat=@
%- encode-atoms:rlp
:: with v=chain-id, r=0, s=0
tx(chain-id [chain-id.tx 0 0 ~])
=+ wid=(met 3 dat)
%- keccak-256:keccak
[wid (rev 3 wid dat)]
:: sign transaction hash with private key
=+ (ecdsa-raw-sign:secp256k1:secp hash pk)
:: complete transaction is raw data, with r and s
:: taken from the signature, and v as per eip-155
%- encode-atoms:rlp
tx(chain-id [:(add (mul chain-id.tx 2) 35 v) r s ~])
--
::
:: rlp en/decoding
::NOTE https://github.com/ethereum/wiki/wiki/RLP
::
++ rlp
|%
::NOTE rlp encoding doesn't really care about leading zeroes,
:: but because we need to disinguish between no-bytes zero
:: and one-byte zero (and also empty list) we end up with
:: this awful type...
+$ item
$% [%l l=(list item)]
[%b b=byts]
==
:: +encode-atoms: encode list of atoms as a %l of %b items
::
++ encode-atoms
|= l=(list @)
^- @
%+ encode %l
%+ turn l
|=(a=@ b+[(met 3 a) a])
::
++ encode
|= in=item
|^ ^- @
?- -.in
%b
?: &(=(1 wid.b.in) (lte dat.b.in 0x7f))
dat.b.in
=- (can 3 ~[b.in [(met 3 -) -]])
(encode-length wid.b.in 0x80)
::
%l
=/ out=@
%+ roll l.in
|= [ni=item en=@]
(cat 3 (encode ni) en)
%^ cat 3 out
(encode-length (met 3 out) 0xc0)
==
::
++ encode-length
|= [len=@ off=@]
?: (lth len 56) (add len off)
=- (cat 3 len -)
:(add (met 3 len) off 55)
--
:: +decode-atoms: decode expecting a %l of %b items, producing atoms within
::
++ decode-atoms
|= dat=@
^- (list @)
=/ i=item (decode dat)
~| [%unexpected-data i]
?> ?=(%l -.i)
%+ turn l.i
|= i=item
~| [%unexpected-list i]
?> ?=(%b -.i)
dat.b.i
::
++ decode
|= dat=@
^- item
=/ bytes=(list @) (flop (rip 3 dat))
=? bytes ?=(~ bytes) ~[0]
|^ item:decode-head
::
++ decode-head
^- [done=@ud =item]
?~ bytes
~| %rlp-unexpected-end
!!
=* byt i.bytes
:: byte in 0x00-0x79 range encodes itself
::
?: (lte byt 0x79)
:- 1
[%b 1^byt]
:: byte in 0x80-0xb7 range encodes string length
::
?: (lte byt 0xb7)
=+ len=(sub byt 0x80)
:- +(len)
:- %b
len^(get-value 1 len)
:: byte in 0xb8-0xbf range encodes string length length
::
?: (lte byt 0xbf)
=+ led=(sub byt 0xb7)
=+ len=(get-value 1 led)
:- (add +(led) len)
:- %b
len^(get-value +(led) len)
:: byte in 0xc0-f7 range encodes list length
::
?: (lte byt 0xf7)
=+ len=(sub byt 0xc0)
:- +(len)
:- %l
%. len
decode-list(bytes (slag 1 `(list @)`bytes))
:: byte in 0xf8-ff range encodes list length length
::
?: (lte byt 0xff)
=+ led=(sub byt 0xf7)
=+ len=(get-value 1 led)
:- (add +(led) len)
:- %l
%. len
decode-list(bytes (slag +(led) `(list @)`bytes))
~| [%rip-not-bloq-3 `@ux`byt]
!!
::
++ decode-list
|= rem=@ud
^- (list item)
?: =(0 rem) ~
=+ ^- [don=@ud =item] ::TODO =/
decode-head
:- item
%= $
rem (sub rem don)
bytes (slag don bytes)
==
::
++ get-value
|= [at=@ud to=@ud]
^- @
(rep 3 (flop (swag [at to] bytes)))
--
--
::
:: abi en/decoding
::NOTE https://solidity.readthedocs.io/en/develop/abi-spec.html
::
++ abi
=> |%
:: solidity types. integer bitsizes ignored
++ etyp
$@ $? :: static
%address %bool
%int %uint
%real %ureal
:: dynamic
%bytes %string
==
$% :: static
[%bytes-n n=@ud]
:: dynamic
[%array-n t=etyp n=@ud]
[%array t=etyp]
==
::
:: solidity-style typed data. integer bitsizes ignored
++ data
$% [%address p=address]
[%string p=tape]
[%bool p=?]
[%int p=@sd]
[%uint p=@ud]
[%real p=@rs]
[%ureal p=@urs]
[%array-n p=(list data)]
[%array p=(list data)]
[%bytes-n p=octs] ::TODO just @, because context knows length?
[%bytes p=octs]
==
--
=, mimes:html
|%
:: encoding
::
++ encode-args
:: encode list of arguments.
::
|= das=(list data)
^- tape
(encode-data [%array-n das])
::
++ encode-data
:: encode typed data into ABI bytestring.
::
|= dat=data
^- tape
?+ -.dat
~| [%unsupported-type -.dat]
!!
::
%array-n
:: enc(X) = head(X[0]) ... head(X[k-1]) tail(X[0]) ... tail(X[k-1])
:: where head and tail are defined for X[i] being of a static type as
:: head(X[i]) = enc(X[i]) and tail(X[i]) = "" (the empty string), or as
:: head(X[i]) = enc(len( head(X[0])..head(X[k-1])
:: tail(X[0])..tail(X[i-1]) ))
:: and tail(X[i]) = enc(X[i]) otherwise.
::
:: so: if it's a static type, data goes in the head. if it's a dynamic
:: type, a reference goes into the head and data goes into the tail.
::
:: in the head, we first put a placeholder where references need to go.
=+ hol=(reap 64 'x')
=/ hes=(list tape)
%+ turn p.dat
|= d=data
?. (is-dynamic-type d) ^$(dat d)
hol
=/ tas=(list tape)
%+ turn p.dat
|= d=data
?. (is-dynamic-type d) ""
^$(dat d)
:: once we know the head and tail, we can fill in the references in head.
=- (weld nes `tape`(zing tas))
^- [@ud nes=tape]
=+ led=(lent (zing hes))
%+ roll hes
|= [t=tape i=@ud nes=tape]
:- +(i)
:: if no reference needed, just put the data.
?. =(t hol) (weld nes t)
:: calculate byte offset of data we need to reference.
=/ ofs=@ud
=- (div - 2) :: two hex digits per byte.
%+ add led :: count head, and
%- lent %- zing :: count all tail data
(scag i tas) :: preceding ours.
=+ ref=^$(dat [%uint ofs])
:: shouldn't hit this unless we're sending over 2gb of data?
~| [%weird-ref-lent (lent ref)]
?> =((lent ref) (lent hol))
(weld nes ref)
::
%array :: where X has k elements (k is assumed to be of type uint256):
:: enc(X) = enc(k) enc([X[1], ..., X[k]])
:: i.e. it is encoded as if it were an array of static size k, prefixed
:: with the number of elements.
%+ weld $(dat [%uint (lent p.dat)])
$(dat [%array-n p.dat])
::
%bytes-n
:: enc(X) is the sequence of bytes in X padded with zero-bytes to a
:: length of 32.
:: Note that for any X, len(enc(X)) is a multiple of 32.
~| [%bytes-n-too-long max=32 actual=p.p.dat]
?> (lte p.p.dat 32)
(pad-to-multiple (render-hex-bytes p.dat) 64 %right)
::
%bytes :: of length k (which is assumed to be of type uint256)
:: enc(X) = enc(k) pad_right(X), i.e. the number of bytes is encoded as a
:: uint256 followed by the actual value of X as a byte sequence, followed
:: by the minimum number of zero-bytes such that len(enc(X)) is a
:: multiple of 32.
%+ weld $(dat [%uint p.p.dat])
(pad-to-multiple (render-hex-bytes p.dat) 64 %right)
::
%string
:: enc(X) = enc(enc_utf8(X)), i.e. X is utf-8 encoded and this value is
:: interpreted as of bytes type and encoded further. Note that the length
:: used in this subsequent encoding is the number of bytes of the utf-8
:: encoded string, not its number of characters.
$(dat [%bytes (lent p.dat) (swp 3 (crip p.dat))])
::
%uint
:: enc(X) is the big-endian encoding of X, padded on the higher-order
:: (left) side with zero-bytes such that the length is a multiple of 32
:: bytes.
(pad-to-multiple (render-hex-bytes (as-octs p.dat)) 64 %left)
::
%bool
:: as in the uint8 case, where 1 is used for true and 0 for false
$(dat [%uint ?:(p.dat 1 0)])
::
%address
:: as in the uint160 case
$(dat [%uint `@ud`p.dat])
==
::
++ is-dynamic-type
|= a=data
?. ?=(%array-n -.a)
?=(?(%string %bytes %array) -.a)
&(!=((lent p.a) 0) (lien p.a is-dynamic-type))
::
:: decoding
::
++ decode-topics decode-arguments
::
++ decode-results
:: rex: string of hex bytes with leading 0x.
|* [rex=@t tys=(list etyp)]
=- (decode-arguments - tys)
%^ rut 9
(rsh [3 2] rex)
(curr rash hex)
::
++ decode-arguments
|* [wos=(list @) tys=(list etyp)]
=/ wos=(list @) wos :: get rid of tmi
=| win=@ud
=< (decode-from 0 tys)
|%
++ decode-from
|* [win=@ud tys=(list etyp)]
?~ tys !!
=- ?~ t.tys dat
[dat $(win nin, tys t.tys)]
(decode-one win ~[i.tys])
::
++ decode-one
::NOTE we take (list etyp) even though we only operate on
:: a single etyp as a workaround for urbit/arvo#673
|* [win=@ud tys=(list etyp)]
=- [nin dat]=- ::NOTE ^= regular form broken
?~ tys !!
=* typ i.tys
=+ wor=(snag win wos)
?+ typ
~| [%unsupported-type typ]
!!
::
?(%address %bool %uint) :: %int %real %ureal
:- +(win)
?- typ
%address `@ux`wor
%uint `@ud`wor
%bool =(1 wor)
==
::
%string
=+ $(tys ~[%bytes])
[nin (trip (swp 3 q.dat))]
::
%bytes
:- +(win)
:: find the word index of the actual data.
=/ lic=@ud (div wor 32)
:: learn the bytelength of the data.
=/ len=@ud (snag lic wos)
(decode-bytes-n +(lic) len)
::
[%bytes-n *]
:- (add win +((div (dec n.typ) 32)))
(decode-bytes-n win n.typ)
::
[%array *]
:- +(win)
:: find the word index of the actual data.
=. win (div wor 32)
:: read the elements from their location.
%- tail
%^ decode-array-n ~[t.typ] +(win)
(snag win wos)
::
[%array-n *]
(decode-array-n ~[t.typ] win n.typ)
==
::
++ decode-bytes-n
|= [fro=@ud bys=@ud]
^- octs
:: parse {bys} bytes from {fro}.
:- bys
%+ rsh
:- 3
=+ (mod bys 32)
?:(=(0 -) - (sub 32 -))
%+ rep 8
%- flop
=- (swag [fro -] wos)
+((div (dec bys) 32))
::
++ decode-array-n
::NOTE we take (list etyp) even though we only operate on
:: a single etyp as a workaround for urbit/arvo#673
::NOTE careful! produces lists without type info
=| res=(list)
|* [tys=(list etyp) fro=@ud len=@ud]
^- [@ud (list)]
?~ tys !!
?: =(len 0) [fro (flop `(list)`res)]
=+ (decode-one fro ~[i.tys]) :: [nin=@ud dat=*]
$(res ^+(res [dat res]), fro nin, len (dec len))
--
--
::
:: communicating with rpc nodes
::NOTE https://github.com/ethereum/wiki/wiki/JSON-RPC
::
++ rpc
:: types
::
=> =, abi
=, format
|%
:: raw call data
++ call-data
$: function=@t
arguments=(list data)
==
::
:: raw transaction data
+$ transaction
$: nonce=@ud
gas-price=@ud
gas=@ud
to=address
value=@ud
data=@ux
chain-id=@ux
==
::
:: ethereum json rpc api
::
:: supported requests.
++ request
$% [%eth-block-number ~]
[%eth-call cal=call deb=block]
$: %eth-new-filter
fro=(unit block)
tob=(unit block)
adr=(list address)
top=(list ?(@ux (list @ux)))
==
[%eth-get-block-by-number bon=@ud txs=?]
[%eth-get-filter-logs fid=@ud]
$: %eth-get-logs
fro=(unit block)
tob=(unit block)
adr=(list address)
top=(list ?(@ux (list @ux)))
==
$: %eth-get-logs-by-hash
has=@
adr=(list address)
top=(list ?(@ux (list @ux)))
==
[%eth-get-filter-changes fid=@ud]
[%eth-get-transaction-count adr=address =block]
[%eth-get-transaction-receipt txh=@ux]
[%eth-send-raw-transaction dat=@ux]
==
::
::TODO clean up & actually use
++ response
$% ::TODO
[%eth-new-filter fid=@ud]
[%eth-get-filter-logs los=(list event-log)]
[%eth-get-logs los=(list event-log)]
[%eth-get-logs-by-hash los=(list event-log)]
[%eth-got-filter-changes los=(list event-log)]
[%eth-transaction-hash haz=@ux]
==
::
++ event-log
$: :: null for pending logs
$= mined %- unit
$: log-index=@ud
transaction-index=@ud
transaction-hash=@ux
block-number=@ud
block-hash=@ux
removed=?
==
::
address=@ux
data=@t
:: event data
::
:: For standard events, the first topic is the event signature
:: hash. For anonymous events, the first topic is the first
:: indexed argument.
:: Note that this does not support the "anonymous event with
:: zero topics" case. This has dubious usability, and using
:: +lest instead of +list saves a lot of ?~ checks.
::
topics=(lest @ux)
==
::
:: data for eth_call.
++ call
$: from=(unit address)
to=address
gas=(unit @ud)
gas-price=(unit @ud)
value=(unit @ud)
data=tape
==
::
:: minimum data needed to construct a read call
++ proto-read-request
$: id=(unit @t)
to=address
call-data
==
::
:: block to operate on.
++ block
$% [%number n=@ud]
[%label l=?(%earliest %latest %pending)]
==
--
::
:: logic
::
|%
++ encode-call
|= call-data
^- tape
::TODO should this check to see if the data matches the function signature?
=- :(weld "0x" - (encode-args arguments))
%+ scag 8
%+ render-hex-bytes 32
%- keccak-256:keccak:crypto
(as-octs:mimes:html function)
::
:: building requests
::
++ json-request
=, eyre
|= [url=purl jon=json]
^- hiss
:^ url %post
%- ~(gas in *math)
~['Content-Type'^['application/json']~]
(some (as-octt (en-json:html jon)))
:: +light-json-request: like json-request, but for %l
::
:: TODO: Exorcising +purl from our system is a much longer term effort;
:: get the current output types for now.
::
++ light-json-request
|= [url=purl:eyre jon=json]
^- request:http
::
:* %'POST'
(crip (en-purl:html url))
~[['content-type' 'application/json']]
(some (as-octt (en-json:html jon)))
==
::
++ batch-read-request
|= req=(list proto-read-request)
^- json
a+(turn req read-request)
::
++ read-request
|= proto-read-request
^- json
%+ request-to-json id
:+ %eth-call
^- call
[~ to ~ ~ ~ `tape`(encode-call function arguments)]
[%label %latest]
::
++ request-to-json
=, enjs:format
|= [riq=(unit @t) req=request]
^- json
%- pairs
=; r=[met=@t pas=(list json)]
::TODO should use request-to-json:rpc:jstd,
:: and probably (fall riq -.req)
:* jsonrpc+s+'2.0'
method+s+met.r
params+a+pas.r
::TODO would just jamming the req noun for id be a bad idea?
?~ riq ~
[id+s+u.riq]~
==
?- -.req
%eth-block-number
['eth_blockNumber' ~]
::
%eth-call
:- 'eth_call'
:~ (eth-call-to-json cal.req)
(block-to-json deb.req)
==
::
%eth-new-filter
:- 'eth_newFilter'
:_ ~
:- %o %- ~(gas by *(map @t json))
=- (murn - same)
^- (list (unit (pair @t json)))
:~ ?~ fro.req ~
`['fromBlock' (block-to-json u.fro.req)]
::
?~ tob.req ~
`['toBlock' (block-to-json u.tob.req)]
::
::TODO fucking tmi
?: =(0 (lent adr.req)) ~
:+ ~ 'address'
?: =(1 (lent adr.req)) (tape (address-to-hex (snag 0 adr.req)))
:- %a
(turn adr.req (cork address-to-hex tape))
::
?~ top.req ~
:+ ~ 'topics'
(topics-to-json top.req)
==
::
%eth-get-block-by-number
:- 'eth_getBlockByNumber'
:~ (tape (num-to-hex bon.req))
b+txs.req
==
::
%eth-get-filter-logs
['eth_getFilterLogs' (tape (num-to-hex fid.req)) ~]
::
%eth-get-logs
:- 'eth_getLogs'
:_ ~
:- %o %- ~(gas by *(map @t json))
=- (murn - same)
^- (list (unit (pair @t json)))
:~ ?~ fro.req ~
`['fromBlock' (block-to-json u.fro.req)]
::
?~ tob.req ~
`['toBlock' (block-to-json u.tob.req)]
::
?: =(0 (lent adr.req)) ~
:+ ~ 'address'
?: =(1 (lent adr.req)) (tape (address-to-hex (snag 0 adr.req)))
:- %a
(turn adr.req (cork address-to-hex tape))
::
?~ top.req ~
:+ ~ 'topics'
(topics-to-json top.req)
==
::
%eth-get-logs-by-hash
:- 'eth_getLogs'
:_ ~ :- %o
%- ~(gas by *(map @t json))
=- (murn - same)
^- (list (unit (pair @t json)))
:~ `['blockHash' (tape (transaction-to-hex has.req))]
::
?: =(0 (lent adr.req)) ~
:+ ~ 'address'
?: =(1 (lent adr.req)) (tape (address-to-hex (snag 0 adr.req)))
:- %a
(turn adr.req (cork address-to-hex tape))
::
?~ top.req ~
:+ ~ 'topics'
(topics-to-json top.req)
==
::
%eth-get-filter-changes
['eth_getFilterChanges' (tape (num-to-hex fid.req)) ~]
::
%eth-get-transaction-count
:- 'eth_getTransactionCount'
:~ (tape (address-to-hex adr.req))
(block-to-json block.req)
==
::
%eth-get-transaction-receipt
['eth_getTransactionReceipt' (tape (transaction-to-hex txh.req)) ~]
::
%eth-send-raw-transaction
['eth_sendRawTransaction' (tape (num-to-hex dat.req)) ~]
==
::
++ eth-call-to-json
=, enjs:format
|= cal=call
^- json
:- %o %- ~(gas by *(map @t json))
=- (murn - same)
^- (list (unit (pair @t json)))
:~ ?~ from.cal ~
`['from' (tape (address-to-hex u.from.cal))]
::
`['to' (tape (address-to-hex to.cal))]
::
?~ gas.cal ~
`['gas' (tape (num-to-hex u.gas.cal))]
::
?~ gas-price.cal ~
`['gasPrice' (tape (num-to-hex u.gas-price.cal))]
::
?~ value.cal ~
`['value' (tape (num-to-hex u.value.cal))]
::
?~ data.cal ~
`['data' (tape data.cal)]
==
::
++ block-to-json
|= dob=block
^- json
?- -.dob
%number s+(crip '0' 'x' ((x-co:co 1) n.dob))
%label s+l.dob
==
::
++ topics-to-json
|= tos=(list ?(@ux (list @ux)))
^- json
:- %a
=/ ttj
;: cork
(cury render-hex-bytes 32)
prefix-hex
tape:enjs:format
==
%+ turn tos
|= t=?(@ (list @))
?@ t
?: =(0 t) ~
(ttj `@`t)
a+(turn t ttj)
::
:: parsing responses
::
::TODO ++ parse-response |= json ^- response
::
++ parse-hex-result
|= j=json
^- @
?> ?=(%s -.j)
(hex-to-num p.j)
::
++ parse-eth-new-filter-res parse-hex-result
::
++ parse-eth-block-number parse-hex-result
::
++ parse-transaction-hash parse-hex-result
::
++ parse-eth-get-transaction-count parse-hex-result
::
++ parse-event-logs
(ar:dejs:format parse-event-log)
::
++ parse-event-log
=, dejs:format
|= log=json
^- event-log
=- ((ot -) log)
:~ =- ['logIndex'^(cu - (mu so))]
|= li=(unit @t)
?~ li ~
=- `((ou -) log) ::TODO not sure if elegant or hacky.
:~ 'logIndex'^(un (cu hex-to-num so))
'transactionIndex'^(un (cu hex-to-num so))
'transactionHash'^(un (cu hex-to-num so))
'blockNumber'^(un (cu hex-to-num so))
'blockHash'^(un (cu hex-to-num so))
'removed'^(uf | bo)
==
::
address+(cu hex-to-num so)
data+so
::
=- topics+(cu - (ar so))
|= r=(list @t)
^- (lest @ux)
?> ?=([@t *] r)
:- (hex-to-num i.r)
(turn t.r hex-to-num)
==
--
::
:: utilities
::TODO give them better homes!
::
++ num-to-hex
|= n=@
^- tape
%- prefix-hex
?: =(0 n)
"0"
%- render-hex-bytes
(as-octs:mimes:html n)
::
++ address-to-hex
|= a=address
^- tape
%- prefix-hex
(render-hex-bytes 20 `@`a)
::
++ transaction-to-hex
|= h=@
^- tape
%- prefix-hex
(render-hex-bytes 32 h)
::
++ prefix-hex
|= a=tape
^- tape
['0' 'x' a]
::
++ render-hex-bytes
:: atom to string of hex bytes without 0x prefix and dots.
|= a=octs
^- tape
((x-co:co (mul 2 p.a)) q.a)
::
++ pad-to-multiple
|= [wat=tape mof=@ud wer=?(%left %right)]
^- tape
=+ len=(lent wat)
?: =(0 len) (reap mof '0')
=+ mad=(mod len mof)
?: =(0 mad) wat
=+ tad=(reap (sub mof mad) '0')
%- weld
?:(?=(%left wer) [tad wat] [wat tad])
::
++ hex-to-num
|= a=@t
(rash (rsh [3 2] a) hex)
--

View File

@ -0,0 +1,257 @@
:: ethio: Asynchronous Ethereum input/output functions.
::
/- rpc=json-rpc
/+ ethereum, strandio
=, ethereum-types
=, jael
::
=> |%
+$ topics (list ?(@ux (list @ux)))
--
|%
:: +request-rpc: send rpc request, with retry
::
++ request-rpc
|= [url=@ta id=(unit @t) req=request:rpc:ethereum]
=/ m (strand:strandio ,json)
^- form:m
;< res=(list [id=@t =json]) bind:m
(request-batch-rpc-strict url [id req]~)
?: ?=([* ~] res)
(pure:m json.i.res)
%+ strand-fail:strandio
%unexpected-multiple-results
[>(lent res)< ~]
:: +request-batch-rpc-strict: send rpc requests, with retry
::
:: sends a batch request. produces results for all requests in the batch,
:: but only if all of them are successful.
::
++ request-batch-rpc-strict
|= [url=@ta reqs=(list [id=(unit @t) req=request:rpc:ethereum])]
|^ %+ (retry:strandio results)
`10
attempt-request
::
+$ results (list [id=@t =json])
::
++ attempt-request
=/ m (strand:strandio ,(unit results))
^- form:m
;< responses=(list response:rpc) bind:m
(request-batch-rpc-loose url reqs)
=- ?~ err
(pure:m `res)
(pure:m ~)
%+ roll responses
|= $: rpc=response:rpc
[res=results err=(list [id=@t code=@t message=@t])]
==
?: ?=(%error -.rpc)
[res [+.rpc err]]
?. ?=(%result -.rpc)
[res [['' 'ethio-rpc-fail' (crip <rpc>)] err]]
[[+.rpc res] err]
--
:: +request-batch-rpc-loose: send rpc requests, with retry
::
:: sends a batch request. produces results for all requests in the batch,
:: including the ones that are unsuccessful.
::
++ request-batch-rpc-loose
|= [url=@ta reqs=(list [id=(unit @t) req=request:rpc:ethereum])]
|^ %+ (retry:strandio results)
`10
attempt-request
::
+$ result response:rpc
+$ results (list response:rpc)
::
++ attempt-request
=/ m (strand:strandio ,(unit results))
^- form:m
=/ =request:http
:* method=%'POST'
url=url
header-list=['Content-Type'^'application/json' ~]
::
^= body
%- some %- as-octt:mimes:html
%- en-json:html
a+(turn reqs request-to-json:rpc:ethereum)
==
;< ~ bind:m
(send-request:strandio request)
;< rep=(unit client-response:iris) bind:m
take-maybe-response:strandio
?~ rep
(pure:m ~)
(parse-responses u.rep)
::
++ parse-responses
|= =client-response:iris
=/ m (strand:strandio ,(unit results))
^- form:m
?> ?=(%finished -.client-response)
?~ full-file.client-response
(pure:m ~)
=/ body=@t q.data.u.full-file.client-response
=/ jon=(unit json) (de-json:html body)
?~ jon
(pure:m ~)
=/ array=(unit (list response:rpc))
((ar:dejs-soft:format parse-one-response) u.jon)
?~ array
(strand-fail:strandio %rpc-result-incomplete-batch >u.jon< ~)
(pure:m array)
::
++ parse-one-response
|= =json
^- (unit response:rpc)
=/ res=(unit [@t ^json])
%. json
=, dejs-soft:format
(ot id+so result+some ~)
?^ res `[%result u.res]
~| parse-one-response=json
:+ ~ %error %- need
%. json
=, dejs-soft:format
(ot id+so error+(ot code+no message+so ~) ~)
--
::
:: +read-contract: calls a read function on a contract, produces result hex
::
++ read-contract
|= [url=@t req=proto-read-request:rpc:ethereum]
=/ m (strand:strandio ,@t)
;< res=(list [id=@t res=@t]) bind:m
(batch-read-contract-strict url [req]~)
?: ?=([* ~] res)
(pure:m res.i.res)
%+ strand-fail:strandio
%unexpected-multiple-results
[>(lent res)< ~]
:: +batch-read-contract-strict: calls read functions on contracts
::
:: sends a batch request. produces results for all requests in the batch,
:: but only if all of them are successful.
::
++ batch-read-contract-strict
|= [url=@t reqs=(list proto-read-request:rpc:ethereum)]
|^ =/ m (strand:strandio ,results)
^- form:m
;< res=(list [id=@t =json]) bind:m
%+ request-batch-rpc-strict url
(turn reqs proto-to-rpc)
=+ ^- [=results =failures]
(roll res response-to-result)
?~ failures (pure:m results)
(strand-fail:strandio %batch-read-failed-for >failures< ~)
::
+$ results (list [id=@t res=@t])
+$ failures (list [id=@t =json])
::
++ proto-to-rpc
|= proto-read-request:rpc:ethereum
^- [(unit @t) request:rpc:ethereum]
:- id
:+ %eth-call
^- call:rpc:ethereum
[~ to ~ ~ ~ `tape`(encode-call:rpc:ethereum function arguments)]
[%label %latest]
::
++ response-to-result
|= [[id=@t =json] =results =failures]
^+ [results failures]
?: ?=(%s -.json)
[[id^p.json results] failures]
[results [id^json failures]]
--
::
::
++ get-latest-block
|= url=@ta
=/ m (strand:strandio ,block)
^- form:m
;< =json bind:m
(request-rpc url `'block number' %eth-block-number ~)
(get-block-by-number url (parse-eth-block-number:rpc:ethereum json))
::
++ get-block-by-number
|= [url=@ta =number:block]
=/ m (strand:strandio ,block)
^- form:m
|^
%+ (retry:strandio ,block) `10
=/ m (strand:strandio ,(unit block))
^- form:m
;< =json bind:m
%+ request-rpc url
:- `'block by number'
[%eth-get-block-by-number number |]
(pure:m (parse-block json))
::
++ parse-block
|= =json
^- (unit block)
=< ?~(. ~ `[[&1 &2] |2]:u)
^- (unit [@ @ @])
~| json
%. json
=, dejs-soft:format
%- ot
:~ hash+parse-hex
number+parse-hex
'parentHash'^parse-hex
==
::
++ parse-hex |=(=json `(unit @)`(some (parse-hex-result:rpc:ethereum json)))
--
::
++ get-logs-by-hash
|= [url=@ta =hash:block contracts=(list address) =topics]
=/ m (strand:strandio (list event-log:rpc:ethereum))
^- form:m
;< =json bind:m
%+ request-rpc url
:* `'logs by hash'
%eth-get-logs-by-hash
hash
contracts
topics
==
%- pure:m
(parse-event-logs:rpc:ethereum json)
::
++ get-logs-by-range
|= $: url=@ta
contracts=(list address)
=topics
=from=number:block
=to=number:block
==
=/ m (strand:strandio (list event-log:rpc:ethereum))
^- form:m
;< =json bind:m
%+ request-rpc url
:* `'logs by range'
%eth-get-logs
`number+from-number
`number+to-number
contracts
topics
==
%- pure:m
(parse-event-logs:rpc:ethereum json)
::
++ get-next-nonce
|= [url=@ta =address]
=/ m (strand:strandio ,@ud)
^- form:m
;< =json bind:m
%^ request-rpc url `'nonce'
[%eth-get-transaction-count address [%label %latest]]
%- pure:m
(parse-eth-get-transaction-count:rpc:ethereum json)
--