Merge branch 'naive/aggregator' into yu/azimuth-rpc

This commit is contained in:
yosoyubik 2021-05-21 12:34:33 +02:00
commit 6cdc3a1cae
5 changed files with 692 additions and 63 deletions

View File

@ -0,0 +1,497 @@
:: aggregator: Azimuth L2 roll aggregator
::
:: general flow is as described below, to ensure transactions actually go
:: through once we start sending it out, in the dumbest reasonable way.
::
:: periodic timer fires:
:: if there are no pending l2 txs, do nothing.
:: else kick off tx submission flow:
:: "freeze" pending txs, store alongside nonce, then increment nonce,
:: kick off thread for sending the corresponding l1 tx:
:: if nonce doesn't match on-chain expected nonce, bail.
:: if we can't afford the tx fee, bail.
:: construct, sign, submit the l1 tx.
:: if thread bailed, retry in five minutes.
:: if thread succeeded, retry in five minutes with higher gas price.
:: when retrying, only do so if l2 txs remain in the "frozen" txs group.
:: on %tx diff from naive, remove the matching tx from the frozen group.
::
::TODO remaining general work:
:: - hook up subscription to azimuth for %tx diffs
:: - hook up thread updates/results
:: - hook up timer callbacks
:: - cache state, upate after every azimuth %fact
:: - properly support private key changes
::
::TODO questions:
:: - it's a bit weird how we just assume the raw and tx in raw-tx to match...
::
/+ naive, default-agent, ethereum, dbug, verb
/= ttttt /tests/lib/naive ::TODO use new lib
::
::TODO /sur file for public types
|%
+$ state-0
$: %0
:: pending: the next l2 txs to be sent
:: sending: the l2 txs currently sending/awaiting l2 confirmation
::TODO should maybe key by [address nonce] instead. same for wires
:: finding: raw-tx-hash reverse lookup for sending map
:: next-nonce: next l1 nonce to use
::
pending=(list pend-tx)
sending=(map nonce:naive [next-gas-price=@ud txs=(list raw-tx:naive)])
finding=(map keccak $?(%confirmed %failed l1-tx-pointer))
next-nonce=@ud
::
:: pk: private key to send the roll
:: frequency: time to wait between sending batches (TODO fancier)
:: endpoint: ethereum rpc endpoint to use
::
pk=@
frequency=@dr
endpoint=@t
==
::
+$ keccak @ux
::
+$ tx-status
$: status=?(%unknown %pending %sending %confirmed %failed)
pointer=(unit l1-tx-pointer)
==
::
+$ l1-tx-pointer
$: =address:ethereum
nonce=@ud
==
::
::TODO cache sender address?
+$ pend-tx [force=? =raw-tx:naive]
::
+$ part-tx
$% [%raw raw=octs]
[%don =tx:naive]
[%ful raw=octs =tx:naive] ::TODO redundant?
==
::
+$ action
$% [%submit force=? sig=@ tx=part-tx]
[%cancel sig=@ keccak=@]
::
[%commit ~] ::TODO maybe pk=(unit @) later
[%config frequency=@dr]
[%setkey pk=@]
[%endpoint endpoint=@t]
[%nonce nonce=@ud]
::TODO contract address, chain..?
==
::
+$ card card:agent:gall
::
::TODO config?
:: ++ contract 0xb581.01cd.3bbb.cc6f.a40b.cdb0.4bb7.1623.b5c7.d39b :: Ropsten
:: TODO: add this to action
++ contract 0x4754.03bf.4e8e.b8d0.2f71.7b0e.553f.869f.a690.425e :: Local
++ chain-id 0x539 :: '1337' (Geth private chain)
::
++ resend-time ~m5
::
++ lverb &
--
::
=| state-0
=* state -
::
%- agent:dbug
%+ verb |
^- agent:gall
::
=<
|_ =bowl:gall
+* this .
do ~(. +> bowl)
def ~(. (default-agent this %|) bowl)
::
++ on-init
^- (quip card _this)
::TODO set default frequency and endpoint?
=. frequency ~h1
[~ this]
::
++ on-save !>(state)
++ on-load
|= old=vase
^- (quip card _this)
[~ this(state !<(state-0 old))]
::
++ on-poke
|= [=mark =vase]
^- (quip card _this)
=^ cards state
?+ mark (on-poke:def mark vase)
%aggregator-action
=+ !<(poke=action vase)
(on-action:do poke)
==
[cards this]
:: +on-peek: scry paths
::TODO reevaluate wrt recent flow changes
::
:: /x/pending -> %noun (list pend-tx)
:: /x/pending/[~ship] -> %noun (list pend-tx)
:: /x/pending/[0xadd.ress] -> %noun (list pend-tx)
:: /x/tx/[0xke.ccak]/status -> %noun tx-status
:: /x/nonce/[~ship]/[0xadd.ress] -> %atom @
::
++ on-peek
|= =path
^- (unit (unit cage))
?+ path ~
[%x %pending ~] ``noun+!>(pending)
::
[%x %pending @ ~]
=* wat i.t.t.path
?~ who=(slaw %p wat)
:: by-address
::
?~ wer=(slaw %ux wat)
[~ ~]
=; pending=(list pend-tx)
``noun+!>(pending)
%+ skim pending
|= pend-tx
::TODO deduce address from sig.raw-tx ?
!!
:: by-ship
::
=; pending=(list pend-tx)
``noun+!>(pending)
%+ skim pending
|= pend-tx
=(u.who ship.from.tx.raw-tx)
::
[%x %tx @ %status ~]
?~ keccak=(slaw %ux i.t.t.path)
[~ ~]
:+ ~ ~
:- %noun
!> ^- tx-status
?^ status=(~(get by finding) u.keccak)
?@ u.status [u.status ~]
[%sending status]
::TODO potentially slow!
=; known=?
[?:(known %pending %unknown) ~]
%+ lien pending
|= [* raw-tx:naive]
=(u.keccak (hash-tx raw))
::
[%x %nonce @ @ ~]
?~ who=(slaw %p i.t.t.path)
[~ ~]
=+ proxy=i.t.t.t.path
?. ?=(proxy:naive proxy)
[~ ~]
=/ [* nas=^state:naive] pending-state:do
::TODO or should we ~ when !(~(has by points.nas) who) ?
=/ =point:naive (~(gut by points.nas) u.who *point:naive)
=+ (proxy-from-point:naive proxy point)
``atom+!>(nonce)
==
::
++ on-arvo
|= [=wire =sign-arvo]
^- (quip card _this)
?+ +<.sign-arvo (on-arvo:def wire sign-arvo)
%wake =^(cards state on-timer:do [cards this])
==
::
++ on-fail
|= [=term =tang]
::TODO if crashed during timer, set new timer? how to detect?
(on-fail:def term tang)
::
++ on-watch on-watch:def
++ on-leave on-leave:def
++ on-agent
|= [=wire =sign:agent:gall]
^- (quip card _this)
~& wire+wire
?. ?=([%send @t *] wire)
(on-agent:def wire sign)
?- -.sign
%poke-ack
?~ p.sign
%- (slog leaf+"Thread started successfully" ~)
[~ this]
%- (slog leaf+"{(trip dap.bowl)} couldn't start thread" u.p.sign)
:_ this
[(leave:spider:do wire)]~
::
%watch-ack
?~ p.sign
[~ this]
=/ =tank leaf+"{(trip dap.bowl)} couldn't start listen to thread"
%- (slog tank u.p.sign)
[~ this]
::
%kick
[~ this]
::
%fact
?+ p.cage.sign (on-agent:def wire sign)
%thread-fail
=+ !<([=term =tang] q.cage.sign)
%- (slog leaf+"{(trip dap.bowl)} failed" leaf+<term> tang)
=^ cards state
(on-thread-result:do (rash i.t.wire dem) %.n^term)
[cards this]
::
%thread-done
~& ['all submitted to' t.wire]
:: is aggregator/send thread expected to maybe return an error?
=+ !<(result=(each @ud term) q.cage.sign)
=^ cards state
(on-thread-result:do (rash i.t.wire dem) result)
[cards this]
==
==
--
::
|_ =bowl:gall
::TODO /lib/sys.hoon?
++ sys
|%
++ b
|%
++ wait
|= [=wire =time]
^- card
[%pass wire %arvo %b %wait time]
--
--
::TODO /lib/spider.hoon?
++ spider
|%
++ start-thread
|= [=wire thread=term arg=vase]
^- (list card)
=/ tid=@ta (rap 3 thread '--' (scot %uv eny.bowl) ~)
:~ [%pass wire %agent [our.bowl %spider] %watch /thread-result/[tid]]
[%pass wire %agent [our.bowl %spider] %poke %spider-start !>([~ `tid thread arg])]
==
::
++ leave
|= =path
^- card
[%pass path %agent [our.bowl %spider] %leave ~]
--
::
++ hash-tx keccak-256:keccak:crypto
::
++ hash-raw-tx
|= =raw-tx:naive
(hash-tx raw.raw-tx)
::
++ part-tx-to-full
|= =part-tx
^- [octs tx:naive]
?+ -.part-tx !!
:: %raw [+.part-tx (decode-tx:naive +.part-tx)]
:: %don [(encode-tx:naive +.part-tx) +.part-tx]
%ful +.part-tx
==
:: +pending-state
::
:: derives tentative state from pending txs and canonical state,
:: discarding invalid pending txs in the process.
::
::TODO maybe want to cache locally, refresh on %fact from azimuth?
::
++ pending-state
^- (quip pend-tx ^state:naive)
:: load current, canonical state
::
=+ .^ nas=^state:naive
%gx
(scot %p our.bowl)
%azimuth
(scot %da now.bowl)
/nas/nas
==
:: apply our pending transactions
::TODO should also apply txs from sending map!
::
=| valid=_pending
|- ^+ [valid nas]
?~ pending [(flop valid) nas]
::
=^ gud=? nas (try-apply nas i.pending)
=? valid gud [i.pending valid]
$(pending t.pending)
:: +try-apply:
::
++ try-apply
|= [nas=^state:naive force=? =raw-tx:naive]
^- [success=? _nas]
?. (verify-sig-and-nonce:naive verifier:ttttt `@`chain-id nas raw-tx)
[force nas]
::
=^ out points.nas (increment-nonce:naive nas from.tx.raw-tx)
::
?~ nex=(receive-tx:naive nas tx.raw-tx)
[force nas]
[& +.u.nex]
::
++ on-action
|= =action
^- (quip card _state)
?- -.action
%commit on-timer
%config [~ state(frequency frequency.action)]
%nonce [~ state(next-nonce nonce.action)]
%endpoint [~ state(endpoint endpoint.action)]
::
%setkey
::TODO what about existing sending entries?
:- ~
?~ pk=(de:base16:mimes:html pk.action)
state
state(pk q.u.pk)
::
%submit
=^ success state
^- [? _state]
%^ take-tx
force.action
sig.action
(part-tx-to-full tx.action)
:: TODO: consider failure case
?> success
[~ state]
::
%cancel
!! ::TODO
==
:: +take-tx: accept submitted l2 tx into the :pending list
::TODO rewrite
::
++ take-tx
|= [force=? =raw-tx:naive]
^- [success=? _state]
=/ [nep=_pending nas=^state:naive] pending-state
=| success=?
:: TODO: actually use try-apply when proper Tx signing in place
::
:: =^ success nas
:: (try-apply nas force raw-tx)
::TODO want to notify about dropped pendings, or no? client prolly polls...
=? pending success (snoc nep [force raw-tx])
::TODO cache nas?
[success state]
:: +set-timer: %wait until next whole :frequency
::
++ set-timer
^- card
%+ wait:b:sys /timer
(mul +((div now.bowl frequency)) frequency)
:: +on-timer: every :frequency, freeze :pending txs roll and start sending it
::
++ on-timer
^- (quip card _state)
=^ cards state
?: =(~ pending) [~ state]
=/ nonce=@ud next-nonce
=: pending ~
next-nonce +(next-nonce)
::
sending
%+ ~(put by sending) nonce
[0 (turn pending tail)]
==
[(send-roll nonce) state]
[[set-timer cards] state]
:: +send-roll: start thread to submit roll from :sending to l1
::
++ send-roll
|= nonce=@ud
^- (list card)
:: if this nonce isn't in the sending queue anymore, it's done
::
?. (~(has by sending) nonce)
~? lverb [dap.bowl %done-sending nonce]
~
:: start the thread, passing in the l2 txs to use
::
::TODO should go ahead and set resend timer in case thread hangs, or nah?
%+ start-thread:spider
/send/(scot %ud nonce)
:- %aggregator-send
!>
:* endpoint
contract
chain-id
pk
nonce
(~(got by sending) nonce)
==
:: +on-thread-result: await resend after thread success or failure
::
++ on-thread-result
|= [nonce=@ud result=(each @ud term)]
^- (quip card _state)
:: update gas price for this tx in state
::
=? sending ?=(%& -.result)
%+ ~(jab by sending) nonce
(cork tail (lead p.result))
:: print error if there was one
::
~? ?=(%| -.result) [dap.bowl %send-error p.result]
:: resend the l1 tx in five minutes
::
:_ state
[(wait:b:sys /resend/(scot %ud nonce) (add resend-time now.bowl))]~
:: +on-naive-diff: process l2 tx confirmations
::
++ on-naive-diff
|= =diff:naive
^- (quip card _state)
?. ?=(%tx -.diff)
[~ state]
=/ =keccak (hash-raw-tx raw-tx.diff)
?~ wer=(~(get by finding) keccak)
[~ state]
:: if we had already seen the tx, no-op
::
?@ u.wer
~? &(?=(%confirmed u.wer) ?=(~ err.diff))
[dap.bowl %weird-double-confirm from.tx.raw-tx.diff]
[~ state]
=* nonce nonce.u.wer
:: remove the tx from the sending map
::
=. sending
?~ sen=(~(get by sending) nonce)
~& [dap.bowl %weird-double-remove]
sending
?~ nin=(find [raw-tx.diff]~ txs.u.sen)
~& [dap.bowl %weird-unknown]
sending
=. txs.u.sen (oust [u.nin 1] txs.u.sen)
?~ txs.u.sen
~? lverb [dap.bowl %done-with-nonce nonce]
(~(del by sending) nonce)
(~(put by sending) nonce u.sen)
:: update the finding map with the new status
::
=. finding
%+ ~(put by finding) keccak
?~ err.diff %confirmed
:: if we kept the forced flag around for longer, we could notify of
:: unexpected tx failures here. would that be useful? probably not?
:: ~? !forced [dap.bowl %aggregated-tx-failed-anyway err.diff]
%failed
[~ state]
::
--

View File

@ -36,7 +36,7 @@
?~ data=(~(get by params) 'data') ~
%. u.data
=, dejs-soft:format
%- ot
%- ot
:~ ['encrypt' so]
['auth' so]
['crypto-suite' so]
@ -55,7 +55,7 @@
(some [u.add.u.ans r.u.ans])
%. u.data
=, dejs-soft:format
%- ot
%- ot
~[['address' (cu to-hex so)] ['reset' bo]]
::
++ address-ship
@ -68,7 +68,7 @@
(some [ship.u.ans u.add.u.ans])
%. u.data
=, dejs-soft:format
%- ot
%- ot
:~ ['ship' (su ;~(pfix sig fed:ag))]
['address' (cu to-hex so)]
==
@ -82,7 +82,7 @@
=, dejs-soft:format
%. u.data
(ot ['address' (cu to-hex so)]~)
::
::
++ ship
|= params=(map @t json)
^- (unit @p)
@ -91,15 +91,15 @@
%. u.data
(ot ['ship' (su ;~(pfix sig fed:ag))]~)
--
::
::
++ ship
|= params=(map @t json)
^- (unit @p)
?~ data=(~(get by params) 'ship') ~
=, dejs-soft:format
%. u.data
(su ;~(pfix sig fed:ag))
::
(su ;~(pfix sig fed:ag))
::
++ address
|= params=(map @t json)
^- (unit @ux)
@ -124,11 +124,11 @@
^- (unit [@p proxy:naive])
?~ from=(~(get by params) 'from') ~
=, dejs-soft:format
%. u.from
%. u.from
%- ot
:~ ['ship' (su ;~(pfix sig fed:ag))]
['proxy' (cu proxy:naive so)]
==
==
::
++ keccak
|= params=(map @t json)
@ -144,8 +144,8 @@
^- (unit octs)
?~ raw=(~(get by params) 'raw') ~
=; ans=(unit (unit @ux))
?~ ans ~
?~ u.ans ~
?~ ans ~
?~ u.ans ~
(some (as-octs:mimes:html u.u.ans))
=, dejs-soft:format
((cu to-hex so) u.raw)
@ -210,7 +210,7 @@
++ en-address |=(a=@ux address+s+(crip "0x{((x-co:co 20) a)}"))
++ en-spawn |=([s=@p a=@ux] ~[(en-ship s) (en-address a)])
++ en-transfer |=([a=@ux r=?] ~[(en-address a) reset+b+r])
++ en-keys
++ en-keys
|= [encrypt=@ auth=@ crypto-suite=@ breach=?]
^- (list [@t json])
:~ ['encrypt' (numb encrypt)]
@ -232,7 +232,7 @@
%- pairs
:~ ['dominion' s+dominion.point]
::
:- 'ownership'
:- 'ownership'
%- pairs
=* own own.point
^- (list [@t json])
@ -264,16 +264,16 @@
?~ escape.net ~
['escape' (ship u.escape.net)]~
== ==
::
::
++ ownership
|= [=address:naive =nonce:naive]
^- json
=, enjs:format
=, enjs:format
%- pairs
:~ ['address' s+(crip "0x{((x-co:co 20) address)}")]
['nonce' (numb nonce)]
==
::
::
++ tx-status
|= =^tx-status
^- json
@ -281,8 +281,8 @@
%- pairs
:~ ['status' s+status.tx-status]
::
:- 'tx'
?~ tx.tx-status ~
:- 'tx'
?~ tx.tx-status ~
s+(crip "0x{((x-co:co 20) u.tx.tx-status)}")
==
--
@ -291,7 +291,7 @@
|= =cord
^- (unit @ux)
=/ parsed=(unit (pair @ud @ux)) (de:base16:mimes:html cord)
?~ parsed
?~ parsed
::~|(%non-hex-cord !!)
~
(some q.u.parsed)
@ -307,10 +307,10 @@
=/ from=(unit [@p proxy:naive]) (from:from-json params)
=/ raw=(unit octs) (raw:from-json params)
=/ data=(unit @p) (ship:data:from-json params)
?. &(?=(^ sig) ?=(^ from) ?=(^ raw) ?=(^ data))
?. &(?=(^ sig) ?=(^ from) ?=(^ raw) ?=(^ data))
[~ ~(parse error id)]
:_ [%result id s+'ok']
%- some
:_ [%result id s+'ok']
%- some
:- %aggregator-action
!>
=; =skim-tx:naive
@ -326,16 +326,16 @@
++ proxy
|= [id=@t params=(map @t json) action=proxy-action]
^- [(unit cage) response:rpc]
?. (params:validate params)
?. (params:validate params)
[~ ~(params error id)]
=/ sig=(unit @ux) (sig:from-json params)
=/ from=(unit [@p proxy:naive]) (from:from-json params)
=/ raw=(unit octs) (raw:from-json params)
=/ data=(unit @ux) (address:data:from-json params)
?. &(?=(^ sig) ?=(^ from) ?=(^ raw) ?=(^ data))
?. &(?=(^ sig) ?=(^ from) ?=(^ raw) ?=(^ data))
[~ ~(parse error id)]
:_ [%result id s+'ok']
%- some
%- some
:- %aggregator-action
!>
=; =skim-tx:naive
@ -358,70 +358,70 @@
++ internal [%error id '-32603' 'Internal error']
++ not-found [%error id '-32000' 'Resource not found']
--
::
::
++ validate
|%
++ params
|= params=(map @t json)
^- ?
=((lent ~(tap by params)) 4)
=((lent ~(tap by params)) 4)
--
--
|%
++ get-point
|= [id=@t params=(map @t json) scry=$-(ship (unit point:naive))]
^- response:rpc
?. =((lent ~(tap by params)) 1)
?. =((lent ~(tap by params)) 1)
~(params error id)
?~ ship=(~(get by params) 'ship')
?~ ship=(~(get by params) 'ship')
~(params error id)
?~ ship=(rush (so:dejs:format u.ship) ;~(pfix sig fed:ag))
~(params error id)
?~ point=(scry u.ship)
?~ point=(scry u.ship)
~(params error id)
[%result id (point:to-json u.point)]
::
++ transfer-point
|= [id=@t params=(map @t json)]
^- [(unit cage) response:rpc]
?. (params:validate params)
?. (params:validate params)
[~ ~(params error id)]
=/ sig=(unit @ux) (sig:from-json params)
=/ from=(unit [ship @t]) (from:from-json params)
=/ raw=(unit octs) (raw:from-json params)
=/ data=(unit [@ux ?]) (address-transfer:data:from-json params)
?: |(?=(~ sig) ?=(~ from) ?=(~ raw) ?=(~ data))
?: |(?=(~ sig) ?=(~ from) ?=(~ raw) ?=(~ data))
[~ ~(parse error id)]
:_ [%result id s+'ok']
%- some
%- some
noun+!>([u.sig u.from u.data])
::
++ configure-keys
|= [id=@t params=(map @t json)]
^- [(unit cage) response:rpc]
?. (params:validate params)
?. (params:validate params)
[~ ~(params error id)]
=/ sig=(unit @ux) (sig:from-json params)
=/ from=(unit [ship @t]) (from:from-json params)
=/ raw=(unit octs) (raw:from-json params)
=/ data=(unit [encrypt=@ auth=@ crypto-suite=@ breach=?])
(keys:data:from-json params)
?. &(?=(^ sig) ?=(^ from) ?=(^ raw) ?=(^ data))
?. &(?=(^ sig) ?=(^ from) ?=(^ raw) ?=(^ data))
[~ ~(parse error id)]
:_ [%result id s+'ok']
%- some
%- some
noun+!>([u.sig u.from u.data])
::
++ spawn
|= [id=@t params=(map @t json)]
^- [(unit cage) response:rpc]
?. (params:validate params)
?. (params:validate params)
[~ ~(params error id)]
=/ sig=(unit @ux) (sig:from-json params)
=/ from=(unit [@p proxy:naive]) (from:from-json params)
=/ raw=(unit octs) (raw:from-json params)
=/ data=(unit [@p @ux]) (address-ship:data:from-json params)
?. &(?=(^ sig) ?=(^ from) ?=(^ raw) ?=(^ data))
?. &(?=(^ sig) ?=(^ from) ?=(^ raw) ?=(^ data))
[~ ~(parse error id)]
:_ [%result id s+'ok']
%- some
@ -440,11 +440,11 @@
++ read-nonce
|= [id=@t params=(map @t json) scry=$-([ship proxy:naive] (unit @))]
^- response:rpc
?. =((lent ~(tap by params)) 3)
?. =((lent ~(tap by params)) 3)
~(params error id)
?~ from=(from:from-json params)
~(parse error id)
?~ nonce=(scry u.from)
?~ nonce=(scry u.from)
~(params error id)
[%result id (numb:enjs:format u.nonce)]
::
@ -454,10 +454,10 @@
|%
:: - readPendingRoll() -> (list pend-tx)
::
++ all
++ all
|= [id=@t params=(map @t json) pending=(list pend-tx)]
^- response:rpc
?. =((lent ~(tap by params)) 0)
?. =((lent ~(tap by params)) 0)
~(params error id)
[%result id (pending:to-json pending)]
:: - readPendingByShip(ship) -> (list pend-tx)
@ -465,7 +465,7 @@
++ ship
|= [id=@t params=(map @t json) scry=$-(@p (list pend-tx))]
^- response:rpc
?. =((lent ~(tap by params)) 1)
?. =((lent ~(tap by params)) 1)
~(params error id)
?~ ship=(ship:from-json params)
~(parse error id)
@ -475,7 +475,7 @@
++ addr
|= [id=@t params=(map @t json) scry=$-(@ux (list pend-tx))]
^- response:rpc
?. =((lent ~(tap by params)) 1)
?. =((lent ~(tap by params)) 1)
~(params error id)
?~ address=(address:from-json params)
~(parse error id)
@ -485,21 +485,21 @@
++ status
|= [id=@t params=(map @t json) scry=$-(@ tx-status)]
^- response:rpc
?. =((lent ~(tap by params)) 1)
?. =((lent ~(tap by params)) 1)
~(params error id)
?~ keccak=(keccak:from-json params)
~(parse error id)
[%result id (tx-status:to-json (scry u.keccak))]
::
:: ++ history
:: |= $: id=@t
:: params=(map @t json)
:: |= $: id=@t
:: params=(map @t json)
:: :: FIXME: use proper type from aggregator/index
:: ::
:: scry=$-([@p proxy:naive] (list tx:naive))
:: ==
:: ^- response:rpc
:: ?. =((lent ~(tap by params)) 1)
:: ?. =((lent ~(tap by params)) 1)
:: ~(params error id)
:: ?~ from=(from:from-json params)
:: ~(parse error id)

View File

@ -108,16 +108,25 @@
++ parse-one-response
|= =json
^- (unit response:rpc)
=/ res=(unit [@t ^json])
?. &(?=([%o *] json) (~(has by p.json) 'error'))
=/ res=(unit [@t ^json])
%. json
=, dejs-soft:format
(ot id+so result+some ~)
?~ res ~
`[%result u.res]
~| parse-one-response=json
=/ error=(unit [id=@t ^json code=@ta mssg=@t])
%. 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 ~) ~)
:: A 'result' member is present in the error
:: response when using ganache, even though
:: that goes against the JSON-RPC spec
::
(ot id+so result+some error+(ot code+no message+so ~) ~)
?~ error ~
=* err u.error
`[%error id.err code.err mssg.err]
--
::
:: +read-contract: calls a read function on a contract, produces result hex

View File

@ -333,6 +333,17 @@
[other batch]
--
::
++ proxy-from-point
|= [=proxy point]
^- [=address =nonce]
?- proxy
%own owner.own
%spawn spawn-proxy.own
%manage management-proxy.own
%vote voting-proxy.own
%transfer transfer-proxy.own
==
::
++ verify-sig-and-nonce
|= [=verifier chain-t=@t =state =raw-tx]
^- ?
@ -340,13 +351,7 @@
=/ point (get-point state ship.from.tx.raw-tx)
?> ?=(^ point) :: we never parse more than four bytes for a ship
=/ need=[=address =nonce]
?- proxy.from.tx.raw-tx
%own owner.own.u.point
%spawn spawn-proxy.own.u.point
%manage management-proxy.own.u.point
%vote voting-proxy.own.u.point
%transfer transfer-proxy.own.u.point
==
(proxy-from-point proxy.from.tx.raw-tx u.point)
:: We include a domain separator to avoid letting signatures be
:: accidentally reused with other applications. We include the name
:: UrbitID, a signature format version number, and the EIP-155 chain

View File

@ -0,0 +1,118 @@
:: aggregator/send: send rollup tx
::
/- rpc=json-rpc
/+ naive, ethereum, ethio, strandio
::
=/ gas-limit=@ud 30.000 ::TODO verify, maybe scale with roll size
=/ batch-function=octs
%- as-octs:mimes:html
%+ rsh [3 28]
(keccak-256:keccak:crypto (as-octs:mimes:html 'batch(bytes)'))
::
|= args=vase
=+ !< $: endpoint=@t
contract=address:ethereum
chain-id=@
pk=@
::
nonce=@ud
next-gas-price=@ud
txs=(list raw-tx:naive)
==
args
=/ m (strand:strandio ,vase)
|^
^- form:m
=* not-sent (pure:m !>(%.y^next-gas-price))
::
=/ =address:ethereum (address-from-prv:key:ethereum pk)
;< expected-nonce=@ud bind:m
(get-next-nonce:ethio endpoint address)
:: if chain expects a different nonce, don't send this transaction
::
?. =(nonce expected-nonce)
not-sent
:: if a gas-price of 0 was specified, fetch the recommended one
::
;< use-gas-price=@ud bind:m
?: =(0 next-gas-price) fetch-gas-price
(pure:(strand:strandio @ud) next-gas-price)
:: if we cannot pay for the transaction, don't bother sending it out
::
=/ max-cost=@ud (mul gas-limit use-gas-price)
::TODO implement %eth-get-balance in /lib/ethio and /lib/ethereum
:: ;< balance=@ud bind:m
:: !!
:: ?: (gth max-cost balance)
:: ~& [%insufficient-aggregator-balance address]
:: not-sent
::
=/ tx-data=@ux
%+ can:naive 3
:_ [batch-function ~]
=; =cord
=/ parsed=(unit (pair @ud @ux))
(de:base16:mimes:html cord)
?~(parsed !! u.parsed)
%- crip
%- encode-args:abi:ethereum
:_ ~
:- %bytes
%+ cad:naive 3
%+ roll txs
|= [=raw-tx:naive out=(list octs)]
[raw.raw-tx 65^sig.raw-tx out]
=/ tx=@ux
=; tx=transaction:rpc:ethereum
(sign-transaction:key:ethereum tx pk)
:* nonce
use-gas-price
gas-limit
contract
0
tx-data
chain-id
==
::
::NOTE this fails the thread if sending fails, which in the app gives us
:: the "retry with same gas price" behavior we want
;< jon=json bind:m
%+ request-rpc:ethio endpoint
[`'sendRawTransaction' %eth-send-raw-transaction tx]
::TODO check that tx-hash in jon is non-zero?
::TODO enforce max here, or in app?
:: add five gwei to gas price of next attempt
::
(pure:m !>(%.y^(add use-gas-price 5.000.000.000)))
::
::TODO should be distilled further, partially added to strandio?
++ fetch-gas-price
=/ m (strand:strandio @ud) ::NOTE return in wei
^- form:m
=/ =request:http
:* method=%'GET'
url='https://api.etherscan.io/api?module=gastracker&action=gasoracle'
header-list=~
~
==
;< ~ bind:m
(send-request:strandio request)
;< rep=(unit client-response:iris) bind:m
take-maybe-response:strandio
=* fallback
~& %fallback-gas-price
(pure:m 40.000.000.000) ::TODO maybe even lower, considering we increment?
?. ?& ?=([~ %finished *] rep)
?=(^ full-file.u.rep)
==
fallback
?~ jon=(de-json:html q.data.u.full-file.u.rep)
fallback
=; res=(unit @ud)
?~ res fallback
%- pure:m
(mul 1.000.000.000 u.res) ::NOTE gwei to wei
%. u.jon
=, dejs-soft:format
(ot 'result'^(ot 'FastGasPrice'^ni ~) ~)
--