Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add world context to transaction tracing API #5836

Merged
merged 7 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 23.7.3
### Additions and Improvements
- Add access to an immutable world view to start/end transaction hooks in the tracing API[#5836](https://github.com/hyperledger/besu/pull/5836)

### Breaking Changes
- Removed support for Kotti network (ETC) [#5816](https://github.com/hyperledger/besu/pull/5816)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,12 @@ private List<TransactionProcessingResult> trace(
.map(parent -> calculateExcessBlobGasForParent(protocolSpec, parent))
.orElse(BlobGas.ZERO));

tracer.traceStartTransaction(transaction);

final WorldUpdater worldUpdater = chainUpdater.getNextUpdater();
tracer.traceStartTransaction(worldUpdater, transaction);
final TransactionProcessingResult result =
transactionProcessor.processTransaction(
blockchain,
chainUpdater.getNextUpdater(),
worldUpdater,
header,
transaction,
header.getCoinbase(),
Expand All @@ -188,7 +188,14 @@ private List<TransactionProcessingResult> trace(
blobGasPrice);

long transactionGasUsed = transaction.getGasLimit() - result.getGasRemaining();
tracer.traceEndTransaction(result.getOutput(), transactionGasUsed, 0);
tracer.traceEndTransaction(
worldUpdater,
transaction,
result.isSuccessful(),
result.getOutput(),
result.getLogs(),
transactionGasUsed,
0);

results.add(result);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,21 @@
import static org.assertj.core.api.Assertions.assertThat;

import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Transaction;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil;
import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.evm.log.Log;
import org.hyperledger.besu.evm.worldstate.WorldView;
import org.hyperledger.besu.plugin.services.TraceService;
import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer;

import java.util.List;

import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -102,4 +109,103 @@ void shouldRetrieveStateUpdatePostTracingForAllBlocks() {
assertThat(worldStateArchive.getMutable().get(addressToVerify).getNonce())
.isEqualTo(persistedNonceForAccount);
}

@Test
void shouldReturnTheCorrectWorldViewForTxStartEnd() {
final TxStartEndTracer txStartEndTracer = new TxStartEndTracer();

// block contains 1 transaction
traceService.traceBlock(31, txStartEndTracer);

assertThat(txStartEndTracer.txStartWorldView).isNotNull();
assertThat(txStartEndTracer.txEndWorldView).isNotNull();

assertThat(txStartEndTracer.txStartTransaction.getNonce())
.isEqualTo(txStartEndTracer.txEndTransaction.getNonce())
.isEqualTo(30);
assertThat(txStartEndTracer.txStartTransaction.getGasLimit())
.isEqualTo(txStartEndTracer.txEndTransaction.getGasLimit())
.isEqualTo(314159);
assertThat(txStartEndTracer.txStartTransaction.getTo().get())
.isEqualTo(txStartEndTracer.txEndTransaction.getTo().get())
.isEqualTo(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"));
assertThat(txStartEndTracer.txStartTransaction.getValue())
.isEqualTo(txStartEndTracer.txEndTransaction.getValue())
.isEqualTo(
Wei.fromHexString(
"0x000000000000000000000000000000000000000000000000000000000000000a"));
assertThat(txStartEndTracer.txStartTransaction.getPayload())
.isEqualTo(txStartEndTracer.txEndTransaction.getPayload())
.isEqualTo(Bytes.fromHexString("0xfd408767"));

assertThat(txStartEndTracer.txEndStatus).isTrue();
assertThat(txStartEndTracer.txEndOutput).isEqualTo(Bytes.fromHexString("0x"));
assertThat(txStartEndTracer.txEndGasUsed).isEqualTo(24303);
assertThat(txStartEndTracer.txEndTimeNs).isNotNull();

assertThat(txStartEndTracer.txEndLogs).isNotEmpty();

final Log actualLog = txStartEndTracer.txEndLogs.get(0);
assertThat(actualLog.getLogger())
.isEqualTo(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"));
assertThat(actualLog.getData())
.isEqualTo(
Bytes.fromHexString(
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe9000000000000000000000000000000000000000000000000000000000000002a"));
assertThat(actualLog.getTopics()).hasSize(4);
assertThat(actualLog.getTopics().get(0))
.isEqualTo(
Bytes.fromHexString(
"0xd5f0a30e4be0c6be577a71eceb7464245a796a7e6a55c0d971837b250de05f4e"));
assertThat(actualLog.getTopics().get(1))
.isEqualTo(
Bytes.fromHexString(
"0x0000000000000000000000000000000000000000000000000000000000000001"));
assertThat(actualLog.getTopics().get(2))
.isEqualTo(
Bytes.fromHexString(
"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b"));
assertThat(actualLog.getTopics().get(3))
.isEqualTo(
Bytes.fromHexString(
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"));
}

private static class TxStartEndTracer implements BlockAwareOperationTracer {
public WorldView txStartWorldView;
public WorldView txEndWorldView;

public Transaction txStartTransaction;
public Transaction txEndTransaction;

public boolean txEndStatus;
public Bytes txEndOutput;
public List<Log> txEndLogs;
public long txEndGasUsed;
public Long txEndTimeNs;

@Override
public void traceStartTransaction(final WorldView worldView, final Transaction transaction) {
txStartWorldView = worldView;
txStartTransaction = transaction;
}

@Override
public void traceEndTransaction(
final WorldView worldView,
final Transaction transaction,
final boolean status,
final Bytes output,
final List<Log> logs,
final long gasUsed,
final long timeNs) {
txEndWorldView = worldView;
txEndTransaction = transaction;
txEndStatus = status;
txEndOutput = output;
txEndLogs = logs;
txEndGasUsed = gasUsed;
txEndTimeNs = timeNs;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ static T8nResult runTest(
final TransactionProcessingResult result;
try {
tracer = tracerManager.getManagedTracer(i, transaction.getHash());
tracer.traceStartTransaction(worldStateUpdater, transaction);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure why the tracer was not called here previously?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems you added the traceEndTransaction @diega . do you know if it's normal to not have the startTransaction ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it looks like it was needed. I never used the --json|--trace flag from the t8n tool, so I cannot say if/how it was working without the tracer start. Probably @shemnon can confirm it

Copy link
Contributor

@shemnon shemnon Sep 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like this should be called somewhere in MainnetTransactionProcessor.processTransaction rather than as part of the lead-up.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same with endTransaction.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shemnon given that gasUsed can't be computed from within processTransaction, that implies to either:

  • dirty processTransaction signature to pass gasUsed only for the sake of tracing;
  • just drop gasUsed from the traceEndTransaction API.

What do you think?

result =
processor.processTransaction(
blockchain,
Expand Down Expand Up @@ -312,12 +313,18 @@ static T8nResult runTest(
.getGasCalculator()
.transactionIntrinsicGasCost(
transaction.getPayload(), transaction.getTo().isEmpty());
tracer.traceEndTransaction(
result.getOutput(), gasUsed - intrinsicGas, timer.elapsed(TimeUnit.NANOSECONDS));
TransactionReceipt receipt =
protocolSpec
.getTransactionReceiptFactory()
.create(transaction.getType(), result, worldState, gasUsed);
tracer.traceEndTransaction(
worldStateUpdater,
transaction,
result.isSuccessful(),
result.getOutput(),
result.getLogs(),
gasUsed - intrinsicGas,
timer.elapsed(TimeUnit.NANOSECONDS));
Bytes gasUsedInTransaction = Bytes.ofUnsignedLong(transactionGasUsed);
receipts.add(receipt);
ObjectNode receiptObject = receiptsArray.addObject();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
import org.hyperledger.besu.datatypes.Transaction;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.log.Log;
import org.hyperledger.besu.evm.operation.Operation.OperationResult;
import org.hyperledger.besu.evm.worldstate.WorldView;

import java.util.List;
import java.util.Optional;

import org.apache.tuweni.bytes.Bytes;
Expand Down Expand Up @@ -67,18 +70,30 @@ default void traceAccountCreationResult(
/**
* Trace the start of a transaction.
*
* @param worldView an immutable view of the execution context
* @param transaction the transaction which will be processed
*/
default void traceStartTransaction(final Transaction transaction) {}
default void traceStartTransaction(final WorldView worldView, final Transaction transaction) {}

Check notice

Code scanning / CodeQL

Useless parameter

The parameter 'worldView' is never used.

Check notice

Code scanning / CodeQL

Useless parameter

The parameter 'transaction' is never used.

/**
* Trace the end of a transaction.
*
* @param worldView an immutable view of the execution context
* @param tx the transaction that just concluded
* @param status true if the transaction is successful, false otherwise
* @param output the bytes output from the transaction
* @param logs the logs emitted by this transaction
* @param gasUsed the gas used by the entire transaction
* @param timeNs the time in nanoseconds it took to execute the transaction
*/
default void traceEndTransaction(final Bytes output, final long gasUsed, final long timeNs) {}
default void traceEndTransaction(
final WorldView worldView,
Fixed Show fixed Hide fixed

Check notice

Code scanning / CodeQL

Useless parameter

The parameter 'worldView' is never used.
final Transaction tx,
Fixed Show fixed Hide fixed

Check notice

Code scanning / CodeQL

Useless parameter

The parameter 'tx' is never used.
final boolean status,

Check notice

Code scanning / CodeQL

Useless parameter

The parameter 'status' is never used.
final Bytes output,
final List<Log> logs,

Check notice

Code scanning / CodeQL

Useless parameter

The parameter 'logs' is never used.
final long gasUsed,
final long timeNs) {}

/**
* Trace the entering of a new context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@

import static com.google.common.base.Strings.padStart;

import org.hyperledger.besu.datatypes.Transaction;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.log.Log;
import org.hyperledger.besu.evm.operation.Operation;
import org.hyperledger.besu.evm.worldstate.WorldView;

import java.io.PrintStream;
import java.io.PrintWriter;
Expand Down Expand Up @@ -216,7 +219,14 @@ public void traceAccountCreationResult(
}

@Override
public void traceEndTransaction(final Bytes output, final long gasUsed, final long timeNs) {
public void traceEndTransaction(
final WorldView _worldView,
final Transaction _tx,
final boolean _status,
final Bytes output,
final List<Log> _logs,
final long gasUsed,
final long timeNs) {
final StringBuilder sb = new StringBuilder(1024);
sb.append("{");
if (output.size() > 0) {
Expand Down