EIP-2930: Access List Transactions (#1712)

https://eips.ethereum.org/EIPS/eip-2930

Signed-off-by: Ratan Rai Sur <ratan.r.sur@gmail.com>
This commit is contained in:
Ratan (Rai) Sur
2021-02-01 12:06:35 -06:00
committed by GitHub
parent 9934403a97
commit 39b2c6e7af
28 changed files with 717 additions and 157 deletions

View File

@@ -80,7 +80,6 @@ import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@SuppressWarnings("rawtypes")
// todo request lucas look at this pr
public class PrivacyReorgTest {
@Rule public final TemporaryFolder folder = new TemporaryFolder();
@@ -93,9 +92,9 @@ public class PrivacyReorgTest {
Bytes.fromBase64String("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=");
private static final String FIRST_BLOCK_WITH_NO_TRANSACTIONS_STATE_ROOT =
"0xc9dcaffbebc7edc20839c80e706430a8fa885e8f703bb89f6e95633cc2b05d4d";
"0xe368938a01d983e331eb0e4ea61224726d06075c1ad525569b369f664067ff26";
private static final String FIRST_BLOCK_WITH_SINGLE_TRANSACTION_STATE_ROOT =
"0x66fbc6ad12ef1da78740093ea2b3362e773e510e27d8f88b68c27bfc1f4d58c8";
"0x9c88988f9602184efc538cf1c2f482a6b8757ff918d234602884dc8e3b983edd";
private static final String BLOCK_WITH_SINGLE_TRANSACTION_RECEIPTS_ROOT =
"0xc8267b3f9ed36df3ff8adb51a6d030716f23eeb50270e7fce8d9822ffa7f0461";
private static final String STATE_ROOT_AFTER_TRANSACTION_APPENDED_TO_EMPTY_STATE =
@@ -338,7 +337,7 @@ public class PrivacyReorgTest {
privateStateRootResolver, blockchain, STATE_ROOT_AFTER_TRANSACTION_APPENDED_TO_EMPTY_STATE);
final String secondForkBlockStateRoot =
"0x57ccc80f4e50d2e669d82aefa7d3bbe763cf47df27665af14c90b2f8641953f5";
"0x2c37a360a700c614b10c980138f64be9ad66fc4a14cd5145199cd0d8ec43d51d";
final Block secondForkBlock =
gen.block(
getBlockOptionsNoTransactionWithDifficulty(
@@ -368,8 +367,7 @@ public class PrivacyReorgTest {
appendBlock(besuController, blockchain, protocolContext, thirdForkBlock);
// Check that the private state did change after reorg
assertPrivateStateRoot(
privateStateRootResolver, blockchain, STATE_ROOT_AFTER_TRANSACTION_APPENDED_TO_EMPTY_STATE);
assertPrivateStateRoot(privateStateRootResolver, blockchain, EMPTY_ROOT_HASH);
}
@SuppressWarnings("unchecked")

View File

@@ -299,11 +299,22 @@ public class BesuEventsImplTest {
blockchain.appendBlock(block, gen.receipts(block));
assertThat(result.get()).isNull();
final var reorgBlock =
final var forkBlock =
gen.block(
new BlockDataGenerator.BlockOptions()
.setParentHash(blockchain.getGenesisBlock().getHash())
.setBlockNumber(blockchain.getGenesisBlock().getHeader().getNumber() + 2));
.setDifficulty(block.getHeader().getDifficulty().subtract(1))
.setBlockNumber(blockchain.getGenesisBlock().getHeader().getNumber() + 1));
blockchain.appendBlock(forkBlock, gen.receipts(forkBlock));
assertThat(result.get()).isNull();
final var reorgBlock =
gen.block(
new BlockDataGenerator.BlockOptions()
.setParentHash(forkBlock.getHash())
.setDifficulty(Difficulty.of(10000000))
.setBlockNumber(forkBlock.getHeader().getNumber() + 1));
List<TransactionReceipt> transactionReceipts = gen.receipts(reorgBlock);
blockchain.appendBlock(reorgBlock, transactionReceipts);
assertThat(result.get()).isNotNull();

View File

@@ -400,7 +400,7 @@ public class BlockchainQueriesTest {
@Test
public void getOmmerByBlockHashAndIndexShouldReturnExpectedOmmerHeader() {
final BlockchainWithData data = setupBlockchain(3);
final BlockchainWithData data = setupBlockchain(4);
final BlockchainQueries queries = data.blockchainQueries;
final Block targetBlock = data.blockData.get(data.blockData.size() - 1).block;
final BlockHeader ommerBlockHeader = targetBlock.getBody().getOmmers().get(0);
@@ -448,7 +448,7 @@ public class BlockchainQueriesTest {
@Test
public void getOmmerByBlockNumberAndIndexShouldReturnExpectedOmmerHeader() {
final BlockchainWithData data = setupBlockchain(3);
final BlockchainWithData data = setupBlockchain(4);
final BlockchainQueries queries = data.blockchainQueries;
final Block targetBlock = data.blockData.get(data.blockData.size() - 1).block;
final List<BlockHeader> ommers = targetBlock.getBody().getOmmers();
@@ -485,7 +485,7 @@ public class BlockchainQueriesTest {
@Test
public void getLatestBlockOmmerByIndexShouldReturnExpectedOmmerHeader() {
final BlockchainWithData data = setupBlockchain(3);
final BlockchainWithData data = setupBlockchain(4);
final BlockchainQueries queries = data.blockchainQueries;
final Block targetBlock = data.blockData.get(data.blockData.size() - 1).block;
final BlockHeader ommerBlockHeader = targetBlock.getBody().getOmmers().get(0);

View File

@@ -0,0 +1,35 @@
/*
*
* * Copyright ConsenSys AG.
* *
* * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* * the License. You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* * specific language governing permissions and limitations under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.core;
import static java.util.Collections.emptyList;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.tuweni.bytes.Bytes32;
public class AccessList extends ArrayList<Map.Entry<Address, List<Bytes32>>> {
public AccessList(final List<Map.Entry<Address, List<Bytes32>>> accessList) {
super(accessList);
}
public static final AccessList EMPTY = new AccessList(emptyList());
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.core;
import static java.util.Collections.emptySet;
import java.util.Set;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.apache.tuweni.bytes.Bytes32;
public class GasAndAccessedState {
final Gas gas;
final Set<Address> accessListAddressSet;
final Multimap<Address, Bytes32> accessListStorageByAddress;
public GasAndAccessedState(
final Gas gas,
final Set<Address> accessListAddressSet,
final Multimap<Address, Bytes32> accessedStorage) {
this.gas = gas;
this.accessListAddressSet = accessListAddressSet;
this.accessListStorageByAddress = accessedStorage;
}
public GasAndAccessedState(final Gas gas) {
this.gas = gas;
this.accessListAddressSet = emptySet();
this.accessListStorageByAddress = HashMultimap.create();
}
public Gas getGas() {
return gas;
}
public Set<Address> getAccessListAddressSet() {
return accessListAddressSet;
}
public Multimap<Address, Bytes32> getAccessListStorageByAddress() {
return accessListStorageByAddress;
}
}

View File

@@ -59,6 +59,10 @@ public class LogsBloomFilter extends DelegatingBytes {
data.size());
}
public LogsBloomFilter(final String logsBloomHexString) {
this(Bytes.fromHexString(logsBloomHexString));
}
public static LogsBloomFilter fromHexString(final String hexString) {
return new LogsBloomFilter(Bytes.fromHexString(hexString));
}

View File

@@ -17,7 +17,6 @@ package org.hyperledger.besu.ethereum.core;
import static com.google.common.base.Preconditions.checkState;
import static org.hyperledger.besu.crypto.Hash.keccak256;
import org.hyperledger.besu.config.experimental.ExperimentalEIPs;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.crypto.SECP256K1.PublicKey;
import org.hyperledger.besu.ethereum.core.encoding.TransactionRLPDecoder;
@@ -73,6 +72,8 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
private final Bytes payload;
private final AccessList accessList;
private final Optional<BigInteger> chainId;
private final Optional<BigInteger> v;
@@ -111,6 +112,7 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
* @param value the value being transferred to the recipient
* @param signature the signature
* @param payload the payload
* @param accessList the list of addresses/storage slots this transaction intends to preload
* @param sender the transaction sender
* @param chainId the chain id to apply the transaction to
* @param v the v value. This is only passed in directly for GoQuorum private transactions
@@ -132,6 +134,7 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
final Wei value,
final SECP256K1.Signature signature,
final Bytes payload,
final AccessList accessList,
final Address sender,
final Optional<BigInteger> chainId,
final Optional<BigInteger> v) {
@@ -149,6 +152,7 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
this.value = value;
this.signature = signature;
this.payload = payload;
this.accessList = accessList;
this.sender = sender;
this.chainId = chainId;
this.v = v;
@@ -178,6 +182,7 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
value,
signature,
payload,
AccessList.EMPTY,
sender,
chainId,
v);
@@ -368,6 +373,10 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
return getTo().isPresent() ? Optional.of(payload) : Optional.empty();
}
public AccessList getAccessList() {
return accessList;
}
/**
* Return the transaction chain id (if it exists)
*
@@ -414,14 +423,16 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
if (hashNoSignature == null) {
hashNoSignature =
computeSenderRecoveryHash(
transactionType,
nonce,
gasPrice,
gasPremium,
feeCap,
gasLimit,
to.orElse(null),
to,
value,
payload,
accessList,
chainId);
}
return hashNoSignature;
@@ -536,36 +547,121 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
}
private static Bytes32 computeSenderRecoveryHash(
final TransactionType transactionType,
final long nonce,
final Wei gasPrice,
final Wei gasPremium,
final Wei feeCap,
final long gasLimit,
final Address to,
final Optional<Address> to,
final Wei value,
final Bytes payload,
final AccessList accessList,
final Optional<BigInteger> chainId) {
final Bytes preimage;
switch (transactionType) {
case FRONTIER:
preimage = frontierPreimage(nonce, gasPrice, gasLimit, to, value, payload, chainId);
break;
case EIP1559:
preimage =
eip1559Preimage(
nonce, gasPrice, gasPremium, feeCap, gasLimit, to, value, payload, chainId);
break;
case ACCESS_LIST:
preimage =
accessListPreimage(
transactionType,
nonce,
gasPrice,
gasLimit,
to,
value,
payload,
accessList,
chainId);
break;
default:
throw new IllegalStateException(
"Developer error. Didn't specify signing hash preimage computation");
}
return keccak256(preimage);
}
private static Bytes frontierPreimage(
final long nonce,
final Wei gasPrice,
final long gasLimit,
final Optional<Address> to,
final Wei value,
final Bytes payload,
final Optional<BigInteger> chainId) {
return keccak256(
RLP.encode(
out -> {
out.startList();
out.writeLongScalar(nonce);
out.writeUInt256Scalar(gasPrice);
out.writeLongScalar(gasLimit);
out.writeBytes(to == null ? Bytes.EMPTY : to);
out.writeUInt256Scalar(value);
out.writeBytes(payload);
if (ExperimentalEIPs.eip1559Enabled && gasPremium != null && feeCap != null) {
out.writeUInt256Scalar(gasPremium);
out.writeUInt256Scalar(feeCap);
}
if (chainId.isPresent()) {
out.writeBigIntegerScalar(chainId.get());
out.writeUInt256Scalar(UInt256.ZERO);
out.writeUInt256Scalar(UInt256.ZERO);
}
out.endList();
}));
return RLP.encode(
rlpOutput -> {
rlpOutput.startList();
rlpOutput.writeLongScalar(nonce);
rlpOutput.writeUInt256Scalar(gasPrice);
rlpOutput.writeLongScalar(gasLimit);
rlpOutput.writeBytes(to.map(Bytes::copy).orElse(Bytes.EMPTY));
rlpOutput.writeUInt256Scalar(value);
rlpOutput.writeBytes(payload);
if (chainId.isPresent()) {
rlpOutput.writeBigIntegerScalar(chainId.get());
rlpOutput.writeUInt256Scalar(UInt256.ZERO);
rlpOutput.writeUInt256Scalar(UInt256.ZERO);
}
rlpOutput.endList();
});
}
private static Bytes eip1559Preimage(
final long nonce,
final Wei gasPrice,
final Wei gasPremium,
final Wei feeCap,
final long gasLimit,
final Optional<Address> to,
final Wei value,
final Bytes payload,
final Optional<BigInteger> chainId) {
return RLP.encode(
rlpOutput -> {
rlpOutput.startList();
rlpOutput.writeLongScalar(nonce);
rlpOutput.writeUInt256Scalar(gasPrice);
rlpOutput.writeLongScalar(gasLimit);
rlpOutput.writeBytes(to.map(Bytes::copy).orElse(Bytes.EMPTY));
rlpOutput.writeUInt256Scalar(value);
rlpOutput.writeBytes(payload);
rlpOutput.writeUInt256Scalar(gasPremium);
rlpOutput.writeUInt256Scalar(feeCap);
if (chainId.isPresent()) {
rlpOutput.writeBigIntegerScalar(chainId.get());
rlpOutput.writeUInt256Scalar(UInt256.ZERO);
rlpOutput.writeUInt256Scalar(UInt256.ZERO);
}
rlpOutput.endList();
});
}
private static Bytes accessListPreimage(
final TransactionType transactionType,
final long nonce,
final Wei gasPrice,
final long gasLimit,
final Optional<Address> to,
final Wei value,
final Bytes payload,
final AccessList accessList,
final Optional<BigInteger> chainId) {
return RLP.encode(
rlpOutput -> {
rlpOutput.startList();
rlpOutput.writeIntScalar(transactionType.getSerializedType());
TransactionRLPEncoder.encodeAccessListInner(
chainId, nonce, gasPrice, gasLimit, to, value, payload, accessList, rlpOutput);
rlpOutput.endList();
});
}
@Override
@@ -574,17 +670,17 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
return false;
}
final Transaction that = (Transaction) other;
return this.chainId.equals(that.chainId)
&& this.gasLimit == that.gasLimit
return Objects.equals(this.chainId, that.chainId)
&& Objects.equals(this.gasLimit, that.gasLimit)
&& Objects.equals(this.gasPrice, that.gasPrice)
&& Objects.equals(this.gasPremium, that.gasPremium)
&& Objects.equals(this.feeCap, that.feeCap)
&& this.nonce == that.nonce
&& this.payload.equals(that.payload)
&& this.signature.equals(that.signature)
&& this.to.equals(that.to)
&& this.value.equals(that.value)
&& this.v.equals(that.v);
&& Objects.equals(this.nonce, that.nonce)
&& Objects.equals(this.payload, that.payload)
&& Objects.equals(this.signature, that.signature)
&& Objects.equals(this.to, that.to)
&& Objects.equals(this.value, that.value)
&& Objects.equals(this.getV(), that.getV());
}
@Override
@@ -610,7 +706,10 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
sb.append("sig=").append(getSignature()).append(", ");
if (chainId.isPresent()) sb.append("chainId=").append(getChainId().get()).append(", ");
if (v.isPresent()) sb.append("v=").append(v.get()).append(", ");
sb.append("payload=").append(getPayload());
sb.append("payload=").append(getPayload()).append(", ");
if (transactionType.equals(TransactionType.ACCESS_LIST)) {
sb.append("accessList=").append(accessList);
}
return sb.append("}").toString();
}
@@ -635,7 +734,7 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
protected long gasLimit = -1L;
protected Address to;
protected Optional<Address> to = Optional.empty();
protected Wei value;
@@ -643,12 +742,19 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
protected Bytes payload;
protected AccessList accessList = AccessList.EMPTY;
protected Address sender;
protected Optional<BigInteger> chainId = Optional.empty();
protected Optional<BigInteger> v = Optional.empty();
public Builder type(final TransactionType transactionType) {
this.transactionType = transactionType;
return this;
}
public Builder chainId(final BigInteger chainId) {
this.chainId = Optional.of(chainId);
return this;
@@ -690,7 +796,7 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
}
public Builder to(final Address to) {
this.to = to;
this.to = Optional.ofNullable(to);
return this;
}
@@ -699,6 +805,11 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
return this;
}
public Builder accessList(final AccessList accessList) {
this.accessList = accessList;
return this;
}
public Builder sender(final Address sender) {
this.sender = sender;
return this;
@@ -709,8 +820,14 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
return this;
}
public Builder type(final TransactionType transactionType) {
this.transactionType = transactionType;
public Builder guessType() {
if (!accessList.isEmpty()) {
transactionType = TransactionType.ACCESS_LIST;
} else if (gasPremium != null || feeCap != null) {
transactionType = TransactionType.EIP1559;
} else {
transactionType = TransactionType.FRONTIER;
}
return this;
}
@@ -722,10 +839,11 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
gasPremium,
feeCap,
gasLimit,
Optional.ofNullable(to),
to,
value,
signature,
payload,
accessList,
sender,
chainId,
v);
@@ -740,19 +858,20 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
}
SECP256K1.Signature computeSignature(final SECP256K1.KeyPair keys) {
final Bytes32 hash =
return SECP256K1.sign(
computeSenderRecoveryHash(
nonce, gasPrice, gasPremium, feeCap, gasLimit, to, value, payload, chainId);
return SECP256K1.sign(hash, keys);
}
public Builder guessType() {
if (gasPremium != null || feeCap != null) {
transactionType = TransactionType.EIP1559;
} else {
transactionType = TransactionType.FRONTIER;
}
return this;
transactionType,
nonce,
gasPrice,
gasPremium,
feeCap,
gasLimit,
to,
value,
payload,
accessList,
chainId),
keys);
}
}
}

View File

@@ -174,38 +174,36 @@ public class TransactionReceipt implements org.hyperledger.besu.plugin.data.Tran
}
private void writeTo(final RLPOutput rlpOutput, final boolean withRevertReason) {
final Bytes receiptBytes =
RLP.encode(
out -> {
out.startList();
// Determine whether it's a state root-encoded transaction receipt
// or is a status code-encoded transaction receipt.
if (stateRoot != null) {
out.writeBytes(stateRoot);
} else {
out.writeLongScalar(status);
}
out.writeLongScalar(cumulativeGasUsed);
out.writeBytes(bloomFilter);
out.writeList(logs, Log::writeTo);
if (withRevertReason && revertReason.isPresent()) {
out.writeBytes(revertReason.get());
}
out.endList();
});
switch (transactionType) {
case FRONTIER:
case EIP1559:
rlpOutput.writeRaw(receiptBytes);
break;
default:
rlpOutput.writeBytes(
Bytes.concatenate(Bytes.of((byte) transactionType.getSerializedType()), receiptBytes));
if (transactionType.equals(TransactionType.FRONTIER)) {
writeToForReceiptTrie(rlpOutput, withRevertReason);
} else {
rlpOutput.writeBytes(RLP.encode(out -> writeToForReceiptTrie(out, withRevertReason)));
}
}
public void writeToForReceiptTrie(final RLPOutput rlpOutput, final boolean withRevertReason) {
if (!transactionType.equals(TransactionType.FRONTIER)) {
rlpOutput.writeIntScalar(transactionType.getSerializedType());
}
rlpOutput.startList();
// Determine whether it's a state root-encoded transaction receipt
// or is a status code-encoded transaction receipt.
if (stateRoot != null) {
rlpOutput.writeBytes(stateRoot);
} else {
rlpOutput.writeLongScalar(status);
}
rlpOutput.writeLongScalar(cumulativeGasUsed);
rlpOutput.writeBytes(bloomFilter);
rlpOutput.writeList(logs, Log::writeTo);
if (withRevertReason && revertReason.isPresent()) {
rlpOutput.writeBytes(revertReason.get());
}
rlpOutput.endList();
}
/**
* Creates a transaction receipt for the given RLP
*
@@ -227,11 +225,10 @@ public class TransactionReceipt implements org.hyperledger.besu.plugin.data.Tran
final RLPInput rlpInput, final boolean revertReasonAllowed) {
RLPInput input = rlpInput;
TransactionType transactionType = TransactionType.FRONTIER;
if (!input.nextIsList()) {
final Bytes bytes = input.readBytes();
transactionType = TransactionType.of(bytes.get(0));
input = new BytesValueRLPInput(bytes.slice(1), false);
System.out.println(bytes.toHexString());
if (!rlpInput.nextIsList()) {
final Bytes typedTransactionReceiptBytes = input.readBytes();
transactionType = TransactionType.of(typedTransactionReceiptBytes.get(0));
input = new BytesValueRLPInput(typedTransactionReceiptBytes.slice(1), false);
}
input.enterList();

View File

@@ -14,6 +14,7 @@
*/
package org.hyperledger.besu.ethereum.core.encoding;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.hyperledger.besu.ethereum.core.Transaction.GO_QUORUM_PRIVATE_TRANSACTION_V_VALUE_MAX;
import static org.hyperledger.besu.ethereum.core.Transaction.GO_QUORUM_PRIVATE_TRANSACTION_V_VALUE_MIN;
import static org.hyperledger.besu.ethereum.core.Transaction.REPLAY_PROTECTED_V_BASE;
@@ -24,6 +25,7 @@ import static org.hyperledger.besu.ethereum.core.Transaction.TWO;
import org.hyperledger.besu.config.GoQuorumOptions;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.ethereum.core.AccessList;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
@@ -32,10 +34,14 @@ import org.hyperledger.besu.ethereum.rlp.RLPInput;
import org.hyperledger.besu.plugin.data.TransactionType;
import java.math.BigInteger;
import java.util.AbstractMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.google.common.collect.ImmutableMap;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
public class TransactionRLPDecoder {
@@ -46,7 +52,11 @@ public class TransactionRLPDecoder {
private static final ImmutableMap<TransactionType, TransactionRLPDecoder.Decoder>
TYPED_TRANSACTION_DECODERS =
ImmutableMap.of(TransactionType.EIP1559, TransactionRLPDecoder::decodeEIP1559);
ImmutableMap.of(
TransactionType.ACCESS_LIST,
TransactionRLPDecoder::decodeAccessList,
TransactionType.EIP1559,
TransactionRLPDecoder::decodeEIP1559);
public static Transaction decode(final RLPInput rlpInput) {
if (rlpInput.nextIsList()) {
@@ -56,13 +66,10 @@ public class TransactionRLPDecoder {
final TransactionType transactionType =
TransactionType.of(typedTransactionBytes.get(0) & 0xff);
final Decoder decoder =
Optional.ofNullable(TYPED_TRANSACTION_DECODERS.get(transactionType))
.orElseThrow(
() ->
new IllegalStateException(
String.format(
"Developer Error. A supported transaction type %s has no associated decoding logic",
transactionType)));
checkNotNull(
TYPED_TRANSACTION_DECODERS.get(transactionType),
"Developer Error. A supported transaction type %s has no associated decoding logic",
transactionType);
return decoder.decode(RLP.input(typedTransactionBytes.slice(1)));
}
}
@@ -104,6 +111,45 @@ public class TransactionRLPDecoder {
return builder.signature(signature).build();
}
private static Transaction decodeAccessList(final RLPInput rlpInput) {
rlpInput.enterList();
final Transaction.Builder preSignatureTransactionBuilder =
Transaction.builder()
.type(TransactionType.ACCESS_LIST)
.chainId(BigInteger.valueOf(rlpInput.readLongScalar()))
.nonce(rlpInput.readLongScalar())
.gasPrice(Wei.of(rlpInput.readUInt256Scalar()))
.gasLimit(rlpInput.readLongScalar())
.to(
rlpInput.readBytes(
addressBytes -> addressBytes.size() == 0 ? null : Address.wrap(addressBytes)))
.value(Wei.of(rlpInput.readUInt256Scalar()))
.payload(rlpInput.readBytes())
.accessList(
new AccessList(
rlpInput.readList(
accessListEntryRLPInput -> {
accessListEntryRLPInput.enterList();
final Map.Entry<Address, List<Bytes32>> accessListEntry =
new AbstractMap.SimpleEntry<>(
Address.wrap(accessListEntryRLPInput.readBytes()),
accessListEntryRLPInput.readList(RLPInput::readBytes32));
accessListEntryRLPInput.leaveList();
return accessListEntry;
})));
final byte recId = (byte) rlpInput.readIntScalar();
final Transaction transaction =
preSignatureTransactionBuilder
.signature(
SECP256K1.Signature.create(
rlpInput.readUInt256Scalar().toBytes().toUnsignedBigInteger(),
rlpInput.readUInt256Scalar().toBytes().toUnsignedBigInteger(),
recId))
.build();
rlpInput.leaveList();
return transaction;
}
static Transaction decodeEIP1559(final RLPInput input) {
input.enterList();

View File

@@ -16,6 +16,8 @@ package org.hyperledger.besu.ethereum.core.encoding;
import static com.google.common.base.Preconditions.checkNotNull;
import org.hyperledger.besu.ethereum.core.AccessList;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.rlp.RLP;
@@ -23,6 +25,7 @@ import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import org.hyperledger.besu.plugin.data.Quantity;
import org.hyperledger.besu.plugin.data.TransactionType;
import java.math.BigInteger;
import java.util.Optional;
import com.google.common.collect.ImmutableMap;
@@ -36,9 +39,25 @@ public class TransactionRLPEncoder {
}
private static final ImmutableMap<TransactionType, Encoder> TYPED_TRANSACTION_ENCODERS =
ImmutableMap.of(TransactionType.EIP1559, TransactionRLPEncoder::encodeEIP1559);
ImmutableMap.of(
TransactionType.ACCESS_LIST,
TransactionRLPEncoder::encodeAccessList,
TransactionType.EIP1559,
TransactionRLPEncoder::encodeEIP1559);
public static void encode(final Transaction transaction, final RLPOutput rlpOutput) {
final TransactionType transactionType =
checkNotNull(
transaction.getType(), "Transaction type for %s was not specified.", transaction);
if (TransactionType.FRONTIER.equals(transactionType)) {
encodeFrontier(transaction, rlpOutput);
} else {
rlpOutput.writeBytes(RLP.encode(output -> encodeForTransactionTrie(transaction, output)));
}
}
public static void encodeForTransactionTrie(
final Transaction transaction, final RLPOutput rlpOutput) {
final TransactionType transactionType =
checkNotNull(
transaction.getType(), "Transaction type for %s was not specified.", transaction);
@@ -46,17 +65,12 @@ public class TransactionRLPEncoder {
encodeFrontier(transaction, rlpOutput);
} else {
final Encoder encoder =
Optional.ofNullable(TYPED_TRANSACTION_ENCODERS.get(transactionType))
.orElseThrow(
() ->
new IllegalStateException(
String.format(
"Developer Error. A supported transaction type %s has no associated encoding logic",
transactionType)));
rlpOutput.writeBytes(
Bytes.concatenate(
Bytes.of((byte) transactionType.getSerializedType()),
RLP.encode(output -> encoder.encode(transaction, output))));
checkNotNull(
TYPED_TRANSACTION_ENCODERS.get(transactionType),
"Developer Error. A supported transaction type %s has no associated encoding logic",
transactionType);
rlpOutput.writeIntScalar(transactionType.getSerializedType());
encoder.encode(transaction, rlpOutput);
}
}
@@ -68,10 +82,79 @@ public class TransactionRLPEncoder {
out.writeBytes(transaction.getTo().map(Bytes::copy).orElse(Bytes.EMPTY));
out.writeUInt256Scalar(transaction.getValue());
out.writeBytes(transaction.getPayload());
writeSignature(transaction, out);
writeSignatureAndRecoveryId(transaction, out);
out.endList();
}
static void encodeAccessList(final Transaction transaction, final RLPOutput rlpOutput) {
rlpOutput.startList();
encodeAccessListInner(
transaction.getChainId(),
transaction.getNonce(),
transaction.getGasPrice(),
transaction.getGasLimit(),
transaction.getTo(),
transaction.getValue(),
transaction.getPayload(),
transaction.getAccessList(),
rlpOutput);
rlpOutput.writeIntScalar(transaction.getSignature().getRecId());
writeSignature(transaction, rlpOutput);
rlpOutput.endList();
}
public static void encodeAccessListInner(
final Optional<BigInteger> chainId,
final long nonce,
final Wei gasPrice,
final long gasLimit,
final Optional<Address> to,
final Wei value,
final Bytes payload,
final AccessList accessList,
final RLPOutput rlpOutput) {
rlpOutput.writeLongScalar(
chainId
.orElseThrow(
() ->
new IllegalArgumentException(
"chainId is required for access list transactions"))
.longValue());
rlpOutput.writeLongScalar(nonce);
rlpOutput.writeUInt256Scalar(gasPrice);
rlpOutput.writeLongScalar(gasLimit);
rlpOutput.writeBytes(to.map(Bytes::copy).orElse(Bytes.EMPTY));
rlpOutput.writeUInt256Scalar(value);
rlpOutput.writeBytes(payload);
/*
Access List encoding should look like this
where hex strings represent raw bytes
[
[
"0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
[
"0x0000000000000000000000000000000000000000000000000000000000000003",
"0x0000000000000000000000000000000000000000000000000000000000000007"
]
],
[
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
[]
]
] */
rlpOutput.writeList(
accessList,
(accessListEntry, accessListEntryRLPOutput) -> {
accessListEntryRLPOutput.startList();
rlpOutput.writeBytes(accessListEntry.getKey());
rlpOutput.writeList(
accessListEntry.getValue(),
(storageKeyBytes, storageKeyBytesRLPOutput) ->
storageKeyBytesRLPOutput.writeBytes(storageKeyBytes));
accessListEntryRLPOutput.endList();
});
}
static void encodeEIP1559(final Transaction transaction, final RLPOutput out) {
out.startList();
out.writeLongScalar(transaction.getNonce());
@@ -84,12 +167,17 @@ public class TransactionRLPEncoder {
transaction.getGasPremium().map(Quantity::getValue).map(Wei::ofNumber).orElseThrow());
out.writeUInt256Scalar(
transaction.getFeeCap().map(Quantity::getValue).map(Wei::ofNumber).orElseThrow());
writeSignature(transaction, out);
writeSignatureAndRecoveryId(transaction, out);
out.endList();
}
private static void writeSignature(final Transaction transaction, final RLPOutput out) {
private static void writeSignatureAndRecoveryId(
final Transaction transaction, final RLPOutput out) {
out.writeBigIntegerScalar(transaction.getV());
writeSignature(transaction, out);
}
private static void writeSignature(final Transaction transaction, final RLPOutput out) {
out.writeBigIntegerScalar(transaction.getSignature().getR());
out.writeBigIntegerScalar(transaction.getSignature().getS());
}

View File

@@ -16,16 +16,26 @@ package org.hyperledger.besu.ethereum.mainnet;
import static org.hyperledger.besu.ethereum.core.Address.BLS12_MAP_FP2_TO_G2;
import org.hyperledger.besu.ethereum.core.AccessList;
import org.hyperledger.besu.ethereum.core.Account;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Gas;
import org.hyperledger.besu.ethereum.core.GasAndAccessedState;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.mainnet.precompiles.BigIntegerModularExponentiationPrecompiledContract;
import org.hyperledger.besu.ethereum.vm.MessageFrame;
import java.math.BigInteger;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
public class BerlinGasCalculator extends IstanbulGasCalculator {
@@ -34,6 +44,8 @@ public class BerlinGasCalculator extends IstanbulGasCalculator {
private static final Gas COLD_SLOAD_COST = Gas.of(2100);
private static final Gas COLD_ACCOUNT_ACCESS_COST = Gas.of(2600);
private static final Gas WARM_STORAGE_READ_COST = Gas.of(100);
private static final Gas ACCESS_LIST_ADDRESS_COST = Gas.of(2400);
private static final Gas ACCESS_LIST_STORAGE_COST = Gas.of(1900);
// redefinitions for EIP-2929
private static final Gas SLOAD_GAS = WARM_STORAGE_READ_COST;
@@ -60,6 +72,35 @@ public class BerlinGasCalculator extends IstanbulGasCalculator {
this(BLS12_MAP_FP2_TO_G2.toArrayUnsafe()[19]);
}
@Override
public GasAndAccessedState transactionIntrinsicGasCostAndAccessedState(
final Transaction transaction) {
// As per https://eips.ethereum.org/EIPS/eip-2930
final AccessList accessList = transaction.getAccessList();
long accessedStorageCount = 0;
final Set<Address> accessedAddresses = new HashSet<>();
final Multimap<Address, Bytes32> accessedStorage = HashMultimap.create();
for (final Map.Entry<Address, List<Bytes32>> accessListEntry : accessList) {
final Address address = accessListEntry.getKey();
accessedAddresses.add(address);
for (final Bytes32 storageKeyBytes : accessListEntry.getValue()) {
accessedStorage.put(address, storageKeyBytes);
++accessedStorageCount;
}
}
return new GasAndAccessedState(
super.transactionIntrinsicGasCostAndAccessedState(transaction)
.getGas()
.plus(ACCESS_LIST_ADDRESS_COST.times(accessList.size()))
.plus(ACCESS_LIST_STORAGE_COST.times(accessedStorageCount)),
accessedAddresses,
accessedStorage);
}
@Override
public boolean isPrecompile(final Address address) {
final byte[] addressBytes = address.toArrayUnsafe();

View File

@@ -21,11 +21,13 @@ import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.LogsBloomFilter;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
import org.hyperledger.besu.ethereum.core.encoding.TransactionRLPEncoder;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie;
import org.hyperledger.besu.ethereum.trie.SimpleMerklePatriciaTrie;
import java.util.List;
import java.util.stream.IntStream;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;
@@ -54,9 +56,15 @@ public final class BodyValidation {
public static Hash transactionsRoot(final List<Transaction> transactions) {
final MerklePatriciaTrie<Bytes, Bytes> trie = trie();
for (int i = 0; i < transactions.size(); ++i) {
trie.put(indexKey(i), RLP.encode(transactions.get(i)::writeTo));
}
IntStream.range(0, transactions.size())
.forEach(
i ->
trie.put(
indexKey(i),
RLP.encode(
rlpOutput ->
TransactionRLPEncoder.encodeForTransactionTrie(
transactions.get(i), rlpOutput))));
return Hash.wrap(trie.getRootHash());
}
@@ -70,9 +78,13 @@ public final class BodyValidation {
public static Hash receiptsRoot(final List<TransactionReceipt> receipts) {
final MerklePatriciaTrie<Bytes, Bytes> trie = trie();
for (int i = 0; i < receipts.size(); ++i) {
trie.put(indexKey(i), RLP.encode(receipts.get(i)::writeTo));
}
IntStream.range(0, receipts.size())
.forEach(
i ->
trie.put(
indexKey(i),
RLP.encode(
rlpOutput -> receipts.get(i).writeToForReceiptTrie(rlpOutput, false))));
return Hash.wrap(trie.getRootHash());
}

View File

@@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.mainnet;
import org.hyperledger.besu.ethereum.core.Account;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Gas;
import org.hyperledger.besu.ethereum.core.GasAndAccessedState;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.vm.GasCalculator;
@@ -113,7 +114,8 @@ public class FrontierGasCalculator implements GasCalculator {
private static final Gas SELF_DESTRUCT_REFUND_AMOUNT = Gas.of(24_000L);
@Override
public Gas transactionIntrinsicGasCost(final Transaction transaction) {
public GasAndAccessedState transactionIntrinsicGasCostAndAccessedState(
final Transaction transaction) {
final Bytes payload = transaction.getPayload();
int zeros = 0;
for (int i = 0; i < payload.size(); i++) {
@@ -132,7 +134,7 @@ public class FrontierGasCalculator implements GasCalculator {
cost = cost.plus(txCreateExtraGasCost());
}
return cost;
return new GasAndAccessedState(cost);
}
/**

View File

@@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.mainnet;
import org.hyperledger.besu.ethereum.core.Account;
import org.hyperledger.besu.ethereum.core.Gas;
import org.hyperledger.besu.ethereum.core.GasAndAccessedState;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.apache.tuweni.bytes.Bytes;
@@ -40,7 +41,8 @@ public class IstanbulGasCalculator extends PetersburgGasCalculator {
private static final Gas NEGATIVE_SSTORE_CLEARS_SCHEDULE = Gas.ZERO.minus(SSTORE_CLEARS_SCHEDULE);
@Override
public Gas transactionIntrinsicGasCost(final Transaction transaction) {
public GasAndAccessedState transactionIntrinsicGasCostAndAccessedState(
final Transaction transaction) {
final Bytes payload = transaction.getPayload();
int zeros = 0;
for (int i = 0; i < payload.size(); i++) {
@@ -59,7 +61,7 @@ public class IstanbulGasCalculator extends PetersburgGasCalculator {
cost = cost.plus(txCreateExtraGasCost());
}
return cost;
return new GasAndAccessedState(cost);
}
@Override

View File

@@ -422,6 +422,14 @@ public abstract class MainnetProtocolSpecs {
enableRevertReason,
quorumCompatibilityMode)
.gasCalculator(BerlinGasCalculator::new)
.transactionValidatorBuilder(
gasCalculator ->
new MainnetTransactionValidator(
gasCalculator,
true,
chainId,
Set.of(TransactionType.FRONTIER, TransactionType.ACCESS_LIST),
quorumCompatibilityMode))
.evmBuilder(
gasCalculator ->
MainnetEvmRegistries.berlin(gasCalculator, chainId.orElse(BigInteger.ZERO)))

View File

@@ -20,6 +20,7 @@ import org.hyperledger.besu.ethereum.core.AccountState;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.EvmAccount;
import org.hyperledger.besu.ethereum.core.Gas;
import org.hyperledger.besu.ethereum.core.GasAndAccessedState;
import org.hyperledger.besu.ethereum.core.MutableAccount;
import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader;
import org.hyperledger.besu.ethereum.core.Transaction;
@@ -288,7 +289,9 @@ public class MainnetTransactionProcessor {
previousBalance,
sender.getBalance());
final Gas intrinsicGas = gasCalculator.transactionIntrinsicGasCost(transaction);
final GasAndAccessedState gasAndAccessedState =
gasCalculator.transactionIntrinsicGasCostAndAccessedState(transaction);
final Gas intrinsicGas = gasAndAccessedState.getGas();
final Gas gasAvailable = Gas.of(transaction.getGasLimit()).minus(intrinsicGas);
LOG.trace(
"Gas available for execution {} = {} - {} (limit - intrinsic)",
@@ -318,6 +321,8 @@ public class MainnetTransactionProcessor {
.blockHashLookup(blockHashLookup)
.isPersistingPrivateState(isPersistingPrivateState)
.transactionHash(transaction.getHash())
.accessListWarmAddresses(gasAndAccessedState.getAccessListAddressSet())
.accessListWarmStorage(gasAndAccessedState.getAccessListStorageByAddress())
.privateMetadataUpdater(privateMetadataUpdater);
final MessageFrame initialFrame;

View File

@@ -55,13 +55,27 @@ public class MainnetTransactionValidator {
final boolean goQuorumCompatibilityMode) {
this(
gasCalculator,
Optional.empty(),
checkSignatureMalleability,
chainId,
Set.of(TransactionType.FRONTIER),
goQuorumCompatibilityMode);
}
public MainnetTransactionValidator(
final GasCalculator gasCalculator,
final boolean checkSignatureMalleability,
final Optional<BigInteger> chainId,
final Set<TransactionType> acceptedTransactionTypes,
final boolean quorumCompatibilityMode) {
this(
gasCalculator,
Optional.empty(),
checkSignatureMalleability,
chainId,
acceptedTransactionTypes,
quorumCompatibilityMode);
}
public MainnetTransactionValidator(
final GasCalculator gasCalculator,
final Optional<TransactionPriceCalculator> transactionPriceCalculator,
@@ -118,7 +132,8 @@ public class MainnetTransactionValidator {
}
}
final Gas intrinsicGasCost = gasCalculator.transactionIntrinsicGasCost(transaction);
final Gas intrinsicGasCost =
gasCalculator.transactionIntrinsicGasCostAndAccessedState(transaction).getGas();
if (intrinsicGasCost.compareTo(Gas.of(transaction.getGasLimit())) > 0) {
return ValidationResult.invalid(
TransactionInvalidReason.INTRINSIC_GAS_EXCEEDS_GAS_LIMIT,

View File

@@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.vm;
import org.hyperledger.besu.ethereum.core.Account;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Gas;
import org.hyperledger.besu.ethereum.core.GasAndAccessedState;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.mainnet.AbstractMessageProcessor;
@@ -64,7 +65,7 @@ public interface GasCalculator {
* @param transaction The transaction
* @return the transaction's intrinsic gas cost
*/
Gas transactionIntrinsicGasCost(Transaction transaction);
GasAndAccessedState transactionIntrinsicGasCostAndAccessedState(Transaction transaction);
// Contract Creation Gas Calculations

View File

@@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.vm;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Collections.emptySet;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Address;
@@ -281,7 +282,9 @@ public class MessageFrame {
final PrivateMetadataUpdater privateMetadataUpdater,
final Hash transactionHash,
final Optional<Bytes> revertReason,
final int maxStackSize) {
final int maxStackSize,
final Set<Address> accessListWarmAddresses,
final Multimap<Address, Bytes32> accessListWarmStorage) {
this.type = type;
this.blockchain = blockchain;
this.messageFrameStack = messageFrameStack;
@@ -320,10 +323,27 @@ public class MessageFrame {
this.transactionHash = transactionHash;
this.revertReason = revertReason;
this.warmedUpAddresses = new HashSet<>();
warmedUpAddresses.add(sender);
warmedUpAddresses.add(contract);
this.warmedUpStorage = HashMultimap.create();
this.warmedUpAddresses = new HashSet<>(accessListWarmAddresses);
this.warmedUpAddresses.add(sender);
this.warmedUpAddresses.add(contract);
this.warmedUpStorage = HashMultimap.create(accessListWarmStorage);
// the warmed up addresses will always be a superset of the address keys in the warmed up
// storage so we can do both warm ups in one pass
accessListWarmAddresses
.parallelStream()
.forEach(
address ->
Optional.ofNullable(worldState.get(address))
.ifPresent(
account ->
warmedUpStorage
.get(address)
.parallelStream()
.forEach(
storageKeyBytes ->
account.getStorageValue(
UInt256.fromBytes(storageKeyBytes)))));
}
/**
@@ -1143,6 +1163,8 @@ public class MessageFrame {
private Hash transactionHash;
private Optional<Bytes> reason = Optional.empty();
private ReturnStack returnStack = new ReturnStack();
private Set<Address> accessListWarmAddresses = emptySet();
private Multimap<Address, Bytes32> accessListWarmStorage = HashMultimap.create();
public Builder type(final Type type) {
this.type = type;
@@ -1280,6 +1302,16 @@ public class MessageFrame {
return this;
}
public Builder accessListWarmAddresses(final Set<Address> accessListWarmAddresses) {
this.accessListWarmAddresses = accessListWarmAddresses;
return this;
}
public Builder accessListWarmStorage(final Multimap<Address, Bytes32> accessListWarmStorage) {
this.accessListWarmStorage = accessListWarmStorage;
return this;
}
private void validate() {
checkState(type != null, "Missing message frame type");
checkState(blockchain != null, "Missing message frame blockchain");
@@ -1335,7 +1367,9 @@ public class MessageFrame {
privateMetadataUpdater,
transactionHash,
reason,
maxStackSize);
maxStackSize,
accessListWarmAddresses,
accessListWarmStorage);
}
}
}

View File

@@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.core;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.stream.Collectors.toUnmodifiableList;
import static java.util.stream.Collectors.toUnmodifiableSet;
import org.hyperledger.besu.crypto.SECP256K1;
@@ -32,11 +33,14 @@ import java.security.Security;
import java.security.spec.ECGenParameterSpec;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.Random;
@@ -349,6 +353,8 @@ public class BlockDataGenerator {
return frontierTransaction(payload, to);
case EIP1559:
return eip1559Transaction(payload, to);
case ACCESS_LIST:
return accessListTransaction(payload, to);
default:
throw new RuntimeException(
String.format(
@@ -357,6 +363,33 @@ public class BlockDataGenerator {
}
}
private Transaction accessListTransaction(final Bytes payload, final Address to) {
return Transaction.builder()
.type(TransactionType.ACCESS_LIST)
.nonce(positiveLong())
.gasPrice(Wei.wrap(bytes32()))
.gasLimit(positiveLong())
.to(to)
.value(Wei.wrap(bytes32()))
.payload(payload)
.accessList(accessList())
.chainId(BigInteger.ONE)
.signAndBuild(generateKeyPair());
}
private AccessList accessList() {
final List<Address> accessedAddresses =
Stream.generate(this::address).limit(1 + random.nextInt(3)).collect(toUnmodifiableList());
final List<Map.Entry<Address, List<Bytes32>>> accessedStorage = new ArrayList<>();
for (int i = 0; i < accessedAddresses.size(); ++i) {
accessedStorage.add(
new AbstractMap.SimpleEntry<>(
accessedAddresses.get(i),
Stream.generate(this::bytes32).limit(2L * i).collect(toUnmodifiableList())));
}
return new AccessList(accessedStorage);
}
private Transaction eip1559Transaction(final Bytes payload, final Address to) {
return Transaction.builder()
.type(TransactionType.EIP1559)
@@ -396,6 +429,23 @@ public class BlockDataGenerator {
return transactions(n, TransactionType.values());
}
public Set<Transaction> transactionsWithAllTypes() {
return transactionsWithAllTypes(0);
}
public Set<Transaction> transactionsWithAllTypes(final int atLeast) {
checkArgument(atLeast >= 0);
final HashSet<TransactionType> remainingTransactionTypes =
new HashSet<>(Set.of(TransactionType.values()));
final HashSet<Transaction> transactions = new HashSet<>();
while (transactions.size() < atLeast || !remainingTransactionTypes.isEmpty()) {
final Transaction newTransaction = transaction();
transactions.add(newTransaction);
remainingTransactionTypes.remove(newTransaction.getType());
}
return transactions;
}
public TransactionReceipt receipt(final long cumulativeGasUsed) {
return new TransactionReceipt(
transactionType(),
@@ -457,7 +507,7 @@ public class BlockDataGenerator {
return LogTopic.wrap(bytesValue(Bytes32.SIZE));
}
private Bytes32 bytes32() {
public Bytes32 bytes32() {
return Bytes32.wrap(bytes(Bytes32.SIZE));
}

View File

@@ -20,6 +20,8 @@ import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.plugin.data.TransactionType;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
@@ -29,11 +31,15 @@ public class TransactionBuilderTest {
@Test
public void guessTypeCanGuessAllTypes() {
final BlockDataGenerator gen = new BlockDataGenerator();
final Transaction.Builder frontierBuilder = Transaction.builder();
final Transaction.Builder eip1559Builder = Transaction.builder().feeCap(Wei.of(5));
final Transaction.Builder accessListBuilder =
Transaction.builder()
.accessList(new AccessList(List.of(Map.entry(gen.address(), List.of(gen.bytes32())))));
final Set<TransactionType> guessedTypes =
Stream.of(frontierBuilder, eip1559Builder)
Stream.of(frontierBuilder, eip1559Builder, accessListBuilder)
.map(transactionBuilder -> transactionBuilder.guessType().build().getType())
.collect(toUnmodifiableSet());

View File

@@ -17,7 +17,10 @@ package org.hyperledger.besu.ethereum.core.encoding;
import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.config.experimental.ExperimentalEIPs;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.rlp.RLP;
@@ -64,4 +67,19 @@ public class TransactionRLPEncoderTest {
.isEqualTo(output.encoded().toHexString());
ExperimentalEIPs.eip1559Enabled = ExperimentalEIPs.EIP1559_ENABLED_DEFAULT_VALUE;
}
@Test
public void blockWithLegacyAndEIP2930TransactionsRoundTrips() {
final BlockDataGenerator gen = new BlockDataGenerator();
final Block block =
gen.block(
BlockDataGenerator.BlockOptions.create()
.addTransaction(gen.transactionsWithAllTypes().toArray(new Transaction[] {})));
assertThat(
Block.readFrom(
RLP.input(RLP.encode(block::writeTo)), new MainnetBlockHeaderFunctions()))
.isEqualTo(block);
}
}

View File

@@ -110,6 +110,7 @@ public class IntrinsicGasTest {
@Test
public void validateGasCost() {
Transaction t = Transaction.readFrom(RLP.input(Bytes.fromHexString(txRlp)));
Assertions.assertThat(gasCalculator.transactionIntrinsicGasCost(t)).isEqualTo(expectedGas);
Assertions.assertThat(gasCalculator.transactionIntrinsicGasCostAndAccessedState(t).getGas())
.isEqualTo(expectedGas);
}
}

View File

@@ -28,6 +28,7 @@ import org.hyperledger.besu.crypto.SECP256K1.KeyPair;
import org.hyperledger.besu.ethereum.core.Account;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Gas;
import org.hyperledger.besu.ethereum.core.GasAndAccessedState;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionFilter;
import org.hyperledger.besu.ethereum.core.TransactionTestFixture;
@@ -79,7 +80,8 @@ public class MainnetTransactionValidatorTest {
.gasLimit(10)
.chainId(Optional.empty())
.createTransaction(senderKeys);
when(gasCalculator.transactionIntrinsicGasCost(transaction)).thenReturn(Gas.of(50));
when(gasCalculator.transactionIntrinsicGasCostAndAccessedState(transaction))
.thenReturn(new GasAndAccessedState(Gas.of(50)));
assertThat(validator.validate(transaction, Optional.empty()))
.isEqualTo(
@@ -290,7 +292,8 @@ public class MainnetTransactionValidatorTest {
assertThat(frontierValidator.validate(transaction, Optional.empty()))
.isEqualTo(ValidationResult.invalid(TransactionInvalidReason.INVALID_TRANSACTION_FORMAT));
when(gasCalculator.transactionIntrinsicGasCost(transaction)).thenReturn(Gas.of(0));
when(gasCalculator.transactionIntrinsicGasCostAndAccessedState(transaction))
.thenReturn(new GasAndAccessedState(Gas.of(0)));
assertThat(eip1559Validator.validate(transaction, Optional.of(1L)))
.isEqualTo(ValidationResult.valid());
@@ -341,7 +344,8 @@ public class MainnetTransactionValidatorTest {
.chainId(Optional.empty())
.createTransaction(senderKeys);
final Optional<Long> basefee = Optional.of(150000L);
when(gasCalculator.transactionIntrinsicGasCost(transaction)).thenReturn(Gas.of(50));
when(gasCalculator.transactionIntrinsicGasCostAndAccessedState(transaction))
.thenReturn(new GasAndAccessedState(Gas.of(50)));
assertThat(validator.validate(transaction, basefee)).isEqualTo(ValidationResult.valid());
ExperimentalEIPs.eip1559Enabled = false;
@@ -372,7 +376,8 @@ public class MainnetTransactionValidatorTest {
.chainId(Optional.empty())
.createTransaction(senderKeys);
when(gasCalculator.transactionIntrinsicGasCost(transaction)).thenReturn(Gas.of(50));
when(gasCalculator.transactionIntrinsicGasCostAndAccessedState(transaction))
.thenReturn(new GasAndAccessedState(Gas.of(50)));
assertThat(validator.validate(transaction, Optional.empty()).isValid()).isTrue();
}

View File

@@ -51,6 +51,7 @@ import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
import org.hyperledger.besu.ethereum.transaction.CallParameter;
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason;
import org.hyperledger.besu.plugin.data.TransactionType;
import org.hyperledger.orion.testutil.OrionKeyUtils;
import java.math.BigInteger;
@@ -106,6 +107,7 @@ public class DefaultPrivacyControllerTest {
private static final Transaction PUBLIC_TRANSACTION =
Transaction.builder()
.type(TransactionType.FRONTIER)
.nonce(0)
.gasPrice(Wei.of(1000))
.gasLimit(3000000)

View File

@@ -289,7 +289,10 @@ public class EvmToolCommand implements Runnable {
Optional.empty());
final Gas intrinsicGasCost =
protocolSpec.getGasCalculator().transactionIntrinsicGasCost(tx);
protocolSpec
.getGasCalculator()
.transactionIntrinsicGasCostAndAccessedState(tx)
.getGas();
final Gas evmGas = gas.minus(messageFrame.getRemainingGas());
out.println();
out.println(

View File

@@ -64,7 +64,7 @@ Calculated : ${currentHash}
tasks.register('checkAPIChanges', FileStateChecker) {
description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
files = sourceSets.main.allJava.files
knownHash = 'FFJnnC23PD/IYNsYnHUT7qF8qHnUucnJwCn4YYPVPE8='
knownHash = 'iPuLs9OOxBC5ZJqJ5yWrkT6LgquIDEZztdRDbSe9E3U='
}
check.dependsOn('checkAPIChanges')

View File

@@ -14,8 +14,11 @@
*/
package org.hyperledger.besu.plugin.data;
import java.util.Arrays;
public enum TransactionType {
FRONTIER(0xf8 /* doesn't end up being used as we don't serialize legacy txs with their type */),
ACCESS_LIST(0x1),
EIP1559(0xf /* placeholder value until we know what the real type byte will be */);
private final int typeValue;
@@ -29,15 +32,12 @@ public enum TransactionType {
}
public static TransactionType of(final int serializedTypeValue) {
if (serializedTypeValue >= 0xc0 && serializedTypeValue <= 0xfe) {
return FRONTIER;
}
for (TransactionType transactionType : TransactionType.values()) {
if (transactionType.typeValue == serializedTypeValue) {
return transactionType;
}
}
throw new IllegalArgumentException(
String.format("Unsupported transaction type %x", serializedTypeValue));
return Arrays.stream(TransactionType.values())
.filter(transactionType -> transactionType.typeValue == serializedTypeValue)
.findFirst()
.orElseThrow(
() ->
new IllegalArgumentException(
String.format("Unsupported transaction type %x", serializedTypeValue)));
}
}