Skip to content

serviceability-instruction: topology + feed builders (RFC-26 R7) - #4056

Merged
juan-malbeclabs merged 8 commits into
mainfrom
feat/rfc26-r7-topology-feed-builders
Jul 23, 2026
Merged

serviceability-instruction: topology + feed builders (RFC-26 R7)#4056
juan-malbeclabs merged 8 commits into
mainfrom
feat/rfc26-r7-topology-feed-builders

Conversation

@juan-malbeclabs

@juan-malbeclabs juan-malbeclabs commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

RFC-26 R7 (stacked on the previous phase's branch). Topology create/delete plus batched clear_topology / assign_topology_node_segments (single-chunk + *_batched; CLEAR_BATCH_SIZE=16 / BACKFILL_BATCH_SIZE=4 moved into the crate). Feed create/update/delete.

All builders route through authorize() -> build_with_permission unless noted; each carries a verbatim account-layout doc-comment copied from its processor.

Testing Verification

  • Unit tests assert the exact AccountMeta list (incl. trailing [payer, system]) and the borsh tag byte for every builder, covering the conditional/variable-account paths.

Closes #4022. Part of RFC-26 (rfcs/rfc26-rust-instruction-builder-library.md).


PR stack (RFC-26 builder library)

Stacked PRs, merge in order (each is based on the previous one's branch):

  1. serviceability-instruction: scaffold pure instruction-builder crate (RFC-26 R0) #4049 — R0 scaffold + exemplars
  2. serviceability-instruction: device domain builders (RFC-26 R1) #4050 — R1 device
  3. serviceability-instruction: link domain builders (RFC-26 R2) #4051 — R2 link
  4. serviceability-instruction: user domain builders (RFC-26 R3) #4052 — R3 user
  5. serviceability-instruction: location/exchange/contributor builders (RFC-26 R4) #4053 — R4 location/exchange/contributor
  6. serviceability-instruction: multicastgroup + allowlist builders (RFC-26 R5) #4054 — R5 multicastgroup + allowlists
  7. serviceability-instruction: tenant + permission builders (RFC-26 R6) #4055 — R6 tenant + permission
  8. serviceability-instruction: topology + feed builders (RFC-26 R7) #4056 — R7 topology + feed ← this PR
  9. serviceability-instruction: accesspass + resource builders (RFC-26 R8) #4057 — R8 accesspass + resource
  10. serviceability-instruction: globalstate/globalconfig/allowlist/index/migrate builders (RFC-26 R9) #4058 — R9 globalstate/config/allowlist/index/migrate

R10 (commands/* migration + program-test) and RF (fixtures) follow as separate PRs.

juan-malbeclabs added a commit that referenced this pull request Jul 14, 2026
…RFC-26 R0) (#4049)

## Summary

RFC-26 **R0**: scaffold the pure, RPC-free instruction-builder crate
`doublezero-serviceability-instruction` — SPL-style builders that return
a single unsigned `Instruction` per serviceability instruction, no
signing/sending.

- `common::build` (no-permission trailing `[payer, system]`) +
`common::build_with_permission` (the deferred, activate-in-one-place
Permission append) + `compute_budget_prelude` (1.4M CU / 256 KiB).
- Four exemplar builders establishing the pattern every later PR copies:
`create_device`, `delete_device` (legacy/atomic), `create_link`,
`create_subscribe_user`.
- Deps limited to `doublezero-serviceability` + `solana-program` +
`solana-system-interface` + `solana-compute-budget-interface` — no RPC
tree.

The `suspend_device` exemplar from the RFC was replaced with
`delete_device` (`SuspendDevice` is a deprecated variant); the RFC and
#4015 were updated, and the "length-detected family" classification was
corrected (only `CreateUser` is length-detected).

## Why

Instruction assembly is currently coupled to signing+sending inside
`commands/*::execute()`; there is no reusable `build_xxx(args) ->
Instruction`. See
[rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md).

## Testing Verification

- Unit tests assert the exact `AccountMeta` list (incl. trailing
`[payer, system]`) and the borsh tag byte for each exemplar, including
the `delete_device` legacy vs atomic layouts and the
`create_subscribe_user` optional-feed placement.

Closes #4015. Part of RFC-26.

---

### PR stack (RFC-26 builder library)

Stacked PRs, merge in order (each is based on the previous one's
branch):

1. #4049 — R0 scaffold + exemplars  ← **this PR**
2. #4050 — R1 device
3. #4051 — R2 link
4. #4052 — R3 user
5. #4053 — R4 location/exchange/contributor
6. #4054 — R5 multicastgroup + allowlists
7. #4055 — R6 tenant + permission
8. #4056 — R7 topology + feed
9. #4057 — R8 accesspass + resource
10. #4058 — R9 globalstate/config/allowlist/index/migrate

R10 (commands/* migration + program-test) and RF (fixtures) follow as
separate PRs.
@juan-malbeclabs
juan-malbeclabs force-pushed the feat/rfc26-r6-tenant-permission-builders branch from 6bf08fb to 410820c Compare July 21, 2026 17:02
@juan-malbeclabs
juan-malbeclabs force-pushed the feat/rfc26-r7-topology-feed-builders branch from bdeefdb to 37c6ca9 Compare July 21, 2026 17:02
@juan-malbeclabs
juan-malbeclabs force-pushed the feat/rfc26-r6-tenant-permission-builders branch from 410820c to a019701 Compare July 21, 2026 17:19
@juan-malbeclabs
juan-malbeclabs force-pushed the feat/rfc26-r7-topology-feed-builders branch from 37c6ca9 to e3295d4 Compare July 21, 2026 17:19
juan-malbeclabs added a commit that referenced this pull request Jul 22, 2026
## Summary

RFC-26 **R1** (stacked on the previous phase's branch). Complete the
device domain on top of the R0 exemplars: update_device (max(old,new)
dz_prefix block), set_device_health, and interface create/delete/update
(Vpnv4 topology PDAs, segment-routing reconcile).

All builders route through `authorize()` -> `build_with_permission`
unless noted; each carries a verbatim account-layout doc-comment copied
from its processor.

## Testing Verification

- Unit tests assert the exact `AccountMeta` list (incl. trailing
`[payer, system]`) and the borsh tag byte for every builder, covering
the conditional/variable-account paths.

Closes #4016. Part of RFC-26
([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)).

---

### PR stack (RFC-26 builder library)

Stacked PRs, merge in order (each is based on the previous one's
branch):

1. #4049 — R0 scaffold + exemplars
2. #4050 — R1 device  ← **this PR**
3. #4051 — R2 link
4. #4052 — R3 user
5. #4053 — R4 location/exchange/contributor
6. #4054 — R5 multicastgroup + allowlists
7. #4055 — R6 tenant + permission
8. #4056 — R7 topology + feed
9. #4057 — R8 accesspass + resource
10. #4058 — R9 globalstate/config/allowlist/index/migrate

R10 (commands/* migration + program-test) and RF (fixtures) follow as
separate PRs.
juan-malbeclabs added a commit that referenced this pull request Jul 22, 2026
## Summary

RFC-26 **R2** (stacked on the previous phase's branch). accept_link,
update_link (LinkUpdateAuthority preamble + conditional tunnel_net /
tunnel-resource / topology-union sections), delete_link (topology
reference-count accounts), set_link_health.

All builders route through `authorize()` -> `build_with_permission`
unless noted; each carries a verbatim account-layout doc-comment copied
from its processor.

## Testing Verification

- Unit tests assert the exact `AccountMeta` list (incl. trailing
`[payer, system]`) and the borsh tag byte for every builder, covering
the conditional/variable-account paths.

Closes #4017. Part of RFC-26
([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)).

---

### PR stack (RFC-26 builder library)

Stacked PRs, merge in order (each is based on the previous one's
branch):

1. #4049 — R0 scaffold + exemplars
2. #4050 — R1 device
3. #4051 — R2 link  ← **this PR**
4. #4052 — R3 user
5. #4053 — R4 location/exchange/contributor
6. #4054 — R5 multicastgroup + allowlists
7. #4055 — R6 tenant + permission
8. #4056 — R7 topology + feed
9. #4057 — R8 accesspass + resource
10. #4058 — R9 globalstate/config/allowlist/index/migrate

R10 (commands/* migration + program-test) and RF (fixtures) follow as
separate PRs.
juan-malbeclabs added a commit that referenced this pull request Jul 22, 2026
## Summary

RFC-26 **R3** (stacked on the previous phase's branch). create_user
(length-detected -> build, no permission), update_user, delete_user,
request_ban_user, check_user_access_pass, and set_user_bgp_status
(metrics-publisher check, no authorize -> build).

All builders route through `authorize()` -> `build_with_permission`
unless noted; each carries a verbatim account-layout doc-comment copied
from its processor.

## Testing Verification

- Unit tests assert the exact `AccountMeta` list (incl. trailing
`[payer, system]`) and the borsh tag byte for every builder, covering
the conditional/variable-account paths.

Closes #4018. Part of RFC-26
([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)).

---

### PR stack (RFC-26 builder library)

Stacked PRs, merge in order (each is based on the previous one's
branch):

1. #4049 — R0 scaffold + exemplars
2. #4050 — R1 device
3. #4051 — R2 link
4. #4052 — R3 user  ← **this PR**
5. #4053 — R4 location/exchange/contributor
6. #4054 — R5 multicastgroup + allowlists
7. #4055 — R6 tenant + permission
8. #4056 — R7 topology + feed
9. #4057 — R8 accesspass + resource
10. #4058 — R9 globalstate/config/allowlist/index/migrate

R10 (commands/* migration + program-test) and RF (fixtures) follow as
separate PRs.
@juan-malbeclabs
juan-malbeclabs force-pushed the feat/rfc26-r6-tenant-permission-builders branch from a019701 to 98cc7f5 Compare July 22, 2026 18:04
@juan-malbeclabs
juan-malbeclabs force-pushed the feat/rfc26-r7-topology-feed-builders branch from e3295d4 to 76b7862 Compare July 22, 2026 18:04
@juan-malbeclabs
juan-malbeclabs force-pushed the feat/rfc26-r6-tenant-permission-builders branch from 98cc7f5 to 60a78c9 Compare July 22, 2026 18:16
@juan-malbeclabs
juan-malbeclabs force-pushed the feat/rfc26-r7-topology-feed-builders branch from 76b7862 to b4e18c4 Compare July 22, 2026 18:16
@juan-malbeclabs
juan-malbeclabs force-pushed the feat/rfc26-r6-tenant-permission-builders branch from 60a78c9 to 6ec8cab Compare July 22, 2026 20:25
@juan-malbeclabs
juan-malbeclabs force-pushed the feat/rfc26-r7-topology-feed-builders branch from b4e18c4 to cf56203 Compare July 22, 2026 20:25
juan-malbeclabs added a commit that referenced this pull request Jul 22, 2026
…FC-26 R4) (#4053)

## Summary

RFC-26 **R4** (stacked on the previous phase's branch). Three
account_index-seeded CRUD domains (create/update/suspend/resume/delete).
Exchange create/update carry globalconfig; set_device_exchange carries
the device; create_contributor carries the owner.

All builders route through `authorize()` -> `build_with_permission`
unless noted; each carries a verbatim account-layout doc-comment copied
from its processor.

## Testing Verification

- Unit tests assert the exact `AccountMeta` list (incl. trailing
`[payer, system]`) and the borsh tag byte for every builder, covering
the conditional/variable-account paths.

Closes #4019. Part of RFC-26
([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)).

---

### PR stack (RFC-26 builder library)

Stacked PRs, merge in order (each is based on the previous one's
branch):

1. #4049 — R0 scaffold + exemplars
2. #4050 — R1 device
3. #4051 — R2 link
4. #4052 — R3 user
5. #4053 — R4 location/exchange/contributor  ← **this PR**
6. #4054 — R5 multicastgroup + allowlists
7. #4055 — R6 tenant + permission
8. #4056 — R7 topology + feed
9. #4057 — R8 accesspass + resource
10. #4058 — R9 globalstate/config/allowlist/index/migrate

R10 (commands/* migration + program-test) and RF (fixtures) follow as
separate PRs.
juan-malbeclabs added a commit that referenced this pull request Jul 23, 2026
…26 R5) (#4054)

## Summary

RFC-26 **R5** (stacked on the previous phase's branch). create/update
(conditional multicast_group_block)/suspend/reactivate/delete,
update_multicast_group_roles, and the four pub/sub allowlist add/remove
builders.

All builders route through `authorize()` -> `build_with_permission`
unless noted; each carries a verbatim account-layout doc-comment copied
from its processor.

## Testing Verification

- Unit tests assert the exact `AccountMeta` list (incl. trailing
`[payer, system]`) and the borsh tag byte for every builder, covering
the conditional/variable-account paths.

Closes #4020. Part of RFC-26
([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)).

---

### PR stack (RFC-26 builder library)

Stacked PRs, merge in order (each is based on the previous one's
branch):

1. #4049 — R0 scaffold + exemplars
2. #4050 — R1 device
3. #4051 — R2 link
4. #4052 — R3 user
5. #4053 — R4 location/exchange/contributor
6. #4054 — R5 multicastgroup + allowlists  ← **this PR**
7. #4055 — R6 tenant + permission
8. #4056 — R7 topology + feed
9. #4057 — R8 accesspass + resource
10. #4058 — R9 globalstate/config/allowlist/index/migrate

R10 (commands/* migration + program-test) and RF (fixtures) follow as
separate PRs.
@juan-malbeclabs
juan-malbeclabs force-pushed the feat/rfc26-r6-tenant-permission-builders branch from 6ec8cab to 7c8171e Compare July 23, 2026 14:31
Base automatically changed from feat/rfc26-r6-tenant-permission-builders to main July 23, 2026 18:46
juan-malbeclabs added a commit that referenced this pull request Jul 23, 2026
…4055)

## Summary

RFC-26 **R6** (stacked on the previous phase's branch). Tenant CRUD +
add/remove administrator + update_payment_status (globalstate writable
on create, read-only elsewhere), and permission
create/update/suspend/resume/delete (target PDA derived from
args.user_payer).

All builders route through `authorize()` -> `build_with_permission`
unless noted; each carries a verbatim account-layout doc-comment copied
from its processor.

## Testing Verification

- Unit tests assert the exact `AccountMeta` list (incl. trailing
`[payer, system]`) and the borsh tag byte for every builder, covering
the conditional/variable-account paths.

Closes #4021. Part of RFC-26
([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)).

---

### PR stack (RFC-26 builder library)

Stacked PRs, merge in order (each is based on the previous one's
branch):

1. #4049 — R0 scaffold + exemplars
2. #4050 — R1 device
3. #4051 — R2 link
4. #4052 — R3 user
5. #4053 — R4 location/exchange/contributor
6. #4054 — R5 multicastgroup + allowlists
7. #4055 — R6 tenant + permission  ← **this PR**
8. #4056 — R7 topology + feed
9. #4057 — R8 accesspass + resource
10. #4058 — R9 globalstate/config/allowlist/index/migrate

R10 (commands/* migration + program-test) and RF (fixtures) follow as
separate PRs.
Topology create/delete plus batched clear_topology / assign_topology_node_segments
(single-chunk builder + *_batched convenience; CLEAR_BATCH_SIZE=16 /
BACKFILL_BATCH_SIZE=4 consts moved into the crate). Feed create/update/delete.
Topology PDA derived from args.name; feed PDA from args.code + args.exchange.

Refs #4022, RFC-26.
…builder

When the topology account still exists and at least one link is cleared, the
clear processor asserts topology_account.is_writable and decrements its
reference_count. The builder passed topology read-only, tripping the assert.
Mark it writable (harmless on the closed-topology path, which skips the write).
…erate SDK byte-parity break (RFC-26 R7 review)
@juan-malbeclabs
juan-malbeclabs force-pushed the feat/rfc26-r7-topology-feed-builders branch from cf56203 to 5f7758f Compare July 23, 2026 19:18

@ben-dz ben-dz 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.

The topology module doc's name-canonicalization rationale is factually wrong: get_topology_pda (pda.rs:121-124) has uppercased internally since c60e14d, so delete/clear/assign deriving "from the raw arg" still canonicalize and a non-uppercase name can never reference a non-existent account or revert — the builders' to_ascii_uppercase() is a PDA no-op whose only effect is packing the canonical name (itself a byte-parity divergence from the SDK for non-canonical input). Either drop the canonicalization or rewrite the rationale, and fix the test comments repeating the claim. Separately, the live SDK ClearTopologyCommand readonly bug this PR correctly documents is user-facing (clearing a live topology is the mandatory precursor to delete) and has no tracking issue. Builder account layouts, writability flags, wire tags, batch math, and the FEED_AUTHORITY/AUTHORIZE_GATED_FLAGS gap claim all verified correct; remaining items are low-severity doc and test-fixture fixes.

  • Medium: the live SDK bug this PR documents at topology.rs:101 ("the SDK command is the one that needs fixing") is real, user-facing, and untracked. smartcontract/sdk/rs/src/commands/topology/clear.rs passes the topology PDA new_readonly, while the processor (clear.rs:124-127) asserts is_writable whenever the topology exists and at least one link is cleared; since delete_topology requires reference_count == 0 (delete.rs:77), clearing a live topology is the mandatory precursor to delete — the SDK/CLI topology clear reverts in exactly its primary use case. No GitHub issue tracks it. File a tracking issue, or fix the one-line new_readonlynew in a small follow-up, so this doc's pointer doesn't dangle indefinitely.

Comment thread crates/doublezero-serviceability-instruction/src/topology.rs Outdated
Comment thread crates/doublezero-serviceability-instruction/src/topology.rs Outdated
Comment thread crates/doublezero-serviceability-instruction/src/feed.rs
Comment thread crates/doublezero-serviceability-instruction/src/feed.rs Outdated
@juan-malbeclabs
juan-malbeclabs force-pushed the feat/rfc26-r7-topology-feed-builders branch from 5f7758f to cccffbc Compare July 23, 2026 20:30

@ben-dz ben-dz 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.

lgtm — all findings from previous rounds verified fixed on the current head (canonicalization removed, SDK clear_topology bug tracked in #4078, batch-limit docs corrected, FEED_AUTHORITY gap tracked in #4079 with a passing canary test, realistic FeedUpdateArgs fixture).

@juan-malbeclabs
juan-malbeclabs enabled auto-merge (squash) July 23, 2026 20:50
@juan-malbeclabs
juan-malbeclabs merged commit 2c05f5d into main Jul 23, 2026
36 checks passed
@juan-malbeclabs
juan-malbeclabs deleted the feat/rfc26-r7-topology-feed-builders branch July 23, 2026 20:59
juan-malbeclabs added a commit that referenced this pull request Jul 24, 2026
#4057)

## Summary

RFC-26 **R8** (stacked on the previous phase's branch). Access-pass set
(conditional tenant pair)/close/check-status/set-feeds, and
resource-extension allocate/create/deallocate/close (resource PDA +
associated account derived from the data-bearing args.resource_type).

All builders route through `authorize()` -> `build_with_permission`
unless noted; each carries a verbatim account-layout doc-comment copied
from its processor.

## Testing Verification

- Unit tests assert the exact `AccountMeta` list (incl. trailing
`[payer, system]`) and the borsh tag byte for every builder, covering
the conditional/variable-account paths.

Closes #4023. Part of RFC-26
([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)).

---

### PR stack (RFC-26 builder library)

Stacked PRs, merge in order (each is based on the previous one's
branch):

1. #4049 — R0 scaffold + exemplars
2. #4050 — R1 device
3. #4051 — R2 link
4. #4052 — R3 user
5. #4053 — R4 location/exchange/contributor
6. #4054 — R5 multicastgroup + allowlists
7. #4055 — R6 tenant + permission
8. #4056 — R7 topology + feed
9. #4057 — R8 accesspass + resource  ← **this PR**
10. #4058 — R9 globalstate/config/allowlist/index/migrate

R10 (commands/* migration + program-test) and RF (fixtures) follow as
separate PRs.
juan-malbeclabs added a commit that referenced this pull request Jul 24, 2026
…migrate builders (RFC-26 R9) (#4058)

## Summary

RFC-26 **R9** (stacked on the previous phase's branch).
init_global_state and migrate (no authorize -> build); setters,
set_global_config (config PDA + all 8 resource pools), foundation/QA
allowlist toggles, index create/delete. After this every buildable
variant has a builder.

All builders route through `authorize()` -> `build_with_permission`
unless noted; each carries a verbatim account-layout doc-comment copied
from its processor.

## Testing Verification

- Unit tests assert the exact `AccountMeta` list (incl. trailing
`[payer, system]`) and the borsh tag byte for every builder, covering
the conditional/variable-account paths.

Closes #4024. Part of RFC-26
([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)).

---

### PR stack (RFC-26 builder library)

Stacked PRs, merge in order (each is based on the previous one's
branch):

1. #4049 — R0 scaffold + exemplars
2. #4050 — R1 device
3. #4051 — R2 link
4. #4052 — R3 user
5. #4053 — R4 location/exchange/contributor
6. #4054 — R5 multicastgroup + allowlists
7. #4055 — R6 tenant + permission
8. #4056 — R7 topology + feed
9. #4057 — R8 accesspass + resource
10. #4058 — R9 globalstate/config/allowlist/index/migrate  ← **this PR**

R10 (commands/* migration + program-test) and RF (fixtures) follow as
separate PRs.
juan-malbeclabs added a commit that referenced this pull request Jul 25, 2026
…4059)

## Summary

RFC-26 **RF**: golden fixtures for the instruction builders + a CI drift
guard.

- Extend the serviceability fixture generator to depend on
`doublezero-serviceability-instruction` and emit, per instruction,
`ix_<name>.bin` (wire bytes = tag + borsh) and `ix_<name>.json` (`{
variant, data_hex, accounts: [{pubkey, is_signer, is_writable}] }`) from
fixed, deterministic inputs.
- Representative set covering the trickiest layouts: `create_device`
(variable dz_prefix), `delete_device` (atomic close), `create_link`,
`create_subscribe_user` (optional feed), `create_user`
(length-detected), `clear_topology` / `assign_topology_node_segments`
(batched), `set_global_config` (config PDA + all 8 pools).
- `make generate-fixtures-check` (regenerate + fail on drift, scoped to
the emitted `.bin`/`.json`) and a `fixtures-check` job in the `rust`
workflow.

These fixtures capture byte-for-byte the current SDK trailing convention
(they will drive Go/Python/TS parity in a later phase).

## Testing Verification

- `make generate-fixtures-check` passes (regenerated output is
byte-identical to the committed fixtures); a hand-edited fixture makes
it fail as expected.

Closes #4026. Part of RFC-26
([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)).

---

### PR stack (RFC-26 builder library)

Stacked PRs, merge in order (each based on the previous one's branch):

1. #4049 — R0 scaffold + exemplars
2. #4050 — R1 device
3. #4051 — R2 link
4. #4052 — R3 user
5. #4053 — R4 location/exchange/contributor
6. #4054 — R5 multicastgroup + allowlists
7. #4055 — R6 tenant + permission
8. #4056 — R7 topology + feed
9. #4057 — R8 accesspass + resource
10. #4058 — R9 globalstate/config/allowlist/index/migrate
11. this PR — RF fixtures + CI guard

R10 (commands/* migration + program-test) is the final PR and will carry
the global changelog entry.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

serviceability: topology + feed instruction builders (Rust)

2 participants