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

HUB debugging continued #1492

Merged
merged 63 commits into from
Nov 16, 2024
Merged

HUB debugging continued #1492

merged 63 commits into from
Nov 16, 2024

Conversation

OlivierBBB
Copy link
Collaborator

No description provided.

added a new "canonical" method for AccountSnapshot's
The WorldUpdater of the frame gets discarded in case of an exceptional
halt. Besu does this BEFORE it traceContextExit. This fix does creates
the

  TxFinalizationSection

before the wiping of the WorldUpdater so as to extract the "unupdated"
balances.

Note. We will have to apply a similar fix to other operations that make
snapshots at the boundaries of context changes: CALL's, CREATE's,
RETURN's and SELFDESTRUCT's to name a few, and more generally those
opcodes that are REVERT sensitive.
@OlivierBBB OlivierBBB linked an issue Nov 9, 2024 that may be closed by this pull request
In the TraceSection() constructor we were using the Hub's

  raisesOogxOrIsUnexceptional

method, which queries the currentTraceSection. However, and at that
point, this trace section isn't the current one. And its 'exceptions'
object is that of the previous opcode.
In the prewarming section we create account and storage fragments using
the current hub.stamp(). However it is only later that we create the
TraceSection. It is this step that raises the HUB_STAMP.

We therefore pre-increment the HUB_STAMP when filling the DOM/SUB
stamps.
There was a bug in that we may want to write the result of a KECCAK even
if we don't set the hashInfoFlag. This occurs for unexceptional KEC's
and for unexceptional, unaborted CREATE2's, both with nonzero size
parameter.
GAS_NEXT now returns

   remainingGas - upfrontGasCost

this works for most opCodes. CALL's and CREATE's have specialized
methods to amend GAS_NEXT depending on the scenario.
Amending GAS_NEXT is more involved for precompiles.
We talk about child context's and child frames a lot. Also child frames
are created also for deployments where "caller" isn't the right term for
the parent context.
This is required for both CALL's and CREATE's that actually "enter" a
child context with non empty bytecode and nonempty initialization code
respectively. We need to write both

  CCSR: CHILD_CONTEXT_SELF_REVERTS
  CCRS: CHILD_CONTEXT_REVERT_STAMP

This information only becomes avaiable once the child frame's relevant
fields were discovered.
This applies whenever we call a SMC with nonempty bytecode.
this fragment is initialized only for the TraceSection's arising from
exceptional opCodes. It is appended to its section at the moment of line
counting.
@@ -74,7 +74,7 @@ public void commit(List<MappedByteBuffer> buffers) {
public void resolvePostExecution(
Hub hub, MessageFrame frame, Operation.OperationResult operationResult) {
gasParameters.gasActual(BigInteger.valueOf(commonValues.gasActual));
gasParameters.gasCost(BigInteger.valueOf(commonValues.gasCost()));
gasParameters.gasCost(BigInteger.valueOf(commonValues.gasCostToTrace()));
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We don't always compute the gas cost (e.g. if an exception occurs that has greater priority, e.g. SUX/SOX/STATICX/MXPX/... but we always fill the GAS_COST column. We fill it with some value which we call gasCostToTrace.

hub.transients.conflation().deploymentInfo(),
isAddressWarm(hub.messageFrame(), address));
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

At times the trace method we are using provides us with a WorldView. When that is the case it is often more accurate to use this world rather than the maybe deprecated hub.messageFrame().getWorldUpdater() that we use in the other canonical method. This method is then preferred.

This issue arose e.g. in traceEndTransaction and was the reason for why some accounts appeared with invalid balances.

@@ -634,7 +634,7 @@ public void traceContextEnter(MessageFrame frame) {

final long callDataContextNumber = callStack.currentCallFrame().contextNumber();

currentFrame().rememberGasNextBeforePausing();
currentFrame().rememberGasNextBeforePausing(this);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We need to get the gasNext from the hub, likely from the commonValuesFragment of the currentTraceSection;

}
}

defers.resolveUponContextExit(this, this.currentFrame());
// TODO: verify me please @Olivier
if (this.currentFrame().opCode() == REVERT || Exceptions.any(pch.exceptions())) {
defers.resolvePostRollback(this, frame, this.currentFrame());
defers.resolveUponRollback(this, frame, this.currentFrame());
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Renaming to be consistent with the other methods.

if (state.getProcessingPhase() != TX_SKIP
&& frame.getState() == MessageFrame.State.COMPLETED_SUCCESS) {
this.state.setProcessingPhase(TX_FINL);
new TxFinalizationSection(this, frame.getWorldUpdater(), false);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There are now 2 places that can trigger the creation of a TxFinalizationSection:

  • traceContextExit
  • tracePostExecution

the reason being: the root context behaves differently in Besu. But looking at Besu I don't remember why that is. And maybe we want to change that and only trace it during traceContextExit

Copy link
Collaborator

Choose a reason for hiding this comment

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

you mean we don't traceContextExit the root context in case of exception ?

recipientSnapshotAfterTxFinalization.turnOnWarmth(); // purely constraints based

final Address coinbaseAddress = coinbaseSnapshotBeforeTxFinalization.address();
coinbaseSnapshotAfterFinalization = AccountSnapshot.canonical(hub, coinbaseAddress);
coinbaseSnapshotAfterFinalization = AccountSnapshot.canonical(hub, world, coinbaseAddress);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Using the correct world to make snapshots. This world is provided to us in traceContextExit (in the unexceptional case.)

@@ -80,7 +80,13 @@ public TxPreWarmingMacroSection(WorldView world, Hub hub) {

final DomSubStampsSubFragment domSubStampsSubFragment =
new DomSubStampsSubFragment(
DomSubStampsSubFragment.DomSubType.STANDARD, hub.stamp(), 0, 0, 0, 0, 0);
DomSubStampsSubFragment.DomSubType.STANDARD,
hub.stamp() + 1,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We have to pre-emptively raise the hub.stamp() as this only happens later.

Copy link
Collaborator

Choose a reason for hiding this comment

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

yes true, we raise h only when creating the section ! :) nice

DomSubStampsSubFragment.standardDomSubStamps(hub.stamp(), 0));

// recipient account fragment
final AccountFragment recipientAccountFragment =
hub.factories()
.accountFragment()
.make(
.makeWithTrm(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is required by the arithmetization, specifically: in the permuted domain the first appearance of an account should be with some trimming.

Copy link
Collaborator

Choose a reason for hiding this comment

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

there is a issue for this, did you link it ?

edit: I did

postOpcodeCallerSnapshot.decrementBalanceBy(value);
preOpcodeCalleeSnapshot.decrementBalanceBy(value);
preOpcodeCalleeSnapshot = postOpcodeCallerSnapshot;
postOpcodeCalleeSnapshot = preOpcodeCallerSnapshot;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Switcheroo

scenarioFragment.setScenario(CALL_SMC_FAILURE_WONT_REVERT);

if (selfCallWithNonzeroValueTransfer) {
childContextExitCallerSnapshot.decrementBalanceBy(value);
reEntryCalleeSnapshot.decrementBalanceBy(value);
}

int childId = hub.currentFrame().childFramesId().getLast();
CallFrame childFrame = hub.callStack().getById(childId);
int childContextRevertStamp = childFrame.revertStamp();
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Note. This revertStamp is that of a "self-induced" revert. Not the actual revert stamp of the child context. The child call / deployment may be successful (in which case we don't enter here) and be reverted later. Here we only care about children that are failures.

OlivierBBB and others added 4 commits November 16, 2024 12:54
Signed-off-by: F Bojarski <ceciestunepoubelle@protonmail.ch>
We (or I) don't really understand when Besu's MessageFrame is
EXCEPTIONAL_HALT, in particular as it relates to

- invalidCodePrefixException
- maxCodeSizeException

for RETURN instructions in a deployment context
@@ -753,7 +753,7 @@ public void tracePostExecution(MessageFrame frame, Operation.OperationResult ope
}

if (frame.getDepth() == 0
&& (frame.getState() == MessageFrame.State.EXCEPTIONAL_HALT || opCode() == REVERT)) {
&& (isExceptional() || opCode() == REVERT)) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's unclear exactly when Besu's MessageFrame enters the EXCEPTIONAL_HALT state. This is particularly true wrt deployment related exceptions (invalidCodePrefixException and maxCodeSizeException) for whatever reason.

Copy link
Collaborator

@letypequividelespoubelles letypequividelespoubelles left a comment

Choose a reason for hiding this comment

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

LGTM, some minor comments

public AccountSnapshot setDeploymentInfo(Hub hub) {
return this.setDeploymentInfo(hub.transients.conflation().deploymentInfo());
}

public AccountSnapshot setDeploymentInfo(DeploymentInfo deploymentInfo) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why not having a @Setter ?

if (state.getProcessingPhase() != TX_SKIP
&& frame.getState() == MessageFrame.State.COMPLETED_SUCCESS) {
this.state.setProcessingPhase(TX_FINL);
new TxFinalizationSection(this, frame.getWorldUpdater(), false);
Copy link
Collaborator

Choose a reason for hiding this comment

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

you mean we don't traceContextExit the root context in case of exception ?

@@ -750,6 +751,11 @@ public void tracePostExecution(MessageFrame frame, Operation.OperationResult ope
if (!this.currentFrame().opCode().isCall() && !this.currentFrame().opCode().isCreate()) {
this.unlatchStack(frame, currentSection);
}

if (frame.getDepth() == 0 && (isExceptional() || opCode() == REVERT)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

That's strange that we don't traceContextExit in case of exception in the root context ...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I probably phrased that wrong. We certainly don't traceContextReEnter as there is nothing to re-enter. TBH I'm not sure it makes sense to have two entry points for TxFinalizationSection.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd prefer to have only one entry point, yes ...

case CREATE2 -> Exceptions.none(exceptions) && aborts.none() && gp.messageSize() > 0;
default -> false;
};
Exceptions.none(exceptions)
Copy link
Collaborator

Choose a reason for hiding this comment

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

not sure it's an optim, we'll add two more operation (Exceptions.none and gp.messageSize > 0 ) for every stack row ... and it doesn't make the code much easier to read

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I exchanged positions so that these checks are done after the switch. I believe that Java is smart enough to ignore conditions in a chain of &&'s if a previous condition was false.

// 2. EOA calls with value transfer (immediately reapStipend)
// 3. SMC calls: gas paid out of pocket
// 4. PRC calls: gas paid out of pocket + special PRC cost + returned gas

Copy link
Collaborator

Choose a reason for hiding this comment

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

can't we get the gasNext from besu (ie from the next section, in seal() method ?

@@ -80,7 +80,13 @@ public TxPreWarmingMacroSection(WorldView world, Hub hub) {

final DomSubStampsSubFragment domSubStampsSubFragment =
new DomSubStampsSubFragment(
DomSubStampsSubFragment.DomSubType.STANDARD, hub.stamp(), 0, 0, 0, 0, 0);
DomSubStampsSubFragment.DomSubType.STANDARD,
hub.stamp() + 1,
Copy link
Collaborator

Choose a reason for hiding this comment

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

yes true, we raise h only when creating the section ! :) nice

DomSubStampsSubFragment.standardDomSubStamps(hub.stamp(), 0));

// recipient account fragment
final AccountFragment recipientAccountFragment =
hub.factories()
.accountFragment()
.make(
.makeWithTrm(
Copy link
Collaborator

Choose a reason for hiding this comment

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

there is a issue for this, did you link it ?

edit: I did

@@ -107,7 +107,7 @@ public void run(Wei senderBalance, Long gasLimit, List<ToyAccount> additionalAcc
ToyTransaction.builder()
.sender(senderAccount)
.to(receiverAccount)
.value(Wei.ONE)
.value(Wei.of(69))
Copy link
Collaborator

Choose a reason for hiding this comment

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

:)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Mostly to disambiguate with other 1's in the trace 🙂.

@@ -41,7 +41,7 @@
*/
@Accessors(fluent = true)
public final class BytecodeRunner {
public static final long DEFAULT_GAS_LIMIT = 25_000_000L;
public static final long DEFAULT_GAS_LIMIT = 61_000_000L;
Copy link
Collaborator

Choose a reason for hiding this comment

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

could use the constant from GlabalConsta 'LINEA_BLOCK_GAS_LIMIT`or whatever

@OlivierBBB
Copy link
Collaborator Author

#1492 (comment)

No sure. I don't think this would work at the boundaries of execution contexts for instance.

@OlivierBBB OlivierBBB merged commit 44af7e6 into arith-dev Nov 16, 2024
3 checks passed
@OlivierBBB OlivierBBB deleted the 1490-hub-debugging-continued branch November 16, 2024 07:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants