Skip to content
Open
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
44 changes: 44 additions & 0 deletions qkc/config/singularity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,48 @@ print('gas_limit ', block.header.evm_gas_limit, block.meta.evm_xshard_gas_limit
"
```

## Execution golden vectors

The execution layer is pinned the same way, but by a script rather than a one-liner:
[`qkc/testdata/gen_exec_golden.py`](../../testdata/gen_exec_golden.py) drives
pyquarkchain's own `EvmState` and `ShardState` and writes
`qkc/testdata/exec_golden/*.json`. It reads the two configs in this directory, so
the vectors are bound to the configs goshard ships rather than to whatever a
pyquarkchain checkout happens to carry.

Three granularities are emitted, each with its own file and its own consumer in
`qkc/core`:

| file | input | pinned output |
| --- | --- | --- |
| `state_level.json` | direct `EvmState` mutations | post state root, per-account reads |
| `message_level.json` | one signed transaction or one cross-shard deposit | post state root, receipts, gas counters, produced deposits, coinbase fees |
| `block_level.json` | whole minor blocks against a shard built from its genesis, with a root chain alongside | the seven values a block commits to, plus the deposits it consumed |

A block-level case carries the shard's genesis allocation, the serialized root
blocks it saw, the deposit lists its neighbours sent, and each block in order.
The allocation is the shard's `GENESIS.ALLOC`, so a consumer reaches the genesis
state root by applying it and nothing else — which is the same self-check the
genesis cases make, one level up.

Regenerate with:

```
# from the root of a pyquarkchain checkout, inside a virtualenv with its
# requirements installed:
python <path-to-goshard>/qkc/testdata/gen_exec_golden.py
```

The checkout is taken from `$PYQUARKCHAIN`, defaulting to the current directory.

Two things guard the result. The script's first two cases are the genesis
allocations themselves, and it fails unless their state roots match the pinned
values above — a mismatch elsewhere is then a real disagreement, not a case
description that never reached `EvmState`. And because that self-check says
nothing about execution — changing `messages.py` leaves the genesis root
untouched — every vector file records the oracle it came from: the pyquarkchain
commit and a digest of each module that decides execution. The script refuses to
run when one of those modules has uncommitted changes; `--allow-dirty` proceeds
and names the edited modules in the output instead.

Consumed by `qkc`, `qkc/config`, `qkc/types`, and `cmd/slave` tests.
48 changes: 48 additions & 0 deletions qkc/testdata/exec_golden/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# exec_golden

Execution golden vectors generated from pyquarkchain by
[`gen_exec_golden.py`](../gen_exec_golden.py). What the three files hold, how
they are regenerated, and how the result is guarded is documented in
[`qkc/config/singularity/README.md`](../../config/singularity/README.md), under
"Execution golden vectors".

This file records the one thing a consumer cannot read off the vectors: which
call each `state_level.json` op stands for.

## State-level ops

A case is an allocation, a list of ops, and the state root the ops commit to.
Every op names a method the generator calls on pyquarkchain's `EvmState`. To
test the Go implementation against the same case, an op has to reach the call
in the third column.

| op | pyquarkchain `EvmState` | Go |
| --- | --- | --- |
| `set_full_shard_key` | `full_shard_key = v` | `EvmState.SetFullShardKey` |
| `delta_token_balance` | `delta_token_balance` | `EvmState.DeltaTokenBalance` |
| `set_token_balance` | `set_token_balance` | `EvmState.SetTokenBalance` |
| `read_account` | `get_balance` | `EvmState.GetBalance` |
| `set_nonce` | `set_nonce` | `EvmState.SetNonce` |
| `increment_nonce` | `increment_nonce` | `EvmState.IncrementNonce` |
| `set_code` | `set_code` | `EvmState.SetCode` |
| `set_storage` | `set_storage_data` | `StateDB.SetState` |
| `reset_balances` | `reset_balances` | `StateDB.ResetBalances` |
| `reset_storage` | `reset_storage` | `StateDB.ResetStorage` |
| `del_account` | `del_account` | `StateDB.DelAccount` |
| `snapshot` | `snapshot` | `EvmState.Snapshot` |
| `revert` | `revert` | `EvmState.RevertToSnapshot` |
| `commit` | `commit` | `EvmState.Commit` |

The two Go receivers are one object. `EvmState` is QuarkChain's, in `qkc/state`;
`StateDB` is geth's, in `core/state`, which this fork has taught QuarkChain's
account rules (`core/state/statedb_qkc.go`). `EvmState` embeds a `*state.StateDB`,
and Go makes an embedded type's methods callable on the outer one, so a row
naming `StateDB` is that method reached through `EvmState` unchanged — no
forwarding code exists for it. `qkc/state` writes its own method only where
QuarkChain's semantics differ from geth's.

## The other two files

`message_level.json` and `block_level.json` are not op lists. Each case is a
whole input — one transaction or deposit, or a sequence of minor blocks — and
the pinned values are listed in the table in the singularity README.
Loading