diff --git a/pkg/arvo/app/azimuth-rpc.hoon b/pkg/arvo/app/azimuth-rpc.hoon index a9ce4340ae..8329712332 100644 --- a/pkg/arvo/app/azimuth-rpc.hoon +++ b/pkg/arvo/app/azimuth-rpc.hoon @@ -148,6 +148,7 @@ %get-point `(get-point id +.params point:scry) %get-dns `(get-dns id +.params dns:scry) %get-naive-state `(get-naive id +.params naive-state:scry) + %get-refresh `(get-refresh id +.params refresh:scry) == -- :: @@ -171,5 +172,11 @@ %gx (~(scry agentio bowl) %azimuth /nas/noun) == + :: + ++ refresh + .^ @dr + %gx + (~(scry agentio bowl) %azimuth /refresh/noun) + == -- -- diff --git a/pkg/arvo/app/azimuth.hoon b/pkg/arvo/app/azimuth.hoon index bca549e523..4e47d5ed9b 100644 --- a/pkg/arvo/app/azimuth.hoon +++ b/pkg/arvo/app/azimuth.hoon @@ -43,6 +43,9 @@ +$ tagged-diff [=id:block diff:naive] +$ network ?(%mainnet %ropsten %local) +$ card card:agent:gall +:: TODO: add to state? +:: +++ refresh ~m5 -- :: =| state=app-state @@ -59,7 +62,14 @@ ^- (quip card _this) =. net.state %local :_ this - [%pass /eth-watcher %agent [our.bowl %eth-watcher] %watch /logs/[dap.bowl]]~ + :_ ~ + :* %pass + /eth-watcher + %agent + [our.bowl %eth-watcher] + %watch + /logs/[dap.bowl] + == :: ++ on-save !>(state) ++ on-load @@ -140,6 +150,7 @@ [%x %nas ~] ``noun+!>(nas.state) [%x %dns ~] ``noun+!>(dns.nas.state) [%x %own ~] ``noun+!>(own.state) + [%x %refresh ~] ``atom+!>(refresh) [%x %point @ ~] ``noun+(point i.t.t.path) == :: @@ -319,7 +330,7 @@ =/ args=vase !> :+ %watch /[dap.bowl] ^- config:eth-watcher - :* url.state =(%czar (clan:title our.bowl)) ~m5 ~h30 + :* url.state =(%czar (clan:title our.bowl)) refresh ~h30 (max launch.net last-snap) ~[azimuth.net] ~[naive.net] diff --git a/pkg/arvo/app/roller-rpc.hoon b/pkg/arvo/app/roller-rpc.hoon index b741c15c75..f1507c20ed 100644 --- a/pkg/arvo/app/roller-rpc.hoon +++ b/pkg/arvo/app/roller-rpc.hoon @@ -145,7 +145,7 @@ [~ ~(parse error:json-rpc id)] =/ method=@tas (enkebab method) ?: ?=(l2-tx method) - (process-rpc id +.params method) + (process-rpc id +.params method over-quota:scry) ?+ method [~ ~(method error:json-rpc id)] %get-point `(get-point id +.params point:scry) %get-ships `(get-ships id +.params ships:scry) @@ -302,6 +302,8 @@ == :: ++ config + ^- [azimuth-config roller-config] + :- refresh .^ roller-config %gx %+ ~(scry agentio bowl) @@ -322,5 +324,18 @@ %gx (~(scry agentio bowl) %roller /predicted/noun) == + :: + ++ refresh + .^ @dr + %gx + (~(scry agentio bowl) %azimuth /refresh/noun) + == + :: + ++ over-quota + |= =ship + .^ ? + %gx + (~(scry agentio bowl) %roller /over-quota/(scot %p ship)/atom) + == -- -- diff --git a/pkg/arvo/app/roller.hoon b/pkg/arvo/app/roller.hoon index 3466331be6..a12b4b8175 100644 --- a/pkg/arvo/app/roller.hoon +++ b/pkg/arvo/app/roller.hoon @@ -30,39 +30,49 @@ verb :: |% -+$ state-0 - $: %0 ++$ app-state + $: %1 :: pending: the next l2 txs to be sent :: sending: l2 txs awaiting l2 confirmation, ordered by nonce :: finding: sig+raw-tx hash reverse lookup for txs in sending map :: history: status of l2 txs by ethereum address, timestamp sorted + :: ship-quota: number of txs submited per ship in the current slice :: next-nonce: next l1 nonce to use :: next-batch: when then next l2 batch will be sent :: pre: predicted l2 state :: own: ownership of azimuth points - :: derive: deferred derivation of predicted/ownership state :: pending=(list pend-tx) sending=(tree [l1-tx-pointer send-tx]) finding=(map keccak ?(%confirmed %failed [=time l1-tx-pointer])) history=(map address:ethereum (tree hist-tx)) + ship-quota=(map ship @ud) next-nonce=(unit @ud) next-batch=time pre=^state:naive own=owners - derive=? :: :: pk: private key to send the roll + :: quota: max numbers of transactions per unit of time (slice) + :: slice: unit of time where txs are allowed to be added to pending + :: derive: defer derivation of predicted/ownership state :: frequency: time to wait between sending batches (TODO fancier) :: endpoint: ethereum rpc endpoint to use :: contract: ethereum contract address :: chain-id: mainnet, ropsten, local (https://chainid.network/) + :: resend-time: time to resend a batch with higher gas prie + :: update-rate: frequency to update the roller's predicted state :: pk=@ + slice=@dr + quota=@ud + derive=? frequency=@dr endpoint=(unit @t) contract=@ux chain-id=@ + resend-time=@dr + update-rate=@dr == :: orp: ordered points in naive state by parent ship :: @@ -73,13 +83,6 @@ :: orh: ordered tx history by (decreasing) timestamp :: ++ orh ((on time roll-tx) gth) -+$ net ?(%mainnet %ropsten %local) -:: -+$ config - $% [%frequency frequency=@dr] - [%setkey pk=@] - [%endpoint endpoint=@t =net] - == :: +$ action $% :: we need to include the address in submit so pending txs show up @@ -98,14 +101,10 @@ :: +$ card card:agent:gall :: -:: TODO: add to config -:: -++ resend-time ~m5 -:: ++ lverb & -- :: -=| state-0 +=| app-state =* state - :: %- agent:dbug @@ -120,12 +119,18 @@ :: ++ on-init ^- (quip card _this) - =. frequency ~h1 - =. contract naive:local-contracts:azimuth - =. chain-id chain-id:local-contracts:azimuth - =^ card next-batch set-timer + =: frequency ~h1 + quota 7 + slice ~d7 + resend-time ~m5 + update-rate ~m5 + contract naive:local-contracts:azimuth + chain-id chain-id:local-contracts:azimuth + == + =^ card next-batch set-roller:timer :_ this :~ card + (set-quota:timer slice) [%pass /azimuth-events %agent [our.bowl %azimuth] %watch /event] == :: @@ -133,7 +138,50 @@ ++ on-load |= old=vase ^- (quip card _this) - [~ this(state !<(state-0 old))] + =| cards=(list card) + :: new additions to app-state + :: + =| ship-quota=(map ship @ud) + =/ slice=@dr ~d7 + =/ quota=@ud 7 + =/ resend-time=@dr ~m5 + =/ update-rate=@dr ~m5 + |^ + =+ !<(old-state=app-states old) + =? cards ?=(%0 -.old-state) + [(set-quota:timer slice)]~ + =? old-state ?=(%0 -.old-state) + ^- app-state + =, old-state + :* %1 + pending sending finding history + ship-quota next-nonce next-batch + pre own pk slice quota derive + frequency endpoint contract chain-id + resend-time update-rate + == + ?> ?=(%1 -.old-state) + [cards this(state old-state)] + :: + ++ app-states $%(state-0 app-state) + ++ state-0 + $: %0 + pending=(list pend-tx) + sending=(tree [l1-tx-pointer send-tx]) + finding=(map keccak ?(%confirmed %failed [=time l1-tx-pointer])) + history=(map address:ethereum (tree hist-tx)) + next-nonce=(unit @ud) + next-batch=time + pre=^state:naive + own=owners + derive=? + pk=@ + frequency=@dr + endpoint=(unit @t) + contract=@ux + chain-id=@ + == + -- :: ++ on-poke |= [=mark =vase] @@ -166,6 +214,9 @@ :: /x/voting/[0xadd.ress] -> %noun (list ship) :: /x/spawning/[0xadd.ress] -> %noun (list ship) :: /x/predicted -> %noun state:naive + :: /x/quota -> %atom @ud + :: /x/slice -> %atom @dr + :: /x/over-quota/[~ship] -> %atom ? :: ++ on-peek |= =path @@ -191,6 +242,9 @@ [%x %voting @ ~] (points-proxy %vote i.t.t.path) [%x %spawning @ ~] (points-proxy %spawn i.t.t.path) [%x %predicted ~] ``noun+!>(pre) + [%x %quota ~] ``atom+!>(quota) + [%x %slice ~] ``atom+!>(slice) + [%x %over-quota @ ~] (over-quota i.t.t.path) == :: ++ pending-by @@ -268,13 +322,8 @@ !> ^- (unit @) ?~ point=(get:orp points.pre u.who) ~ - =/ nonce=@ - =< nonce - (proxy-from-point:naive proxy u.point) - %- some - %+ roll pending - |= [pend-tx nonce=_nonce] - ?:(=([u.who proxy] from.tx.raw-tx) +(nonce) nonce) + =< `nonce + (proxy-from-point:naive proxy u.point) :: ++ spawned |= wat=@t @@ -334,8 +383,11 @@ :* next-batch frequency resend-time + update-rate contract chain-id + slice + quota == :: ++ points-proxy @@ -346,6 +398,13 @@ ?~ addr=(slaw %ux wat) ~ ~(tap in (~(get ju own) [proxy u.addr])) + :: + ++ over-quota + |= wat=@t + ?~ who=(slaw %p wat) [~ ~] + =/ [exceeded=? *] (quota-exceeded u.who) + ``atom+!>(exceeded) + :: -- :: ++ on-arvo @@ -356,8 +415,13 @@ ?+ +<.sign-arvo (on-arvo:def wire sign-arvo) %wake =^(cards state on-timer:do [cards this]) == + [%quota-timer ~] + ?+ +<.sign-arvo (on-arvo:def wire sign-arvo) + %wake =^(cards state on-quota-timer:do [cards this]) + == :: [%predict ~] + ~& "predicting" ?+ +<.sign-arvo (on-arvo:def wire sign-arvo) %wake =. own.state canonical-owners:do @@ -673,8 +737,8 @@ == :: +predicted-state :: -:: derives predicted state from applying pending/sending txs to -:: the canonical state, discarding invalid txs in the process. +:: derives predicted state from applying pending & sending txs to +:: the provided naive state, discarding invalid txs in the process :: ++ predicted-state |= nas=^state:naive @@ -750,15 +814,16 @@ [time roll-tx(status %failed)] =? finding !gud (~(put by finding) keccak %failed) $(txs t.txs, ups (weld ups nups)) - :: - ++ try-apply - |= [nas=^state:naive force=? =raw-tx:naive] - ^- [[? ups=(list update)] _state] - =/ [success=? predicted=_nas ups=(list update) owners=_own] - (apply-raw-tx:dice force raw-tx nas own chain-id) - :- [success ups] - state(pre predicted, own owners) -- +:: +try-apply: maybe apply the given l2 tx to the naive state +:: +++ try-apply + |= [nas=^state:naive force=? =raw-tx:naive] + ^- [[? ups=(list update)] _state] + =/ [success=? predicted=_nas ups=(list update) owners=_own] + (apply-raw-tx:dice force raw-tx nas own chain-id) + :- [success ups] + state(pre predicted, own owners) :: ++ get-l1-address |= [=tx:naive nas=^state:naive] @@ -789,7 +854,11 @@ |= =config ^- (quip card _state) ?- -.config - %frequency [~ state(frequency frequency.config)] + %frequency [~ state(frequency frequency.config)] + %resend-time [~ state(resend-time time.config)] + %update-rate [~ state(update-rate rate.config)] + %slice [~ state(slice slice.config)] + %quota [~ state(quota quota.config)] :: %endpoint :- ~ @@ -865,22 +934,50 @@ ++ take-tx |= =pend-tx ^- (quip card _state) - =. pending (snoc pending pend-tx) - =^ cards history (update-history [pend-tx]~ %pending) + =* ship ship.from.tx.raw-tx.pend-tx + =/ [exceeded=? next-quota=@] (quota-exceeded ship) + ?: exceeded [~ state] + =: pending (snoc pending pend-tx) + ship-quota (~(put by ship-quota) ship next-quota) + == + =^ [gud=? cards-1=(list update)] state + (try-apply pre force.pend-tx raw-tx.pend-tx) + ?. gud + :_ state + :: %point and (%failed) %tx updates + :: + (emit cards-1) + =^ cards-2 history + (update-history [pend-tx]~ %pending) :: toggle derivation :: :_ state(derive ?:(derive | derive)) - %+ weld (emit cards) - ?. derive ~ - :: derive predicted state in 1m. + ;: welp + (emit cards-1) :: %point updates + (emit cards-2) :: %tx updates :: - [(wait:b:sys /predict (add ~m1 now.bowl))]~ -:: +set-timer: %wait until next whole :frequency + ?. derive ~ + :: defer updating predicted/ownership state from canonical + :: + [(wait:b:sys /predict (add update-rate now.bowl))]~ + == :: -++ set-timer - ^- [=card =time] - =+ time=(mul +((div now.bowl frequency)) frequency) - [(wait:b:sys /timer time) time] +++ timer + |% + :: +set-roller: %wait until next whole :frequency + :: + ++ set-roller + ^- [=card =time] + =+ time=(mul +((div now.bowl frequency)) frequency) + [(wait:b:sys /timer time) time] + :: +set-roller: %wait until next whole :slice + :: + ++ set-quota + |= slice=@dr + ^- card + =+ time=(mul +((div now.bowl slice)) slice) + (wait:b:sys /quota-timer time) + -- :: +on-timer: every :frequency, freeze :pending txs roll and start sending it :: ++ on-timer @@ -924,8 +1021,14 @@ (emit updates-2) (send-roll get-address nonce) == - =^ card next-batch set-timer + =^ card next-batch set-roller:timer [[card cards] state] +:: +on-quota-timer: resets tx quota for all ships +:: +++ on-quota-timer + ^- (quip card _state) + :- [(set-quota:timer slice)]~ + state(ship-quota *(map ship @ud)) :: ++ update-history |= [txs=(list pend-tx) =status] @@ -952,6 +1055,13 @@ ^- (list card) ?~ endpoint ~?(lverb [dap.bowl %no-endpoint] ~) (start-thread:spider wire [%roller-nonce !>([u.endpoint pk])]) +:: +++ quota-exceeded + |= =ship + ^- [? @ud] + ?~ quota=(~(get by ship-quota) ship) + [| 1] + [(gte u.quota quota.state) +(u.quota)] :: +out-of-sync: checks if the previous nonce has been sent :: ++ out-of-sync @@ -1108,9 +1218,9 @@ :_ state(derive ?:(derive | derive)) %+ weld cards ?. derive ~ - :: derive predicted/ownership state in 1m. + :: defer updating predicted/ownership state from canonical :: - [(wait:b:sys /predict (add ~m1 now.bowl))]~ + [(wait:b:sys /predict (add update-rate now.bowl))]~ :: ?: ?=(%point -.diff) [~ state] ?> ?=(%tx -.diff) diff --git a/pkg/arvo/gen/roller/commit.hoon b/pkg/arvo/gen/roller/commit.hoon index d2b627717c..f04a62e111 100644 --- a/pkg/arvo/gen/roller/commit.hoon +++ b/pkg/arvo/gen/roller/commit.hoon @@ -1,3 +1,4 @@ +:: Submits a new L2 batch with all pending transactions :: :- %say |= * diff --git a/pkg/arvo/gen/roller/config.hoon b/pkg/arvo/gen/roller/config.hoon new file mode 100644 index 0000000000..261514635b --- /dev/null +++ b/pkg/arvo/gen/roller/config.hoon @@ -0,0 +1,7 @@ +:: Updates a configuration option for /app/roller +:: +/- *dice +:: +:- %say +|= [* [=config ~] ~] +[%roller-action %config config] diff --git a/pkg/arvo/gen/roller/frequency.hoon b/pkg/arvo/gen/roller/frequency.hoon new file mode 100644 index 0000000000..b4485a3097 --- /dev/null +++ b/pkg/arvo/gen/roller/frequency.hoon @@ -0,0 +1,4 @@ +:: +:- %say +|= [* [freq=@dr ~] ~] +[%roller-action %config %frequency freq] diff --git a/pkg/arvo/gen/roller/local.hoon b/pkg/arvo/gen/roller/local.hoon index ef8ac2224b..f1202a8ed8 100644 --- a/pkg/arvo/gen/roller/local.hoon +++ b/pkg/arvo/gen/roller/local.hoon @@ -1,3 +1,4 @@ +:: Configures /app/roller to listen to a local Ethereum node :: :- %say |= * diff --git a/pkg/arvo/gen/roller/quota.hoon b/pkg/arvo/gen/roller/quota.hoon new file mode 100644 index 0000000000..a5ca7845b2 --- /dev/null +++ b/pkg/arvo/gen/roller/quota.hoon @@ -0,0 +1,5 @@ +:: Modifies the number of txs a ship is allowed to send, per unit of time (slice) +:: +:- %say +|= [* [quota=@ud ~] ~] +[%roller-action %config %quota quota] diff --git a/pkg/arvo/gen/roller/ropsten.hoon b/pkg/arvo/gen/roller/ropsten.hoon index ad2ab2fe3a..af363f0dde 100644 --- a/pkg/arvo/gen/roller/ropsten.hoon +++ b/pkg/arvo/gen/roller/ropsten.hoon @@ -1,3 +1,4 @@ +:: Configures /app/roller to listen to a Ropsten Infura node :: :- %say |= * diff --git a/pkg/arvo/gen/roller/setkey.hoon b/pkg/arvo/gen/roller/setkey.hoon index 473099cb5e..7e884027b9 100644 --- a/pkg/arvo/gen/roller/setkey.hoon +++ b/pkg/arvo/gen/roller/setkey.hoon @@ -1,3 +1,4 @@ +:: Loads a private key into the roller and retrieves its L1 nonce :: :- %say |= [* [pk=@t ~] ~] diff --git a/pkg/arvo/gen/roller/slice.hoon b/pkg/arvo/gen/roller/slice.hoon new file mode 100644 index 0000000000..1b0de984ea --- /dev/null +++ b/pkg/arvo/gen/roller/slice.hoon @@ -0,0 +1,5 @@ +:: Modifies the unit of time (e.g. ~d1) for each ship's quota +:: +:- %say +|= [* [slice=@dr ~] ~] +[%roller-action %config %slice slice] diff --git a/pkg/arvo/lib/azimuth-roll-rpc.hoon b/pkg/arvo/lib/azimuth-roll-rpc.hoon index f46ff6e7ec..28fedcb8f7 100644 --- a/pkg/arvo/lib/azimuth-roll-rpc.hoon +++ b/pkg/arvo/lib/azimuth-roll-rpc.hoon @@ -345,17 +345,27 @@ :: ++ tx-status |=(=^tx-status ^-(json s+status.tx-status)) :: - ++ config - |= roller-config + ++ roller-config + |= [az=^azimuth-config ro=^roller-config] ^- json %- pairs - :~ ['nextBatch' (time next-batch)] - ['frequency' (numb (div frequency ~s1))] - ['refreshTime' (numb (div refresh-time ~s1))] - ['contract' (hex 20 contract)] - ['chainId' (numb chain-id)] + :~ ['azimuthRefreshRate' (numb (div refresh-rate.az ~s1))] + ['nextBatch' (time next-batch.ro)] + ['frequency' (numb (div frequency.ro ~s1))] + ['rollerResendTime' (numb (div resend-time.ro ~s1))] + ['rollerUpdateRate' (numb (div update-rate.ro ~s1))] + ['contract' (hex 20 contract.ro)] + ['chainId' (numb chain-id.ro)] + ['timeSlice' (numb (div slice.ro ~s1))] + ['rollerQuota' (numb quota.ro)] == :: + ++ azimuth-config + |= config=^azimuth-config + ^- json + %- pairs + ['refreshRate' (numb (div refresh-rate.config ~s1))]~ + :: ++ hex |= [p=@ q=@] ^- json @@ -482,18 +492,20 @@ [%result id (ships:to-json (scry u.ship))] :: ++ process-rpc - |= [id=@t params=(map @t json) action=l2-tx] + |= [id=@t params=(map @t json) action=l2-tx over-quota=$-(@p ?)] ^- [(unit cage) response:rpc] ?. =((lent ~(tap by params)) 4) [~ ~(params error:json-rpc id)] =+ ^- $: sig=(unit @) - from=(unit [ship proxy:naive]) + from=(unit [=ship proxy:naive]) addr=(unit @ux) == =, from-json [(sig params) (from params) (address params)] ?: |(?=(~ sig) ?=(~ from) ?=(~ addr)) [~ ~(parse error:json-rpc id)] + ?: (over-quota ship.u.from) + `[%error id '-32002' 'Max tx quota exceeded'] =/ tx=(unit tx:naive) (build-l2-tx action u.from params) ?~ tx [~ ~(parse error:json-rpc id)] =+ (gen-tx-octs:lib u.tx) @@ -578,11 +590,11 @@ [%result id (hist-txs:to-json (scry u.address))] :: ++ get-config - |= [id=@t params=(map @t json) =roller-config] + |= [id=@t params=(map @t json) config=[azimuth-config roller-config]] ^- response:rpc ?. =((lent ~(tap by params)) 0) ~(params error:json-rpc id) - [%result id (config:to-json roller-config)] + [%result id (roller-config:to-json config)] :: ++ hash-transaction |= [id=@t params=(map @t json) chain-id=@ header=? reverse=?] @@ -617,4 +629,11 @@ ?. =((lent ~(tap by params)) 0) ~(params error:json-rpc id) [%result id (naive-state:to-json state)] +:: +++ get-refresh + |= [id=@t params=(map @t json) =azimuth-config] + ^- response:rpc + ?. =((lent ~(tap by params)) 0) + ~(params error:json-rpc id) + [%result id (azimuth-config:to-json azimuth-config)] -- diff --git a/pkg/arvo/sur/dice.hoon b/pkg/arvo/sur/dice.hoon index 3baccc4e41..8b6bd09b2a 100644 --- a/pkg/arvo/sur/dice.hoon +++ b/pkg/arvo/sur/dice.hoon @@ -5,13 +5,31 @@ |% +$ owner [=proxy:naive =address:naive] +$ owners (jug owner ship) ++$ net ?(%mainnet %ropsten %local) +:: ++$ config + $% [%frequency frequency=@dr] + [%setkey pk=@] + [%endpoint endpoint=@t =net] + [%resend-time time=@dr] + [%update-rate rate=@dr] + [%slice slice=@dr] + [%quota quota=@ud] + == +:: ++$ azimuth-config + $: refresh-rate=@dr + == :: +$ roller-config $: next-batch=time frequency=@dr - refresh-time=@dr + resend-time=@dr + update-rate=@dr contract=@ux chain-id=@ + slice=@dr + quota=@ud == :: +$ keccak @ux diff --git a/pkg/arvo/ted/roller/send.hoon b/pkg/arvo/ted/roller/send.hoon index c47cf17933..443f0cb5ff 100644 --- a/pkg/arvo/ted/roller/send.hoon +++ b/pkg/arvo/ted/roller/send.hoon @@ -69,11 +69,14 @@ q.batch-data chain-id == +:: log batch tx-hash to getTransactionReceipt(tx-hash) +:: +~? &(?=(%result -.response) ?=(%s -.res.response)) + ^-([nonce=@ud batch-hash=@t] nonce^(so:dejs:format res.response)) %- pure:m !> ^- (each @ud [term @t]) :: TODO: capture if the tx fails (e.g. Runtime Error: revert) :: check that tx-hash in +.response is non-zero? -:: log tx-hash to getTransactionReceipt(tx-hash)? :: enforce max here, or in app? :: ?+ -.response %.n^[%error 'unexpected rpc response']