Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
6 changes: 1 addition & 5 deletions src/challengeV2/EdgeChallengeManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -399,12 +399,8 @@ contract EdgeChallengeManager is IEdgeChallengeManager, Initializable {

assertionChain.validateConfig(prevAssertionHash, prevConfig);

// TODO(PR 427): OSP contracts are marked as pending work in the PR.
// Inbox-position-based checks no longer apply; use type(uint256).max as
// a stopgap until OSP is rewired against `nextParentChainBlockHash`.
ExecutionContext memory execCtx = ExecutionContext({
maxInboxMessagesRead: type(uint256).max,
bridge: assertionChain.bridge(),
targetParentChainBlockHash: prevConfig.nextParentChainBlockHash,
initialWasmModuleRoot: prevConfig.wasmModuleRoot
});

Expand Down
23 changes: 17 additions & 6 deletions src/mocks/SimpleOneStepProofEntry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "../state/Deserialize.sol";

contract SimpleOneStepProofEntry is IOneStepProofEntry {
using GlobalStateLib for GlobalState;
using MELStateLib for MELState;

// End the batch after 2000 steps. This results in 11 blocks for an honest validator.
// This constant must be synchronized with the one in execution/engine.go
Expand All @@ -26,23 +27,33 @@ contract SimpleOneStepProofEntry is IOneStepProofEntry {
uint256 step,
bytes32 beforeHash,
bytes calldata proof
) external view returns (bytes32 afterHash) {
) external pure returns (bytes32 afterHash) {
if (proof.length == 0) {
revert("EMPTY_PROOF");
}
GlobalState memory globalState;
uint256 offset;
(globalState.u64Vals[0], offset) = Deserialize.u64(proof, offset);
(globalState.u64Vals[1], offset) = Deserialize.u64(proof, offset);
if (step > 0 && (beforeHash[0] == 0 || globalState.getPositionInMessage() == 0)) {
(globalState.bytes32Vals[3], offset) = Deserialize.b32(proof, offset); // MELNextMsgHash
(globalState.u64Vals[0], offset) = Deserialize.u64(proof, offset); // MELMsgCount
(globalState.u64Vals[1], offset) = Deserialize.u64(proof, offset); // MELExecutedMsgCount

MELState memory melState;
(melState.parentChainBlockHash, offset) = Deserialize.b32(proof, offset);

if (step > 0 && (beforeHash[0] == 0 || globalState.getMELNextMsgHash() == bytes32(0))) {
// We end the block when the first byte of the hash hits 0 or we advance a batch
return beforeHash;
}
if (globalState.getInboxPosition() >= execCtx.maxInboxMessagesRead) {
// We can't continue further because we've hit the max inbox messages read
if (
melState.parentChainBlockHash == execCtx.targetParentChainBlockHash
&& globalState.getMELExecutedMsgCount() >= globalState.getMELMsgCount()
) {
// We can't continue further because we've executed all messages up to this melState
return beforeHash;
}
require(globalState.hash() == beforeHash, "BAD_PROOF");

// TODO: modify this logic once execution_engine.go is modified
globalState.u64Vals[1]++;
if (globalState.u64Vals[1] % STEPS_PER_BATCH == 0) {
globalState.u64Vals[0]++;
Expand Down
3 changes: 1 addition & 2 deletions src/osp/IOneStepProver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import "../bridge/ISequencerInbox.sol";
import "../bridge/IBridge.sol";

struct ExecutionContext {
uint256 maxInboxMessagesRead;
IBridge bridge;
bytes32 targetParentChainBlockHash;
bytes32 initialWasmModuleRoot;
}

Expand Down
17 changes: 16 additions & 1 deletion src/osp/OneStepProofEntry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ contract OneStepProofEntry is IOneStepProofEntry {
using MerkleProofLib for MerkleProof;
using MachineLib for Machine;
using GlobalStateLib for GlobalState;
using MELStateLib for MELState;
using MultiStackLib for MultiStack;

using ValueStackLib for ValueStack;
Expand Down Expand Up @@ -104,9 +105,22 @@ contract OneStepProofEntry is IOneStepProofEntry {
GlobalState memory globalState;
(globalState, offset) = Deserialize.globalState(proof, offset);
require(globalState.hash() == mach.globalStateHash, "BAD_GLOBAL_STATE");

MELState memory melState;
(melState, offset) = Deserialize.melState(proof, offset);
require(melState.hash() == globalState.getMELStateHash(), "BAD_MEL_STATE");

// The machine has finished processing a message and we're at the start of the next execution segment (machineStep == 0).
// If the MELState is not at its target (meaning that it hasn't finished extracting messages),
// or there are still messages to be processed in MEL, we kickstart the machine.
if (
mach.status == MachineStatus.FINISHED && machineStep == 0
&& globalState.getInboxPosition() < execCtx.maxInboxMessagesRead
&& (
// Machine hasn't extracted messages for this assertion (should only happen before the extraction process is started)
melState.parentChainBlockHash != execCtx.targetParentChainBlockHash
// Machine finishes extracting all messages, but hasn't finished executing them yet
|| globalState.getMELExecutedMsgCount() < globalState.getMELMsgCount()
)
) {
// Kickstart the machine
return getStartMachineHash(mach.globalStateHash, execCtx.initialWasmModuleRoot);
Expand Down Expand Up @@ -187,6 +201,7 @@ contract OneStepProofEntry is IOneStepProofEntry {
)
|| (opcode >= Instructions.VALIDATE_CERTIFICATE && opcode <= Instructions.UNLINK_MODULE)
|| (opcode >= Instructions.NEW_COTHREAD && opcode <= Instructions.SWITCH_COTHREAD)
|| (opcode == Instructions.GET_END_PARENT_CHAIN_BLOCK_HASH)
) {
prover = proverHostIo;
} else {
Expand Down
157 changes: 31 additions & 126 deletions src/osp/OneStepProverHostIo.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
import "../state/ModuleMemory.sol";
import "./IOneStepProver.sol";
import "./ICustomDAProofValidator.sol";
import "../bridge/Messages.sol";
import "../bridge/IBridge.sol";

contract OneStepProverHostIo is IOneStepProver {
using GlobalStateLib for GlobalState;
Expand All @@ -26,9 +24,6 @@
using StackFrameLib for StackFrameWindow;

uint256 private constant LEAF_SIZE = 32;
uint256 private constant INBOX_NUM = 2;
uint64 private constant INBOX_HEADER_LEN = 40;
uint64 private constant DELAYED_HEADER_LEN = 112 + 1;

// CustomDA proof format constants
uint256 private constant CERT_SIZE_LEN = 8;
Expand Down Expand Up @@ -357,124 +352,6 @@
return isValid;
}

function validateSequencerInbox(
ExecutionContext calldata execCtx,
uint64 msgIndex,
bytes calldata message
) internal view returns (bool) {
require(message.length >= INBOX_HEADER_LEN, "BAD_SEQINBOX_PROOF");

uint64 afterDelayedMsg;
(afterDelayedMsg,) = Deserialize.u64(message, 32);
bytes32 messageHash = keccak256(message);
bytes32 beforeAcc;
bytes32 delayedAcc;

if (msgIndex > 0) {
beforeAcc = execCtx.bridge.sequencerInboxAccs(msgIndex - 1);
}
if (afterDelayedMsg > 0) {
delayedAcc = execCtx.bridge.delayedInboxAccs(afterDelayedMsg - 1);
}
bytes32 acc = keccak256(abi.encodePacked(beforeAcc, messageHash, delayedAcc));
require(acc == execCtx.bridge.sequencerInboxAccs(msgIndex), "BAD_SEQINBOX_MESSAGE");
return true;
}

function validateDelayedInbox(
ExecutionContext calldata execCtx,
uint64 msgIndex,
bytes calldata message
) internal view returns (bool) {
require(message.length >= DELAYED_HEADER_LEN, "BAD_DELAYED_PROOF");

bytes32 beforeAcc;

if (msgIndex > 0) {
beforeAcc = execCtx.bridge.delayedInboxAccs(msgIndex - 1);
}

bytes32 messageDataHash = keccak256(message[DELAYED_HEADER_LEN:]);
bytes1 kind = message[0];
uint256 sender;
(sender,) = Deserialize.u256(message, 1);

bytes32 messageHash = keccak256(
abi.encodePacked(kind, uint160(sender), message[33:DELAYED_HEADER_LEN], messageDataHash)
);
bytes32 acc = Messages.accumulateInboxMessage(beforeAcc, messageHash);

require(acc == execCtx.bridge.delayedInboxAccs(msgIndex), "BAD_DELAYED_MESSAGE");
return true;
}

function executeReadInboxMessage(
ExecutionContext calldata execCtx,
Machine memory mach,
Module memory mod,
Instruction calldata inst,
bytes calldata proof
) internal view {
uint256 messageOffset = mach.valueStack.pop().assumeI32();
uint256 ptr = mach.valueStack.pop().assumeI32();
uint256 msgIndex = mach.valueStack.pop().assumeI64();
if (
inst.argumentData == Instructions.INBOX_INDEX_SEQUENCER
&& msgIndex >= execCtx.maxInboxMessagesRead
) {
mach.status = MachineStatus.ERRORED;
return;
}

if (ptr + 32 > mod.moduleMemory.size || ptr % LEAF_SIZE != 0) {
mach.status = MachineStatus.ERRORED;
return;
}

uint256 leafIdx = ptr / LEAF_SIZE;
uint256 proofOffset = 0;
bytes32 leafContents;
MerkleProof memory merkleProof;
(leafContents, proofOffset, merkleProof) =
mod.moduleMemory.proveLeaf(leafIdx, proof, proofOffset);

{
// TODO: support proving via an authenticated contract
require(proof[proofOffset] == 0, "UNKNOWN_INBOX_PROOF");
proofOffset++;

function(ExecutionContext calldata, uint64, bytes calldata) internal view returns (bool)
inboxValidate;

bool success;
if (inst.argumentData == Instructions.INBOX_INDEX_SEQUENCER) {
inboxValidate = validateSequencerInbox;
} else if (inst.argumentData == Instructions.INBOX_INDEX_DELAYED) {
inboxValidate = validateDelayedInbox;
} else {
mach.status = MachineStatus.ERRORED;
return;
}
success = inboxValidate(execCtx, uint64(msgIndex), proof[proofOffset:]);
if (!success) {
mach.status = MachineStatus.ERRORED;
return;
}
}

require(proof.length >= proofOffset, "BAD_MESSAGE_PROOF");
uint256 messageLength = proof.length - proofOffset;

uint32 i = 0;
for (; i < 32 && messageOffset + i < messageLength; i++) {
leafContents =
setLeafByte(leafContents, i, uint8(proof[proofOffset + messageOffset + i]));
}

mod.moduleMemory.merkleRoot = merkleProof.computeRootFromMemory(leafIdx, leafContents);
mach.valueStack.push(ValueLib.newI32(i));
}

function executeHaltAndSetFinished(
ExecutionContext calldata,
Machine memory mach,
Expand Down Expand Up @@ -694,6 +571,34 @@
mach.switchCoThreadStacks();
}

function executeGetEndParentChainBlockHash(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shouldn't this talk to the rollup contract to verify the parent assertion of the challenge has the correct parent chain block hash that is claimed in the inputs here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hey @rauljordan , that function is writing the targetParentChainBlockHash available in the ExecutionContext to the Machine. The ExecutionContext is created when calling EdgeChallengeManager.confirmEdgeByOneStepProof(), and it takes the nextParentChainBlockHash from the prevAssertion (here). The prevAssertion is included in the challenge by both contenders, and its config is passed and validated against the on-chain assertion in that same function (so it can be trusted).

Let me know if this answers your question, or if I'm missing something 🙏 .

ExecutionContext calldata execCtx,
Machine memory mach,
Module memory mod,
Instruction calldata,
bytes calldata proof
) internal pure {
// Pop pointer to leaf from the value stack where the target parent chain block hash will be written to
uint256 ptr = mach.valueStack.pop().assumeI32();

// Validate the leaf
if (!mod.moduleMemory.isValidLeaf(ptr)) {
mach.status = MachineStatus.ERRORED;
return;
}

// Prove the leaf in memory
uint256 leafIdx = ptr / LEAF_SIZE;
uint256 proofOffset = 0;
MerkleProof memory merkleProof;
(, , merkleProof) =
mod.moduleMemory.proveLeaf(leafIdx, proof, proofOffset);

// Update merkle root
mod.moduleMemory.merkleRoot =
merkleProof.computeRootFromMemory(leafIdx, execCtx.targetParentChainBlockHash);
}
Comment thread
TucksonDev marked this conversation as resolved.
Dismissed

function executeOneStep(
ExecutionContext calldata execCtx,
Machine calldata startMach,
Expand All @@ -719,8 +624,6 @@
impl = executeValidatePreimage;
} else if (opcode == Instructions.READ_PRE_IMAGE) {
impl = executeReadPreImage;
} else if (opcode == Instructions.READ_INBOX_MESSAGE) {
impl = executeReadInboxMessage;
} else if (opcode == Instructions.HALT_AND_SET_FINISHED) {
impl = executeHaltAndSetFinished;
} else if (opcode == Instructions.LINK_MODULE) {
Expand All @@ -733,8 +636,10 @@
impl = executePopCoThread;
} else if (opcode == Instructions.SWITCH_COTHREAD) {
impl = executeSwitchCoThread;
} else if (opcode == Instructions.GET_END_PARENT_CHAIN_BLOCK_HASH) {
impl = executeGetEndParentChainBlockHash;
} else {
revert("INVALID_MEMORY_OPCODE");
revert("INVALID_HOSTIO_OPCODE");
}

impl(execCtx, mach, mod, inst, proof);
Expand Down
52 changes: 52 additions & 0 deletions src/state/Deserialize.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import "./MerkleProof.sol";
import "./ModuleMemoryCompact.sol";
import "./Module.sol";
import "./GlobalState.sol";
import "./MELState.sol";

library Deserialize {
function u8(
Expand Down Expand Up @@ -92,6 +93,16 @@ library Deserialize {
offset++;
}

function addr(
bytes calldata proof,
uint256 startOffset
) internal pure returns (address ret, uint256 offset) {
offset = startOffset;
uint256 retInt;
(retInt, offset) = u256(proof, offset);
ret = address(uint160(retInt));
}

function value(
bytes calldata proof,
uint256 startOffset
Expand Down Expand Up @@ -252,6 +263,47 @@ library Deserialize {

state = GlobalState({bytes32Vals: bytes32Vals, u64Vals: u64Vals});
}

function melState(
bytes calldata proof,
uint256 startOffset
) internal pure returns (MELState memory state, uint256 offset) {
offset = startOffset;

// Initialize with dummy values to avoid filling up the stack
state = MELState({
version: 0,
parentChainId: 0,
parentChainBlockNumber: 0,
batchPostingTargetAddress: address(0),
delayedMessagePostingTargetAddress: address(0),
parentChainBlockHash: bytes32(0),
parentChainPreviousBlockHash: bytes32(0),
batchCount: 0,
msgCount: 0,
localMsgAccumulator: bytes32(0),
delayedMessagesRead: 0,
delayedMessagesSeen: 0,
delayedMessageInboxAcc: bytes32(0),
delayedMessageOutboxAcc: bytes32(0)
});

// Fill in the actual values
(state.version, offset) = u16(proof, offset);
(state.parentChainId, offset) = u64(proof, offset);
(state.parentChainBlockNumber, offset) = u64(proof, offset);
(state.batchPostingTargetAddress, offset) = addr(proof, offset);
(state.delayedMessagePostingTargetAddress, offset) = addr(proof, offset);
(state.parentChainBlockHash, offset) = b32(proof, offset);
(state.parentChainPreviousBlockHash, offset) = b32(proof, offset);
(state.batchCount, offset) = u64(proof, offset);
(state.msgCount, offset) = u64(proof, offset);
(state.localMsgAccumulator, offset) = b32(proof, offset);
(state.delayedMessagesRead, offset) = u64(proof, offset);
(state.delayedMessagesSeen, offset) = u64(proof, offset);
(state.delayedMessageInboxAcc, offset) = b32(proof, offset);
(state.delayedMessageOutboxAcc, offset) = b32(proof, offset);
}

function machine(
bytes calldata proof,
Expand Down
Loading
Loading