11package net .flashbots ;
22
3- import java .io .IOException ;
43import java .math .BigInteger ;
54import java .util .ArrayList ;
65import java .util .List ;
76import java .util .Objects ;
87import java .util .concurrent .CompletableFuture ;
9- import java .util .concurrent .ExecutionException ;
8+ import java .util .concurrent .CountDownLatch ;
109import java .util .concurrent .TimeUnit ;
11- import java .util .concurrent .TimeoutException ;
1210import java .util .function .Consumer ;
11+ import java .util .stream .Collectors ;
1312
1413import static org .slf4j .LoggerFactory .getLogger ;
1514
4039import okhttp3 .sse .EventSource ;
4140import org .slf4j .Logger ;
4241import org .web3j .crypto .Credentials ;
42+ import org .web3j .crypto .RawTransaction ;
43+ import org .web3j .crypto .Sign ;
44+ import org .web3j .crypto .TransactionEncoder ;
4345import org .web3j .protocol .Web3j ;
4446import org .web3j .protocol .core .methods .response .EthTransaction ;
47+ import org .web3j .protocol .core .methods .response .Transaction ;
48+ import org .web3j .utils .Numeric ;
4549
4650/**
4751 * The type MevShareClient.
4852 *
4953 * @author kaichen
5054 * @since 0.1.0
5155 */
52- public class MevShareClient implements MevShareApi {
56+ public class MevShareClient implements MevShareApi , AutoCloseable {
5357
5458 private static final Logger LOGGER = getLogger (MevShareClient .class );
5559
@@ -189,19 +193,67 @@ public CompletableFuture<String> sendPrivateTransaction(String signedRawTx, Priv
189193
190194 private CompletableFuture <SimBundleResponse > createSimulateBundle (
191195 final BundleItemType .HashItem firstTx , final BundleParams params , final SimBundleOptions options ) {
192- return getTransaction (firstTx .getHash ()).thenComposeAsync (tx -> {
196+ return getTransaction (firstTx .getHash ()).thenCompose (tx -> {
193197 if (tx .getTransaction ().isEmpty ()) {
194- throw new MevShareApiException ("Target transaction did not appear on chain" );
198+ return CompletableFuture .failedFuture (
199+ new MevShareApiException ("Target transaction did not appear on chain" ));
195200 }
196- var simBlock = options != null
201+ var simBlock = options != null && options . getParentBlock () != null
197202 ? options .getParentBlock ()
198203 : tx .getTransaction ().get ().getBlockNumber ().subtract (BigInteger .ONE );
199204
205+ Transaction transaction = tx .getTransaction ().get ();
206+ LOGGER .debug ("Transaction {}" , transaction );
207+
208+ RawTransaction rawTransaction ;
209+ if ("0x2" .equalsIgnoreCase (transaction .getType ())) {
210+ rawTransaction = RawTransaction .createTransaction (
211+ transaction .getChainId (),
212+ transaction .getNonce (),
213+ transaction .getGas (),
214+ transaction .getTo (),
215+ transaction .getValue (),
216+ transaction .getInput (),
217+ transaction .getMaxPriorityFeePerGas (),
218+ transaction .getMaxFeePerGas ());
219+ } else if ("0x1" .equalsIgnoreCase (transaction .getType ())) {
220+ rawTransaction = RawTransaction .createTransaction (
221+ transaction .getChainId (),
222+ transaction .getNonce (),
223+ transaction .getGasPrice (),
224+ transaction .getGas (),
225+ transaction .getTo (),
226+ transaction .getValue (),
227+ transaction .getInput (),
228+ transaction .getAccessList ().stream ()
229+ .map (accessListObject -> {
230+ org .web3j .crypto .AccessListObject accessListObjectRaw =
231+ new org .web3j .crypto .AccessListObject ();
232+ accessListObjectRaw .setAddress (accessListObject .getAddress ());
233+ accessListObjectRaw .setStorageKeys (accessListObject .getStorageKeys ());
234+ return accessListObjectRaw ;
235+ })
236+ .collect (Collectors .toList ()));
237+ } else {
238+ rawTransaction = RawTransaction .createTransaction (
239+ transaction .getNonce (),
240+ transaction .getGasPrice (),
241+ transaction .getGas (),
242+ transaction .getTo (),
243+ transaction .getValue (),
244+ transaction .getInput ());
245+ }
246+
247+ Sign .SignatureData signatureData = new Sign .SignatureData (
248+ Sign .getVFromRecId ((int ) transaction .getV ()),
249+ Numeric .hexStringToByteArray (transaction .getR ()),
250+ Numeric .hexStringToByteArray (transaction .getS ()));
251+
200252 var body = new ArrayList <>(params .getBody ());
201253 body .set (
202254 0 ,
203255 new BundleItemType .TxItem ()
204- .setTx (tx . getTransaction (). get (). getInput ( ))
256+ .setTx (Numeric . toHexString ( TransactionEncoder . encode ( rawTransaction , signatureData ) ))
205257 .setCanRevert (false ));
206258 var paramsWithSignedTx = params .clone ().setBody (body );
207259
@@ -213,34 +265,55 @@ private CompletableFuture<SimBundleResponse> createSimulateBundle(
213265 }
214266
215267 private CompletableFuture <EthTransaction > getTransaction (final String hash ) {
216- return CompletableFuture .supplyAsync (() -> {
217- Disposable subscribe = null ;
218- try {
219- // try to get tx first
220- EthTransaction res = web3j .ethGetTransactionByHash (hash ).send ();
221- if (res .getTransaction ().isPresent ()) {
222- return res ;
223- }
224-
268+ return web3j .ethGetTransactionByHash (hash ).sendAsync ().thenCompose (res -> {
269+ if (res .getTransaction ().isEmpty ()) {
270+ final CountDownLatch latch = new CountDownLatch (1 );
225271 final CompletableFuture <EthTransaction > txFuture = new CompletableFuture <>();
226- subscribe = web3j .blockFlowable (false ).subscribe (block -> {
227- EthTransaction hashTx = web3j .ethGetTransactionByHash (hash ).send ();
228- if (hashTx .getTransaction ().isPresent ()) {
229- txFuture .complete (hashTx );
272+ Disposable disposable = null ;
273+ try {
274+ disposable = web3j .blockFlowable (false ).subscribe (block -> {
275+ EthTransaction hashTx =
276+ web3j .ethGetTransactionByHash (hash ).send ();
277+ if (hashTx .getTransaction ().isPresent ()) {
278+ txFuture .complete (hashTx );
279+ latch .countDown ();
280+ }
281+ });
282+
283+ try {
284+ latch .await (5 , TimeUnit .MINUTES );
285+ } catch (InterruptedException e ) {
286+ LOGGER .error ("Interrupted while waiting for transaction by hash" , e );
287+ if (!disposable .isDisposed ()) {
288+ disposable .dispose ();
289+ }
290+ Thread .currentThread ().interrupt ();
291+ return CompletableFuture .failedFuture (
292+ new MevShareApiException ("Interrupted while waiting for transaction by hash" , e ));
293+ }
294+
295+ if (!disposable .isDisposed ()) {
296+ disposable .dispose ();
297+ }
298+
299+ if (!txFuture .isDone ()) {
300+ return CompletableFuture .failedFuture (
301+ new MevShareApiException ("Failed to get transaction by hash after 5 minutes" ));
302+ }
303+ } finally {
304+ if (disposable != null && !disposable .isDisposed ()) {
305+ disposable .dispose ();
230306 }
231- });
232- return txFuture .get (5 , TimeUnit .MINUTES );
233- } catch (InterruptedException | ExecutionException | TimeoutException | IOException e ) {
234- LOGGER .error ("Failed to get transaction by hash" , e );
235- if (e instanceof InterruptedException ) {
236- Thread .currentThread ().interrupt ();
237- }
238- throw new MevShareApiException ("Failed to get transaction by hash" , e );
239- } finally {
240- if (subscribe != null ) {
241- subscribe .dispose ();
242307 }
308+ return txFuture ;
243309 }
310+ return CompletableFuture .completedFuture (res );
244311 });
245312 }
313+
314+ @ Override
315+ public void close () {
316+ this .web3j .shutdown ();
317+ this .provider .close ();
318+ }
246319}
0 commit comments