Skip to content

Witness generation#1

Draft
jsign wants to merge 8 commits into
forks/amsterdamfrom
jsign-temp-1
Draft

Witness generation#1
jsign wants to merge 8 commits into
forks/amsterdamfrom
jsign-temp-1

Conversation

@jsign
Copy link
Copy Markdown
Owner

@jsign jsign commented Jan 20, 2026

Note: this PR isn't meant to be merged, but only to show/comment on work

This is a dummy/temp PR for an experimental way to create execution witness in specs.

Main things included:

  • Add an IncrementalMPT which allows to have a full MPT. This is required to create the pre-state proofs by walking the tree, and also do in-place updates for post-state root calculation and capture potential sibilings in branch compressions.
  • Track changes: bytecodes accessed, ancestors requried, and accounts/storage-slots.
  • From the tracked changes, construct the full ExecutionWitness
  • Do extra wirings so the ExecutionWitness appears in the generated fixtures.

The original way of calculating the post-state root (i.e. patricialize method) still fully exists. This is done because I also on all tests make the spec check that the post-state root calculated with the new IncrementalMPT is coherent with the patricialize calculation -- at least to get higher confidence the new MPT is correct (has helped to catch bugs during development). Filled all tests (~9k tests) and all pass (thus IncrementalMPT seems to be working okay).

Edit 1: having this done, next steps on the spec side can be adding the guest program component with its full API to have a full skeleton of things -- and we can keep iterating/polishing. I will see to do that soon too.
Edit 2: Orthogonal next steps would be running the fixtures against EL clients to see if they generate the same execution witenss, or there are spec bugs etc.

@jsign jsign changed the title Jsign temp 1 Witness generation Jan 20, 2026
@jsign jsign force-pushed the jsign-temp-1 branch 6 times, most recently from ff9b9b8 to 6f2fc39 Compare January 20, 2026 15:20
Signed-off-by: jsign <jsign.uy@gmail.com>
fixture_source_url: str,
gas_benchmark_value: int,
fixed_opcode_count: int | None,
witness_generator: Any,
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This is removing the old witness generator thing we included in the specs which use an external Reth tool. Since we generate it from the specs now, we can clean it up.

return recent_block_hashes


def get_last_256_block_headers(chain: BlockChain) -> List[Bytes]:
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This is a helper method to feed the witness generator with the info that requires to add the ancestors in the header. In the worst case, it can need the last 256 ancestors -- it will include in the witness the minimal amount of them as required (see next code diff right after).


"""
system_contract_code = get_account(block_env.state, target_address).code
track_bytecode_access(block_env.state, system_contract_code)
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

process_checked_system_transaction and process_unchecked_system_transaction are the two methods used in the spec to call system contracts.

In both cases, we track that the bytecode is required for the witness.

)

# Track parent block access for witness (EIP-2935 system call)
track_block_hash_access(block_env.state, block_env.number - Uint(1))
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

For the 2935 system contract being called right below (L820) -- we track the need of requiring the parent block hash as expected.



@dataclass
class WitnessState:
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This is the main class that tracks what is going on during a block execution, so it can create the witness.

if node is None:
return

_record_witness(mpt.witness, node)
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Note.


Returns the new/updated node for this position.
"""
_record_witness(mpt.witness, node)
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Note


Returns the updated node (may be different type or None).
"""
_record_witness(mpt.witness, node)
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Note.


if len(non_empty) == 1 and node.value == b"":
idx, child = non_empty[0]
_record_witness(mpt.witness, child) # Record the surviving child
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Note. Here is the important recording of the surviving sibiling in a branch compression in a branch node.

if hasattr(t8n.fork, "get_witness"):
witness = t8n.fork.get_witness(block_env.state)
self.execution_witness = {
"nodes": sorted(
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

nodes are sorted here to be deterministic -- although I think I'll change things a bit to avoid doing this in t8n, and have a more enshrined method get_witness_output which has Lists for each field and we define more clearly there potential sorting stuff.

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.

1 participant