Generate All Transaction Types and TransactionReceipt Types in BlockDataGenerator (#1726)

Signed-off-by: Ratan Rai Sur <ratan.r.sur@gmail.com>
pull/1803/head
Ratan (Rai) Sur 4 years ago committed by GitHub
parent ea82ca47aa
commit d06393d50f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java
  2. 7
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesTest.java
  3. 128
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/TransactionReceipt.java
  4. 4
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Wei.java
  5. 4
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPDecoder.java
  6. 3
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPEncoder.java
  7. 122
      ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java
  8. 14
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPEncoderTest.java
  9. 2
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/LimitedTransactionsMessages.java
  10. 1
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/LimitedTransactionsMessagesTest.java
  11. 9
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageSenderTest.java

@ -80,6 +80,7 @@ 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();
@ -92,9 +93,9 @@ public class PrivacyReorgTest {
Bytes.fromBase64String("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=");
private static final String FIRST_BLOCK_WITH_NO_TRANSACTIONS_STATE_ROOT =
"0x1bdf13f6d14c7322d6e695498aab258949e55574bef7eac366eb777f43d7dd2b";
"0xc9dcaffbebc7edc20839c80e706430a8fa885e8f703bb89f6e95633cc2b05d4d";
private static final String FIRST_BLOCK_WITH_SINGLE_TRANSACTION_STATE_ROOT =
"0x16979b290f429e06d86a43584c7d8689d4292ade9a602e5c78e2867c6ebd904e";
"0x66fbc6ad12ef1da78740093ea2b3362e773e510e27d8f88b68c27bfc1f4d58c8";
private static final String BLOCK_WITH_SINGLE_TRANSACTION_RECEIPTS_ROOT =
"0xc8267b3f9ed36df3ff8adb51a6d030716f23eeb50270e7fce8d9822ffa7f0461";
private static final String STATE_ROOT_AFTER_TRANSACTION_APPENDED_TO_EMPTY_STATE =
@ -260,7 +261,7 @@ public class PrivacyReorgTest {
gen.block(getBlockOptionsNoTransaction(blockchain.getGenesisBlock(), firstBlockStateRoot));
final String secondBlockStateRoot =
"0x35c315ee7d272e5b612d454ee87c948657310ab33208b57122f8d0525e91f35e";
"0x7e887f91d2a6205f4a643701aba022c2db0bac5ab235102ab7477edd7a8a4317";
final Block secondBlock =
gen.block(
getBlockOptionsWithTransaction(
@ -287,7 +288,7 @@ public class PrivacyReorgTest {
.plus(blockchain.getBlockByNumber(2).get().getHeader().getDifficulty());
final String forkBlockStateRoot =
"0x4a33bdf9d16e6dd4f4c67f1638971f663f132ebceac0c7c65c9a3f35172af4de";
"0x486b886bde6472e8d706f8eb4fb6378ebbdceb4848a5a8d69a726575b22e41b6";
final Block forkBlock =
gen.block(
getBlockOptionsNoTransactionWithDifficulty(
@ -337,7 +338,7 @@ public class PrivacyReorgTest {
privateStateRootResolver, blockchain, STATE_ROOT_AFTER_TRANSACTION_APPENDED_TO_EMPTY_STATE);
final String secondForkBlockStateRoot =
"0xd35eea814b8b5a0b12e690ab320785f3a33d9685bbf6875637c40a64203915da";
"0x57ccc80f4e50d2e669d82aefa7d3bbe763cf47df27665af14c90b2f8641953f5";
final Block secondForkBlock =
gen.block(
getBlockOptionsNoTransactionWithDifficulty(
@ -355,7 +356,7 @@ public class PrivacyReorgTest {
// Add another private transaction
final String thirdForkBlockStateRoot =
"0xe22344ade05260177b79dcc6c4fed8f87ab95a506c2a6147631ac6547cf44846";
"0x8fe42678733e6099e7b10b9b1d4684b8f2ce3d6479cb122ea12932ef304a1793";
final Block thirdForkBlock =
gen.block(
getBlockOptionsWithTransactionAndDifficulty(

@ -451,12 +451,13 @@ public class BlockchainQueriesTest {
final BlockchainWithData data = setupBlockchain(3);
final BlockchainQueries queries = data.blockchainQueries;
final Block targetBlock = data.blockData.get(data.blockData.size() - 1).block;
final BlockHeader ommerBlockHeader = targetBlock.getBody().getOmmers().get(1);
final List<BlockHeader> ommers = targetBlock.getBody().getOmmers();
final int ommerIndex = ommers.size() - 1;
final BlockHeader retrievedOmmerBlockHeader =
queries.getOmmer(targetBlock.getHeader().getNumber(), 1).get();
queries.getOmmer(targetBlock.getHeader().getNumber(), ommerIndex).get();
assertThat(retrievedOmmerBlockHeader).isEqualTo(ommerBlockHeader);
assertThat(retrievedOmmerBlockHeader).isEqualTo(ommers.get(ommerIndex));
}
@Test

@ -15,6 +15,8 @@
package org.hyperledger.besu.ethereum.core;
import org.hyperledger.besu.ethereum.mainnet.TransactionReceiptType;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPException;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
@ -171,26 +173,37 @@ public class TransactionReceipt implements org.hyperledger.besu.plugin.data.Tran
writeTo(out, true);
}
private void writeTo(final RLPOutput out, final boolean withRevertReason) {
if (!transactionType.equals(TransactionType.FRONTIER)) {
out.writeRaw(Bytes.of((byte) transactionType.getSerializedType()));
}
out.startList();
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());
// 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));
}
out.endList();
}
/**
@ -206,57 +219,52 @@ public class TransactionReceipt implements org.hyperledger.besu.plugin.data.Tran
/**
* Creates a transaction receipt for the given RLP
*
* @param input the RLP-encoded transaction receipt
* @param rlpInput the RLP-encoded transaction receipt
* @param revertReasonAllowed whether the rlp input is allowed to have a revert reason
* @return the transaction receipt
*/
public static TransactionReceipt readFrom(
final RLPInput input, final boolean revertReasonAllowed) {
final RLPInput rlpInput, final boolean revertReasonAllowed) {
RLPInput input = rlpInput;
TransactionType transactionType = TransactionType.FRONTIER;
try {
input.enterList();
} catch (RLPException rlpe) {
if (rlpe.getMessage().contains("Expected current item to be a list")) {
// This is an EIP-2718 receipt
transactionType = TransactionType.of(input.readByte());
input.enterList();
} else {
throw rlpe;
}
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());
}
try {
// Get the first element to check later to determine the
// correct transaction receipt encoding to use.
final RLPInput firstElement = input.readAsRlp();
final long cumulativeGas = input.readLongScalar();
// The logs below will populate the bloom filter upon construction.
// TODO consider validating that the logs and bloom filter match.
final LogsBloomFilter bloomFilter = LogsBloomFilter.readFrom(input);
final List<Log> logs = input.readList(Log::readFrom);
final Optional<Bytes> revertReason;
if (input.isEndOfCurrentList()) {
revertReason = Optional.empty();
} else {
if (!revertReasonAllowed) {
throw new RLPException("Unexpected value at end of TransactionReceipt");
}
revertReason = Optional.of(input.readBytes());
input.enterList();
// Get the first element to check later to determine the
// correct transaction receipt encoding to use.
final RLPInput firstElement = input.readAsRlp();
final long cumulativeGas = input.readLongScalar();
// The logs below will populate the bloom filter upon construction.
// TODO consider validating that the logs and bloom filter match.
final LogsBloomFilter bloomFilter = LogsBloomFilter.readFrom(input);
final List<Log> logs = input.readList(Log::readFrom);
final Optional<Bytes> revertReason;
if (input.isEndOfCurrentList()) {
revertReason = Optional.empty();
} else {
if (!revertReasonAllowed) {
throw new RLPException("Unexpected value at end of TransactionReceipt");
}
revertReason = Optional.of(input.readBytes());
}
// Status code-encoded transaction receipts have a single
// byte for success (0x01) or failure (0x80).
if (firstElement.raw().size() == 1) {
final int status = firstElement.readIntScalar();
return new TransactionReceipt(
transactionType, status, cumulativeGas, logs, bloomFilter, revertReason);
} else {
final Hash stateRoot = Hash.wrap(firstElement.readBytes32());
return new TransactionReceipt(
transactionType, stateRoot, cumulativeGas, logs, bloomFilter, revertReason);
}
} finally {
// Status code-encoded transaction receipts have a single
// byte for success (0x01) or failure (0x80).
if (firstElement.raw().size() == 1) {
final int status = firstElement.readIntScalar();
input.leaveList();
return new TransactionReceipt(
transactionType, status, cumulativeGas, logs, bloomFilter, revertReason);
} else {
final Hash stateRoot = Hash.wrap(firstElement.readBytes32());
input.leaveList();
return new TransactionReceipt(
transactionType, stateRoot, cumulativeGas, logs, bloomFilter, revertReason);
}
}
@ -328,7 +336,7 @@ public class TransactionReceipt implements org.hyperledger.besu.plugin.data.Tran
}
final TransactionReceipt other = (TransactionReceipt) obj;
return logs.equals(other.getLogs())
&& stateRoot.equals(other.stateRoot)
&& Objects.equals(stateRoot, other.stateRoot)
&& cumulativeGasUsed == other.getCumulativeGasUsed()
&& status == other.status;
}

@ -18,7 +18,7 @@ import org.hyperledger.besu.plugin.data.Quantity;
import java.math.BigInteger;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.BaseUInt256Value;
import org.apache.tuweni.units.bigints.UInt256;
@ -61,7 +61,7 @@ public final class Wei extends BaseUInt256Value<Wei> implements Quantity {
return new Wei((BigInteger) value);
}
public static Wei wrap(final Bytes32 value) {
public static Wei wrap(final Bytes value) {
return new Wei(UInt256.fromBytes(value));
}

@ -96,9 +96,7 @@ public class TransactionRLPDecoder {
} else {
// otherwise this is an EIP-1559 transaction
builder.type(TransactionType.EIP1559);
builder
.gasPremium(Wei.of(maybeGasPremiumOrV.toBigInteger()))
.feeCap(Wei.of(maybeFeeCapOrR.toBigInteger()));
builder.gasPremium(Wei.wrap(maybeGasPremiumOrV)).feeCap(Wei.wrap(maybeFeeCapOrR));
v = maybeVOrS.toBigInteger();
r = input.readUInt256Scalar().toBytes().toUnsignedBigInteger();
s = input.readUInt256Scalar().toBytes().toUnsignedBigInteger();

@ -16,7 +16,6 @@ package org.hyperledger.besu.ethereum.core.encoding;
import static com.google.common.base.Preconditions.checkNotNull;
import org.hyperledger.besu.config.experimental.ExperimentalEIPs;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.rlp.RLP;
@ -77,8 +76,6 @@ public class TransactionRLPEncoder {
}
static void encodeEIP1559(final Transaction transaction, final RLPOutput out) {
ExperimentalEIPs.eip1559MustBeEnabled();
out.startList();
out.writeLongScalar(transaction.getNonce());
out.writeNull();

@ -15,7 +15,7 @@
package org.hyperledger.besu.ethereum.core;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.stream.Collectors.toSet;
import static java.util.stream.Collectors.toUnmodifiableSet;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.crypto.SecureRandomProvider;
@ -42,7 +42,6 @@ import java.util.OptionalLong;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import com.google.common.base.Supplier;
@ -304,8 +303,8 @@ public class BlockDataGenerator {
}
final List<Transaction> defaultTxs = new ArrayList<>();
if (options.hasTransactions()) {
defaultTxs.add(transaction());
defaultTxs.add(transaction());
defaultTxs.add(transaction(options.getTransactionTypes()));
defaultTxs.add(transaction(options.getTransactionTypes()));
}
return new BlockBody(options.getTransactions(defaultTxs), ommers);
@ -315,17 +314,66 @@ public class BlockDataGenerator {
return header(positiveLong(), body(BlockOptions.create().hasOmmers(false)));
}
private TransactionType transactionType() {
return transactionType(TransactionType.values());
}
private TransactionType transactionType(final TransactionType... transactionTypes) {
return transactionTypes[random.nextInt(transactionTypes.length)];
}
public Transaction transaction() {
return transaction(bytes32());
return transaction(transactionType());
}
public Transaction transaction(final TransactionType... transactionTypes) {
return transaction(transactionType(transactionTypes));
}
public Transaction transaction(final TransactionType transactionType) {
return transaction(transactionType, bytes32(), address());
}
public Transaction transaction(final Bytes payload) {
return transaction(payload, address());
return transaction(transactionType(), payload);
}
public Transaction transaction(final TransactionType transactionType, final Bytes payload) {
return transaction(transactionType, payload, address());
}
public Transaction transaction(
final TransactionType transactionType, final Bytes payload, final Address to) {
switch (transactionType) {
case FRONTIER:
return frontierTransaction(payload, to);
case EIP1559:
return eip1559Transaction(payload, to);
default:
throw new RuntimeException(
String.format(
"Developer Error. No random transaction generator defined for %s",
transactionType));
}
}
private Transaction eip1559Transaction(final Bytes payload, final Address to) {
return Transaction.builder()
.type(TransactionType.EIP1559)
.nonce(positiveLong())
.gasPrice(Wei.ZERO)
.gasPremium(Wei.wrap(bytes32()))
.feeCap(Wei.wrap(bytes32()))
.gasLimit(positiveLong())
.to(to)
.value(Wei.of(positiveLong()))
.payload(payload)
.chainId(BigInteger.ONE)
.signAndBuild(generateKeyPair());
}
public Transaction transaction(final Bytes payload, final Address to) {
private Transaction frontierTransaction(final Bytes payload, final Address to) {
return Transaction.builder()
// TODO support more EIP-2718 types as they're added
.type(TransactionType.FRONTIER)
.nonce(positiveLong())
.gasPrice(Wei.wrap(bytes32()))
@ -337,42 +385,33 @@ public class BlockDataGenerator {
.signAndBuild(generateKeyPair());
}
public Set<Transaction> transactions(final int n, final TransactionType... transactionTypes) {
return Stream.generate(() -> transaction(transactionTypes))
.parallel()
.limit(n)
.collect(toUnmodifiableSet());
}
public Set<Transaction> transactions(final int n) {
Wei gasPrice = Wei.wrap(bytes32());
long gasLimit = positiveLong();
Address to = address();
Wei value = Wei.wrap(bytes32());
int chainId = 1;
Bytes32 payload = bytes32();
final SECP256K1.Signature signature = SECP256K1.sign(payload, generateKeyPair());
final Set<Transaction> txs =
IntStream.range(0, n)
.parallel()
.mapToObj(
v ->
new Transaction(
v,
gasPrice,
gasLimit,
Optional.of(to),
value,
signature,
payload,
to,
Optional.of(BigInteger.valueOf(chainId))))
.collect(toSet());
return txs;
return transactions(n, TransactionType.values());
}
public TransactionReceipt receipt(final long cumulativeGasUsed) {
return new TransactionReceipt(
hash(), cumulativeGasUsed, Arrays.asList(log(), log()), Optional.empty());
transactionType(),
random.nextInt(2),
cumulativeGasUsed,
Arrays.asList(log(), log()),
Optional.empty());
}
public TransactionReceipt receipt(final Bytes revertReason) {
return new TransactionReceipt(
hash(), positiveLong(), Arrays.asList(log(), log()), Optional.of(revertReason));
transactionType(),
random.nextInt(2),
positiveLong(),
Arrays.asList(log(), log()),
Optional.of(revertReason));
}
public TransactionReceipt receipt() {
@ -380,7 +419,8 @@ public class BlockDataGenerator {
}
public TransactionReceipt receipt(final List<Log> logs) {
return new TransactionReceipt(hash(), positiveLong(), logs, Optional.empty());
return new TransactionReceipt(
transactionType(), random.nextInt(2), positiveLong(), logs, Optional.empty());
}
public UInt256 storageKey() {
@ -521,6 +561,7 @@ public class BlockDataGenerator {
private Optional<Long> timestamp = Optional.empty();
private boolean hasOmmers = true;
private boolean hasTransactions = true;
private TransactionType[] transactionTypes = TransactionType.values();
public static BlockOptions create() {
return new BlockOptions();
@ -530,6 +571,10 @@ public class BlockDataGenerator {
return transactions.isEmpty() ? defaultValue : transactions;
}
public TransactionType[] getTransactionTypes() {
return transactionTypes;
}
public List<BlockHeader> getOmmers(final List<BlockHeader> defaultValue) {
return ommers.isEmpty() ? defaultValue : ommers;
}
@ -646,6 +691,11 @@ public class BlockDataGenerator {
return this;
}
public BlockOptions transactionTypes(final TransactionType... transactionTypes) {
this.transactionTypes = transactionTypes;
return this;
}
public BlockOptions hasOmmers(final boolean hasOmmers) {
this.hasOmmers = hasOmmers;
return this;

@ -15,7 +15,6 @@
package org.hyperledger.besu.ethereum.core.encoding;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.hyperledger.besu.config.experimental.ExperimentalEIPs;
import org.hyperledger.besu.ethereum.core.Transaction;
@ -66,17 +65,4 @@ public class TransactionRLPEncoderTest {
.isEqualTo(output.encoded().toHexString());
ExperimentalEIPs.eip1559Enabled = ExperimentalEIPs.EIP1559_ENABLED_DEFAULT_VALUE;
}
@Test
public void encodeEIP1559TxFailureNotEnabled() {
ExperimentalEIPs.eip1559Enabled = false;
assertThatThrownBy(
() ->
TransactionRLPEncoder.encode(
TransactionRLPDecoder.decode(RLP.input(Bytes.fromHexString(EIP1559_TX_RLP))),
new BytesValueRLPOutput()))
.isInstanceOf(RuntimeException.class)
.hasMessageContaining("EIP-1559 feature flag");
ExperimentalEIPs.eip1559Enabled = ExperimentalEIPs.EIP1559_ENABLED_DEFAULT_VALUE;
}
}

@ -46,7 +46,7 @@ public final class LimitedTransactionsMessages {
transaction.writeTo(encodedTransaction);
Bytes encodedBytes = encodedTransaction.encoded();
if (messageSize != 0 // always at least one message
&& (encodedBytes.size() > LIMIT || messageSize + encodedBytes.size() > LIMIT)) {
&& messageSize + encodedBytes.size() > LIMIT) {
break;
}
message.writeRaw(encodedBytes);

@ -42,7 +42,6 @@ public class LimitedTransactionsMessagesTest {
@Test
public void createLimited() {
final Set<Transaction> transactions = generator.transactions(6000);
final Set<Transaction> remainingTransactions = new HashSet<>(transactions);
final LimitedTransactionsMessages firstMessage =

@ -81,14 +81,7 @@ public class TransactionsMessageSenderTest {
final Set<Transaction> firstBatch = getTransactionsFromMessage(sentMessages.get(0));
final Set<Transaction> secondBatch = getTransactionsFromMessage(sentMessages.get(1));
final int expectedFirstBatchSize = 5219, expectedSecondBatchSize = 781, toleranceDelta = 50;
assertThat(firstBatch)
.hasSizeBetween(
expectedFirstBatchSize - toleranceDelta, expectedFirstBatchSize + toleranceDelta);
assertThat(secondBatch)
.hasSizeBetween(
expectedSecondBatchSize - toleranceDelta, expectedSecondBatchSize + toleranceDelta);
assertThat(firstBatch).hasSizeGreaterThan(secondBatch.size());
assertThat(Sets.union(firstBatch, secondBatch)).isEqualTo(transactions);
}

Loading…
Cancel
Save