Skip to content

Commit 49c71be

Browse files
Add floor price tests for TX_SKIP address collisions (#2465)
1 parent 903d668 commit 49c71be

File tree

2 files changed

+100
-20
lines changed

2 files changed

+100
-20
lines changed

arithmetization/src/test/java/net/consensys/linea/zktracer/forkSpecific/prague/floorprice/TrivialExecutionTests.java

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,18 @@ void txSkipTest(
4949
Bytes callData,
5050
boolean provideAccessList,
5151
DominantCost dominantCostPrediction,
52+
boolean imposeSenderRecipientCollision,
5253
TestInfo testInfo) {
5354
BytecodeRunner bytecodeRunner = BytecodeRunner.of(Bytes.EMPTY);
5455
AccessListEntry accessListEntry =
5556
new AccessListEntry(Address.fromHexString("0xABCD"), List.of());
5657
List<AccessListEntry> accessList = provideAccessList ? List.of(accessListEntry) : List.of();
57-
bytecodeRunner.run(callData, accessList, chainConfig, testInfo);
58+
if (!imposeSenderRecipientCollision) {
59+
bytecodeRunner.run(callData, accessList, chainConfig, testInfo);
60+
} else {
61+
bytecodeRunner.runWithImposedSenderRecipientAddressCollision(
62+
callData, accessList, chainConfig, testInfo);
63+
}
5864
// Test blocks contain 4 transactions: 2 system transactions, 1 user transaction (the one we
5965
// created) and 1 noop transaction.
6066
if (isPostPrague(fork)) {
@@ -71,14 +77,20 @@ void trivialCalleeTest(
7177
Bytes callData,
7278
boolean provideAccessList,
7379
DominantCost dominantCostPrediction,
80+
boolean imposeSenderRecipientCollision,
7481
TestInfo testInfo) {
7582
BytecodeCompiler program = BytecodeCompiler.newProgram(chainConfig);
7683
program.op(OpCode.STOP);
7784
BytecodeRunner bytecodeRunner = BytecodeRunner.of(program.compile());
7885
AccessListEntry accessListEntry =
7986
new AccessListEntry(Address.fromHexString("0xABCD"), List.of());
8087
List<AccessListEntry> accessList = provideAccessList ? List.of(accessListEntry) : List.of();
81-
bytecodeRunner.run(callData, accessList, chainConfig, testInfo);
88+
if (!imposeSenderRecipientCollision) {
89+
bytecodeRunner.run(callData, accessList, chainConfig, testInfo);
90+
} else {
91+
bytecodeRunner.runWithImposedSenderRecipientAddressCollision(
92+
callData, accessList, chainConfig, testInfo);
93+
}
8294
// Test blocks contain 4 transactions: 2 system transactions, 1 user transaction (the one we
8395
// created) and 1 noop transaction.
8496
if (isPostPrague(fork)) {
@@ -89,6 +101,8 @@ void trivialCalleeTest(
89101
}
90102

91103
static Stream<Arguments> testSource() {
104+
final boolean noSenderRecipientCollision = false;
105+
final boolean senderRecipientCollision = true;
92106
/*
93107
Here we change the callData (specifically the length and the CallDataSetting) to make comparingEffectiveRefundsVsFloorCost.result()
94108
become true in UserTransaction.comparingEffectiveRefundToFloorCostComputationRow.
@@ -103,12 +117,14 @@ Here we change the callData (specifically the length and the CallDataSetting) to
103117
Arguments.of(
104118
buildCallData(CallDataSetting.ALL_ZEROS, true, 400),
105119
true,
106-
DominantCost.EXECUTION_COST_DOMINATES));
120+
DominantCost.EXECUTION_COST_DOMINATES,
121+
noSenderRecipientCollision));
107122
arguments.add(
108123
Arguments.of(
109124
buildCallData(CallDataSetting.ALL_ZEROS, true, 401),
110125
true,
111-
DominantCost.FLOOR_COST_DOMINATES));
126+
DominantCost.FLOOR_COST_DOMINATES,
127+
noSenderRecipientCollision));
112128

113129
// Case ALL_NON_ZEROS_EXCEPT_FOR_FIRST.
114130
// The transaction execution cost (TX_SKIP) is 21000 + 2400 + 16*length.
@@ -118,12 +134,14 @@ Here we change the callData (specifically the length and the CallDataSetting) to
118134
Arguments.of(
119135
buildCallData(CallDataSetting.ALL_NON_ZEROS_EXCEPT_FOR_FIRST, false, 100),
120136
true,
121-
DominantCost.EXECUTION_COST_DOMINATES));
137+
DominantCost.EXECUTION_COST_DOMINATES,
138+
noSenderRecipientCollision));
122139
arguments.add(
123140
Arguments.of(
124141
buildCallData(CallDataSetting.ALL_NON_ZEROS_EXCEPT_FOR_FIRST, false, 101),
125142
true,
126-
DominantCost.FLOOR_COST_DOMINATES));
143+
DominantCost.FLOOR_COST_DOMINATES,
144+
noSenderRecipientCollision));
127145

128146
// Case ZEROS_AND_NON_ZEROS.
129147
// caveat for simplicity we consider even sizes.
@@ -134,12 +152,28 @@ Here we change the callData (specifically the length and the CallDataSetting) to
134152
Arguments.of(
135153
buildCallData(CallDataSetting.ZEROS_AND_NON_ZEROS, false, 160),
136154
true,
137-
DominantCost.EXECUTION_COST_DOMINATES));
155+
DominantCost.EXECUTION_COST_DOMINATES,
156+
noSenderRecipientCollision));
138157
arguments.add(
139158
Arguments.of(
140159
buildCallData(CallDataSetting.ZEROS_AND_NON_ZEROS, false, 162),
141160
true,
142-
DominantCost.FLOOR_COST_DOMINATES));
161+
DominantCost.FLOOR_COST_DOMINATES,
162+
noSenderRecipientCollision));
163+
164+
// Case ALL_ZEROS with sender-recipient collision.
165+
// This explores a case which blew up on mainnet, where a transaction had sender == recipient,
166+
// non-empty call
167+
// data, and a bug in the senderAddressCollision() case of TxSkipSection was triggered.
168+
//
169+
// The transaction execution cost (TX_SKIP) is 21000 + 4.
170+
// The floor cost is 21000 + 10.
171+
arguments.add(
172+
Arguments.of(
173+
buildCallData(CallDataSetting.ALL_ZEROS, true, 1),
174+
false,
175+
DominantCost.FLOOR_COST_DOMINATES,
176+
senderRecipientCollision));
143177

144178
return arguments.stream();
145179
}
@@ -152,7 +186,9 @@ enum CallDataSetting {
152186
}
153187

154188
static Bytes buildCallData(CallDataSetting callDataSetting, boolean startsWithZero, int length) {
155-
Preconditions.checkArgument(length > 1, "length must be at least 2");
189+
Preconditions.checkArgument(
190+
callDataSetting != CallDataSetting.ZEROS_AND_NON_ZEROS || length > 1,
191+
"length must be at least 2");
156192
return switch (callDataSetting) {
157193
case ALL_ZEROS -> Bytes.fromHexString("00".repeat(length));
158194
case ZEROS_AND_NON_ZEROS -> Bytes.fromHexString(

testing/src/main/java/net/consensys/linea/testing/BytecodeRunner.java

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,12 @@ public void run(
141141
Wei.fromEth(1), DEFAULT_GAS_LIMIT, List.of(), payload, accessList, chainConfig, testInfo);
142142
}
143143

144+
public void runWithImposedSenderRecipientAddressCollision(
145+
Bytes payload, List<AccessListEntry> accessList, ChainConfig chainConfig, TestInfo testInfo) {
146+
this.runWithImposedSenderRecipientAddressCollision(
147+
Wei.fromEth(1), DEFAULT_GAS_LIMIT, List.of(), payload, accessList, chainConfig, testInfo);
148+
}
149+
144150
public void run(
145151
Wei senderBalance,
146152
Long gasLimit,
@@ -162,19 +168,49 @@ public void run(
162168
ChainConfig chainConfig,
163169
TestInfo testInfo) {
164170
buildToyExecutionEnvironmentV2(
165-
senderBalance, gasLimit, additionalAccounts, payload, accessList, chainConfig, testInfo);
171+
senderBalance,
172+
gasLimit,
173+
additionalAccounts,
174+
payload,
175+
accessList,
176+
chainConfig,
177+
testInfo,
178+
false);
166179
toyExecutionEnvironmentV2.run();
167180
}
168181

169-
private void buildToyExecutionEnvironmentV2(
182+
public void runWithImposedSenderRecipientAddressCollision(
170183
Wei senderBalance,
171184
Long gasLimit,
172185
List<ToyAccount> additionalAccounts,
173186
Bytes payload,
174187
List<AccessListEntry> accessList,
175188
ChainConfig chainConfig,
176189
TestInfo testInfo) {
190+
buildToyExecutionEnvironmentV2(
191+
senderBalance,
192+
gasLimit,
193+
additionalAccounts,
194+
payload,
195+
accessList,
196+
chainConfig,
197+
testInfo,
198+
true);
199+
toyExecutionEnvironmentV2.run();
200+
}
201+
202+
private void buildToyExecutionEnvironmentV2(
203+
Wei senderBalance,
204+
Long gasLimit,
205+
List<ToyAccount> additionalAccounts,
206+
Bytes payload,
207+
List<AccessListEntry> accessList,
208+
ChainConfig chainConfig,
209+
TestInfo testInfo,
210+
boolean imposeSenderRecipientCollision) {
177211
checkArgument(byteCode != null, "byteCode cannot be empty");
212+
final long transactionValue = 272; // 256 + 16, easier for debugging
213+
final long gasPrice = 8;
178214

179215
final KeyPair keyPair = new SECP256K1().generateKeyPair();
180216
final Address senderAddress =
@@ -186,21 +222,27 @@ private void buildToyExecutionEnvironmentV2(
186222
final Long selectedGasLimit = Optional.of(gasLimit).orElse(DEFAULT_GAS_LIMIT);
187223

188224
final ToyAccount receiverAccount =
189-
ToyAccount.builder()
190-
.balance(Wei.fromEth(1))
191-
.nonce(6)
192-
.address(Address.fromHexString("0x1111111111111111111111111111111111111111"))
193-
.code(byteCode)
194-
.build();
225+
imposeSenderRecipientCollision
226+
? ToyAccount.builder()
227+
.balance(senderBalance.subtract(transactionValue + gasPrice * selectedGasLimit))
228+
.nonce(5 + 1)
229+
.address(senderAddress)
230+
.build()
231+
: ToyAccount.builder()
232+
.balance(Wei.fromEth(1))
233+
.nonce(6)
234+
.address(Address.fromHexString("0x1111111111111111111111111111111111111111"))
235+
.code(byteCode)
236+
.build();
195237

196238
final ToyTransaction.ToyTransactionBuilder txBuilder =
197239
ToyTransaction.builder()
198240
.sender(senderAccount)
199241
.to(receiverAccount)
200-
.value(Wei.of(272)) // 256 + 16, easier for debugging
242+
.value(Wei.of(transactionValue)) // 256 + 16, easier for debugging
201243
.keyPair(keyPair)
202244
.gasLimit(selectedGasLimit)
203-
.gasPrice(Wei.of(8));
245+
.gasPrice(Wei.of(gasPrice));
204246
if (!payload.isEmpty()) {
205247
txBuilder.payload(payload);
206248
}
@@ -212,7 +254,9 @@ private void buildToyExecutionEnvironmentV2(
212254

213255
final List<ToyAccount> accounts = new ArrayList<>();
214256
accounts.add(senderAccount);
215-
accounts.add(receiverAccount);
257+
if (!imposeSenderRecipientCollision) {
258+
accounts.add(receiverAccount);
259+
}
216260
accounts.addAll(additionalAccounts);
217261

218262
toyExecutionEnvironmentV2 =

0 commit comments

Comments
 (0)