mirror of
https://github.com/urbit/shrub.git
synced 2024-12-19 16:51:42 +03:00
cf838fd1d7
If a batch gets bigger than a max size defined by the ethereum node the raw transaction is sent to, the /ted/roller/send thread will crash and the batch will be blocked, stopping any subsequent batches to be sent. This detects when the current batch reaches a certain threshold and only includes transactions up to that point, moving the ones that are not sent back to the pending queue, adjusting their history and finding status.
166 lines
4.7 KiB
Plaintext
166 lines
4.7 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)
|
||
~& >>> [%unexpected-nonce nonce expected+expected-nonce]
|
||
%- pure:m
|
||
!> ^- [%.n @tas @t]
|
||
:+ %.n
|
||
%not-sent
|
||
?: (lth expected-nonce nonce)
|
||
:: if ahead, it will use the same next-gas-price when resending
|
||
::
|
||
%ahead-nonce
|
||
:: if behind, start out-of-sync flow
|
||
::
|
||
%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)< ~]
|
||
::
|
||
--
|