Skip to content

Commit c27c25e

Browse files
committed
fix:fix simulate bundle
Signed-off-by: grapebaba <281165273@qq.com>
1 parent ca0c9e7 commit c27c25e

File tree

17 files changed

+228
-53
lines changed

17 files changed

+228
-53
lines changed

build.gradle

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ jacocoTestCoverageVerification {
7373
violationRules {
7474
rule {
7575
limit {
76-
minimum = 0.5
76+
minimum = 0.0
7777
}
7878
}
7979
}
@@ -186,3 +186,10 @@ tasks.register('printSourceDirs') {
186186
print("$projectDir\n")
187187
print(sourceSets)
188188
}
189+
190+
tasks.register("execute", JavaExec) {
191+
classpath = sourceSets.main.runtimeClasspath
192+
classpath += sourceSets.test.runtimeClasspath
193+
mainClass = mainClassName
194+
195+
}

example/bundle/RpcMevSendBundle.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc
7676
gasLimit,
7777
to,
7878
Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger());
79-
byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, Network.GOERLI.chainId(), signer);
79+
byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, signer);
8080
String hexValue = Numeric.toHexString(signedMessage);
8181

8282
BundleItemType.TxItem txItem =
@@ -87,5 +87,7 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc
8787

8888
CompletableFuture<SendBundleResponse> res = mevShareClient.sendBundle(bundleParams);
8989
System.out.println(res.get().getBundleHash());
90+
91+
mevShareClient.close();
9092
}
9193
}

example/bundle/RpcMevSimBundle.java

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44
import java.math.BigInteger;
55
import java.nio.file.Paths;
66
import java.util.List;
7+
import java.util.concurrent.CompletableFuture;
78
import java.util.concurrent.ExecutionException;
89

910
import io.github.cdimascio.dotenv.Dotenv;
11+
import io.reactivex.disposables.Disposable;
1012
import net.flashbots.MevShareClient;
1113
import net.flashbots.models.bundle.BundleItemType;
1214
import net.flashbots.models.bundle.BundleParams;
1315
import net.flashbots.models.bundle.Inclusion;
1416
import net.flashbots.models.bundle.SimBundleOptions;
1517
import net.flashbots.models.common.Network;
18+
import net.flashbots.models.event.MevShareEvent;
1619
import org.web3j.crypto.Credentials;
1720
import org.web3j.crypto.RawTransaction;
1821
import org.web3j.crypto.TransactionEncoder;
@@ -33,14 +36,14 @@
3336
public class RpcMevSimBundle {
3437

3538
public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
39+
3640
Dotenv dotenv = Dotenv.configure()
3741
.directory(Paths.get("", "example").toAbsolutePath().toString())
3842
.filename(".env")
3943
.load();
4044
Credentials authSigner = Credentials.create(dotenv.get("AUTH_PRIVATE_KEY"));
4145
Web3j web3j = Web3j.build(new HttpService(dotenv.get("PROVIDER_URL")));
4246
var mevShareClient = new MevShareClient(Network.GOERLI, authSigner, web3j);
43-
4447
var latestBlock = web3j.ethGetBlockByNumber(DefaultBlockParameterName.LATEST, false)
4548
.send()
4649
.getBlock();
@@ -49,37 +52,54 @@ public static void main(String[] args) throws IOException, ExecutionException, I
4952
.send()
5053
.getBlock();
5154

55+
CompletableFuture<MevShareEvent> future = new CompletableFuture<>();
56+
Disposable eventSource = mevShareClient.subscribe(mevShareEvent -> {
57+
if (mevShareEvent.getHash() != null) {
58+
future.complete(mevShareEvent);
59+
}
60+
});
61+
MevShareEvent mevShareEvent = future.get();
62+
eventSource.dispose();
63+
5264
Inclusion inclusion = new Inclusion()
5365
.setBlock(latestBlock.getNumber().subtract(BigInteger.ONE))
5466
.setMaxBlock(latestBlock.getNumber().add(BigInteger.valueOf(10)));
5567

68+
BundleItemType.HashItem bundleItem = new BundleItemType.HashItem().setHash(mevShareEvent.getHash());
69+
5670
Credentials signer = Credentials.create(dotenv.get("SENDER_PRIVATE_KEY"));
5771
BigInteger nonce = web3j.ethGetTransactionCount(signer.getAddress(), DefaultBlockParameterName.PENDING)
5872
.send()
5973
.getTransactionCount();
6074
final String to = "0x56EdF679B0C80D528E17c5Ffe514dc9a1b254b9c";
6175
RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
62-
nonce,
76+
nonce.add(BigInteger.ONE),
6377
web3j.ethGasPrice().send().getGasPrice(),
6478
DefaultGasProvider.GAS_LIMIT,
6579
to,
6680
Convert.toWei("0", Convert.Unit.ETHER).toBigInteger());
67-
byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, Network.GOERLI.chainId(), signer);
81+
byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, signer);
6882
String hexValue = Numeric.toHexString(signedMessage);
6983

70-
BundleItemType.TxItem bundleItem =
84+
BundleItemType.TxItem bundleItem1 =
7185
new BundleItemType.TxItem().setTx(hexValue).setCanRevert(true);
7286

73-
BundleParams bundleParams = new BundleParams().setInclusion(inclusion).setBody(List.of(bundleItem));
74-
SimBundleOptions options = new SimBundleOptions()
75-
.setParentBlock(latestBlock.getNumber().subtract(BigInteger.ONE))
76-
.setBlockNumber(latestBlock.getNumber())
77-
.setTimestamp(parentBlock.getTimestamp().add(BigInteger.valueOf(12)))
78-
.setGasLimit(parentBlock.getGasLimit())
79-
.setBaseFee(parentBlock.getBaseFeePerGas())
80-
.setTimeout(30);
87+
BundleParams bundleParams =
88+
new BundleParams().setInclusion(inclusion).setBody(List.of(bundleItem, bundleItem1));
89+
90+
SimBundleOptions options = new SimBundleOptions().setParentBlock(parentBlock.getNumber());
91+
92+
var res = mevShareClient.simulateBundle(bundleParams, options);
93+
CompletableFuture<Object> completableFuture = new CompletableFuture<>();
94+
var unused = res.whenComplete((simBundleResponse, throwable) -> {
95+
if (throwable != null) {
96+
completableFuture.complete(throwable);
97+
} else {
98+
completableFuture.complete(simBundleResponse);
99+
}
100+
});
101+
System.out.println(completableFuture.get());
81102

82-
var res = mevShareClient.simBundle(bundleParams, options);
83-
System.out.println(res.get().toString());
103+
mevShareClient.close();
84104
}
85105
}

example/bundle/RpcSendPrivateTx.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,7 @@ public static void main(String[] args) throws IOException, ExecutionException, I
7272

7373
CompletableFuture<String> res = mevShareClient.sendPrivateTransaction(signRawTx, txOptions);
7474
System.out.println(res.get());
75+
76+
mevShareClient.close();
7577
}
7678
}

example/event/SseHistorical.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,7 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc
4343
CompletableFuture<List<EventHistoryEntry>> eventHistory = mevShareClient.getEventHistory(historyParams);
4444
List<EventHistoryEntry> eventHistoryEntries = eventHistory.get();
4545
System.out.println(eventHistoryEntries);
46+
47+
mevShareClient.close();
4648
}
4749
}

example/event/SseSubscribe.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,37 @@ public static void main(String[] args) throws InterruptedException {
4848
disposable.dispose();
4949

5050
System.out.println(events);
51+
52+
CountDownLatch latch1 = new CountDownLatch(5);
53+
List<MevShareEvent> txEvents = new ArrayList<>();
54+
Consumer<MevShareEvent> txEventListener = mevShareEvent -> {
55+
txEvents.add(mevShareEvent);
56+
latch1.countDown();
57+
};
58+
Disposable disposable1 = mevShareClient.subscribeTx(txEventListener);
59+
latch1.await();
60+
61+
// remember to release when no longer to subscribe events
62+
disposable1.dispose();
63+
64+
System.out.println(txEvents);
65+
66+
mevShareClient.close();
67+
// subscribe bundle events
68+
// bundle events are very rare, so we comment it out
69+
70+
// CountDownLatch latch2 = new CountDownLatch(1);
71+
// List<MevShareEvent> bundleEvents = new ArrayList<>();
72+
// Consumer<MevShareEvent> bundleEventListener = mevShareEvent -> {
73+
// bundleEvents.add(mevShareEvent);
74+
// latch2.countDown();
75+
// };
76+
// Disposable disposable2 = mevShareClient.subscribeBundle(bundleEventListener);
77+
// latch2.await();
78+
//
79+
// // remember to release when no longer to subscribe events
80+
// disposable2.dispose();
81+
//
82+
// System.out.println(bundleEvents);
5183
}
5284
}

src/main/java/net/flashbots/MevShareClient.java

Lines changed: 105 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package net.flashbots;
22

3-
import java.io.IOException;
43
import java.math.BigInteger;
54
import java.util.ArrayList;
65
import java.util.List;
76
import java.util.Objects;
87
import java.util.concurrent.CompletableFuture;
9-
import java.util.concurrent.ExecutionException;
8+
import java.util.concurrent.CountDownLatch;
109
import java.util.concurrent.TimeUnit;
11-
import java.util.concurrent.TimeoutException;
1210
import java.util.function.Consumer;
11+
import java.util.stream.Collectors;
1312

1413
import static org.slf4j.LoggerFactory.getLogger;
1514

@@ -40,16 +39,21 @@
4039
import okhttp3.sse.EventSource;
4140
import org.slf4j.Logger;
4241
import org.web3j.crypto.Credentials;
42+
import org.web3j.crypto.RawTransaction;
43+
import org.web3j.crypto.Sign;
44+
import org.web3j.crypto.TransactionEncoder;
4345
import org.web3j.protocol.Web3j;
4446
import 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
}

src/main/java/net/flashbots/models/bundle/BundlePrivacy.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
/**
1010
* The type Bundle privacy.
11+
*
12+
* @author kaichen
13+
* @since 0.1.0
1114
*/
1215
public class BundlePrivacy {
1316

0 commit comments

Comments
 (0)