Skip to content

Add early RANDAO pre-consensus emission SIP - #101

Open
shane-moore wants to merge 22 commits into
ssvlabs:mainfrom
shane-moore:feat/early-randao-preconsensus
Open

Add early RANDAO pre-consensus emission SIP#101
shane-moore wants to merge 22 commits into
ssvlabs:mainfrom
shane-moore:feat/early-randao-preconsensus

Conversation

@shane-moore

@shane-moore shane-moore commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Operators may emit the existing Proposer-duty RANDAO partial signature up to 2 slots before the proposal slot, so clusters can reconstruct randao_reveal before the slot starts instead of inside the post-Gloas ~3s block-production budget.

No new duty, role, message kind, domain, topic, or container is introduced. Protocol changes are limited to target-slot activation, message-validation timing, message-kind-scoped ordering, and complete-schedule duty handling. Receivers MAY additionally retain an otherwise-valid Unknown-duty candidate locally, but retention is optional and does not change the initial IGNORE gossip verdict.

The rules activate from the stamped proposal slot S: they apply when epoch(S) >= GLOAS_FORK_EPOCH, including when the permitted window for the first Gloas slots begins before the fork in wall-clock time. The SSV fork schedule is not an input.

Rationale and design discussion: #100
Addresses: ssvlabs/ssv-spec#373

iurii-ssv
iurii-ssv previously approved these changes Jul 24, 2026

@iurii-ssv iurii-ssv left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM overall, the suggestions below are mostly minor clarifications.

Comment thread sips/early_randao_preconsensus.md Outdated
Comment thread sips/early_randao_preconsensus.md Outdated
Comment thread sips/early_randao_preconsensus.md Outdated
Comment thread sips/early_randao_preconsensus.md Outdated
Comment thread sips/early_randao_preconsensus.md Outdated
iurii-ssv
iurii-ssv previously approved these changes Jul 25, 2026

@iurii-ssv iurii-ssv left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Just two non-blocking nits.

Comment thread sips/early_randao_preconsensus.md Outdated
Comment thread sips/early_randao_preconsensus.md Outdated
iurii-ssv
iurii-ssv previously approved these changes Jul 27, 2026

@iurii-ssv iurii-ssv left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM!

Found while implementing the receiver rules in Anchor: existing client
pipelines run contextual checks before signature verification, and a strict
signatures-first order would drop the duplicate-count shield in front of RSA.
Structural and canonical-form checks now run first and define the Early
RANDAO candidate; for candidates the signature-vs-contextual order is
implementation-defined, short-circuiting only on non-retaining verdicts, and
any outcome that retains, accepts, forwards, or mutates state requires a
valid operator signature (invalid signature plus Unknown duty is a pinned
REJECT). Checks are staged: RSA gates retention and forwarding, BLS gates
consumption. Vectors added for the pinned and flexible cases.
Pins receiver-divergence points found while mapping the rules onto Anchor's
duty tracker and quarantine design. Known now requires the epoch's complete
fetched proposer schedule (a locally filtered subset is not Known, so an
honest share cannot be IGNOREd as Known-unassigned by a partial view).
Retention is admission-conditioned rather than unconditional; the
byte-identical occupied-key case keeps the original entry, full-capacity
eviction has the incoming candidate compete under an exact total order (the
unreachable fourth tiebreak is dropped), and promotion is restated via
promotion candidates so an entry cannot both promote and fail revalidation.
Vectors added for each case.
The summary said the SIP activates at the Gloas-aligned SSV fork, but the
ePBS SIP (ssvlabs#94, checked at d5197bc) defines no SSV-network fork; its
validation rules gate on epoch(msg.slot) >= GLOAS_FORK_EPOCH. This SIP now
matches that model explicitly: activation is Ethereum-gated by the
epoch(S) >= GLOAS_FORK_EPOCH conjunct, the SSV fork-equality conjunct is
window-boundary protection only (it keeps the 2-slot emission window from
spanning any scheduled SSV fork), and a new S >= EARLY_RANDAO_LEAD conjunct
resolves the genesis underflow normatively, surfaced by the slot-zero unit
test in the Anchor implementation. Vectors spell out the boundary outcomes.
@shane-moore

shane-moore commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed some commits tightening the validation contract, from some findings while implementing the receiver rules in anchor:

  1. 0affc08 Pin validation ordering and stage authentication checks. The previous text required signatures strictly before the contextual rules. Anchor's pipeline (and, from our reading, go-ssv's) runs the cheap contextual checks first, and reordering would cost the duplicate-count shield in front of RSA verification. Now: structural and canonical-form checks MUST run first and define an "Early RANDAO candidate"; for candidates the signature-vs-contextual order is implementation-defined, short-circuiting only on non-retaining verdicts; any outcome that retains, accepts, forwards, or mutates state MUST first pass operator-signature verification (invalid signature plus Unknown duty is a pinned REJECT, never retained). Staging is explicit: RSA gates retention and forwarding, BLS gates consumption.

  2. 60d143e Make duty-view and retention behavior deterministic. Pins points where two conformant receivers could diverge: Known requires the epoch's complete fetched proposer schedule (Anchor's duty tracker holds a locally filtered subset, which must not count as Known); retention is admission-conditioned; a byte-identical occupied-key duplicate keeps the original entry; full-capacity eviction has the incoming candidate compete under an exact total order; promotion is restated via promotion candidates (revalidate, promote only on success).

Test vectors added for each change.

The predicate required the SSV fork at S - EARLY_RANDAO_LEAD to equal the SSV
fork at S, making the first two slots of every SSV fork activation epoch
ineligible. It protects against nothing. Every fork-sensitive artifact of a
partial signature (gossip topic, domain, role gating) is derived from the
message's stamped slot rather than from emission time, so a message emitted
inside a window that spans a fork activation is published, validated, and
consumed entirely under the fork active at S. The SIP already relied on this
for the Ethereum boundary, where the first slot of GLOAS_FORK_EPOCH is
eligible and its window starts pre-fork; forbidding the same thing for SSV
forks was inconsistent.

Eligibility is now S >= EARLY_RANDAO_LEAD and epoch(S) >= GLOAS_FORK_EPOCH,
a pure function of the slot and the Ethereum fork schedule.

@MatheusFranco99 MatheusFranco99 left a comment

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.

Good work @shane-moore !!
First review batch :)

Comment thread sips/early_randao_preconsensus.md Outdated

**Summary**

Operators may emit their existing block-proposal RANDAO partial signature up to 2 slots before the proposal slot, so the cluster reconstructs `randao_reveal` before the slot starts instead of inside the post-Gloas ~3s block-production budget. No new duty, role, message kind, domain, topic, or container: the existing Proposer-duty `RandaoPartialSig` message is emitted earlier, stamped with the proposal slot as today. Changes are confined to message-validation timing, ordering, and duty-handling rules plus a bounded receive-side buffer. Activates at `GLOAS_FORK_EPOCH`, the same Ethereum gate the ePBS SIP uses for its own validation rules; the dependency on the ePBS SIP is activation coupling only.

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.

up to 2 slots before the proposal slot

Hmm, just by curiosity, why 2 slots, exactly?
Also, is there an edge case, like first epoch slot, in which one can't do 2 slots or even 1 in advance?
And if so how should them behave?
Possibly, clear edge-cases are the 1st and 2nd slots of the fork, right?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch. The previous wording compressed three separate points.

EARLY_RANDAO_LEAD = 2 is the maximum earliness receivers must support, not an assertion that gossip needs two slots. The window gives a producer an opportunity during S - 2 and another during S - 1 if the earlier local publication attempt fails. Conforming clients may emit at different points in that window. Receivers accumulate shares asynchronously and reconstruct at 2f+1, so different schedules remain compatible, but the latest share needed for quorum determines when the reveal becomes available. We did not select a longer receiver window because it would increase reveal and stale-duty exposure without an identified need.

S >= EARLY_RANDAO_LEAD is only a genesis underflow guard for calculating S - 2. It excludes absolute slots 0 and 1, not the first two slots of every epoch or the Gloas activation epoch.

Let F be the first slot of a non-genesis GLOAS_FORK_EPOCH. There is intentionally no activation warm-up:

  • S = F is eligible, and its producer window opens at slot_start(F - 2).
  • S = F + 1 is eligible, and its window opens at slot_start(F - 1).
  • Target slots before F remain ineligible.

The separate 500 ms producer delay remains guidance inside those windows. Because receiver timing also includes the clock tolerance, receiver validation and quarantine must be active no later than receiver-local slot_start(F - 2) - EARLY_RANDAO_CLOCK_TOLERANCE.

Commit c859af6 now makes this target-slot activation model explicit in the summary, rationale, qualifying-message rules, producer behavior, and cross-client test expectations. We also aligned Anchor's receiver, quarantine, and producer issues so implementations do not accidentally introduce a current-epoch gate.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Correction to part of my earlier reply: the activation model described there is unchanged, but the two-slot value is no longer used to prescribe an S - 2 attempt followed by an S - 1 retry.

It remains the maximum receiver window. The recommended producer attempt is now exactly S - 1; the 500 ms offset and coordinated second pre-slot attempt were removed.

Addressed in b29db38.

Comment thread sips/early_randao_preconsensus.md Outdated

- No new duty: RANDAO always has an in-slot consumer (the Proposer duty), and a separate duty would still need the in-slot pre-consensus as its fallback. This differs from `ProposerPreferences` (defined in the ePBS SIP, [#94](https://github.com/ssvlabs/SIPs/pull/94)), which must reconstruct before the slot and is mutable.
- The 2-slot window is chosen for operational margin, not gossip latency. The window is the fork-pinned receiver rule; the emission moment inside it is producer policy, tunable post-fork without a protocol change.
- The per-(signer, slot) duplicate limit stays 1. Each logical partial has exactly one valid byte encoding, gossip message identifiers are content-derived, and gossip layers deduplicate before validation, so a second copy either never reaches validation or is the receiver's first copy.

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.

gossip layers deduplicate before validation, so a second copy either never reaches validation or is the receiver's first copy.

Just to add: byzantine duplicates with a different signature for the same (duty, signer) won't be dropped by the naive gossip hash deduplication, but it will be by our app message validation logic that accepts only 1 partial beacon signature per signer per type (pre- or post- consensus) and per duty.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good point. A byte-identical copy shares the original content-derived gossip ID, but changing the inner partial signature and re-signing the envelope normally produces a different gossip ID. That variant reaches application validation, where the existing one-message limit for the (MessageID, signer, slot) key and pre-consensus or post-consensus class stops it.

I narrowed the rationale to distinguish those cases. The duplicate limit itself does not need to change.

Addressed in c13987a.

Comment thread sips/early_randao_preconsensus.md Outdated
- No new duty: RANDAO always has an in-slot consumer (the Proposer duty), and a separate duty would still need the in-slot pre-consensus as its fallback. This differs from `ProposerPreferences` (defined in the ePBS SIP, [#94](https://github.com/ssvlabs/SIPs/pull/94)), which must reconstruct before the slot and is mutable.
- The 2-slot window is chosen for operational margin, not gossip latency. The window is the fork-pinned receiver rule; the emission moment inside it is producer policy, tunable post-fork without a protocol change.
- The per-(signer, slot) duplicate limit stays 1. Each logical partial has exactly one valid byte encoding, gossip message identifiers are content-derived, and gossip layers deduplicate before validation, so a second copy either never reaches validation or is the receiver's first copy.
- Because gossip layers never unmark a seen message, an IGNORE of an early partial permanently discards that operator's share for the window. The Unknown-duty retention rule and the dedicated clock tolerance narrow that failure class for honest shares without eliminating it; the residual cases are documented in *Duty assignment* (stale-`Known` reorg), the retention admission rule (refresh-pending losses), and Security Considerations.

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.

never unmark a seen message

I think they do so, but I also think we are just fine in here since it should be a window of 1 or 2 epochs. We just need to confirm.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Confirmed — they do expire, but with a wide margin: go-ssv's msgIDCacheTTL is 6.4 min (~32 slots) against this SIP's 24 s window. So "never unmark" is stronger than what implementations do; the conclusion holds, and stating the margin would be more convincing than the absolute.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed, thanks. I replaced both absolute claims, "never unmark" and "a few seconds," with the narrower statement that gossip duplicate-cache lifetimes can exceed the entire useful Early RANDAO window. The recovery wording now says the in-slot copy may be suppressed, since restart and local cache state matter.

Addressed in a939402.

Comment thread sips/early_randao_preconsensus.md Outdated
Comment on lines +28 to +32
| Constant | Value |
| -------- | ----- |
| `EARLY_RANDAO_LEAD` | 2 slots |
| `EARLY_RANDAO_CLOCK_TOLERANCE` | 1000 ms |
| `MAX_QUARANTINED_MESSAGES` | 4096 |

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 think it could be useful to add a third column for a short description for each so the reading flow provides more intuition, what do you think?
For example EARLY_RANDAO_CLOCK_TOLERANCE appears only after some time in the document.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed, addressed in 8a098c9.

- Proposer-role `MessageID`; type `RandaoPartialSig`; exactly one `PartialSignatureMessage` entry;
- `slot` = the proposal slot `S`; signed object `SSZUint64(epoch(S))` under `DOMAIN_RANDAO`, domain epoch `epoch(S)`;
- canonical SSZ; deterministic BLS share signature; deterministic RSA (PKCS#1 v1.5) operator signature; exactly one outer signer, equal to the embedded operator ID;
- eligibility predicate: `S >= EARLY_RANDAO_LEAD` and `epoch(S) >= GLOAS_FORK_EPOCH` (the Ethereum Gloas fork epoch, as used by the ePBS SIP, [#94](https://github.com/ssvlabs/SIPs/pull/94)).

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.

S >= EARLY_RANDAO_LEAD

Hmm, I got confused here... the slot number should be above 2? Or it was more in the sense:

  • First enabled slot ever: $S - \text{[GLOAS FORK EPOCH.first slot]} \geq 2$?
  • Or distance to duty slot: $0 \leq S (duty.slot) - now.slot \leq 2$ ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Answered this together with the two-slot and fork-boundary questions here. In short, S >= EARLY_RANDAO_LEAD is only the absolute-slot genesis guard; the allowed distance from the current time is enforced separately by the producer and receiver timing rules.

Comment thread sips/early_randao_preconsensus.md Outdated
- canonical SSZ; deterministic BLS share signature; deterministic RSA (PKCS#1 v1.5) operator signature; exactly one outer signer, equal to the embedded operator ID;
- eligibility predicate: `S >= EARLY_RANDAO_LEAD` and `epoch(S) >= GLOAS_FORK_EPOCH` (the Ethereum Gloas fork epoch, as used by the ePBS SIP, [#94](https://github.com/ssvlabs/SIPs/pull/94)).

The predicate is a pure function of `S`, evaluated identically by producers and receivers, never re-evaluated against wall-clock time. Activation is Ethereum-gated by `epoch(S) >= GLOAS_FORK_EPOCH`, matching the ePBS SIP's own validation gating; the `S >= EARLY_RANDAO_LEAD` conjunct keeps the producer's emission window well defined and excludes the first `EARLY_RANDAO_LEAD` slots at genesis. The SSV fork schedule plays no part, and an emission window that spans an SSV fork activation is fine: every fork-sensitive artifact of a partial signature (gossip topic, domain, role gating) is derived from the message's stamped slot rather than from the moment it was emitted, so such a message is published, validated, and consumed entirely under the fork active at `S`. The same holds for a window spanning the Ethereum fork boundary, which this predicate likewise permits. Containers violating the canonical form fall to existing structural rules (REJECT); BLS-share validity is not evaluated during message validation (see Message validation). Messages failing the predicate, and all non-randao messages, keep today's validation unchanged.

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 first EARLY_RANDAO_LEAD slots at genesis

Ah, got it. But does it really make sense to do so? Shouldn't it be the first two of the activation epoch?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Covered this in the consolidated response here. We are keeping the genesis guard and not excluding the first two activation-epoch slots: F and F + 1 are eligible target slots, including their pre-fork emission windows.


For a locally known proposer duty at eligible slot `S`, an operator:

- MAY broadcast its qualifying randao partial at any wall-clock time in `[slot_start(S - EARLY_RANDAO_LEAD), slot_start(S))`;

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.

But what about the first slot of the fork?
Is there a warm-up, or, for this case, it would end up just sending on its slot and that's it?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Covered this in the consolidated response here. There is no warm-up: target slot F may emit from F - 2, so receiver support must be active before wall-clock Gloas.

Comment thread sips/early_randao_preconsensus.md Outdated
- MAY broadcast its qualifying randao partial at any wall-clock time in `[slot_start(S - EARLY_RANDAO_LEAD), slot_start(S))`;
- MUST NOT broadcast it before `slot_start(S - EARLY_RANDAO_LEAD)` by its own clock;
- SHOULD delay emission at least 500 ms past `slot_start(S - EARLY_RANDAO_LEAD)` (assumed maximum pairwise honest clock disparity: 1 s);
- SHOULD still execute the existing in-slot emission at `S` unconditionally; a running origin's identical re-publish is absorbed by its own gossip layer (expected, not an error), and a restarted origin's re-publish aids recovery;

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.

Wait, so it always also try to emit again once the slot starts?
I think there could be a problem here since re-creating a message and BLS-signing it takes time. Only if we have it cached...
But maybe a flag about the message being published well or not is enough, no? What do you think?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, the ordinary in-slot path remains a SHOULD regardless of the recorded early outcome.

A local published flag is not enough to suppress it. Local enqueue, gossip publication, and peer receipt are distinct states, and successful local publication does not prove that the intended peers received the share.

The revised text also makes clear that a completed reconstruction MAY be returned immediately to the in-slot consumer without waiting for repeated signing or publication. Implementations MAY reuse cached valid signing artifacts or schedule emission independently.

Addressed in b29db38.

Comment thread sips/early_randao_preconsensus.md Outdated

- MAY broadcast its qualifying randao partial at any wall-clock time in `[slot_start(S - EARLY_RANDAO_LEAD), slot_start(S))`;
- MUST NOT broadcast it before `slot_start(S - EARLY_RANDAO_LEAD)` by its own clock;
- SHOULD delay emission at least 500 ms past `slot_start(S - EARLY_RANDAO_LEAD)` (assumed maximum pairwise honest clock disparity: 1 s);

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, sorry I got lost in here. What is the purpose of doing that? Trying to let the receiver accept the msg due to the possibility of it having an earlier clock time and still being in the duty slot - 3? We can align msg validation for that rather than the logic (as we already do if I'm not mistaken)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

You are right. The 500 ms value was additional producer-side slack, while EARLY_RANDAO_CLOCK_TOLERANCE = 1000 ms already handles the assumed clock disparity at the legal receiver boundary. Once the recommended emission moved to S - 1, the 500 ms offset no longer served a protocol purpose.

I removed it. The producer now SHOULD make its first early attempt at slot_start(S - 1), while the two-slot receiver window and 1000 ms receiver tolerance remain unchanged. I am open to another recommended lead if you think there is a better choice.

Addressed in b29db38.

@iurii-ssv iurii-ssv left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Added some potential clarifications to consider

Comment thread sips/early_randao_preconsensus.md
Comment thread sips/early_randao_preconsensus.md Outdated
Comment thread sips/early_randao_preconsensus.md Outdated
Comment thread sips/early_randao_preconsensus.md Outdated
Comment thread sips/early_randao_preconsensus.md Outdated
- No new duty: RANDAO always has an in-slot consumer (the Proposer duty), and a separate duty would still need the in-slot pre-consensus as its fallback. This differs from `ProposerPreferences` (defined in the ePBS SIP, [#94](https://github.com/ssvlabs/SIPs/pull/94)), which must reconstruct before the slot and is mutable.
- The 2-slot window is chosen for operational margin, not gossip latency. The window is the fork-pinned receiver rule; the emission moment inside it is producer policy, tunable post-fork without a protocol change.
- The per-(signer, slot) duplicate limit stays 1. Each logical partial has exactly one valid byte encoding, gossip message identifiers are content-derived, and gossip layers deduplicate before validation, so a second copy either never reaches validation or is the receiver's first copy.
- Because gossip layers never unmark a seen message, an IGNORE of an early partial permanently discards that operator's share for the window. The Unknown-duty retention rule and the dedicated clock tolerance narrow that failure class for honest shares without eliminating it; the residual cases are documented in *Duty assignment* (stale-`Known` reorg), the retention admission rule (refresh-pending losses), and Security Considerations.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Confirmed — they do expire, but with a wide margin: go-ssv's msgIDCacheTTL is 6.4 min (~32 slots) against this SIP's 24 s window. So "never unmark" is stronger than what implementations do; the conclusion holds, and stating the margin would be more convincing than the absolute.

iurii-ssv
iurii-ssv previously approved these changes Jul 31, 2026

@iurii-ssv iurii-ssv left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Just a minor followup.

Comment thread sips/early_randao_preconsensus.md Outdated
Comment thread sips/early_randao_preconsensus.md Outdated
Comment thread sips/early_randao_preconsensus.md Outdated
Comment thread sips/early_randao_preconsensus.md Outdated
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.

3 participants