Skip to content

JAM: Parachain Service Design#11883

Open
bkchr wants to merge 90 commits into
masterfrom
bkchr-parachain-service-doc
Open

JAM: Parachain Service Design#11883
bkchr wants to merge 90 commits into
masterfrom
bkchr-parachain-service-doc

Conversation

@bkchr

@bkchr bkchr commented Apr 24, 2026

Copy link
Copy Markdown
Member

A potential design for the JAM parachain service.

Comment thread designs/parachain-service-on-jam.md Outdated

The Refine entry point of the Parachain Service executes the **Parachain Validation Function
(PVF)** for each work item. This is a stateless, in-core computation performed by guarantors
(validators assigned to the relevant JAM core).

@burdges burdges Apr 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not just guarantors. Also auditors or whatever JAM's term for approval checkers is. I guess this can be swept under the rug here, like all the availability, etc which is identical for all services in JAM, and basically the same in polkadot until one or both of them starts down the polkadot-fellows/RFCs@64c3792 path.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We should not have forks anymore on JAM, so we don't need this?

On JAM, PVFs execute inside a child PVM instance spawned by the Parachain Service's Refine
function. **Hashing**, **trie operations**, and **signature verification** are expected to
move into PVM guest code — transpilation to native code should bring acceptable performance,
though benchmarks are needed to confirm exact numbers.

@burdges burdges Apr 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

transpilation to native code should bring acceptable performance

Oh? When did this happen? I'll be surprised if this were the case. As I understand it, individual chain times seem bound by asymmetric cryptography like signatures, but soon msms and pairings too.

In theory, we could even find that multi-threading msms and pairings gives better performance by liberating the CPU cache faster. Although tests of that theory could be run on kusma now

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is kind of an assumption. Also JAM doesn't provide any host functions for this right now.

As RISCV supports similar extensions as X86_64, I would assume that we could even support something like vector instructions?

CC @koute

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

On PVM crypto stuff will definitely be slower than native code currently; by how much exactly - well, we should write a service and benchmark it. (:

Later on we can add SIMD instructions to accelerate crypto, but it most likely won't happen for JAM 1.0 (as it would be a pretty big extension to the instruction set, and we do want to finally get JAM 1.0 out of the door). That said, even with SIMD inside PVM the crypto implemented as hostcalls will still be faster because that can take full advantage of the hardware available to the host (and we can't do this for PVM without making it completely unportable and making it super complex).

So, performance-wise you get: native hostcalls > future PVM with SIMD > current PVM.

Anyhow, it might be worth it to just.... add crypto hostcalls to JAM for whatever important crypto algorithms we use, considering those hostcalls would be relatively simple. But, again, it'd be nice to have some concrete numbers before we make that decision.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Later on we can add SIMD instructions to accelerate crypto

It'd rock for hashing, weird lattice crypto, etc, but..

Do we know if WASM SIMD yields anything resembling the native SIMD? In PVM & WASM, cryptography would compile using 32 bit limbs, right? This could change many other optimisations, possibly including nerfing the SIMD. In popular libraries, we could probably maintain forks that make existing WASM SIMD optimisations available in PVM, but this maybe a higher maintenance burden than hostcalls.

It's likely curve25519-dalek and/or arkworks have WASM SIMD support now, but if not then some C code somewhere does.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we know if WASM SIMD yields anything resembling the native SIMD? In PVM & WASM, cryptography would compile using 32 bit limbs, right? This could change many other optimisations, possibly including nerfing the SIMD. In popular libraries, we could probably maintain forks that make existing WASM SIMD optimisations available in PVM, but this maybe a higher maintenance burden than hostcalls.

WASM SIMD support is irrelevant here, since we're not targeting WASM. (: But in general, its performance is significantly lower than pure native.

Anyway, off the top of my head I roughly see three options (note: I haven't explored this in detail yet):

  • Use RISC-V vector extensions. Pros: libraries are already starting to support that without us doing anything, Cons: it's not clear what performance we can get, and it's not clear if it can be recompiled well into SIMD on AMD64 (since the model is quite different)
  • Add AMD64 SIMD to PVM. Pros: maximum performance, Cons: complex, tricky to support in libraries - we'd have to port them ourselves and/or patch in support in LLVM
  • A custom SIMD instruction set (that's somewhere in the middle). This is potentially best of both worlds, assuming we can (by default) recompile RISC-V vector extension into this instruction set, and for critical libraries we could add manual ports.

Refine function uses `export()` to write outbound message payloads into DA segments, and
Accumulate only records the message hashes and channel metadata on-chain. See
[paritytech/polkadot-sdk#10449](https://github.com/paritytech/polkadot-sdk/pull/10449)
for a potential specification of XCMP.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So the collators/builders for recieving parachains would fetch the messages from the sending parachain's availability? Ok We believe this adds latency, so we could add a faster path, or not.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I would use export as some kind of escape hatch, to ensure that the parachain can access the data. For normal operations I would assume that there is a faster way to fetch these messages.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There are four possible sources: sending collators, backers / garentors, availability, and auditors / approval checkers. And they obtain & offer the block in that order, meaning availability has the block before auditors / approval checkers.

Sending collators could starve the recipient most easily, so they only really make sense if delivery is unreliable, or you trust some fallback. I've always felt nervous that fallbacks would've buggy code, so I've only suggested these for unreliable delivery. Backers / garentors are not much better. Both can send the message really early though.

Availability incurs the cost of contacting 1/3rd of the validators, not really great for a collator, and maybe slow, but always works under our security assumptions.

Auditors / approval checkers costs only a few connections. Auditors / approval checkers lack the nice clean threat models of availability, but they do have some reasonable threat model, so they make sense if you want one single easy to debug scheme without fallbacks and without hundreds of network connections. The problem is they occur last in ELVES, so they might add more latency than availability, even if they run faster and wait on few connections.

I think most parity devs who considered the problems figure they'd do both one of the first two and availability as fallback, and just suffer debugging the code that hardly ever runs in production.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You may want to be explicit here that the DA is the ultimate fallback/correctness path and that a lower-latency delivery path is preferred (e.g. receiver may fetch off-chain from sending collators/guarantors with hash verification)

@zdave-parity

Copy link
Copy Markdown
Contributor

Are we sure we want to have just one parachains service? Wondering if each parachain should have its own service instead. If each parachain gets its own service then it is free to evolve on its own, rather than being constrained by what is exposed by the parachain host service and when that is updated etc. Obviously there are downsides to this, eg each parachain would need its own copy of the parachain service code, and in the case of critical bugs every parachain service's code would need to be updated separately.

Comment thread designs/parachain-service-on-jam.md Outdated
|---|---|---|
| Collation (collator builds candidate + PoV) | **Collect** | Gather inputs off-chain |
| Backing (validator group checks PoV) | **Refine** | Stateless off-chain validation |
| Availability + Approval | **Guarantees + Assurances** | Attest correctness, ensure data available |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Availability is still called Availability in JAM, (availability) assurances are signed bitfields. Approval is called Auditing. Guarantees are produced by guarantors and I would think belong in the row above; refine is just one part of guaranteeing/backing.

Comment thread designs/parachain-service-on-jam.md Outdated

struct PvfEntry {
/// Length of the solicited preimage, used when forgetting it later.
code_len: u32,

@zdave-parity zdave-parity Apr 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

An entry is presumably created before the preimage is provided, what happens if two different code_lens are given for the same hash? Obviously at least one is wrong, but we have no way of knowing which before the preimage is provided?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

They need to lock-up tokens for this. So, they hopefully don't fuck this up. In the worst case, they could still just unregister the preimage.

Generally the same arguments to the solicit host function in JAM, because there you also don't know before the real length.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess my point is that this is shared so depending on how it works exactly you may be able to stop some other parachain upgrading by providing a bogus length with the same hash? A simple fix could be to move the length to the key.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Generally the same arguments to the solicit host function in JAM, because there you also don't know before the real length.

In JAM the length is included in the key. So you can have multiple solicits with the same hash and different lengths.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

A simple fix could be to move the length to the key.

Yeah sounds okay.

Comment thread designs/parachain-service-on-jam.md Outdated
pvf_registry: Map<ValidationCodeHash, PvfEntry>,

/// Stores when pending upgrades timeout.
pending_upgrades_timeouts: Map<Timeslot, Vec<(ValidationCodeHash, ParaId)>>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need to actively time out upgrades? Could this happen lazily when processing a work-item for the parachain?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The code could already be in the state, the parachain just not using it. IMO we should clean this up if possible. Someone could also request an upgrade and then never produce a block again. Yeah, all are not that strong arguments.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The parachain will presumably have paid a deposit to store the code, if they want their deposit back they can make another block? Not sure how much it matters but I think keeping always-run code to a minimum is probably a good policy.

/// work-bundle budget (~13.6 MB, shared with authtoken, authconfig,
/// extrinsics, and imports) minus whatever the package uses for
/// everything else.
pov: Vec<u8>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Presumably the PoV would be provided as extrinsic data?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah this entire struct should be put into the extrinsics. I will improve the name to make it more clear.

Initially, each work package will contain a single work item (one parachain candidate).
Support for multiple items per package may be added later.

The `ParaId` for each work item is **not** stored in the work item itself. Instead, it is

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is a bit weird to me. I think ideally authorization should be totally separate from the services that are being executed. In the case of on-demand coretime why would the authorization need a parachain ID?

If you require the auth config to contain a parachain ID for parachain services, then either you need special authorization for parachains vs other things (why?) or you need a parachain ID for non-parachain things which doesn't make sense.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Why do authorizers need to be generic across different services? For me this makes no sense :D

I have done it this way to bind the authorizer to a ParaId. Point being that otherwise someone could abuse an authorizer to execute something different. For example if collator A is part of the AH collator set. If we do not bind the authorizer to the ParaId, collator A could submit a work package for a different parachain and we could not detect it. (I don't want to match on authorizers in accumulate, which is any way too late and doesn't feel like the right place for this)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why do authorizers need to be generic across different services?

Authorizers are for cores, not for services. You can have an authorizer that specifically allows you to execute some service, but not sure it makes sense for a service to require use of a particular authorizer. Maybe that's possible but it seems kind of backwards to me.

Point being that otherwise someone could abuse an authorizer to execute something different

It's fine for an authorizer to only allow execution of particular work-packages. The authorization code has access to the work-package for this reason.

I don't want to match on authorizers in accumulate, which is any way too late and doesn't feel like the right place for this

I'm saying probably neither refine nor accumulate should care what authorizer was used?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'm saying probably neither refine nor accumulate should care what authorizer was used?

Then it should not have access to the authorizer trace. Which is an opaque type and the service can be the only one knowing how to interpret this.

Maybe that's possible but it seems kind of backwards to me.

Backwards to what? I mean this doesn't change anything in JAM and there you can still do whatever you want. I'm basically just using what is available.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

BTW @zdave-parity if you have better ideas on how to bind a authorizer to a ParaId. Or provide some explanation on why we don't need it. I'm also not fully sold on my design here, but I could not come up with anything better.

We could also not have the paraids as the output and instead just check that the paraids in the config are matching the paraids stored in the work items, but then we are doubling the space usage (not that wild for 32 byte per paraid, but still wasted space).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

authorizer to pass on the parachain ID when it may not care about it at all?

The authorizer will still care :D Because it would compare authorizer config para_id with the work item para_ids.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The authorizer will still care :D Because it would compare authorizer config para_id with the work item para_ids.

In the case of a static allocation of a core to a parachain yes. But why would the authorizer for some on-demand coretime care about the parachain ID or even that it's being used to execute a parachain vs some other service?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

vs some other service?

I don't get why you are talking about other services? We don't talk about this here and this also doesn't prevent any service to use whatever authorizer they like.

But why would the authorizer for some on-demand coretime care about the parachain ID

Yeah not really required there, but also doesn't really a problem? It will just work as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't get why you are talking about other services? We don't talk about this here and this also doesn't prevent any service to use whatever authorizer they like.

Because we presumably want to be able to use on-demand coretime with any service. IDK exactly how on-demand coretime will work but if parachains require an authorizer which outputs a parachain ID then the authorizer for the on-demand coretime will need to do this. I see no reason to require this and it just seems like an annoying constraint from the perspective of the on-demand coretime system?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Because we presumably want to be able to use on-demand coretime with any service. IDK exactly how on-demand coretime will work but if parachains require an authorizer which outputs a parachain ID

But it does not require this, as I already told you above. You will be able to for example register any kind of authorizer in the coretime chain. This will not enforce anything. Only when the authorizer is being used, it is enforced.

So, you can use whatever authorizer you want for your service.

Comment thread designs/parachain-service-on-jam.md Outdated

```rust
// Top-level service state (conceptual, not final)
struct ParachainServiceState {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Where are messages between parachains stored?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I skipped messages here completely as written, because we first need a final design. This is kind of orthogonal to this document. My assumption is that we will just shove through some hashes in the future and messages are probably exported to the DA layer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is the whole point of availability exports in JAM. They don't save much bandwidth usually, which is why we never did them in Polkadot, but they do save bandwidth here.

Comment thread designs/parachain-service-on-jam.md Outdated

Accumulate for the Parachain Service covers the parachain-specific parts of what the
relay chain's `enact_candidate` does today; availability, approvals, disputes, and
rewards are handled by JAM natively (see §2). The work splits into two categories:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Rewards are not handled by JAM natively. Also slashing is not (assuming there will be slashing).

@burdges burdges Apr 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should move them largely off the relay chain for current polkadot too, so this would more say they'll be handled the same.

#9687
polkadot-fellows/RFCs#119
burdges/Polkadot-RFCs#3

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Rewards are not handled by JAM natively. Also slashing is not (assuming there will be slashing).

Yeah that is wrong. A mix up.

authorizers currently in the core's authorizer pool.

### 7.1 Authorizer Design: AURA Example

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's probably fine to have an AURA-like thing to assign coretime to collators, but I do not think you can rely on this alone. You also need to check collator assignments in accumulate. Authorization concerns whether or not a work-package can be executed on a core or not. It does not determine whether or not a work-package can be accumulated for a service. The latter is presumably important for avoiding censorship etc.

It will (presumably) be possible for anyone to pay for a bit of coretime with their own authorizer, which they can use to execute your parachain and bypass any checks you put in your authorizer (because they're not using it). If you need constraints on who can author blocks and when then these need to be enforced by the parachain accumulation logic directly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You also need to check collator assignments in accumulate

No. For sure the parachain own logic executed in refine will check that the collator is actually allowed to build a block. Putting it "into JAM" can actually be done by anyone that has a valid authorizer token.
Accumulate for sure should not require any special authorizer selection scheme and I don't see any reason it should dictate this one.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

BTW, I just talk here about AURA to have some example authorizer scheme to illustrate on how it could work/that it could work at all.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For sure the parachain own logic executed in refine will check that the collator is actually allowed to build a block

Ok that's fine and has the same effect.

Putting it "into JAM" can actually be done by anyone that has a valid authorizer token. Accumulate for sure should not require any special authorizer selection scheme and I don't see any reason it should dictate this one.

I don't think either refine or accumulate should care what authorizer was used. You are saying that authorized_paras will come from the authorizer config? You can't trust the authorizer config so I don't know what the purpose of this is. And it means you can't use any old authorizer, because eg its config needs to have some particular format?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You can't trust the authorizer config so I don't know what the purpose of this is.

Why can it not trust it? The authorizer hash is hash(authorizer_code ++ authorizer_config) which is set when setting the authorizer queue. So, I don't see how it can not trust it

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

but if you want to restrict your service to only work with a particular authorizer you have to implement that yourself?

It doesn't do this? It doesn't restrict to one particular authorizer, it only requires that the authorizer returns the para_id as first value of the auth_trace. I don't get why you think that this is enforcing a particular authorizer.

In the parachain case you would want to check that the collator was in the collator set and allowed to build a block in that slot or something like this. You could have the authorizer do these checks and then check in the service logic (refine/accumulate) that a specific authorizer was used. If you need the logic in the authorizer as well then this would save executing the logic twice. Is this what you're saying?

I just want that work packages get rejected as early as possible, which is the job of the authorizer ;) Again, I'm not enforcing that any particular authorizer is used, just that the authorizer has one particular return value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I just want that work packages get rejected as early as possible, which is the job of the authorizer ;)

I disagree. The job of the authorizer is only to check that the core can be used. This may involve checking the work-package, eg if the core is reserved for a specific parachain.

By design anyone can create a work-package for any service and get this accumulated. The service code can make this accumulation not do anything, but it will still happen. The purpose of authorization is only to make sure that this work-package execution has been paid for in some way.

I don't get why you think that this is enforcing a particular authorizer.

I said that the only way a service can trust the authorizer config is if it enforces use of a particular authorizer. That wasn't the best way of wording it, I meant the only way a service can trust that the authorizer has done something in particular (eg check the collator is in the collator set) is if it enforces uses of a particular authorizer. How can a service trust what the authorizer did otherwise? You said "The authorizer hash is hash(authorizer_code ++ authorizer_config) which is set when setting the authorizer queue", but the authorizer queue is for a core not for a service. There's nothing that requires a particular authorizer queue to be used for a particular service, unless you specifically check for this in your service code and make work-packages which don't use this authorizer queue no-ops in accumulate.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

By design anyone can create a work-package for any service and get this accumulated. The service code can make this accumulation not do anything, but it will still happen. The purpose of authorization is only to make sure that this work-package execution has been paid for in some way.

I don't get why you are mixing here "generic JAM" with the parachain service. For sure, if your service allows anyone to create work packages and they pay for this on their own or you have some authorizers pre-registered, you can do whatever you want.

However, in the parachain case with AURA we want to prevent for example that someone is wasting a slot they are not assigned to. A slot will be wasted in the moment the authorizer is letting them through. I don't get why you are against preventing this as early as possible.

Again, this is the design of the parachain service and recommendations for the authorizer. This doesn't change anything with JAM in general.

That wasn't the best way of wording it, I meant the only way a service can trust that the authorizer has done something in particular (eg check the collator is in the collator set) is if it enforces uses of a particular authorizer.

But even the parachain service itself doesn't care. It just requires that the service is outputting the para_id and then it is up to the one paying the core time to put there the correct authorizer. It is also totally fine that I may buy extra core time for asset hub or whatever. Nothing will stop me from doing this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For sure, if your service allows anyone to create work packages and they pay for this on their own

My point is this is true for all services, by the design of JAM. You can't get away from it. You can only make work-packages you don't want no-ops in the accumulate stage.

However, in the parachain case with AURA we want to prevent for example that someone is wasting a slot they are not assigned to. A slot will be wasted in the moment the authorizer is letting them through

What do you mean by wasting a slot? If you mean that in the case where you have a core assigned specifically to a parachain, that you don't want that core to ever be used for anything else, then sure. That is what the authorizer is for.

But even the parachain service itself doesn't care

Then I don't get why you want the parachain ID to be output from the authorizer? Is it just so it doesn't need to be included in the work-package?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Then I don't get why you want the parachain ID to be output from the authorizer? Is it just so it doesn't need to be included in the work-package?

We are turning in circles :D As already explained, I could add it to the authorizer + the work item. Basically duplicating it. Not that problematic as it is only 32 bytes, but we also already have the auth trace. Again, we need the para_id in the auth_config to bound the authorizer to the parachain. Preventing that anyone is abusing the authorizer (I already explained all of this above).

I don't really get why you are focusing that much on this. It doesn't change anything in how JAM is working, it just tries to make parachains work with JAM. If you have a better idea on how to "bound an authorizer" to a parachain, I'm happy to hear.

@bkchr

bkchr commented Apr 27, 2026

Copy link
Copy Markdown
Member Author

Are we sure we want to have just one parachains service? Wondering if each parachain should have its own service instead.

You would have different trust assumptions between these services, resulting in no trust at all between the different instances. Also then you don't need any "parachain service" at all and could just write the parachain as a service. I don't see this as something we want to do.

@burdges

burdges commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

You would have different trust assumptions between these services, resulting in no trust at all between the different instances.

Yes & no. JAM ELVES must do global roll backs like current ELVES, otherwise nothing can trust anything. Accumulates being contracts means you could only message between services whose accumulates trust & handle one another.

@bkchr

bkchr commented Apr 28, 2026

Copy link
Copy Markdown
Member Author

Yes & no. JAM ELVES must do global roll backs like current ELVES, otherwise nothing can trust anything.

Yeah for sure.

Accumulates being contracts means you could only message between services whose accumulates trust & handle one another.

I meant it this way. They are basically other code and could do whatever they want. Messaging between services is also "more complicated".

Comment thread designs/parachain-service-on-jam.md Outdated
Comment on lines +85 to +88
│ │ Services Layer │ │
│ │ ┌──────────────────────┐ ┌──────────────────┐ │ │
│ │ │ Parachain Service │ │ Other Services │ │ │
│ │ │ ┌────────────────┐ │ │ │ │ │

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Slightly scuffed ASCII art.

Comment on lines +345 to +346
3. Fetches the PVF bytecode via `historical_lookup` (using `validation_code_hash`).
4. Instantiates a child PVM with the PVF.

@koute koute Apr 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Side note: this is out-of-scope of this prototype, but this could be made (probably significantly) more efficient by splitting module compilation and instantiation hostcalls, and merging lookup with compilation into a single hostcall.

cc @zdave-parity

So currently what you'd have to do:

  1. fetch the PVF bytecode from the database with historical_lookup and write it into the service's address space (at least one copy, possibly two if a fetch from the database need a separate copy)
  2. read it back when compiling it into a new PVM module with machine (effectively another copy) and write the native machine code into memory (effectively yet another copy, just with on-the-fly translation into native machine code)
  3. instantiate the VM with the new module

So that's effectively three copies of the same data back-and-forth at minimum, plus all of the extra processing to recompile it (calculate gas costs from scratch, etc.).

Now, what we could have is something like this:

  1. call a module_handle = compile_from_historical_lookup(id, hash, slot)
  2. the module is fetched, compiled (same as before), and saved into a PolkaVM-managed persistent cache a handle to this module is returned
  3. now it can be instantiated with instantiate(module_handle)

But here's the difference: now any subsequent call (per PolkaVM version and unique PVF, since we coul perma-cache this on disk) would look like this:

  1. call a module = compile_from_historical_lookup(id, hash, slot)
  2. the module is already in cache; a handle to already compiled module is returned
  3. now it can be instantiated with instantiate(module_handle)

This gets rid of the overhead of at least three copies of the data, the recompilation overhead, the gas cost calculation overhead, and the page gas metering costs (copying the data will not only cost time but also extra gas with the new page gas metering), and with good enough engineering could maybe even allow us to assume that compile_from_historical_lookup is almost zero-cost (i.e. assume the cache is always hot, since someone would have to constantly upload new code blobs to exploit this, and so we could engineer such attack to be impractical).


Anyway, just my two cents. Once we have this service up and running we could demonstrate the performance difference. (:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm I really like this idea, certainly worth making an issue in the GP repo to discuss if you haven't done so yet.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah makes sense :D

Comment on lines +379 to +394
These forward the full JAM fetch functionality to the PVF:

| Host function | Returns | Purpose |
|---|---|---|
| `lookup(hash: Hash)` | `Option<Vec<u8>>` | Fetch a preimage (e.g. PVF code) |
| `foreign_lookup(service: ServiceId, hash: Hash)` | `Option<Vec<u8>>` | Fetch a preimage from another service's store |
| `gas()` | `u64` | Query the remaining gas budget |
| `work_package()` | `WorkPackage` | Access the full encoded work package |
| `work_package_context()` | `RefineContext` | Access the refinement context (anchor, lookup-anchor, prerequisites) |
| `auth_config()` | `Vec<u8>` | Access the authorizer config blob |
| `auth_token()` | `Vec<u8>` | Access the authorization token blob |
| `work_items_summary()` | `Vec<WorkItemSummary>` | Summary of all work items (service, code hash, gas limits, counts, payload length) |
| `work_item_summary(index: u32)` | `Option<WorkItemSummary>` | Summary of a specific work item by index |
| `work_item_payload(index: u32)` | `Option<Vec<u8>>` | Payload of a specific work item by index |
| `import_segments()` | `Vec<SegmentMeta>` | Import segments metadata |
| `import_segment(index: u32)` | `Option<Vec<u8>>` | A specific import segment by index |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note: similar to my previous comment, with the current JAM design some of these will also require (unnecessary) extra copies (first copy into the service's memory, then copy into the inner VM memory).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah we would basically need some kind of "forward host call" to child?

@zdave-parity

Copy link
Copy Markdown
Contributor

Accumulates being contracts means you could only message between services whose accumulates trust & handle one another.

I meant it this way. They are basically other code and could do whatever they want. Messaging between services is also "more complicated".

Certainly messaging between services is more complicated, but I don't think it's that complicated? You can read other service state directly and synchronously, and transfers execute unconditionally in the same block.

Parachains are also just code and can do whatever they want? AIUI SPREE is intended to address this to some extent? Does that actually exist now? If not, can you give an example of some case which wouldn't work with parachains as separate services but would work with them all in one service?

If parachains are standalone services rather than special entities on a parachain service, then they are on equal footing with non-parachain services. Neither would be second-class citizens. Messaging could be done via a defined protocol that any service could implement, rather than only being available to parachains? Maybe this is wishful thinking :D

@zdave-parity

Copy link
Copy Markdown
Contributor

Also then you don't need any "parachain service" at all and could just write the parachain as a service

FWIW that is exactly what I'm thinking, that the "parachain service" could be more a way of building a parachain as a JAM service. So like a framework for building a JAM service which runs a chain.

@bkchr

bkchr commented Apr 28, 2026

Copy link
Copy Markdown
Member Author

Parachains are also just code and can do whatever they want? AIUI SPREE is intended to address this to some extent? Does that actually exist now? If not, can you give an example of some case which wouldn't work with parachains as separate services but would work with them all in one service?

SPREE doesn't exist yet, but for it to exist we need one parachain service instance. At least in my brain :) SPREE would be some kind of small piece of code that runs next to the parachain code. The collator just calls it while building the block. In refine we also need to execute it. Now if we assume that every parachain is its own service, SPREE would also be its own service (it would need to be its own service, so that you can have one code hash that you are trusting). We would get a work package with two work items of different services. Let's assume the parachain execution succeeds, but SPREE would fail. So, we could not accept the parachain result. But as the parachain is its own service there is no way to enforce this.

If we have the same parachain service across all parachains, we have "total control" over refine. So, we can execute SPREE alongside the package and can be sure that we either abort the entire item or let it succeed.

bkchr and others added 30 commits July 6, 2026 20:32
The invariant does not consider a service holding a plain Solicit on the old code
blob during a code upgrade.

This can otherwise fail when run with:
quint run main.qnt --invariant=code_activation_releases_old --max-steps=45 --max-samples=60000 --seed=0x754cfa

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
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.

8 participants