Skip to content

HUB consistency arguments #1746

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

Merged
merged 49 commits into from
Feb 6, 2025
Merged

HUB consistency arguments #1746

merged 49 commits into from
Feb 6, 2025

Conversation

OlivierBBB
Copy link
Collaborator

No description provided.

@OlivierBBB
Copy link
Collaborator Author

OlivierBBB commented Jan 28, 2025

26c3912 addresses Bug in scn/CALL_SMC_FAILURE_WILL_REVERT self calls.

The point being: our MemoryRange object now should always remember the
relevant context number. MemoryRange.EMPTY, however, doesn't remember
it. This is problematic when encountering a context row after the
context row which updated the return data after an exception.
@@ -733,8 +733,6 @@ public void tracePostExecution(MessageFrame frame, Operation.OperationResult ope
if (isExceptional()) {
this.currentTraceSection()
.exceptionalContextFragment(ContextFragment.executionProvidesEmptyReturnData(this));
this.squashCurrentFrameOutputData();
this.squashParentFrameReturnData();
}
Copy link
Collaborator Author

@OlivierBBB OlivierBBB Jan 28, 2025

Choose a reason for hiding this comment

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

squashParentFrameReturnData replaces the value of MemorySpan returnData provided to the parent frame in exceptionalContextFragment, which is

MemorySpan(currentFrame.contextnumber())

which has a nonzero context number, with

MemorySpan.EMPTY

which has a context number of 0. This was a mistake dating back to the time when we had both a returnDataContextNumber and a returnDataSpan rather than the current system of having both united in a MemorySpan object.

@@ -1100,7 +1098,7 @@ public void squashCurrentFrameOutputData() {
}

public void squashParentFrameReturnData() {
callStack.parentCallFrame().outputDataRange(MemoryRange.EMPTY);
callStack.parentCallFrame().returnDataRange(MemoryRange.EMPTY);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Typo

newState.address(), newState.deploymentNumber(), newState.deploymentStatus());
} catch (RuntimeException e) {
codeFragmentIndex = 0;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

CFI's are required on all account-rows, but they are looked up only on those where requiresRomlex = true.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I still don't understand the need for this change : in case requiresRomlex is false, the previous method gives 0 and shouldn't raise an exception

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

in case requiresRomlex is false, the previous method gives 0 and shouldn't raise an exception

I may have mixed things up then. I vaguely remember getting an exception at some point, but I may be misremembering.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The reason, as discussed earlier, seems to be that when we request the CFI of an account with either empty code or whose byte code does not get loaded into the ROM it may throw an exception, which is what I remember. I may change it to

if (cfiRequired) {
  // old way
} else {
  // above way with error handling
}

to make sure we don't have "required CFI requests" that don't find a match in the ROM_LEX map.

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 now check that if cfiRequired = true that getting the CFI doesn't throw a run time exception.

@@ -123,7 +129,7 @@ private void setSnapshots(Hub hub, WorldView world) {
senderGasRefundNew =
senderIsCoinbase(hub)
? coinbaseGasRefund.deepCopy()
: AccountSnapshot.canonical(hub, world, senderAddress);
: AccountSnapshot.canonical(hub, world, senderAddress).turnOnWarmth();
Copy link
Collaborator Author

@OlivierBBB OlivierBBB Jan 28, 2025

Choose a reason for hiding this comment

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

The comment on coinbase address warmth at transaction end, in particular in case of address collisions, explains why we do this.

calleeThird = calleeSecondNew.deepCopy().setDeploymentNumber(hub);
calleeThirdNew = calleeFirst.deepCopy().setDeploymentNumber(hub);
}

Copy link
Collaborator Author

@OlivierBBB OlivierBBB Jan 28, 2025

Choose a reason for hiding this comment

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

See Consensys/linea-specification#83 for an explanation.

@@ -203,6 +203,7 @@ public void rememberGasNextBeforePausing(Hub hub) {
this.parentId = parentId;
this.callDataRange = callDataRange;
this.returnAtRange = returnAtRange;
this.outputDataRange = new MemoryRange(contextNumber);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The outputDataRange, if not overwritten nontrivially at traceContextExit, should at least remember its own context number, as that will always be correct and provides its parent frame at least with the correct returnDataContextNumber by means of returnDataRange.contextNumber().

/** Get to fucking work GitHub */
public void squashReturnData() {
returnDataRange(MemoryRange.EMPTY);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

A relic of past frustration when GitHub runners were down for a few days 😅

Bytes val1 = getStack(frame, 0);
Bytes val2 = getStack(frame, depth);
Bytes topValue = getStack(frame, 0);
Bytes botValue = getStack(frame, depth);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Simple mix up. Was invisible before stack consistency constraints.

destructedAccountsSnapshot.add(
AccountSnapshot.fromAddress(
address, true, hub.deploymentNumberOf(address), hub.deploymentStatusOf(address)));
destructedAccountsSnapshot.add(AccountSnapshot.canonical(hub, 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.

Using canonical gives us access to the correct code, nonce, etc ...

Bytecode.EMPTY,
deploymentInfo.deploymentNumber(address),
deploymentStatus);
this.nonce(0).balance(Wei.ZERO).code(Bytecode.EMPTY).setDeploymentInfo(deploymentInfo);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The wipe method must be invoked after the SELFDESTRUCT'ing account's deployment information underwent a freshDeployment.

defers.resolveAtEndTransaction(this, world, tx, isSuccessful);
defers.resolveAfterTransactionFinalization(this, world);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The only items that are in the resolveAfterTransactionFinalization defer registry are the "account wiping rows" of successful SELFDESTRUCT's.

coinbaseWarmthAtTransactionEnd =
isExceptional() || opCode() == REVERT
? txStack.current().coinbaseWarmthAfterTxInit(this)
: frame.isAddressWarm(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.

If the root context reverts Besu's frame will revert and reset account warmths. This will clash with conventions where e.g. the sender is warm at transaction end in the ZKEVM, regardless of circumstances. This isn't an issue for the sender but it may be an issue for the coinbase in case the coinbase is either the sender or the recipient.

defer.resolveAtEndTransaction(hub, world, tx, isSuccessful);
}
postTransactionDefers.clear();
endTransactionDefers.clear();
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Nothing happens here, it's just the renaming of postTransactionDefers to endTransactionDefers.

for (AfterTransactionFinalizationDefer defer : afterTransactionFinalizationDefers) {
defer.resolveAfterTransactionFinalization(hub, worldView);
}
afterTransactionFinalizationDefers.clear();
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Wiping this registry is important 🙂.

.pAccountDeploymentStatus(oldState.deploymentStatus())
.pAccountDeploymentNumberNew(newState.deploymentNumber())
.pAccountDeploymentStatusNew(newState.deploymentStatus())
.pAccountDeploymentNumberInfty(deploymentNumberInfinity)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Just reordering.

Copy link

sonarqubecloud bot commented Feb 6, 2025

.deploymentStatus(true)
.code(recipientValueReceptionNew.code());
}

Copy link
Collaborator Author

@OlivierBBB OlivierBBB Feb 6, 2025

Choose a reason for hiding this comment

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

This is required deployment transactions get converted to either

  • deployed byte code
  • no deployed byte code

and this requires a transition from the state of the account where there is initialization code (recipientValueReceptionNew.code()) to one where there isn't. We thus grab the initialization code to fill the "pre-state" with it.

@@ -112,7 +112,7 @@ public void resolveAtEndTransaction(
// may have to be modified in case of address collision
senderNew = canonical(hub, world, sender.address(), isPrecompile(sender.address()));
recipientNew = canonical(hub, world, recipient.address(), isPrecompile(recipient.address()));
coinbaseNew = canonical(hub, world, coinbase.address(), isPrecompile(recipient.address()));
coinbaseNew = canonical(hub, world, coinbase.address(), isPrecompile(coinbase.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.

typo

calleeSecond = reEntryCalleeSnapshot.deepCopy().setDeploymentInfo(hub);
calleeSecondNew = calleeFirst.deepCopy().setDeploymentInfo(hub);
calleeSecond = reEntryCalleeSnapshot.deepCopy().setDeploymentNumber(hub);
calleeSecondNew = calleeFirst.deepCopy().setDeploymentNumber(hub);
Copy link
Collaborator Author

@OlivierBBB OlivierBBB Feb 6, 2025

Choose a reason for hiding this comment

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

The deployment number may have evolved, but it is the deployment status we are walking back.

calleeSecond = calleeFirstNew.deepCopy().setDeploymentInfo(hub);
calleeSecondNew = calleeFirst.deepCopy().setDeploymentInfo(hub);
calleeSecond = calleeFirstNew.deepCopy().setDeploymentNumber(hub);
calleeSecondNew = calleeFirst.deepCopy().setDeploymentNumber(hub);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The deployment number may have evolved, but it is the deployment status we are walking back.


this.addFragment(finalUnexceptionalContextFragment);
accountWipingNew.wipe(hub.transients().conflation().deploymentInfo());
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 the raison d'être of the AfterTransactionFinalization defer registry. It is so that we don't raise the deployment number of SELFDESTRUCT'ing accounts prior to executing the TX_FINL phase. This messes things up in case the COINBASE is among the SELFDESTRUCT'ing accounts.

@@ -126,7 +125,7 @@ public void commit(List<MappedByteBuffer> buffers) {
traceStackSettings(op, trace);
traceBillingSettings(op, trace);
trace
.opcode(Bytes.ofUnsignedInt(i))
.opcode(UnsignedByte.of(i))
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@DavePearce I've had to correct this a couple of times here and there. Just letting you know, not sure if it's a corset change that's at the source of this change in signature.

destructedAccountsSnapshot.add(
AccountSnapshot.fromAddress(
address, true, hub.deploymentNumberOf(address), hub.deploymentStatusOf(address)));
destructedAccountsSnapshot.add(AccountSnapshot.canonical(hub, world, 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.

This was a very stupid oversight / temporary solution to a problem that only became noticeable with account consistency on. The fromAddress assigns empty code, zero balance, zero nonce etc. to the AccountSnapshot it is creating. Unsurprisingly this broke account consistency constraints.

|| coinbaseIsPrecompile
|| coinbaseIsSender
|| coinbaseIsRecipient;
}
Copy link
Collaborator Author

@OlivierBBB OlivierBBB Feb 6, 2025

Choose a reason for hiding this comment

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

This method logs whether the COINBASE address will be warm at the end of TX_INIT. This is required in case of a REVERT'ing transaction with an address collision. It will be made obsolete by EIP-3651.

Copy link
Collaborator

@lorenzogentile404 lorenzogentile404 left a comment

Choose a reason for hiding this comment

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

accountWipingNew.wipe(hub.transients().conflation().deploymentInfo()); returns a new AccountSnapshot that is not used.

@OlivierBBB
Copy link
Collaborator Author

accountWipingNew.wipe(hub.transients().conflation().deploymentInfo()); returns a new AccountSnapshot that is not used.

As discussed on slack, this is not the case.

@OlivierBBB OlivierBBB merged commit 1f032b9 into arith-dev Feb 6, 2025
7 checks passed
@OlivierBBB OlivierBBB deleted the HUB-consistency-arguments branch February 6, 2025 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants