Skip to content
Draft
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,4 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
/data/
/graypaper-gas-2/
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,28 @@ docker buildx build --platform linux/amd64,linux/arm64 -t jamdottech/pyjamaz -t

## License
https://github.com/JAMdotTech/pyjamaz/blob/main/LICENSE

## Graypaper 0.8 PVM tests

The default instruction suite uses the updated `graypaper-gas-3/pvm/programs`
fixtures, copied unchanged into `test/fixtures/pvm/gas-cost`. The upstream
`gas-tests` fixtures are in `test/fixtures/pvm/integration-tests` and check block
costs for Doom, Pinky, and prime-sieve without executing those programs.

Run from the repository root with the project dependencies installed:

```bash
python -m pytest test/test_pvm_instructions.py -q
PVM_TEST_VECTORS=fixtures/pvm/integration-tests/ python -m pytest test/test_pvm_instructions.py -q
python -m pytest test/test_graypaper_08.py test/test_pvm_gas_regressions.py test/test_pvm_memory.py test/test_cpython_mmap_memory.py test/test_hostcalls_general.py test/test_hostcalls_accumulate.py -q
```

Set `PVM_INTERPRETER` to `GRAYPAPER`, `CPYTHON`, `NUMBA_JIT`, or `NUMBA_AOT`
(the default) to select a backend. Rebuild precompiled Numba caches when deploying
this change; `NUMBA_CACHE_DIR` can select an empty directory for verification.

This PVM uses the 0.8 opcode and hostcall numbering, `grow_heap`, and the hostcall
gas schedule in Appendix I.4.5. Programs built against the earlier ABI must be
rebuilt. The interpreter's `pc` retains the stopping instruction for diagnostics
and the supplied vector format; host invocation results and saved inner-VM state
return zero for a final halt or panic, as specified by Appendix A.1.
5 changes: 2 additions & 3 deletions pyjamaz/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from pyjamaz.models.app import StateDump, Trace, D3LEntry
from pyjamaz.models.common import WorkPackage, WorkReport, WorkPackageBundle, WorkPackageStatus, \
WorkPackageReportableStatus, WorkPackageReadyStatus, BlockDesc, WorkPackageReportedStatus, WorkExecResult, \
WorkDigest, WorkPackageSpec
WorkDigest, WorkPackageSpec, Credential, Guarantee
from pyjamaz.runtime.types import WorkPackageQueueItem
from pyjamaz.settings import SOLO_MODE, DEBUG, SKIP_VALIDATE_GUARANTEES
from pyjamaz.signing import Ed25519Keypair, BandersnatchKeypair
Expand All @@ -40,8 +40,7 @@
from pyjamaz.state.components import Timeslot, Entropy, Safrole, ValidatorArchive, ValidatorPool, ValidatorQueue, \
RecentHistory, Disputes, Assurances, Statistics, PrivilegedServices, AuthorizerQueues, AuthorizerPools, Services, \
AccumulationQueue, AccumulationHistory, RecentAccumulationLog
from pyjamaz.models.block import Block, Header, Extrinsic, ExtrinsicDisputes, Guarantee, Credential, \
Assurance
from pyjamaz.models.block import Block, Header, Extrinsic, ExtrinsicDisputes, Assurance
from pyjamaz.models.state import JamState, ServicesState, SafroleState, EntropyState, PendingChanges
from pyjamaz.models.stf_output import STFOutput
from pyjamaz.transport.pubsub import PubSub, PubSubSignal
Expand Down
16 changes: 15 additions & 1 deletion pyjamaz/graypaper_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
TICKET_SUBMISSION_END_SLOT = 10 #Y
VALIDATOR_COUNT = 6 #V
CORE_COUNT = 2 #C
TICKET_ENTRIES = 3 #N
TICKET_ENTRIES = 4 # ceil(2 * E / default validator count)
MAXIMUM_EXTRINSIC_TICKETS = 3 # K
ROTATION_PERIOD_CORE = 4 # R
PREIMAGE_EXPUNGE_TIMESLOTS = 32 #D=
Expand All @@ -64,3 +64,17 @@
GAS_TOTAL = 20_000_000 # GT
GAS_REFINE = 1_000_000_000 # GR
MAXIMUM_AGE_LOOKUP_ANCHOR = 24 # L


def valid_validator_count(count: int, *, core_count: int = CORE_COUNT) -> bool:
return (
isinstance(count, int) and not isinstance(count, bool)
and 6 <= count <= 3 * core_count and count % 3 == 0
)


def ticket_entries(validator_count: int) -> int:
"""GP-0.8.0-eq:6.6: ceiling of two epochs divided by pending set size."""
if not valid_validator_count(validator_count):
raise ValueError("invalid validator count")
return (2 * EPOCH_TIMESLOTS + validator_count - 1) // validator_count
17 changes: 14 additions & 3 deletions pyjamaz/hostcalls/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import logging
from functools import wraps
from inspect import signature

from pyjamaz.pvm.exceptions import PanicError
from pyjamaz.pvm.constants import ExitCondition, ExitReason
from pyjamaz.pvm.invocation import InvocationMutationOutput


def hostcall(cost: int):
if cost < 0:
def hostcall(cost_method):
"""
Decorator to determine the gas cost to charge for this hostcall
- A fixed charge: @hostcall(48) charges 48 gas
- A cost function: @hostcall(fetch_cost) calculates the charge from the call’s arguments
- A generated cost function: @hostcall(gas_cost_by_size(600, (248, 11))) charges 600 + ceil(248 × r11 / 1024)
"""
if not callable(cost_method) and cost_method < 0:
raise ValueError("hostcall cost must be non-negative")

def hostcall_inner(func):
parameters = signature(func)

@wraps(func)
def hc_wrapped(*args, **kwargs):
Expand All @@ -30,7 +38,10 @@ def hc_wrapped(*args, **kwargs):
if invocation_output is None:
raise PanicError("hostcall could not locate invocation_output")

invocation_output.gas_limit -= cost
# Note: for dynamic argument handling, we use .bind(...) to map positional and keyword arguments to names,
# this way cost functions can consistently access values such as args['registers'] and args['memory'].
gas_cost = cost_method(parameters.bind(*args, **kwargs).arguments) if callable(cost_method) else cost_method
invocation_output.gas_limit = int(invocation_output.gas_limit) - gas_cost
if invocation_output.gas_limit < 0:
logging.debug(f"hostcall {func} gas_limit reached")
invocation_output.exit_condition = ExitCondition(reason=ExitReason.out_of_gas)
Expand Down
Loading
Loading