mirror of
https://github.com/urbit/shrub.git
synced 2024-12-23 10:52:12 +03:00
165 lines
4.6 KiB
Plaintext
165 lines
4.6 KiB
Plaintext
:: roller/send: send rollup tx
|
||
::
|
||
/- rpc=json-rpc, *dice
|
||
/+ naive, ethereum, ethio, strandio
|
||
::
|
||
::
|
||
|= args=vase
|
||
=+ !<(rpc-send-roll args)
|
||
=/ m (strand:strandio ,vase)
|
||
|^
|
||
^- form:m
|
||
::
|
||
=/ =address:ethereum (address-from-prv:key:ethereum pk)
|
||
;< expected-nonce=@ud bind:m
|
||
(get-next-nonce:ethio endpoint address)
|
||
:: Infura enforces a max calldata size (32, 64, 128 Kb?) so we calculate how
|
||
:: many txs are included in a batch of that size, and only send those
|
||
::
|
||
=/ max-calldata=@ud 128.000
|
||
=/ [n-txs=@ud batch-data=octs]
|
||
=| n-txs=@ud
|
||
=| size=@ud
|
||
=| out=(list octs)
|
||
|- ^- [@ud octs]
|
||
?~ txs
|
||
[n-txs (cad:naive 3 (flop out))]
|
||
=* raw-tx i.txs
|
||
=. size :(add 65 p.raw.raw-tx size)
|
||
?: (gth size max-calldata)
|
||
[n-txs (cad:naive 3 (flop out))]
|
||
%_ $
|
||
n-txs +(n-txs)
|
||
txs t.txs
|
||
out [raw.raw-tx 65^sig.raw-tx out]
|
||
==
|
||
:: if the batch is malformed, emit error to kick it out of sending
|
||
::
|
||
?~ (parse-roll:naive q.batch-data)
|
||
(pure:m !>(%.n^[%not-sent %batch-parse-error]))
|
||
:: if chain expects a different nonce, don't send this transaction
|
||
::
|
||
?. =(nonce expected-nonce)
|
||
%- pure:m
|
||
!> ^- [%.n @tas @t]
|
||
:+ %.n
|
||
%not-sent
|
||
?: (lth expected-nonce nonce)
|
||
:: if ahead, use the same next-gas-price when resending
|
||
::
|
||
%ahead-nonce
|
||
:: if behind, start out-of-sync flow if batch was not sent before
|
||
::
|
||
%behind-nonce
|
||
:: 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)
|
||
::
|
||
:: each l2 signature is 65 bytes + XX bytes for the raw data
|
||
:: from the ethereum yellow paper:
|
||
:: gasLimit = G_transaction + G_txdatanonzero × dataByteLength
|
||
:: where
|
||
:: G_transaction = 21000 gas (base fee)
|
||
:: + G_txdatanonzero = 16 gas (previously 68; see EIP-2028)
|
||
:: * dataByteLength = (65 + raw) * (lent txs) bytes
|
||
::
|
||
:: 1.000 gas are added to the base fee as extra, for emitting the log
|
||
::
|
||
=/ gas-limit=@ud (add 22.000 (mul 16 p.batch-data))
|
||
=/ max-cost=@ud (mul gas-limit use-gas-price)
|
||
;< balance=@ud bind:m
|
||
(get-balance:ethio endpoint address)
|
||
?: (gth max-cost balance)
|
||
:: if we cannot pay for the transaction, don't bother sending it out
|
||
::
|
||
(pure:m !>(%.n^[%not-sent %insufficient-roller-balance]))
|
||
::
|
||
::NOTE this fails the thread if sending fails, which in the app gives us
|
||
:: the "retry with same gas price" behavior we want
|
||
::
|
||
;< =response:rpc bind:m
|
||
%+ send-batch endpoint
|
||
=; tx=transaction:rpc:ethereum
|
||
(sign-transaction:key:ethereum tx pk)
|
||
:* nonce
|
||
use-gas-price
|
||
gas-limit
|
||
contract
|
||
0
|
||
q.batch-data
|
||
chain-id
|
||
==
|
||
:: log batch tx-hash to getTransactionReceipt(tx-hash)
|
||
::
|
||
~? &(?=(%result -.response) ?=(%s -.res.response))
|
||
^- [nonce=@ud batch-hash=@t gas=@ud sent-txs=@ud bytes=@ud]
|
||
:* nonce
|
||
(so:dejs:format res.response)
|
||
use-gas-price
|
||
n-txs
|
||
p.batch-data
|
||
==
|
||
%- pure:m
|
||
!> ^- (each [@ud @ud] [term @t])
|
||
:: TODO: capture if the tx fails (e.g. Runtime Error: revert)
|
||
:: check that tx-hash in +.response is non-zero?
|
||
::
|
||
?+ -.response %.n^[%error 'unexpected rpc response']
|
||
%error %.n^[%error message.response]
|
||
:: add five gwei to gas price of next attempt
|
||
::
|
||
%result %.y^[n-txs (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 fallback-gas-price)
|
||
?. ?& ?=([~ %finished *] rep)
|
||
?=(^ full-file.u.rep)
|
||
:: get suggested price only for mainnet txs
|
||
::
|
||
=(chain-id 1)
|
||
==
|
||
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'^(su dem) ~) ~)
|
||
::
|
||
++ send-batch
|
||
|= [endpoint=@ta batch=@ux]
|
||
=/ m (strand:strandio ,response:rpc)
|
||
^- form:m
|
||
=/ req=[(unit @t) request:rpc:ethereum]
|
||
[`'sendRawTransaction' %eth-send-raw-transaction batch]
|
||
;< res=(list response:rpc) bind:m
|
||
(request-batch-rpc-loose:ethio endpoint [req]~)
|
||
?: ?=([* ~] res)
|
||
(pure:m i.res)
|
||
%+ strand-fail:strandio
|
||
%unexpected-multiple-results
|
||
[>(lent res)< ~]
|
||
::
|
||
--
|