Skip to content

Commit 82e169b

Browse files
authored
Merge pull request #29 from status-im/feat/gas-estimation-at-l2-node
modified RLNProverForwarderValidator to send transaction gas estimation data at l2 node
2 parents 410c450 + 27ccf6a commit 82e169b

File tree

7 files changed

+233
-4
lines changed

7 files changed

+233
-4
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ The Status Network RLN validator system can be configured using various CLI opti
5353
- **`--plugin-linea-rln-karma-service`**: Karma service endpoint in `host:port` format (default: `localhost:50052`)
5454
- **`--plugin-linea-rln-deny-list-path`**: Path to the gasless deny list file (default: `/var/lib/besu/gasless-deny-list.txt`)
5555

56+
### Troubleshooting
57+
- If a 0-gas (gasless) transaction is accepted by the RPC but not included, check the sequencer logs for RLN proof cache timeouts or epoch mismatches and ensure `--plugin-linea-rln-epoch-mode=TEST` is used in local demos.
58+
- If paid transactions fail for “min gas price” or “upfront cost” locally, ensure L2 genesis has `baseFeePerGas=0` and sequencer `min-gas-price=0` in config.
59+
- Premium gas transactions (gas > configured threshold) bypass RLN validation by design.
60+
61+
### CI
62+
- GitHub Actions runs sequencer unit tests (Java 21) with Gradle caching. JNI-dependent RLN native tests are excluded from CI.
63+
5664
## How to contribute
5765

5866
Contributions are welcome!

besu-plugins/linea-sequencer/sequencer/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/LineaTransactionPoolValidatorFactory.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,11 @@ public PluginTransactionPoolValidator createTransactionValidator() {
101101
new RlnProverForwarderValidator(
102102
rlnValidatorConf,
103103
true, // enabled
104-
sharedServiceManager.getKarmaServiceClient()));
104+
sharedServiceManager.getKarmaServiceClient(),
105+
transactionSimulationService,
106+
blockchainService,
107+
tracerConfiguration,
108+
l1L2BridgeConfiguration));
105109
}
106110

107111
// Conditionally add RLN Validator (for proof verification)

besu-plugins/linea-sequencer/sequencer/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/validators/RlnProverForwarderValidator.java

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,28 @@
2020
import io.grpc.ManagedChannelBuilder;
2121
import java.io.Closeable;
2222
import java.io.IOException;
23+
import java.math.BigInteger;
2324
import java.util.Optional;
2425
import java.util.concurrent.TimeUnit;
2526
import java.util.concurrent.atomic.AtomicInteger;
2627
import net.consensys.linea.config.LineaRlnValidatorConfiguration;
28+
import net.consensys.linea.config.LineaTracerConfiguration;
29+
import net.consensys.linea.plugins.config.LineaL1L2BridgeSharedConfiguration;
2730
import net.consensys.linea.sequencer.txpoolvalidation.shared.KarmaServiceClient;
2831
import net.consensys.linea.sequencer.txpoolvalidation.shared.KarmaServiceClient.KarmaInfo;
32+
import net.consensys.linea.zktracer.LineCountingTracer;
33+
import net.consensys.linea.zktracer.ZkCounter;
34+
import net.consensys.linea.zktracer.ZkTracer;
2935
import net.vac.prover.Address;
3036
import net.vac.prover.RlnProverGrpc;
3137
import net.vac.prover.SendTransactionReply;
3238
import net.vac.prover.SendTransactionRequest;
3339
import net.vac.prover.U256;
3440
import net.vac.prover.Wei;
3541
import org.hyperledger.besu.datatypes.Transaction;
42+
import org.hyperledger.besu.plugin.data.ProcessableBlockHeader;
43+
import org.hyperledger.besu.plugin.services.BlockchainService;
44+
import org.hyperledger.besu.plugin.services.TransactionSimulationService;
3645
import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionPoolValidator;
3746
import org.slf4j.Logger;
3847
import org.slf4j.LoggerFactory;
@@ -91,18 +100,44 @@ public class RlnProverForwarderValidator implements PluginTransactionPoolValidat
91100
// Karma service for gasless validation
92101
private final KarmaServiceClient karmaServiceClient;
93102

103+
// Simulation dependencies for estimating gas used
104+
private final TransactionSimulationService transactionSimulationService;
105+
private final BlockchainService blockchainService;
106+
private final LineaTracerConfiguration tracerConfiguration;
107+
private final LineaL1L2BridgeSharedConfiguration l1L2BridgeConfiguration;
108+
94109
/**
95110
* Creates a new RLN Prover Forwarder Validator with default gRPC channel management.
96111
*
97112
* @param rlnConfig Configuration for RLN validation including prover service endpoint
98113
* @param enabled Whether the validator is enabled (should be false in sequencer mode)
99114
* @param karmaServiceClient Service for checking karma eligibility for gasless transactions
100115
*/
116+
public RlnProverForwarderValidator(
117+
LineaRlnValidatorConfiguration rlnConfig,
118+
boolean enabled,
119+
KarmaServiceClient karmaServiceClient,
120+
TransactionSimulationService transactionSimulationService,
121+
BlockchainService blockchainService,
122+
LineaTracerConfiguration tracerConfiguration,
123+
LineaL1L2BridgeSharedConfiguration l1L2BridgeSharedConfiguration) {
124+
this(
125+
rlnConfig,
126+
enabled,
127+
karmaServiceClient,
128+
transactionSimulationService,
129+
blockchainService,
130+
tracerConfiguration,
131+
l1L2BridgeSharedConfiguration,
132+
null);
133+
}
134+
135+
/** Backward-compatible constructor used by existing tests. New dependencies default to null. */
101136
public RlnProverForwarderValidator(
102137
LineaRlnValidatorConfiguration rlnConfig,
103138
boolean enabled,
104139
KarmaServiceClient karmaServiceClient) {
105-
this(rlnConfig, enabled, karmaServiceClient, null);
140+
this(rlnConfig, enabled, karmaServiceClient, null, null, null, null, null);
106141
}
107142

108143
/**
@@ -113,7 +148,7 @@ public RlnProverForwarderValidator(
113148
* @param enabled Whether the validator is enabled (should be false in sequencer mode)
114149
*/
115150
public RlnProverForwarderValidator(LineaRlnValidatorConfiguration rlnConfig, boolean enabled) {
116-
this(rlnConfig, enabled, null, null);
151+
this(rlnConfig, enabled, null, null, null, null, null, null);
117152
}
118153

119154
/**
@@ -132,10 +167,18 @@ public RlnProverForwarderValidator(LineaRlnValidatorConfiguration rlnConfig, boo
132167
LineaRlnValidatorConfiguration rlnConfig,
133168
boolean enabled,
134169
KarmaServiceClient karmaServiceClient,
170+
TransactionSimulationService transactionSimulationService,
171+
BlockchainService blockchainService,
172+
LineaTracerConfiguration tracerConfiguration,
173+
LineaL1L2BridgeSharedConfiguration l1L2BridgeSharedConfiguration,
135174
ManagedChannel providedChannel) {
136175
this.rlnConfig = rlnConfig;
137176
this.enabled = enabled;
138177
this.karmaServiceClient = karmaServiceClient;
178+
this.transactionSimulationService = transactionSimulationService;
179+
this.blockchainService = blockchainService;
180+
this.tracerConfiguration = tracerConfiguration;
181+
this.l1L2BridgeConfiguration = l1L2BridgeSharedConfiguration;
139182

140183
if (enabled) {
141184
if (providedChannel != null) {
@@ -317,6 +360,15 @@ public Optional<String> validateTransaction(
317360
.setValue(ByteString.copyFrom(chainId.toByteArray()))
318361
.build()));
319362

363+
// Provide an estimated gas units value. As an initial implementation,
364+
// simulate execution to estimate gas used when possible; fallback to tx gas limit.
365+
long estimatedGasUsed = estimateGasUsed(transaction);
366+
LOG.debug(
367+
"Estimated gas used for tx {}: {}",
368+
transaction.getHash().toHexString(),
369+
estimatedGasUsed);
370+
requestBuilder.setEstimatedGasUsed(estimatedGasUsed);
371+
320372
SendTransactionRequest request = requestBuilder.build();
321373

322374
LOG.debug(
@@ -347,6 +399,50 @@ public Optional<String> validateTransaction(
347399
}
348400
}
349401

402+
private LineCountingTracer createLineCountingTracer(
403+
final ProcessableBlockHeader pendingBlockHeader, final BigInteger chainId) {
404+
var lineCountingTracer =
405+
tracerConfiguration != null && tracerConfiguration.isLimitless()
406+
? new ZkCounter(l1L2BridgeConfiguration)
407+
: new ZkTracer(
408+
net.consensys.linea.zktracer.Fork.LONDON, l1L2BridgeConfiguration, chainId);
409+
lineCountingTracer.traceStartConflation(1L);
410+
lineCountingTracer.traceStartBlock(pendingBlockHeader, pendingBlockHeader.getCoinbase());
411+
return lineCountingTracer;
412+
}
413+
414+
private long estimateGasUsed(final Transaction transaction) {
415+
try {
416+
// Fast-path: simple ETH transfer with empty calldata
417+
if (transaction.getTo().isPresent()
418+
&& transaction.getPayload().isEmpty()
419+
&& transaction.getValue().getAsBigInteger().signum() > 0) {
420+
return 21_000L;
421+
}
422+
423+
if (transactionSimulationService == null || blockchainService == null) {
424+
return transaction.getGasLimit();
425+
}
426+
427+
final var pendingBlockHeader = transactionSimulationService.simulatePendingBlockHeader();
428+
final var chainId = blockchainService.getChainId().orElse(BigInteger.ZERO);
429+
final var tracer = createLineCountingTracer(pendingBlockHeader, chainId);
430+
final var maybeSimulationResults =
431+
transactionSimulationService.simulate(
432+
transaction, java.util.Optional.empty(), pendingBlockHeader, tracer, false, true);
433+
434+
if (maybeSimulationResults.isPresent()) {
435+
final var sim = maybeSimulationResults.get();
436+
if (sim.isSuccessful()) {
437+
return sim.result().getEstimateGasUsedByTransaction();
438+
}
439+
}
440+
} catch (final Exception ignored) {
441+
// fall through to fallback below
442+
}
443+
return transaction.getGasLimit();
444+
}
445+
350446
/**
351447
* Closes the gRPC channel and cleans up resources.
352448
*

besu-plugins/linea-sequencer/sequencer/src/main/proto/rln_proof_service.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ message SendTransactionRequest {
135135
optional Address sender = 2;
136136
optional U256 chainId = 3;
137137
bytes transactionHash = 4 [(max_size) = 32];
138+
// Estimated gas units the transaction is expected to consume (best-effort)
139+
// Backward-compatible addition; receivers may ignore if unsupported.
140+
uint64 estimated_gas_used = 5;
138141
}
139142

140143
message SendTransactionReply {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright Consensys Software Inc.
3+
*
4+
* This file is dual-licensed under either the MIT license or Apache License 2.0.
5+
* See the LICENSE-MIT and LICENSE-APACHE files in the repository root for details.
6+
*
7+
* SPDX-License-Identifier: MIT OR Apache-2.0
8+
*/
9+
package net.consensys.linea.sequencer.txpoolvalidation.validators;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
13+
import io.grpc.ManagedChannel;
14+
import io.grpc.inprocess.InProcessChannelBuilder;
15+
import io.grpc.inprocess.InProcessServerBuilder;
16+
import io.grpc.stub.StreamObserver;
17+
import java.util.concurrent.CountDownLatch;
18+
import java.util.concurrent.TimeUnit;
19+
import net.vac.prover.RlnProverGrpc;
20+
import net.vac.prover.SendTransactionReply;
21+
import net.vac.prover.SendTransactionRequest;
22+
import org.apache.tuweni.bytes.Bytes;
23+
import org.hyperledger.besu.datatypes.Address;
24+
import org.hyperledger.besu.datatypes.Wei;
25+
import org.junit.jupiter.api.AfterEach;
26+
import org.junit.jupiter.api.BeforeEach;
27+
import org.junit.jupiter.api.Test;
28+
29+
class RlnProverForwarderValidatorEstimatedGasTest {
30+
31+
private io.grpc.Server server;
32+
private ManagedChannel channel;
33+
34+
private volatile SendTransactionRequest capturedRequest;
35+
36+
@BeforeEach
37+
void setUp() throws Exception {
38+
final String serverName = InProcessServerBuilder.generateName();
39+
40+
server =
41+
InProcessServerBuilder.forName(serverName)
42+
.directExecutor()
43+
.addService(
44+
new RlnProverGrpc.RlnProverImplBase() {
45+
@Override
46+
public void sendTransaction(
47+
SendTransactionRequest request, StreamObserver<SendTransactionReply> resp) {
48+
capturedRequest = request;
49+
resp.onNext(SendTransactionReply.newBuilder().setResult(true).build());
50+
resp.onCompleted();
51+
}
52+
})
53+
.build()
54+
.start();
55+
56+
channel = InProcessChannelBuilder.forName(serverName).directExecutor().build();
57+
}
58+
59+
@AfterEach
60+
void tearDown() {
61+
if (channel != null) {
62+
channel.shutdownNow();
63+
}
64+
if (server != null) {
65+
server.shutdownNow();
66+
}
67+
}
68+
69+
@Test
70+
void forwardsEstimatedGasUsed_21000_forSimpleEthTransfer() throws Exception {
71+
final var validator =
72+
new RlnProverForwarderValidator(
73+
/* rlnConfig */ null,
74+
/* enabled */ true,
75+
/* karmaServiceClient */ null,
76+
/* txSim */ null,
77+
/* blockchain */ null,
78+
/* tracerConfig */ null,
79+
/* l1l2 */ null,
80+
/* providedChannel */ channel);
81+
82+
// Create a simple ETH transfer: to set, empty data, value > 0
83+
final org.hyperledger.besu.crypto.SECPSignature fakeSig =
84+
org.hyperledger.besu.crypto.SECPSignature.create(
85+
new java.math.BigInteger("1"),
86+
new java.math.BigInteger("2"),
87+
(byte) 0,
88+
new java.math.BigInteger("3"));
89+
90+
final org.hyperledger.besu.ethereum.core.Transaction tx =
91+
org.hyperledger.besu.ethereum.core.Transaction.builder()
92+
.sender(Address.fromHexString("0x2222222222222222222222222222222222222222"))
93+
.to(Address.fromHexString("0x1111111111111111111111111111111111111111"))
94+
.gasLimit(21_000)
95+
.gasPrice(Wei.of(1))
96+
.payload(Bytes.EMPTY)
97+
.value(Wei.of(1))
98+
.signature(fakeSig)
99+
.build();
100+
101+
final CountDownLatch latch = new CountDownLatch(1);
102+
// validateTransaction performs a blocking gRPC call; just invoke and then assert capture
103+
final var maybeError =
104+
validator.validateTransaction(
105+
(org.hyperledger.besu.datatypes.Transaction) tx, /* isLocal */
106+
true, /* hasPriority */
107+
false);
108+
assertThat(maybeError).isEmpty();
109+
latch.countDown();
110+
latch.await(100, TimeUnit.MILLISECONDS);
111+
112+
assertThat(capturedRequest).isNotNull();
113+
assertThat(capturedRequest.getEstimatedGasUsed()).isEqualTo(21_000L);
114+
}
115+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0x6A461f1BE039c0588A519Ef45C338dD2b388C703
1+
0x5767aB2Ed64666bFE27e52D4675EDd60Ec7D6EDF

docker/config/l2-node-besu/log4j.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
<Logger name="org.hyperledger.besu.ethereum.api.jsonrpc" level="TRACE" additivity="false">
2929
<AppenderRef ref="Console"/>
3030
</Logger>
31+
<Logger name="net.consensys.linea.sequencer.txpoolvalidation" level="DEBUG" additivity="false">
32+
<AppenderRef ref="Console"/>
33+
</Logger>
3134
<Logger name="io.opentelemetry" level="WARN" additivity="false">
3235
<AppenderRef ref="Console"/>
3336
</Logger>

0 commit comments

Comments
 (0)