From 7c68b5a711a3e465533a59dc23adb6ec88ec19fb Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Sat, 28 Mar 2026 20:59:59 +0500 Subject: [PATCH 1/5] feat: make the aggregator node part of the e3 committee --- agent/flow-trace/00_INDEX.md | 14 +- .../flow-trace/03_E3_REQUEST_AND_COMMITTEE.md | 15 +- agent/flow-trace/04_DKG_AND_COMPUTATION.md | 48 +++-- .../flow-trace/05_FAILURE_REFUND_SLASHING.md | 4 +- crates/README.md | 8 +- crates/aggregator/src/committee_finalizer.rs | 126 ++++++++---- crates/aggregator/src/ext.rs | 19 +- crates/aggregator/src/publickey_aggregator.rs | 86 +++++++- .../src/threshold_plaintext_aggregator.rs | 105 +++++++++- .../src/ciphernode_builder.rs | 15 +- crates/cli/src/start.rs | 20 +- crates/config/src/app_config.rs | 84 +++----- crates/entrypoint/src/start/mod.rs | 1 - crates/entrypoint/src/start/start.rs | 13 +- crates/events/src/committee.rs | 17 ++ crates/evm/src/ciphernode_registry_sol.rs | 183 ++++++++++++++++-- crates/evm/src/enclave_sol_writer.rs | 139 ++++++++++++- crates/sortition/Readme.md | 4 +- crates/sortition/src/ciphernode_selector.rs | 7 + crates/sortition/src/sortition.rs | 34 ++++ dappnode/README.md | 18 +- dappnode/config.template.yaml | 2 - dappnode/docker-compose.yml | 1 - dappnode/entrypoint.sh | 1 - dappnode/setup-wizard.yml | 16 -- deploy/agg.yaml | 2 - deploy/cn2.yaml | 2 +- deploy/cn3.yaml | 2 +- deploy/cn4.yaml | 14 ++ deploy/copy-secrets.sh | 6 +- deploy/docker-compose.yml | 18 +- deploy/inspect.sh | 4 +- deploy/local/nodes.sh | 1 - deploy/local/start.sh | 2 +- deploy/swarm_deployment.md | 4 +- docs/pages/CRISP/introduction.mdx | 2 +- docs/pages/ciphernode-operators/running.mdx | 4 +- .../tickets-and-sortition.mdx | 3 +- docs/pages/project-template.mdx | 8 +- examples/CRISP/Readme.md | 2 +- examples/CRISP/enclave.config.yaml | 8 - examples/CRISP/scripts/dev_cipher.sh | 2 - examples/CRISP/scripts/setup_testnet.sh | 5 +- packages/enclave-contracts/README.md | 3 + .../contracts/Enclave.sol/Enclave.json | 2 +- .../IBondingRegistry.json | 2 +- .../ICiphernodeRegistry.json | 45 ++++- .../interfaces/IEnclave.sol/IEnclave.json | 2 +- .../ISlashingManager.json | 2 +- .../CiphernodeRegistryOwnable.json | 50 +++-- .../EnclaveTicketToken.json | 2 +- .../interfaces/ICiphernodeRegistry.sol | 25 ++- .../registry/CiphernodeRegistryOwnable.sol | 22 ++- .../contracts/test/MockCiphernodeRegistry.sol | 43 +++- .../enclave-contracts/deployed_contracts.json | 34 ++-- packages/enclave-contracts/hardhat.config.ts | 2 + packages/enclave-contracts/tasks/enclave.ts | 33 ++++ .../CiphernodeRegistryOwnable.spec.ts | 32 +-- templates/default/enclave.config.yaml | 8 - templates/default/scripts/dev_ciphernodes.sh | 2 - tests/integration/base.sh | 17 +- tests/integration/enclave.config.yaml | 22 +-- tests/integration/fns.sh | 80 +++++++- tests/integration/persist.sh | 24 ++- 64 files changed, 1142 insertions(+), 379 deletions(-) create mode 100644 deploy/cn4.yaml diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index 9c51134355..db5869e089 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -49,9 +49,10 @@ e. Collect shares → verify C2/C3 proofs (2-phase) f. Decrypt shares → calc decryption key → C4a/C4b proofs g. Exchange DecryptionKeyShared → verify C4 proofs - h. Publish KeyshareCreated → aggregator + h. Publish KeyshareCreated → finalized committee aggregation candidates -10. PK AGG Aggregator aggregates pk_shares → aggregate PK +10. PK AGG Primary finalized committee member aggregates pk_shares + → fallbacks are derived from later committee ranks → C5 proof (proves aggregation correct) → publishCommittee() on-chain → KeyPublished stage @@ -60,9 +61,10 @@ 12. DECRYPT Committee members produce decryption shares → C6 proof per share (proves share correctly derived) - → Broadcast to aggregator + → Broadcast to finalized committee aggregation candidates -13. AGGREGATE Aggregator combines M+1 shares → plaintext +13. AGGREGATE Primary finalized committee member combines M+1 shares → plaintext + → fallbacks submit later if higher-priority ranks miss their slot → C7 proof (proves reconstruction correct) 14. COMPLETE publishPlaintextOutput() → rewards distributed @@ -172,7 +174,7 @@ _Found during source-code cross-referencing of these trace documents._ | 4 | `activate()` calls `register()` → `registerOperator()` which has `require(!registered, AlreadyRegistered())`. So activate **reverts** for already-registered operators. It only works for re-registration after deregistration. | BondingRegistry.sol:308 | 01_REGISTRATION | | 5 | `E3Requested` event is `(uint256 e3Id, E3 e3, IE3Program indexed e3Program)` — seed and params are inside the E3 struct, not separate parameters. | IEnclave.sol:82 | 03_E3_REQUEST | | 6 | `finalizeCommittee()` checks `>=` deadline, not `>`. | CiphernodeRegistryOwnable.sol | 03_E3_REQUEST | -| 7 | `publishCommittee()` is `onlyOwner` restricted — centralized trust assumption acknowledged in contract TODOs. | CiphernodeRegistryOwnable.sol | 04_DKG | +| 7 | `publishCommittee()` is permissionless and proof-gated. Any caller with a valid C5 proof can submit; ownership is no longer part of the access control path. | CiphernodeRegistryOwnable.sol | 04_DKG | | 8 | `CommitteePublished` event emits `(e3Id, nodes, publicKey, proof)` — full PK bytes and C5 proof, not just pkHash. | CiphernodeRegistryOwnable.sol | 04_DKG | | 9 | `_validateNodeEligibility` calls `bondingRegistry.getTicketBalanceAtBlock()` (not `ticketToken.getPastVotes()` directly). | CiphernodeRegistryOwnable.sol:668 | 03_E3_REQUEST | | 10 | Lane A slashing uses **attestation-based** verification (committee quorum votes), not direct ZK proof re-verification on-chain. `proposeSlash()` decodes voter addresses, agrees, data hashes, and ECDSA signatures — not ZK proofs. | SlashingManager.sol | 05_FAILURE | @@ -182,7 +184,7 @@ _Found during source-code cross-referencing of these trace documents._ | # | Concern | Severity | Detail | | --- | ---------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | **Deregister-before-slash race** | Accepted | SlashingManager Lane B (evidence+appeal) has a window during which the operator can deregister and claim their exit. If they do, the slash executes against 0 funds. The contract comments acknowledge this as an accepted tradeoff for the appeal window design. | -| 2 | **`publishCommittee()` is centralized** | High | Only the contract owner can publish the committee public key. A malicious or compromised owner could publish a fake key. The contract has `// TODO` and `// SECURITY` comments acknowledging this. | +| 2 | **Committee publication is proof-gated** | Info | `publishCommittee()` is now permissionless. Safety depends on the C5 proof and committee-stage checks rather than a privileged owner account, which removes the original centralization risk. | | 3 | **`gracePeriod` is dead code** | Medium | `gracePeriod` is stored and validated during config updates but never actually used in any timeout check. Either the deadlines already bake in sufficient buffer, or this is a missing feature. | | 4 | **`activate` CLI command is misleading** | Low | Named "activate" but actually calls "register" — will fail for already-registered operators. There's no standalone way to trigger re-evaluation of active status; instead, `_updateOperatorStatus()` runs automatically inside `addTicketBalance()`, `bondLicense()`, etc. | | 5 | **Active-job load balancing bug fixed** | Info | The Rust `NodeStateStore.available_tickets()` subtracts `active_jobs` from total tickets, reducing the chance of busy nodes being selected for new E3s. Previously, the `Sortition` actor's `Handler` was missing match arms for `E3Failed` and `E3StageChanged`, causing these events to fall to the default `_ => ()` — the typed handlers for decrementing jobs were dead code. This has been fixed: E3Failed and E3StageChanged are now routed to their handlers, and `finalized_committees` is cleaned up in `decrement_jobs_for_e3` to prevent unbounded memory growth. | diff --git a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md index 56be89b2f4..45aa13b13b 100644 --- a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md +++ b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md @@ -127,9 +127,9 @@ EnclaveSolReader decodes IEnclave::E3Requested log │ └─ Creates Fhe instance from BFV params │ └─ Stores as dependency in E3Context │ -├─ PublicKeyAggregatorExtension.on_event(): (aggregator only) -│ └─ Spins up PublicKeyAggregator actor -│ └─ State: Collecting (waiting for N keyshares) +├─ PublicKeyAggregatorExtension.on_event(): +│ └─ Spins up PublicKeyAggregator actor on each selected node +│ └─ State: Pending until CommitteeFinalized resolves finalized aggregation duty │ └─ Sortition actor receives E3Requested: │ @@ -233,13 +233,16 @@ CiphernodeRegistrySolWriter receives TicketGenerated event ## Step 3: Committee Finalization -### 3a. Deadline Timer (Rust-Side, Aggregator) +### 3a. Deadline Timer (Rust-Side, Selected Ciphernodes) ``` -CommitteeFinalizer actor receives CommitteeRequested event +CommitteeFinalizer actor receives CommitteeRequested event on each selected node +│ +├─ Resolves this node's local score rank from sortition +│ └─ If the node is outside the provisional top-N, no timer is scheduled │ ├─ Calculates wait time: -│ wait = committeeDeadline - currentTimestamp + buffer +│ wait = committeeDeadline - currentTimestamp + buffer + local_rank_stagger │ ├─ Schedules timer │ diff --git a/agent/flow-trace/04_DKG_AND_COMPUTATION.md b/agent/flow-trace/04_DKG_AND_COMPUTATION.md index f84608d22f..2c143aa45a 100644 --- a/agent/flow-trace/04_DKG_AND_COMPUTATION.md +++ b/agent/flow-trace/04_DKG_AND_COMPUTATION.md @@ -448,21 +448,24 @@ ThresholdKeyshare receives AllThresholdSharesCollected pk_share, // public key share signed_proof // ZK proof of correct generation } - → Broadcast to aggregator via P2P + → Broadcast to finalized committee aggregation candidates via P2P ``` --- -## Phase 2: Public Key Aggregation (Aggregator Only, with C5 Proof) +## Phase 2: Public Key Aggregation (Eligible Finalized Committee Members, with C5 Proof) ``` -PublicKeyAggregator (AGGREGATOR) collects KeyshareCreated events + PublicKeyAggregator starts on each selected node and collects KeyshareCreated events │ ├─ KeyshareCreatedFilterBuffer gates events: │ └─ Only forwards KeyshareCreated from verified committee members │ └─ Buffers until CommitteeFinalized is known │ -├─ When threshold_n keyshares collected: + ├─ When CommitteeFinalized resolves this node into the finalized fallback chain: + │ └─ State becomes Eligible(rank) + │ + ├─ When threshold_n keyshares collected on an eligible node: │ │ │ ├─ 1. Aggregate public key shares: │ │ aggregate_pk = Fhe::get_aggregate_public_key( @@ -497,22 +500,21 @@ PublicKeyAggregator (AGGREGATOR) collects KeyshareCreated events │ e3_id, aggregate_pk, pk_hash, node_list │ } │ -└─ CiphernodeRegistrySolWriter (AGGREGATOR) receives PublicKeyAggregated: - └─ Calls contract.publishCommittee(e3_id, nodes, publicKey, pkHash) +└─ CiphernodeRegistrySolWriter on each eligible node receives PublicKeyAggregated: + ├─ Checks finalized committee rank via Sortition + ├─ Reads on-chain committee stage / pk hash to avoid duplicate submission + ├─ Waits rank-based delay (rank 0 first, later ranks staggered) + └─ Calls contract.publishCommittee(e3_id, nodes, publicKey, pkHash) │ │ ┌─── ON-CHAIN (CiphernodeRegistryOwnable) ──────────┐ │ │ │ │ │ publishCommittee(e3Id, nodes, pk, pkHash) { │ - │ │ ⚠️ **onlyOwner** — this is centralized! │ - │ │ (acknowledged in contract with a TODO/SECURITY │ - │ │ comment; the owner can publish any key) │ - │ │ │ │ │ 1. require(initialized && finalized) │ │ │ 2. require(publicKeyHashes[e3Id] == 0) │ │ │ → Can only publish once │ - │ │ 3. require(nodes.length == committee.length) │ - │ │ 4. publicKeyHashes[e3Id] = pkHash │ - │ │ 5. enclave.onCommitteePublished(e3Id, pkHash) │ + │ │ 3. verify C5 proof / committee metadata │ + │ │ 4. publicKeyHashes[e3Id] = pkHash │ + │ │ 5. enclave.onCommitteePublished(e3Id, pkHash) │ │ │ │ │ │ │ │ ┌─ Enclave.sol ────────────────────────┐ │ │ │ │ │ onCommitteePublished(e3Id, pkHash) {│ │ @@ -625,17 +627,22 @@ EnclaveSolReader decodes CiphertextOutputPublished event │ signed_proof: SignedProofPayload(C6), │ node: address │ } - │ → Broadcast via P2P to aggregator + │ → Broadcast via P2P to finalized committee aggregation candidates │ └─ State: Decrypting → Completed ``` --- -## Phase 5: Plaintext Aggregation (Aggregator Only, with C6 Verification & C7 Proof) + ## Phase 5: Plaintext Aggregation (Eligible Finalized Committee Members, with C6 Verification & C7 Proof) ``` -ThresholdPlaintextAggregator receives DecryptionshareCreated events + ThresholdPlaintextAggregator runs on nodes inside the finalized fallback chain + │ + ├─ On startup / CommitteeFinalized: + │ └─ Resolves whether this node is eligible to aggregate plaintext for this e3_id + │ + ThresholdPlaintextAggregator receives DecryptionshareCreated events │ ├─ For each share: │ ├─ Verify sender is in committee: @@ -643,7 +650,7 @@ ThresholdPlaintextAggregator receives DecryptionshareCreated events │ ├─ If verified: add_share(party_id, decryption_share) │ └─ If not: ignore │ -├─ C6 VERIFICATION (per-share, on aggregator): + ├─ C6 VERIFICATION (per-share, on eligible aggregation node): │ ShareVerificationActor receives C6 signed proofs │ ├─ ECDSA recovery + ZK verification (same 2-phase as C2/C3) │ ├─ On failure: SignedProofFailed → accusation pipeline @@ -692,8 +699,11 @@ ThresholdPlaintextAggregator receives DecryptionshareCreated events │ │ │ └─ Publish PlaintextAggregated { e3_id, decrypted_output } │ -└─ EnclaveSolWriter (AGGREGATOR) receives PlaintextAggregated: - └─ Calls contract.publishPlaintextOutput(e3Id, output, proof) +└─ EnclaveSolWriter on each eligible node receives PlaintextAggregated: + ├─ Checks finalized committee rank via Sortition + ├─ Reads on-chain E3 stage before submitting + ├─ Waits rank-based delay (rank 0 first, later ranks staggered) + └─ Calls contract.publishPlaintextOutput(e3Id, output, proof) │ │ ┌─── ON-CHAIN (Enclave.sol) ─────────────────────────┐ │ │ │ diff --git a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md index 9d88c27b10..70c1da0ecf 100644 --- a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md +++ b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md @@ -977,13 +977,13 @@ When CommitteeMemberExpelled event arrives from EVM: │ │ → May trigger share processing with reduced set │ └─ Does NOT hold committee state — fully delegated to Sortition │ -├─ PublicKeyAggregator (aggregator, receives raw event): +├─ PublicKeyAggregator (eligible finalized fallback nodes, receives raw event): │ ├─ Only processes raw events (party_id: None) │ ├─ Ignores enriched events (party_id: Some) to avoid double-processing │ └─ Reduces threshold_n │ └─ May trigger aggregation if enough keyshares collected │ -├─ KeyshareCreatedFilterBuffer (aggregator): +├─ KeyshareCreatedFilterBuffer (eligible finalized fallback nodes): │ ├─ Only processes raw events (party_id: None) │ └─ Removes expelled node from committee filter set │ diff --git a/crates/README.md b/crates/README.md index c4647c3558..381e9e7b06 100644 --- a/crates/README.md +++ b/crates/README.md @@ -49,14 +49,14 @@ sequenceDiagram CS->>+S: has node? S--)-CS: yes CS--)E3: CiphernodeSelected - E3->>PKA: Create new PublicKeyAggreator for this e3_id + E3->>PKA: Create new PublicKeyAggregator for this e3_id on selected nodes E3->>KS: Create new Keyshare for this e3_id loop KS--)PKA: KeyshareCreated PKA->>+S: has node? S--)-PKA: yes end - PKA--)EVM: PublicKeyAggregated + PKA--)EVM: PublicKeyAggregated by the highest-priority eligible node PKA--)PKA: Stop ``` @@ -72,13 +72,13 @@ sequenceDiagram participant S as Sortition EVM--)E3: CiphertextOutputPublished - E3->>PTA: Create new PlaintextAggreator for this e3_id + E3->>PTA: Create new PlaintextAggregator for this e3_id on selected nodes loop KS--)PTA: DecryptionShareCreated PTA->>+S: has node? S--)-PTA: yes end - PTA--)EVM: PlaintextAggregated + PTA--)EVM: PlaintextAggregated by the highest-priority eligible node PTA--)+KS: PlaintextAggregated PTA--)PTA: Stop KS--)-KS: Stop diff --git a/crates/aggregator/src/committee_finalizer.rs b/crates/aggregator/src/committee_finalizer.rs index cbbabc5255..a91369ded8 100644 --- a/crates/aggregator/src/committee_finalizer.rs +++ b/crates/aggregator/src/committee_finalizer.rs @@ -10,28 +10,34 @@ use e3_events::{ E3Failed, E3Stage, E3StageChanged, EType, EffectsEnabled, EnclaveEvent, EnclaveEventData, EventType, Shutdown, TypedEvent, }; +use e3_sortition::{GetLocalNodeSortitionRank, Sortition}; use e3_utils::{NotifySync, MAILBOX_LIMIT}; use std::collections::HashMap; use std::time::Duration; use tracing::{error, info}; +const FINALIZATION_BUFFER_SECONDS: u64 = 1; +const FINALIZATION_INTERVAL_SECONDS: u64 = 1; + /// CommitteeFinalizer is an actor that listens to CommitteeRequested events and dispatches /// CommitteeFinalizeRequested events after the submission deadline has passed. pub struct CommitteeFinalizer { bus: BusHandle, + sortition: Addr, pending_committees: HashMap, } impl CommitteeFinalizer { - pub fn new(bus: &BusHandle) -> Self { + pub fn new(bus: &BusHandle, sortition: Addr) -> Self { Self { bus: bus.clone(), + sortition, pending_committees: HashMap::new(), } } - pub fn attach(bus: &BusHandle) -> Addr { - let addr = CommitteeFinalizer::new(bus).start(); + pub fn attach(bus: &BusHandle, sortition: Addr) -> Addr { + let addr = CommitteeFinalizer::new(bus, sortition).start(); // Subscribe to state-building / cleanup events immediately bus.subscribe_all( @@ -99,8 +105,13 @@ impl Handler> for CommitteeFinalizer { let ec = msg.get_ctx().clone(); let e3_id = msg.e3_id.clone(); let committee_deadline = msg.committee_deadline; - - const FINALIZATION_BUFFER_SECONDS: u64 = 1; + let local_rank_request = GetLocalNodeSortitionRank { + e3_id: msg.e3_id.clone(), + seed: msg.seed, + size: msg.threshold[1], + chain_id: msg.chain_id, + }; + let sortition = self.sortition.clone(); let e3_id_for_log = e3_id.clone(); let fut = async move { // TODO: we should have no dependencies on e3_evm here. Reason being that this is core @@ -108,69 +119,98 @@ impl Handler> for CommitteeFinalizer { // shell. This means we should not hold an address to a shell actor even and should use // the eventbus to communicate. // see https://github.com/gnosisguild/enclave/issues/989 - match e3_evm::helpers::get_current_timestamp().await { - Ok(timestamp) => Some(timestamp), + let current_timestamp = match e3_evm::helpers::get_current_timestamp().await { + Ok(timestamp) => timestamp, Err(e) => { error!( e3_id = %e3_id_for_log, error = %e, "Failed to get current timestamp from RPC" ); - None + return None; } - } + }; + + let local_rank = match sortition.send(local_rank_request).await { + Ok(rank) => rank, + Err(e) => { + error!( + e3_id = %e3_id_for_log, + error = %e, + "Failed to get local sortition rank for committee finalization" + ); + return None; + } + }; + + Some((current_timestamp, local_rank)) }; let e3_id_for_async = e3_id; ctx.spawn( fut.into_actor(self) - .then(move |current_timestamp, act, ctx| { - if let Some(current_timestamp) = current_timestamp { - let seconds_until_deadline = if committee_deadline > current_timestamp { - (committee_deadline - current_timestamp) + FINALIZATION_BUFFER_SECONDS - } else { + .then(move |result, act, ctx| { + if let Some((current_timestamp, local_rank)) = result { + if let Some(rank) = local_rank { + let base_delay = if committee_deadline > current_timestamp { + (committee_deadline - current_timestamp) + + FINALIZATION_BUFFER_SECONDS + } else { + info!( + e3_id = %e3_id_for_async, + committee_deadline = committee_deadline, + current_timestamp = current_timestamp, + "Submission deadline already passed, finalizing with fallback buffer" + ); + FINALIZATION_BUFFER_SECONDS + }; + let seconds_to_wait = + base_delay + (rank * FINALIZATION_INTERVAL_SECONDS); + info!( e3_id = %e3_id_for_async, committee_deadline = committee_deadline, current_timestamp = current_timestamp, - "Submission deadline already passed, finalizing with buffer" + rank = rank, + seconds_to_wait = seconds_to_wait, + "Scheduling committee finalization" ); - FINALIZATION_BUFFER_SECONDS - }; - - info!( - e3_id = %e3_id_for_async, - committee_deadline = committee_deadline, - current_timestamp = current_timestamp, - seconds_to_wait = seconds_until_deadline, - "Scheduling committee finalization" - ); - let bus = act.bus.clone(); - let e3_id_clone = e3_id_for_async.clone(); + let bus = act.bus.clone(); + let e3_id_clone = e3_id_for_async.clone(); - let handle = ctx.run_later( - Duration::from_secs(seconds_until_deadline), - move |act, _ctx| { - info!(e3_id = %e3_id_clone, "Dispatching CommitteeFinalizeRequested event"); + let handle = ctx.run_later( + Duration::from_secs(seconds_to_wait), + move |act, _ctx| { + info!(e3_id = %e3_id_clone, rank = rank, "Dispatching CommitteeFinalizeRequested event"); - trap(EType::Sortition, &act.bus.with_ec(&ec), || { - bus.publish(CommitteeFinalizeRequested { - e3_id: e3_id_clone.clone(), - },ec)?; - Ok(()) - }); + trap(EType::Sortition, &act.bus.with_ec(&ec), || { + bus.publish(CommitteeFinalizeRequested { + e3_id: e3_id_clone.clone(), + },ec)?; + Ok(()) + }); - act.pending_committees.remove(&e3_id_clone.to_string()); - }, - ); + act.pending_committees.remove(&e3_id_clone.to_string()); + }, + ); - act.pending_committees - .insert(e3_id_for_async.to_string(), handle); + if let Some(existing) = act + .pending_committees + .insert(e3_id_for_async.to_string(), handle) + { + ctx.cancel_future(existing); + } + } else { + info!( + e3_id = %e3_id_for_async, + "Node is outside the local pre-finalization committee ranking, skipping finalize scheduling" + ); + } } else { error!( e3_id = %e3_id_for_async, - "Skipping committee finalization due to timestamp fetch failure" + "Skipping committee finalization due to timestamp or sortition lookup failure" ); } diff --git a/crates/aggregator/src/ext.rs b/crates/aggregator/src/ext.rs index 90c3ee9657..81c0509ef9 100644 --- a/crates/aggregator/src/ext.rs +++ b/crates/aggregator/src/ext.rs @@ -27,13 +27,15 @@ use e3_sortition::Sortition; pub struct PublicKeyAggregatorExtension { bus: BusHandle, params_preset: BfvPreset, + node_address: String, } impl PublicKeyAggregatorExtension { - pub fn create(bus: &BusHandle, params_preset: BfvPreset) -> Box { + pub fn create(bus: &BusHandle, params_preset: BfvPreset, node_address: &str) -> Box { Box::new(Self { bus: bus.clone(), params_preset, + node_address: node_address.to_owned(), }) } } @@ -77,6 +79,8 @@ impl E3Extension for PublicKeyAggregatorExtension { e3_id, sync_state, self.params_preset.clone(), + &self.node_address, + meta.threshold_m, ); ctx.set_event_recipient("publickey", Some(value)); @@ -111,6 +115,10 @@ impl E3Extension for PublicKeyAggregatorExtension { ctx.e3_id.clone(), sync_state, self.params_preset.clone(), + &self.node_address, + ctx.get_dependency(META_KEY) + .ok_or_else(|| anyhow!(ERROR_PUBKEY_META_MISSING))? + .threshold_m, ); // send to context @@ -126,6 +134,8 @@ fn create_publickey_aggregator( e3_id: E3id, sync_state: Persistable, params_preset: BfvPreset, + node_address: &str, + threshold_m: usize, ) -> Recipient { KeyshareCreatedFilterBuffer::new( PublicKeyAggregator::new( @@ -134,6 +144,8 @@ fn create_publickey_aggregator( bus, e3_id, params_preset, + node_address: node_address.to_owned(), + threshold_m, }, sync_state, ) @@ -148,6 +160,7 @@ pub struct ThresholdPlaintextAggregatorExtension { bus: BusHandle, sortition: Addr, params_preset: BfvPreset, + node_address: String, } impl ThresholdPlaintextAggregatorExtension { @@ -155,11 +168,13 @@ impl ThresholdPlaintextAggregatorExtension { bus: &BusHandle, sortition: &Addr, params_preset: BfvPreset, + node_address: &str, ) -> Box { Box::new(Self { bus: bus.clone(), sortition: sortition.clone(), params_preset, + node_address: node_address.to_owned(), }) } } @@ -201,6 +216,7 @@ impl E3Extension for ThresholdPlaintextAggregatorExtension { sortition: self.sortition.clone(), e3_id: e3_id.clone(), params_preset: self.params_preset.clone(), + node_address: self.node_address.clone(), }, sync_state, ) @@ -230,6 +246,7 @@ impl E3Extension for ThresholdPlaintextAggregatorExtension { sortition: self.sortition.clone(), e3_id: ctx.e3_id.clone(), params_preset: self.params_preset.clone(), + node_address: self.node_address.clone(), }, sync_state, ) diff --git a/crates/aggregator/src/publickey_aggregator.rs b/crates/aggregator/src/publickey_aggregator.rs index 09791cb83a..54e9bb534b 100644 --- a/crates/aggregator/src/publickey_aggregator.rs +++ b/crates/aggregator/src/publickey_aggregator.rs @@ -9,12 +9,13 @@ use actix::prelude::*; use anyhow::Result; use e3_data::Persistable; use e3_events::{ - prelude::*, BusHandle, ComputeResponse, ComputeResponseKind, DKGRecursiveAggregationComplete, - Die, E3Failed, E3Stage, E3id, EnclaveEvent, EnclaveEventData, EventContext, FailureReason, - KeyshareCreated, OrderedSet, PartyProofsToVerify, PkAggregationProofPending, - PkAggregationProofRequest, PkAggregationProofSigned, Proof, ProofType, ProofVerificationPassed, - PublicKeyAggregated, Seed, Sequenced, ShareVerificationComplete, ShareVerificationDispatched, - SignedProofFailed, SignedProofPayload, TypedEvent, VerificationKind, ZkResponse, + prelude::*, BusHandle, CommitteeFinalized, ComputeResponse, ComputeResponseKind, + DKGRecursiveAggregationComplete, Die, E3Failed, E3Stage, E3id, EnclaveEvent, EnclaveEventData, + EventContext, FailureReason, KeyshareCreated, OrderedSet, PartyProofsToVerify, + PkAggregationProofPending, PkAggregationProofRequest, PkAggregationProofSigned, Proof, + ProofType, ProofVerificationPassed, PublicKeyAggregated, Seed, Sequenced, + ShareVerificationComplete, ShareVerificationDispatched, SignedProofFailed, SignedProofPayload, + TypedEvent, VerificationKind, ZkResponse, }; use e3_events::{trap, EType}; use e3_fhe::{Fhe, GetAggregatePublicKey}; @@ -83,12 +84,22 @@ impl PublicKeyAggregatorState { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum AggregationDuty { + PendingCommittee, + Eligible { rank: usize }, + Inactive, +} + pub struct PublicKeyAggregator { fhe: Arc, bus: BusHandle, e3_id: E3id, state: Persistable, params_preset: BfvPreset, + node_address: String, + threshold_m: usize, + duty: AggregationDuty, /// DKG recursive aggregation events received before entering GeneratingC5Proof. early_dkg_proofs: Vec>, } @@ -98,6 +109,8 @@ pub struct PublicKeyAggregatorParams { pub bus: BusHandle, pub e3_id: E3id, pub params_preset: BfvPreset, + pub node_address: String, + pub threshold_m: usize, } /// Aggregate PublicKey for a committee of nodes. This actor listens for KeyshareCreated events @@ -114,10 +127,47 @@ impl PublicKeyAggregator { e3_id: params.e3_id, state, params_preset: params.params_preset, + node_address: params.node_address, + threshold_m: params.threshold_m, + duty: AggregationDuty::PendingCommittee, early_dkg_proofs: Vec::new(), } } + fn handle_committee_finalized(&mut self, msg: CommitteeFinalized) { + if msg.e3_id != self.e3_id { + return; + } + + let committee = e3_events::Committee::new(msg.committee); + self.duty = match committee.aggregation_rank_for(&self.node_address, self.threshold_m) { + Some(rank) => { + info!( + e3_id = %self.e3_id, + node = %self.node_address, + rank = rank, + "Node is in the finalized public-key aggregation priority chain" + ); + if rank == 0 { + info!( + e3_id = %self.e3_id, + node = %self.node_address, + "[AGGREGATOR] Node is the current primary public-key aggregator" + ); + } + AggregationDuty::Eligible { rank } + } + None => { + info!( + e3_id = %self.e3_id, + node = %self.node_address, + "Node is outside the finalized public-key aggregation priority chain" + ); + AggregationDuty::Inactive + } + }; + } + pub fn add_keyshare( &mut self, keyshare: ArcBytes, @@ -637,6 +687,10 @@ impl PublicKeyAggregator { /// Publish `PublicKeyAggregated` when both C5 and cross-node fold are complete. fn try_publish_complete(&mut self) -> Result<()> { + if !matches!(self.duty, AggregationDuty::Eligible { .. }) { + return Ok(()); + } + let PublicKeyAggregatorState::GeneratingC5Proof { public_key, nodes, @@ -852,6 +906,9 @@ impl Handler for PublicKeyAggregator { EnclaveEventData::KeyshareCreated(data) => { self.notify_sync(ctx, TypedEvent::new(data, ec)) } + EnclaveEventData::CommitteeFinalized(data) => { + self.notify_sync(ctx, TypedEvent::new(data, ec)) + } EnclaveEventData::ShareVerificationComplete(data) => { self.notify_sync(ctx, TypedEvent::new(data, ec)) } @@ -908,6 +965,19 @@ impl Handler for PublicKeyAggregator { } } +impl Handler> for PublicKeyAggregator { + type Result = (); + + fn handle( + &mut self, + msg: TypedEvent, + _: &mut Self::Context, + ) -> Self::Result { + let (msg, _) = msg.into_components(); + self.handle_committee_finalized(msg); + } +} + impl Handler> for PublicKeyAggregator { type Result = (); @@ -918,6 +988,10 @@ impl Handler> for PublicKeyAggregator { ) -> Self::Result { let (event, ec) = event.into_components(); trap(EType::PublickeyAggregation, &self.bus.with_ec(&ec), || { + if !matches!(self.duty, AggregationDuty::Eligible { .. }) { + return Ok(()); + } + let e3_id = event.e3_id.clone(); let pubkey = event.pubkey.clone(); let node = event.node.clone(); diff --git a/crates/aggregator/src/threshold_plaintext_aggregator.rs b/crates/aggregator/src/threshold_plaintext_aggregator.rs index 60b2967243..6cf2813364 100644 --- a/crates/aggregator/src/threshold_plaintext_aggregator.rs +++ b/crates/aggregator/src/threshold_plaintext_aggregator.rs @@ -18,7 +18,9 @@ use e3_events::{ ShareVerificationDispatched, SignedProofPayload, TypedEvent, VerificationKind, ZkResponse, }; use e3_fhe_params::BfvPreset; -use e3_sortition::{E3CommitteeContainsRequest, E3CommitteeContainsResponse, Sortition}; +use e3_sortition::{ + E3CommitteeContainsRequest, E3CommitteeContainsResponse, GetFinalizedCommittee, Sortition, +}; use e3_trbfv::{ calculate_threshold_decryption::CalculateThresholdDecryptionRequest, TrBFVConfig, TrBFVRequest, TrBFVResponse, @@ -168,11 +170,21 @@ impl ThresholdPlaintextAggregatorState { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum AggregationDuty { + PendingCommittee, + Eligible { rank: usize }, + Inactive, +} + pub struct ThresholdPlaintextAggregator { bus: BusHandle, sortition: Addr, e3_id: E3id, params_preset: BfvPreset, + node_address: String, + duty: AggregationDuty, + pending_shares: Vec>, state: Persistable, /// C6 cross-node proof fold state. c6_fold: ProofFoldState, @@ -187,6 +199,7 @@ pub struct ThresholdPlaintextAggregatorParams { pub sortition: Addr, pub e3_id: E3id, pub params_preset: BfvPreset, + pub node_address: String, } impl ThresholdPlaintextAggregator { @@ -199,6 +212,9 @@ impl ThresholdPlaintextAggregator { sortition: params.sortition, e3_id: params.e3_id, params_preset: params.params_preset, + node_address: params.node_address, + duty: AggregationDuty::PendingCommittee, + pending_shares: Vec::new(), state, c6_fold: ProofFoldState::new(), c7_proofs_pending: None, @@ -206,6 +222,55 @@ impl ThresholdPlaintextAggregator { } } + fn resolve_aggregation_duty(&mut self, committee: e3_events::Committee) { + self.duty = match committee.aggregation_rank_for(&self.node_address, self.threshold_m()) { + Some(rank) => { + info!( + e3_id = %self.e3_id, + node = %self.node_address, + rank = rank, + "Node is in the finalized plaintext aggregation priority chain" + ); + AggregationDuty::Eligible { rank } + } + None => { + info!( + e3_id = %self.e3_id, + node = %self.node_address, + "Node is outside the finalized plaintext aggregation priority chain" + ); + AggregationDuty::Inactive + } + }; + } + + fn threshold_m(&self) -> usize { + self.state + .get() + .and_then(|state| match state { + ThresholdPlaintextAggregatorState::Collecting(ref data) => { + Some(data.threshold_m as usize) + } + ThresholdPlaintextAggregatorState::VerifyingC6(ref data) => { + Some(data.threshold_m as usize) + } + ThresholdPlaintextAggregatorState::Computing(ref data) => { + Some(data.threshold_m as usize) + } + ThresholdPlaintextAggregatorState::GeneratingC7Proof(ref data) => { + Some(data.threshold_m as usize) + } + ThresholdPlaintextAggregatorState::Complete(_) => None, + }) + .unwrap_or_default() + } + + fn flush_pending_shares(&mut self, ctx: &mut Context) { + for event in std::mem::take(&mut self.pending_shares) { + ctx.notify(event); + } + } + pub fn add_share( &mut self, party_id: u64, @@ -599,6 +664,34 @@ impl Actor for ThresholdPlaintextAggregator { type Context = Context; fn started(&mut self, ctx: &mut Self::Context) { ctx.set_mailbox_capacity(MAILBOX_LIMIT); + + let sortition = self.sortition.clone(); + let e3_id = self.e3_id.clone(); + ctx.spawn( + async move { sortition.send(GetFinalizedCommittee { e3_id }).await } + .into_actor(self) + .map(|result, act, ctx| match result { + Ok(Some(committee)) => { + act.resolve_aggregation_duty(committee); + if matches!(act.duty, AggregationDuty::Eligible { .. }) { + act.flush_pending_shares(ctx); + } else { + act.pending_shares.clear(); + ctx.stop(); + } + } + Ok(None) => { + warn!(e3_id = %act.e3_id, "No finalized committee available for plaintext aggregation; stopping actor"); + act.pending_shares.clear(); + ctx.stop(); + } + Err(err) => { + warn!(e3_id = %act.e3_id, error = %err, "Failed to resolve finalized committee for plaintext aggregation; stopping actor"); + act.pending_shares.clear(); + ctx.stop(); + } + }), + ); } } @@ -640,6 +733,16 @@ impl Handler> for ThresholdPlaintextAggregato debug!(state=?self.state, "Aggregator has been closed for collecting so ignoring this event."); return Ok(()); }; + + match self.duty { + AggregationDuty::PendingCommittee => { + self.pending_shares.push(msg); + return Ok(()); + } + AggregationDuty::Inactive => return Ok(()), + AggregationDuty::Eligible { .. } => {} + } + let node = msg.node.clone(); let e3_id = msg.e3_id.clone(); let request = E3CommitteeContainsRequest::new(e3_id, node, msg, ctx.address()); diff --git a/crates/ciphernode-builder/src/ciphernode_builder.rs b/crates/ciphernode-builder/src/ciphernode_builder.rs index 44c7b44899..7c7b8765e8 100644 --- a/crates/ciphernode-builder/src/ciphernode_builder.rs +++ b/crates/ciphernode-builder/src/ciphernode_builder.rs @@ -455,6 +455,7 @@ impl CiphernodeBuilder { &self.chains, &mut provider_cache, &bus, + sortition.clone(), &self.contract_components, self.pubkey_agg, ) @@ -508,6 +509,7 @@ impl CiphernodeBuilder { e3_builder = e3_builder.with(PublicKeyAggregatorExtension::create( &bus, aggregator_preset, + &addr, )); if self.keyshare.is_none() { @@ -529,6 +531,7 @@ impl CiphernodeBuilder { &bus, &sortition, aggregator_preset, + &addr, )) } @@ -677,6 +680,7 @@ async fn setup_evm_system( chains: &Vec, provider_cache: &mut ProviderCache, bus: &BusHandle, + sortition: Addr, contract_components: &ContractComponents, pubkey_agg: bool, ) -> Result { @@ -696,7 +700,12 @@ async fn setup_evm_system( if contract_components.enclave { let write_provider = provider_cache.ensure_write_provider(chain).await?; let contract = &chain.contracts.enclave; - EnclaveSolWriter::attach(&bus, write_provider.clone(), contract.address()?); + EnclaveSolWriter::attach( + &bus, + write_provider.clone(), + contract.address()?, + sortition.clone(), + ); system.with_contract(contract.address()?, move |next| { EnclaveSolReader::setup(&next).recipient() }); @@ -735,13 +744,13 @@ async fn setup_evm_system( &bus, write_provider.clone(), contract.address()?, - pubkey_agg, + sortition.clone(), ); info!("CiphernodeRegistrySolWriter attached for publishing committees"); if pubkey_agg { info!("Attaching CommitteeFinalizer for score sortition"); - CommitteeFinalizer::attach(&bus); + CommitteeFinalizer::attach(&bus, sortition.clone()); } } Err(e) => error!( diff --git a/crates/cli/src/start.rs b/crates/cli/src/start.rs index 3f5c54416e..ae7d6c13f6 100644 --- a/crates/cli/src/start.rs +++ b/crates/cli/src/start.rs @@ -12,7 +12,7 @@ use crate::{ }; use anyhow::Result; use e3_ciphernode_builder::CiphernodeHandle; -use e3_config::{AppConfig, NodeRole}; +use e3_config::AppConfig; use e3_console::Console; use e3_events::{prelude::*, Shutdown}; use e3_socket_server::start_socket_server; @@ -84,23 +84,7 @@ pub async fn build_ciphernode( // add cli peers to the config config.add_peers(peers); - let node = match config.role() { - // Launch in aggregator configuration - NodeRole::Aggregator { - pubkey_write_path, - plaintext_write_path, - } => { - e3_entrypoint::start::aggregator_start::execute( - &config, - pubkey_write_path, - plaintext_write_path, - ) - .await? - } - - // Launch in ciphernode configuration - NodeRole::Ciphernode => e3_entrypoint::start::start::execute(&config).await?, - }; + let node = e3_entrypoint::start::start::execute(&config).await?; Ok(node) } diff --git a/crates/config/src/app_config.rs b/crates/config/src/app_config.rs index 7e027c5a0c..01fd60633d 100644 --- a/crates/config/src/app_config.rs +++ b/crates/config/src/app_config.rs @@ -22,26 +22,6 @@ use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::{collections::HashMap, env, path::PathBuf}; -/// Either "aggregator" or "ciphernode" -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] -#[serde(rename_all = "lowercase")] -#[serde(tag = "type")] -pub enum NodeRole { - /// Aggregator role - Aggregator { - pubkey_write_path: Option, - plaintext_write_path: Option, - }, - /// Ciphernode role - Ciphernode, -} - -impl Default for NodeRole { - fn default() -> Self { - NodeRole::Ciphernode - } -} - /// The structure within the app configuration #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(default)] @@ -65,9 +45,10 @@ pub struct NodeDefinition { pub data_dir: PathBuf, /// Override the base folder for enclave configuration defaults to `~/.config/enclave/{name}` on linux pub config_dir: PathBuf, - /// The node role eg. "ciphernode" or "aggregator" - #[serde(default)] - pub role: NodeRole, + /// Optional test-only path for writing the aggregated public key emitted by this node. + pub pubkey_write_path: Option, + /// Optional test-only path for writing the decrypted plaintext emitted by this node. + pub plaintext_write_path: Option, /// If a net key has not been set autogenerate one on start pub autonetkey: bool, /// If a password has not been set autogenerate one on start @@ -88,7 +69,8 @@ impl Default for NodeDefinition { log_file: PathBuf::from("log"), // ~/.config/enclave/log config_dir: std::path::PathBuf::new(), // ~/.config/enclave data_dir: std::path::PathBuf::new(), // ~/.config/enclave - role: NodeRole::Ciphernode, + pubkey_write_path: None, + plaintext_write_path: None, autonetkey: false, autopassword: false, autowallet: false, @@ -377,24 +359,20 @@ impl AppConfig { &self.nodes } - /// Get the node's role and enriched relevant provided configuration - pub fn role(&self) -> NodeRole { - match self.node_def().role.clone() { - NodeRole::Aggregator { - pubkey_write_path, - plaintext_write_path, - } => NodeRole::Aggregator { - // Normalize paths so that these paths are based on the config dir if they are - // relative - pubkey_write_path: pubkey_write_path - .as_ref() - .map(|p| self.paths.relative_to_config(p)), - plaintext_write_path: plaintext_write_path - .as_ref() - .map(|p| self.paths.relative_to_config(p)), - }, - NodeRole::Ciphernode => NodeRole::Ciphernode, - } + /// Optional normalized path for writing this node's aggregated public key. + pub fn pubkey_write_path(&self) -> Option { + self.node_def() + .pubkey_write_path + .as_ref() + .map(|p| self.paths.relative_to_config(p)) + } + + /// Optional normalized path for writing this node's aggregated plaintext output. + pub fn plaintext_write_path(&self) -> Option { + self.node_def() + .plaintext_write_path + .as_ref() + .map(|p| self.paths.relative_to_config(p)) } /// Get the value of autonetkey @@ -599,10 +577,8 @@ nodes: peers: - "one" - "two" - role: - type: aggregator - pubkey_write_path: "./output/pubkey.bin" - plaintext_write_path: "./output/plaintext.txt" + pubkey_write_path: "./output/pubkey.bin" + plaintext_write_path: "./output/plaintext.txt" "#; { @@ -661,13 +637,12 @@ nodes: // Write paths should be relative to config file if they are relative assert_eq!( - config.role(), - NodeRole::Aggregator { - pubkey_write_path: Some(PathBuf::from("/default/config/output/pubkey.bin")), - plaintext_write_path: Some(PathBuf::from( - "/default/config/output/plaintext.txt" - )) - } + config.pubkey_write_path(), + Some(PathBuf::from("/default/config/output/pubkey.bin")) + ); + assert_eq!( + config.plaintext_write_path(), + Some(PathBuf::from("/default/config/output/plaintext.txt")) ); }; Ok(()) @@ -702,7 +677,8 @@ nodes: expected_config_dir.join("enclave.config.yaml") ); - assert_eq!(config.role(), NodeRole::Ciphernode); + assert!(config.pubkey_write_path().is_none()); + assert!(config.plaintext_write_path().is_none()); Ok(()) }); diff --git a/crates/entrypoint/src/start/mod.rs b/crates/entrypoint/src/start/mod.rs index 53458bcd64..1ec7e86a99 100644 --- a/crates/entrypoint/src/start/mod.rs +++ b/crates/entrypoint/src/start/mod.rs @@ -4,5 +4,4 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -pub mod aggregator_start; pub mod start; diff --git a/crates/entrypoint/src/start/start.rs b/crates/entrypoint/src/start/start.rs index f1db8bd827..f1b4952208 100644 --- a/crates/entrypoint/src/start/start.rs +++ b/crates/entrypoint/src/start/start.rs @@ -8,6 +8,7 @@ use anyhow::Result; use e3_ciphernode_builder::{CiphernodeBuilder, CiphernodeHandle}; use e3_config::AppConfig; use e3_crypto::Cipher; +use e3_test_helpers::{PlaintextWriter, PublicKeyWriter}; use e3_zk_prover::ZkBackend; use rand::SeedableRng; use rand_chacha::rand_core::OsRng; @@ -24,18 +25,28 @@ pub async fn execute(config: &AppConfig) -> Result { .with_persistence(&config.log_file(), &config.db_file()) .with_sortition_score() .with_chains(&config.chains()) - .with_contract_enclave_reader() + .with_contract_enclave_full() .with_contract_bonding_registry() .with_max_threads() .with_contract_ciphernode_registry() .with_contract_slashing_manager() .with_trbfv() .with_zkproof(backend) + .with_pubkey_aggregation() + .with_threshold_plaintext_aggregation() .with_net(config.peers(), config.quic_port()) .with_shared_store() .with_shared_eventstore() .build() .await?; + if let Some(path) = config.pubkey_write_path() { + PublicKeyWriter::attach(&path, node.bus().clone()); + } + + if let Some(path) = config.plaintext_write_path() { + PlaintextWriter::attach(&path, node.bus().clone()); + } + Ok(node) } diff --git a/crates/events/src/committee.rs b/crates/events/src/committee.rs index b55a2a04e9..54d6987f0b 100644 --- a/crates/events/src/committee.rs +++ b/crates/events/src/committee.rs @@ -70,6 +70,23 @@ impl Committee { &self.members } + /// Number of committee members eligible to act as aggregators for this E3. + /// + /// This is the committee's failure budget, derived from the existing M/N + /// threshold. Rank 0 is the primary aggregator, rank 1 the first fallback, + /// and so on up to `len - threshold_m - 1`. + pub fn aggregator_count(&self, threshold_m: usize) -> usize { + self.len().saturating_sub(threshold_m) + } + + /// Resolve the current node's aggregation rank from the finalized committee + /// ordering. Returns `None` when the node is outside the fallback chain. + pub fn aggregation_rank_for(&self, addr: &str, threshold_m: usize) -> Option { + let party_id = self.party_id_for(addr)? as usize; + let aggregator_count = self.aggregator_count(threshold_m); + (party_id < aggregator_count).then_some(party_id) + } + pub fn len(&self) -> usize { self.members.len() } diff --git a/crates/evm/src/ciphernode_registry_sol.rs b/crates/evm/src/ciphernode_registry_sol.rs index d9e06e27a9..2236e09363 100644 --- a/crates/evm/src/ciphernode_registry_sol.rs +++ b/crates/evm/src/ciphernode_registry_sol.rs @@ -24,9 +24,14 @@ use e3_events::{ EffectsEnabled, EnclaveEvent, EnclaveEventData, EventSubscriber, EventType, OrderedSet, Proof, PublicKeyAggregated, Seed, Shutdown, TicketGenerated, TicketId, }; +use e3_sortition::{GetFinalizedCommittee, Sortition}; use e3_utils::{ArcBytes, NotifySync, MAILBOX_LIMIT}; +use std::time::Duration; +use tokio::time::sleep; use tracing::{error, info, trace}; +const COMMITTEE_SUBMISSION_INTERVAL_SECS: u64 = 2; + sol!( #[sol(rpc)] #[derive(Debug)] @@ -270,6 +275,7 @@ pub struct CiphernodeRegistrySolWriter

{ provider: EthProvider

, contract_address: Address, bus: BusHandle, + sortition: Addr, } impl CiphernodeRegistrySolWriter

{ @@ -277,11 +283,13 @@ impl CiphernodeRegistrySolWriter bus: &BusHandle, provider: EthProvider

, contract_address: Address, + sortition: Addr, ) -> Result { Ok(Self { provider, contract_address, bus: bus.clone(), + sortition, }) } @@ -289,23 +297,26 @@ impl CiphernodeRegistrySolWriter bus: &BusHandle, provider: EthProvider

, contract_address: Address, - is_aggregator: bool, + sortition: Addr, ) { let runner = run_once::({ let bus = bus.clone(); move |_| { - let addr = - CiphernodeRegistrySolWriter::new(&bus, provider, contract_address)?.start(); - - if is_aggregator { - bus.subscribe_all( - &[ - EventType::PublicKeyAggregated, - EventType::CommitteeFinalizeRequested, - ], - addr.clone().into(), - ) - } + let addr = CiphernodeRegistrySolWriter::new( + &bus, + provider, + contract_address, + sortition.clone(), + )? + .start(); + + bus.subscribe_all( + &[ + EventType::PublicKeyAggregated, + EventType::CommitteeFinalizeRequested, + ], + addr.clone().into(), + ); bus.subscribe_all( &[ @@ -414,6 +425,19 @@ impl Handler { + info!(e3_id = %e3_id, "Committee already finalized or no longer requested, skipping finalizeCommittee submission"); + return; + } + Err(err) => { + bus.err(EType::Evm, err); + return; + } + Ok(true) => {} + } + info!("Finalizing committee for E3 {:?}", e3_id); let result = finalize_committee_on_registry(provider, contract_address, e3_id).await; @@ -422,8 +446,16 @@ impl Handler { - error!("Failed to finalize committee: {}", format_evm_error(&err)); - bus.err(EType::Evm, err); + let decoded = format_evm_error(&err); + if decoded.contains("CommitteeAlreadyFinalized") { + info!( + "Committee finalization already landed on-chain: {}", + decoded + ); + } else { + error!("Failed to finalize committee: {}", decoded); + bus.err(EType::Evm, err); + } } } }) @@ -443,8 +475,61 @@ impl Handler rank, + Err(err) => { + bus.err(EType::Evm, err); + return; + } + }; + + let Some(rank) = rank else { + info!(e3_id = %e3_id, node = %my_address, "Node is outside the finalized aggregation priority chain, skipping committee publication"); + return; + }; + + if rank > 0 { + let delay = Duration::from_secs(rank as u64 * COMMITTEE_SUBMISSION_INTERVAL_SECS); + info!(e3_id = %e3_id, node = %my_address, rank = rank, ?delay, "Waiting before fallback committee publication attempt"); + sleep(delay).await; + } + + match should_submit_committee_public_key( + provider.clone(), + contract_address, + e3_id.clone(), + ) + .await + { + Ok(false) => { + info!(e3_id = %e3_id, node = %my_address, rank = rank, "Public key already published or committee not finalizable, skipping committee publication"); + return; + } + Err(err) => { + bus.err(EType::Evm, err); + return; + } + Ok(true) => {} + } + let result = publish_committee_to_registry( provider, contract_address, @@ -460,8 +545,13 @@ impl Handler { - error!("Failed to publish committee: {}", format_evm_error(&err)); - bus.err(EType::Evm, err); + let decoded = format_evm_error(&err); + if decoded.contains("CommitteeAlreadyPublished") { + info!("Committee publication already landed on-chain: {}", decoded); + } else { + error!("Failed to publish committee: {}", decoded); + bus.err(EType::Evm, err); + } } } }) @@ -542,6 +632,61 @@ pub async fn finalize_committee_on_registry( + provider: EthProvider

, + contract_address: Address, + e3_id: E3id, +) -> Result { + let e3_id_u256: U256 = e3_id.try_into()?; + let contract = ICiphernodeRegistry::new(contract_address, provider.provider()); + let stage = contract.getCommitteeStage(e3_id_u256).call().await?; + Ok(stage == 1u8) +} + +async fn should_submit_committee_public_key( + provider: EthProvider

, + contract_address: Address, + e3_id: E3id, +) -> Result { + let e3_id_u256: U256 = e3_id.try_into()?; + let contract = ICiphernodeRegistry::new(contract_address, provider.provider()); + let stage = contract.getCommitteeStage(e3_id_u256).call().await?; + if stage != 2u8 { + return Ok(false); + } + + let public_key_hash = contract.publicKeyHashes(e3_id_u256).call().await?; + Ok(public_key_hash == B256::ZERO) +} + +async fn committee_threshold_m( + provider: EthProvider

, + contract_address: Address, + e3_id: &E3id, +) -> Result { + let e3_id_u256: U256 = e3_id.clone().try_into()?; + let contract = ICiphernodeRegistry::new(contract_address, provider.provider()); + let viability = contract.getCommitteeViability(e3_id_u256).call().await?; + Ok(viability.thresholdM as usize) +} + +async fn aggregation_rank_for_e3( + sortition: &Addr, + provider: EthProvider

, + contract_address: Address, + e3_id: &E3id, + node_address: &str, +) -> Result> { + let committee = sortition + .send(GetFinalizedCommittee { + e3_id: e3_id.clone(), + }) + .await? + .ok_or_else(|| anyhow::anyhow!("No finalized committee available for {}", e3_id))?; + let threshold_m = committee_threshold_m(provider, contract_address, e3_id).await?; + Ok(committee.aggregation_rank_for(node_address, threshold_m)) +} + pub async fn publish_committee_to_registry( provider: EthProvider

, contract_address: Address, @@ -605,10 +750,10 @@ impl CiphernodeRegistrySol { bus: &BusHandle, provider: EthProvider

, contract_address: Address, - is_aggregator: bool, + sortition: Addr, ) where P: Provider + WalletProvider + Clone + 'static, { - CiphernodeRegistrySolWriter::attach(bus, provider, contract_address, is_aggregator); + CiphernodeRegistrySolWriter::attach(bus, provider, contract_address, sortition); } } diff --git a/crates/evm/src/enclave_sol_writer.rs b/crates/evm/src/enclave_sol_writer.rs index f35ab1c4fa..958fe7a88d 100644 --- a/crates/evm/src/enclave_sol_writer.rs +++ b/crates/evm/src/enclave_sol_writer.rs @@ -17,7 +17,7 @@ use alloy::{ primitives::{Bytes, U256}, rpc::types::TransactionReceipt, }; -use anyhow::Result; +use anyhow::{bail, Result}; use e3_events::BusHandle; use e3_events::EnclaveEventData; use e3_events::EventType; @@ -26,10 +26,16 @@ use e3_events::{prelude::*, EffectsEnabled}; use e3_events::{run_once, EnclaveEvent}; use e3_events::{E3Stage, E3StageChanged}; use e3_events::{E3id, EType, PlaintextAggregated, Proof}; +use e3_sortition::{GetFinalizedCommittee, Sortition}; use e3_utils::NotifySync; use e3_utils::MAILBOX_LIMIT; +use e3_zk_helpers::CiphernodesCommitteeSize; +use std::time::Duration; +use tokio::time::sleep; use tracing::info; +const PLAINTEXT_SUBMISSION_INTERVAL_SECS: u64 = 2; + sol!( #[sol(rpc)] IEnclave, @@ -41,6 +47,7 @@ pub struct EnclaveSolWriter

{ provider: EthProvider

, contract_address: Address, bus: BusHandle, + sortition: Addr, } impl EnclaveSolWriter

{ @@ -48,19 +55,28 @@ impl EnclaveSolWriter

{ bus: &BusHandle, provider: EthProvider

, contract_address: Address, + sortition: Addr, ) -> Result { Ok(Self { provider, contract_address, bus: bus.clone(), + sortition, }) } - pub fn attach(bus: &BusHandle, provider: EthProvider

, contract_address: Address) { + pub fn attach( + bus: &BusHandle, + provider: EthProvider

, + contract_address: Address, + sortition: Addr, + ) { let addr = run_once::({ let bus = bus.clone(); move |_| { - let addr = EnclaveSolWriter::new(&bus, provider, contract_address)?.start(); + let addr = + EnclaveSolWriter::new(&bus, provider, contract_address, sortition.clone())? + .start(); bus.subscribe_all( &[ EventType::PlaintextAggregated, @@ -122,6 +138,7 @@ impl Handler Handler rank, + Err(err) => { + bus.err(EType::Evm, err); + return; + } + }; + + let Some(rank) = rank else { + info!(e3_id = %e3_id, node = %my_address, "Node is outside the finalized plaintext aggregation priority chain, skipping plaintext submission"); + return; + }; + + if rank > 0 { + let delay = + Duration::from_secs(rank as u64 * PLAINTEXT_SUBMISSION_INTERVAL_SECS); + info!(e3_id = %e3_id, node = %my_address, rank = rank, ?delay, "Waiting before fallback plaintext publication attempt"); + sleep(delay).await; + } + + match should_submit_plaintext_output( + provider.clone(), + contract_address, + e3_id.clone(), + ) + .await + { + Ok(false) => { + info!(e3_id = %e3_id, node = %my_address, rank = rank, "Plaintext already published or E3 no longer in CiphertextReady, skipping plaintext submission"); + return; + } + Err(err) => { + bus.err(EType::Evm, err); + return; + } + Ok(true) => {} + } + let result = publish_plaintext_output( provider, contract_address, @@ -168,13 +233,21 @@ impl Handler { - bus.err( - EType::Evm, - anyhow::anyhow!( - "Error publishing plaintext output: {}", - format_evm_error(&err) - ), - ); + let decoded = format_evm_error(&err); + if decoded.contains("InvalidStage") + || decoded.contains("CommitteeDutiesCompleted") + || decoded.contains("CiphertextOutputNotPublished") + { + info!( + "Plaintext publication already resolved on-chain: {}", + decoded + ); + } else { + bus.err( + EType::Evm, + anyhow::anyhow!("Error publishing plaintext output: {}", decoded), + ); + } } } } @@ -266,6 +339,52 @@ async fn publish_plaintext_output( .await } +async fn should_submit_plaintext_output( + provider: EthProvider

, + contract_address: Address, + e3_id: E3id, +) -> Result { + let e3_id_u256: U256 = e3_id.try_into()?; + let contract = IEnclave::new(contract_address, provider.provider()); + let stage = contract.getE3Stage(e3_id_u256).call().await?; + Ok(stage == 4u8) +} + +async fn plaintext_threshold_m( + provider: EthProvider

, + contract_address: Address, + e3_id: &E3id, +) -> Result { + let e3_id_u256: U256 = e3_id.clone().try_into()?; + let contract = IEnclave::new(contract_address, provider.provider()); + let e3 = contract.getE3(e3_id_u256).call().await?; + let committee = match e3.committeeSize { + 0 => CiphernodesCommitteeSize::Micro, + 1 => CiphernodesCommitteeSize::Small, + 2 => CiphernodesCommitteeSize::Medium, + 3 => CiphernodesCommitteeSize::Large, + _ => bail!("Unknown committee size in E3"), + }; + Ok(committee.values().threshold) +} + +async fn plaintext_aggregation_rank_for_e3( + sortition: &Addr, + provider: EthProvider

, + contract_address: Address, + e3_id: &E3id, + node_address: &str, +) -> Result> { + let committee = sortition + .send(GetFinalizedCommittee { + e3_id: e3_id.clone(), + }) + .await? + .ok_or_else(|| anyhow::anyhow!("No finalized committee available for {}", e3_id))?; + let threshold_m = plaintext_threshold_m(provider, contract_address, e3_id).await?; + Ok(committee.aggregation_rank_for(node_address, threshold_m)) +} + async fn process_e3_failure( provider: EthProvider

, contract_address: Address, diff --git a/crates/sortition/Readme.md b/crates/sortition/Readme.md index 0a6eaea8de..ce92fbf9fa 100644 --- a/crates/sortition/Readme.md +++ b/crates/sortition/Readme.md @@ -102,7 +102,7 @@ sequenceDiagram Keyshare->>Keyshare: Persist secret key Keyshare->>EventBus: KeyshareCreated(e3Id, node, pubkey, chainId) - Note over EventBus,PublicKeyAggregator: Phase 7: Public Key Aggregation + Note over EventBus,PublicKeyAggregator: Phase 7: Public Key Aggregation on eligible committee ranks EventBus->>PublicKeyAggregator: KeyshareCreated PublicKeyAggregator->>Sortition: GetNodesForE3(e3Id, chainId) @@ -114,7 +114,7 @@ sequenceDiagram PublicKeyAggregator->>PublicKeyAggregator: fhe.get_aggregate_public_key(keyshares) PublicKeyAggregator->>PublicKeyAggregator: Aggregate public key shares PublicKeyAggregator->>EventBus: PublicKeyAggregated(e3Id, pubkey, nodes, chainId) - PublicKeyAggregator->>CiphernodeRegistry: publishPublicKey(e3Id, pubkey, nodes) + PublicKeyAggregator->>CiphernodeRegistry: publishPublicKey(e3Id, pubkey, nodes) after rank-based delay end Note over Operator,PlaintextAggregator: Phase 8: Encryption & Computation diff --git a/crates/sortition/src/ciphernode_selector.rs b/crates/sortition/src/ciphernode_selector.rs index 481e0cee35..296d2c06b9 100644 --- a/crates/sortition/src/ciphernode_selector.rs +++ b/crates/sortition/src/ciphernode_selector.rs @@ -235,6 +235,13 @@ impl Handler> for CiphernodeSelector { party_id = party_id, "Node is in finalized committee, emitting CiphernodeSelected" ); + if party_id == 0 { + info!( + node = self.address, + e3_id = %msg.e3_id, + "[SORTITION] Node is the finalized committee primary (party_id=0)" + ); + } bus.publish( CiphernodeSelected { diff --git a/crates/sortition/src/sortition.rs b/crates/sortition/src/sortition.rs index d46805e6bc..70f0e817a5 100644 --- a/crates/sortition/src/sortition.rs +++ b/crates/sortition/src/sortition.rs @@ -204,6 +204,21 @@ impl Deref for E3CommitteeContainsResponse { } } +#[derive(Message, Clone, Debug, PartialEq, Eq)] +#[rtype(result = "Option")] +pub struct GetLocalNodeSortitionRank { + pub e3_id: E3id, + pub seed: Seed, + pub size: usize, + pub chain_id: u64, +} + +#[derive(Message, Clone, Debug, PartialEq, Eq)] +#[rtype(result = "Option")] +pub struct GetFinalizedCommittee { + pub e3_id: E3id, +} + /// Sortition actor that manages the sortition algorithm and the node state. pub struct Sortition { /// Persistent map of `chain_id -> SortitionBackend`. @@ -737,6 +752,25 @@ where } } +impl Handler for Sortition { + type Result = MessageResult; + + fn handle(&mut self, msg: GetLocalNodeSortitionRank, _: &mut Self::Context) -> Self::Result { + MessageResult( + self.get_node_index(msg.e3_id, msg.seed, msg.size, msg.chain_id) + .map(|(party_index, _)| party_index), + ) + } +} + +impl Handler for Sortition { + type Result = MessageResult; + + fn handle(&mut self, msg: GetFinalizedCommittee, _: &mut Self::Context) -> Self::Result { + MessageResult(self.get_committee(&msg.e3_id)) + } +} + impl Handler> for Sortition { type Result = (); diff --git a/dappnode/README.md b/dappnode/README.md index 370ba5c532..e041f12515 100644 --- a/dappnode/README.md +++ b/dappnode/README.md @@ -49,7 +49,6 @@ Once this package is published to the DAppStore: - `RPC_URL` – WebSocket RPC endpoint (e.g. `wss://ethereum-sepolia-rpc.publicnode.com`) - `NETWORK` – e.g. `sepolia`, `mainnet`, `localhost` - Contract addresses + deploy blocks - - Node role (`ciphernode` or `aggregator`) - Optional keys and peers 4. Confirm and finish the installation. @@ -126,10 +125,6 @@ All runtime configuration is done via environment variables. They are: - **`NETWORK`** Logical network name written into the Enclave config (e.g. `sepolia`, `mainnet`, `localhost`). -- **`NODE_ROLE`** - - `ciphernode` – participate in threshold decryption. - - `aggregator` – coordinate operations, requires a wallet key. - - **`ETH_ADDRESS`** Optional Ethereum address to bind the node to. Leave empty to let Enclave handle it. @@ -163,8 +158,8 @@ block heights. - **`NETWORK_PRIVATE_KEY`** Optional libp2p network key. If set, `entrypoint.sh` calls: - `enclave net set-key --config /data/config.yaml --net-keypair "$NETWORK_PRIVATE_KEY"` -- **`PRIVATE_KEY`** Optional Ethereum private key (hex). Only needed for aggregator mode. If set, - `entrypoint.sh` calls: +- **`PRIVATE_KEY`** Optional Ethereum private key (hex). Provide it when the node must submit + on-chain transactions. If set, `entrypoint.sh` calls: - `enclave wallet set --config /data/config.yaml --private-key "$PRIVATE_KEY"` ### Peers @@ -189,11 +184,12 @@ screen after installation, as per DAppNode’s env behavior. At container startup, `entrypoint.sh`: 1. Validates `RPC_URL` is non-empty and starts with `ws://` or `wss://`. -2. Applies sensible defaults for `NETWORK`, `QUIC_PORT`, `NODE_ROLE`, and `LOG_LEVEL`. +2. Applies sensible defaults for `NETWORK`, `QUIC_PORT`, and `LOG_LEVEL`. 3. Uses `envsubst` to render `config.template.yaml` into `/data/config.yaml`, substituting: - - node address, role, ports - - network name and RPC URL - - contract addresses and deploy blocks + +- node address and ports +- network name and RPC URL +- contract addresses and deploy blocks 4. Optionally programs password, network key, and wallet key via the `enclave` CLI. 5. Builds CLI args, including verbosity and `--peer` flags from `PEERS`. diff --git a/dappnode/config.template.yaml b/dappnode/config.template.yaml index a5f5703542..254a34145c 100644 --- a/dappnode/config.template.yaml +++ b/dappnode/config.template.yaml @@ -4,8 +4,6 @@ node: address: '${NODE_ADDRESS}' quic_port: ${QUIC_PORT} - role: - type: ${NODE_ROLE} autonetkey: true autopassword: true autowallet: true diff --git a/dappnode/docker-compose.yml b/dappnode/docker-compose.yml index 512f8abd4d..9f6a05602c 100644 --- a/dappnode/docker-compose.yml +++ b/dappnode/docker-compose.yml @@ -16,7 +16,6 @@ services: RPC_URL: '' # Network & node config NETWORK: 'sepolia' - NODE_ROLE: 'ciphernode' NODE_ADDRESS: '' QUIC_PORT: '37173' PEERS: '' diff --git a/dappnode/entrypoint.sh b/dappnode/entrypoint.sh index e3ecdb7568..e4ae969d36 100644 --- a/dappnode/entrypoint.sh +++ b/dappnode/entrypoint.sh @@ -27,7 +27,6 @@ fi # Set defaults export NETWORK="${NETWORK:-sepolia}" export QUIC_PORT="${QUIC_PORT:-37173}" -export NODE_ROLE="${NODE_ROLE:-ciphernode}" export NODE_ADDRESS="${NODE_ADDRESS:-}" export LOG_LEVEL="${LOG_LEVEL:-info}" diff --git a/dappnode/setup-wizard.yml b/dappnode/setup-wizard.yml index 183fb8b259..286cb2d571 100644 --- a/dappnode/setup-wizard.yml +++ b/dappnode/setup-wizard.yml @@ -53,22 +53,6 @@ fields: required: false pattern: '^(0x[a-fA-F0-9]{64})?$' - # Node settings - - id: NODE_ROLE - target: - type: environment - name: NODE_ROLE - service: ciphernode - title: 'Node Role' - default: 'ciphernode' - description: | - - **ciphernode**: Participate in threshold decryption - - **aggregator**: Coordinate operations - enum: - - ciphernode - - aggregator - required: false - # Contracts - id: ENCLAVE_CONTRACT target: diff --git a/deploy/agg.yaml b/deploy/agg.yaml index e767a2b2f0..d19a4841dc 100644 --- a/deploy/agg.yaml +++ b/deploy/agg.yaml @@ -1,8 +1,6 @@ node: address: '${ADDRESS}' quic_port: ${QUIC_PORT} - role: - type: aggregator peers: - '/dns4/cn1/udp/9091/quic-v1' - '/dns4/cn2/udp/9092/quic-v1' diff --git a/deploy/cn2.yaml b/deploy/cn2.yaml index 3445012c76..311bca41da 100644 --- a/deploy/cn2.yaml +++ b/deploy/cn2.yaml @@ -4,7 +4,7 @@ node: peers: - '/dns4/cn1/udp/9091/quic-v1' - '/dns4/cn3/udp/9093/quic-v1' - - '/dns4/aggregator/udp/9094/quic-v1' + - '/dns4/cn4/udp/9094/quic-v1' chains: - name: 'sepolia' rpc_url: '${RPC_URL}' diff --git a/deploy/cn3.yaml b/deploy/cn3.yaml index e12592eb8f..043d858e6b 100644 --- a/deploy/cn3.yaml +++ b/deploy/cn3.yaml @@ -4,7 +4,7 @@ node: peers: - '/dns4/cn1/udp/9091/quic-v1' - '/dns4/cn2/udp/9092/quic-v1' - - '/dns4/aggregator/udp/9094/quic-v1' + - '/dns4/cn4/udp/9094/quic-v1' chains: - name: 'sepolia' rpc_url: '${RPC_URL}' diff --git a/deploy/cn4.yaml b/deploy/cn4.yaml new file mode 100644 index 0000000000..d19a4841dc --- /dev/null +++ b/deploy/cn4.yaml @@ -0,0 +1,14 @@ +node: + address: '${ADDRESS}' + quic_port: ${QUIC_PORT} + peers: + - '/dns4/cn1/udp/9091/quic-v1' + - '/dns4/cn2/udp/9092/quic-v1' + - '/dns4/cn3/udp/9093/quic-v1' +chains: + - name: 'sepolia' + rpc_url: '${RPC_URL}' + contracts: + enclave: '${SEPOLIA_ENCLAVE_ADDRESS}' + ciphernode_registry: '${SEPOLIA_CIPHERNODE_REGISTRY_ADDRESS}' + bonding_registry: '${SEPOLIA_BONDING_REGISTRY}' diff --git a/deploy/copy-secrets.sh b/deploy/copy-secrets.sh index 2bfc6132b6..c8010794e3 100755 --- a/deploy/copy-secrets.sh +++ b/deploy/copy-secrets.sh @@ -17,14 +17,14 @@ YELLOW='\033[1;33m' NC='\033[0m' # No Color # List of target files -TARGETS=("cn1" "cn2" "cn3" "agg") +TARGETS=("cn1" "cn2" "cn3" "cn4") # Sample network private keys NETWORK_KEY_CN1="0x11a1e500a548b70d88184a1e042900c0ed6c57f8710bcc35dc8c85fa33d3f580" NETWORK_KEY_CN2="0x21a1e500a548b70d88184a1e042900c0ed6c57f8710bcc35dc8c85fa33d3f580" NETWORK_KEY_CN3="0x31a1e500a548b70d88184a1e042900c0ed6c57f8710bcc35dc8c85fa33d3f580" -NETWORK_KEY_AGG="0x41a1e500a548b70d88184a1e042900c0ed6c57f8710bcc35dc8c85fa33d3f580" -NET_KEYS=($NETWORK_KEY_CN1 $NETWORK_KEY_CN2 $NETWORK_KEY_CN3 $NETWORK_KEY_AGG) +NETWORK_KEY_CN4="0x41a1e500a548b70d88184a1e042900c0ed6c57f8710bcc35dc8c85fa33d3f580" +NET_KEYS=($NETWORK_KEY_CN1 $NETWORK_KEY_CN2 $NETWORK_KEY_CN3 $NETWORK_KEY_CN4) # Check if source file exists if [ ! -f "$SOURCE" ]; then diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 7fc5b3445f..32ed7b0c70 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -9,7 +9,6 @@ services: target: secrets.json env_file: .env environment: - AGGREGATOR: 'false' ADDRESS: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' QUIC_PORT: 9091 deploy: @@ -28,7 +27,6 @@ services: target: secrets.json env_file: .env environment: - AGGREGATOR: 'false' ADDRESS: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC' QUIC_PORT: 9092 deploy: @@ -47,7 +45,6 @@ services: target: secrets.json env_file: .env environment: - AGGREGATOR: 'false' ADDRESS: '0x90F79bf6EB2c4f870365E785982E1f101E93b906' QUIC_PORT: 9093 deploy: @@ -56,19 +53,18 @@ services: networks: - global-network - aggregator: + cn4: image: { { IMAGE } } depends_on: - cn1 volumes: - - ./agg.yaml:/home/ciphernode/.config/enclave/config.yaml:ro - - agg-data:/home/ciphernode/.local/share/enclave + - ./cn4.yaml:/home/ciphernode/.config/enclave/config.yaml:ro + - cn4-data:/home/ciphernode/.local/share/enclave secrets: - - source: secrets_agg + - source: secrets_cn4 target: secrets.json env_file: .env environment: - AGGREGATOR: 'true' ADDRESS: '0x8626a6940E2eb28930eFb4CeF49B2d1F2C9C1199' QUIC_PORT: 9094 deploy: @@ -84,14 +80,14 @@ secrets: file: cn2.secrets.json secrets_cn3: file: cn3.secrets.json - secrets_agg: - file: agg.secrets.json + secrets_cn4: + file: cn4.secrets.json volumes: cn1-data: cn2-data: cn3-data: - agg-data: + cn4-data: networks: global-network: diff --git a/deploy/inspect.sh b/deploy/inspect.sh index 20959b5a2b..50a09afddd 100755 --- a/deploy/inspect.sh +++ b/deploy/inspect.sh @@ -42,7 +42,7 @@ get_logs_by_version enclave_cn3 echo "" echo "=================================" -echo " AGG " +echo " CN4 " echo "=================================" -get_logs_by_version enclave_aggregator +get_logs_by_version enclave_cn4 diff --git a/deploy/local/nodes.sh b/deploy/local/nodes.sh index 7b4e091fdc..a344192a27 100755 --- a/deploy/local/nodes.sh +++ b/deploy/local/nodes.sh @@ -8,7 +8,6 @@ concurrently \ --prefix-colors "blue,yellow" \ "anvil" \ "cd examples/CRISP && \ - enclave wallet set --name ag --private-key "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" && enclave wallet set --name cn1 --private-key "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" && enclave wallet set --name cn2 --private-key "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" && enclave wallet set --name cn3 --private-key "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6" && diff --git a/deploy/local/start.sh b/deploy/local/start.sh index 20beab7df5..41d36fc953 100755 --- a/deploy/local/start.sh +++ b/deploy/local/start.sh @@ -100,7 +100,7 @@ concurrently \ --names "ANVIL,NODES" \ --prefix-colors "blue,yellow" \ "anvil" \ - "cd examples/CRISP && enclave wallet set --name ag --private-key '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' && enclave nodes up -v" & + "cd examples/CRISP && enclave wallet set --name cn1 --private-key '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d' && enclave wallet set --name cn2 --private-key '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a' && enclave wallet set --name cn3 --private-key '0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6' && enclave wallet set --name cn4 --private-key '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a' && enclave wallet set --name cn5 --private-key '0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba' && enclave nodes up -v" & INFRA_PID=$! diff --git a/deploy/swarm_deployment.md b/deploy/swarm_deployment.md index 62555ed683..866ae5ccd5 100644 --- a/deploy/swarm_deployment.md +++ b/deploy/swarm_deployment.md @@ -106,7 +106,7 @@ To deploy with swarm we need to set up the secrets file for our cluster. ## What it does -- Copies `example.secrets.json` to create `cn1/2/3` and `agg.secrets.json` files +- Copies `example.secrets.json` to create `cn1/2/3/4.secrets.json` files - Skips existing files - Warns with yellow arrows (==>) if any files are identical to the example @@ -134,7 +134,7 @@ This will deploy the following services: ``` ❯ docker service ls ID NAME MODE REPLICAS IMAGE PORTS -tr44go8vevh1 enclave_aggregator replicated 1/1 ghcr.io/gnosisguild/ciphernode:latest +tr44go8vevh1 enclave_cn4 replicated 1/1 ghcr.io/gnosisguild/ciphernode:latest kdqktv85xcuv enclave_cn1 replicated 1/1 ghcr.io/gnosisguild/ciphernode:latest nguul381w6mu enclave_cn2 replicated 1/1 ghcr.io/gnosisguild/ciphernode:latest zgmwmv7cd63j enclave_cn3 replicated 1/1 ghcr.io/gnosisguild/ciphernode:latest diff --git a/docs/pages/CRISP/introduction.mdx b/docs/pages/CRISP/introduction.mdx index e1590b7aac..2f2b51ffc7 100644 --- a/docs/pages/CRISP/introduction.mdx +++ b/docs/pages/CRISP/introduction.mdx @@ -43,7 +43,7 @@ CRISP/ ├── crates/ # Rust libraries used by the server ├── circuits/ # Noir zero-knowledge circuits ├── scripts/ # Development scripts for running, testing, and deployment -├── enclave.config.yaml # Ciphernodes + aggregator config +├── enclave.config.yaml # Ciphernode config └── docker-compose.yaml # Optional multi-node deployment ``` diff --git a/docs/pages/ciphernode-operators/running.mdx b/docs/pages/ciphernode-operators/running.mdx index 157ce2720a..944d944fc6 100644 --- a/docs/pages/ciphernode-operators/running.mdx +++ b/docs/pages/ciphernode-operators/running.mdx @@ -21,7 +21,6 @@ DappNode provides a user-friendly interface for running a ciphernode with minima - `RPC_URL` - WebSocket RPC endpoint (e.g., `wss://ethereum-sepolia-rpc.publicnode.com`) - `NETWORK` - Network name (e.g., `sepolia`, `mainnet`) - Contract addresses and deploy blocks - - Node role (`ciphernode` or `aggregator`) - Optional: encryption password, network key, private key 4. Confirm and complete the installation @@ -33,12 +32,11 @@ DappNode provides a user-friendly interface for running a ciphernode with minima | --------------------- | -------------------------------------------- | -------- | | `RPC_URL` | WebSocket RPC endpoint | Yes | | `NETWORK` | Network name (sepolia, mainnet, etc.) | No | -| `NODE_ROLE` | `ciphernode` or `aggregator` | No | | `NODE_ADDRESS` | Your Ethereum address | No | | `QUIC_PORT` | UDP port for P2P networking (default: 37173) | No | | `ENCRYPTION_PASSWORD` | Password to encrypt local data | No | | `NETWORK_PRIVATE_KEY` | libp2p network key (ed25519) | No | -| `PRIVATE_KEY` | Ethereum private key (for aggregator) | No | +| `PRIVATE_KEY` | Ethereum private key (for on-chain writes) | No | | `PEERS` | Comma-separated peer multiaddresses | No | --- diff --git a/docs/pages/ciphernode-operators/tickets-and-sortition.mdx b/docs/pages/ciphernode-operators/tickets-and-sortition.mdx index b1e2b036b6..8937b4cd17 100644 --- a/docs/pages/ciphernode-operators/tickets-and-sortition.mdx +++ b/docs/pages/ciphernode-operators/tickets-and-sortition.mdx @@ -134,7 +134,8 @@ After the window closes and ≥ `threshold_n` tickets are present: 2. If enough operators submitted, the registry emits `CommitteeFinalized(e3Id, committee)` 3. If the threshold isn't met, `CommitteeFormationFailed` is emitted and the E3 fails 4. Selected nodes generate and publish key shares -5. Aggregated public key is published via `publishCommittee` +5. The finalized committee ordering determines the primary aggregation node, which publishes via + `publishCommittee` while later ranks act as fallbacks if needed ## Parameters diff --git a/docs/pages/project-template.mdx b/docs/pages/project-template.mdx index 42c6d6bd8a..1933cbd195 100644 --- a/docs/pages/project-template.mdx +++ b/docs/pages/project-template.mdx @@ -51,8 +51,8 @@ image is ready; skip this by exporting `SKIP_PROGRAM_COMPILE=1`. flags. - `server/.env`: holds the operator wallet key (never commit this file), RPC endpoints, and cron API tokens. -- `enclave.config.yaml`: describes your Ciphernodes and aggregator profile. The default file mirrors - the template you saw earlier in this repo. +- `enclave.config.yaml`: describes your ciphernode profiles. The default file mirrors the template + you saw earlier in this repo. ## Integrating the SDK @@ -82,8 +82,8 @@ contracts. ## Customizing Ciphernodes -`enclave.config.yaml` includes multiple node profiles (`cn1`, `cn2`, `cn3`, `ag`). Update the -addresses, peers, and QUIC ports to match your deployment. For production, replace +`enclave.config.yaml` includes multiple node profiles (`cn1`, `cn2`, `cn3`, `cn4`, `cn5`). Update +the addresses, peers, and QUIC ports to match your deployment. For production, replace `autonetkey/autopassword` with explicit secrets and run `enclave nodes up --detach` from a systemd service or container entrypoint. diff --git a/examples/CRISP/Readme.md b/examples/CRISP/Readme.md index efa8abb953..4a6fdf823e 100644 --- a/examples/CRISP/Readme.md +++ b/examples/CRISP/Readme.md @@ -24,7 +24,7 @@ CRISP/ ├── crates/ # Rust libraries used by the server ├── circuits/ # Noir zero-knowledge circuits ├── scripts/ # Development scripts for running, testing, and deployment -├── enclave.config.yaml # Ciphernodes + aggregator config +├── enclave.config.yaml # Ciphernode config └── docker-compose.yaml # Optional multi-node deployment ``` diff --git a/examples/CRISP/enclave.config.yaml b/examples/CRISP/enclave.config.yaml index 04dc00d664..221ee71ca3 100644 --- a/examples/CRISP/enclave.config.yaml +++ b/examples/CRISP/enclave.config.yaml @@ -61,11 +61,3 @@ nodes: ctrl_port: 50505 autonetkey: true autopassword: true - ag: - address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' - quic_port: 9206 - ctrl_port: 50506 - autonetkey: true - autopassword: true - role: - type: "aggregator" diff --git a/examples/CRISP/scripts/dev_cipher.sh b/examples/CRISP/scripts/dev_cipher.sh index 7760f6e8b2..9be991388f 100755 --- a/examples/CRISP/scripts/dev_cipher.sh +++ b/examples/CRISP/scripts/dev_cipher.sh @@ -8,14 +8,12 @@ rm -rf ./.enclave/data rm -rf ./.enclave/config rm -rf $READYFILE -PRIVATE_KEY_AG="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" PRIVATE_KEY_CN1="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" PRIVATE_KEY_CN2="0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" PRIVATE_KEY_CN3="0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6" PRIVATE_KEY_CN4="0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a" PRIVATE_KEY_CN5="0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba" -enclave wallet set --name ag --private-key "$PRIVATE_KEY_AG" enclave wallet set --name cn1 --private-key "$PRIVATE_KEY_CN1" enclave wallet set --name cn2 --private-key "$PRIVATE_KEY_CN2" enclave wallet set --name cn3 --private-key "$PRIVATE_KEY_CN3" diff --git a/examples/CRISP/scripts/setup_testnet.sh b/examples/CRISP/scripts/setup_testnet.sh index c7578aff97..f1f6468ddc 100644 --- a/examples/CRISP/scripts/setup_testnet.sh +++ b/examples/CRISP/scripts/setup_testnet.sh @@ -35,7 +35,6 @@ if [ ! -f .env ]; then fi source .env -enclave wallet set --name ag --private-key "$PRIVATE_KEY_AG" enclave wallet set --name cn1 --private-key "$PRIVATE_KEY_CN1" enclave wallet set --name cn2 --private-key "$PRIVATE_KEY_CN2" enclave wallet set --name cn3 --private-key "$PRIVATE_KEY_CN3" @@ -55,7 +54,7 @@ CN5=$(yq -r '.nodes.cn5.address' ./enclave.config.yaml) echo "Minting tokens" -# The aggregator is supposed to be the contract owner for testing +# The deployer key is still used for admin-only testnet setup calls export PRIVATE_KEY="$PRIVATE_KEY_AG" pnpm ciphernode:mint:tokens --ciphernode-address "$CN1" --network "sepolia" @@ -79,7 +78,7 @@ pnpm ciphernode:add:self --network "sepolia" echo "CIPHERNODES HAVE BEEN ADDED." -# Reset the private key to the aggregator for further operations (owner of contracts) +# Reset the private key to the deployer/admin account for follow-up operations export PRIVATE_KEY="$PRIVATE_KEY_AG" # wait diff --git a/packages/enclave-contracts/README.md b/packages/enclave-contracts/README.md index 8becd5eb25..250ca59cf3 100644 --- a/packages/enclave-contracts/README.md +++ b/packages/enclave-contracts/README.md @@ -111,6 +111,9 @@ To publish the public key of a committee, run pnpm run hardhat --network [network] committee:publish --e3-id [e3-id] --nodes [node address],[node address] --public-key [publickey] --proof [hex-encoded pk proof] ``` +`publishCommittee` is permissionless. Any eligible caller with a valid C5 proof +can submit the committee public key. + To activate an E3, run ```sh diff --git a/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json b/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json index c7b335ec71..d154c7b8bd 100644 --- a/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json +++ b/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json @@ -2565,5 +2565,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/Enclave.sol", - "buildInfoId": "solc-0_8_28-d3db7173950582d145915864c28ba15e975a7c98" + "buildInfoId": "solc-0_8_28-a5c2ec0646a40f95c66bc21729b7a906479f7537" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index 5300c1d0ae..ff4b422942 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -940,5 +940,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-d3db7173950582d145915864c28ba15e975a7c98" + "buildInfoId": "solc-0_8_28-a5c2ec0646a40f95c66bc21729b7a906479f7537" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index 7563739e37..4092dfa639 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -619,6 +619,30 @@ "internalType": "address[]", "name": "committeeNodes", "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "committeeScores", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "e3Id", + "type": "uint256" + } + ], + "name": "getCommitteeStage", + "outputs": [ + { + "internalType": "enum ICiphernodeRegistry.CommitteeStage", + "name": "stage", + "type": "uint8" } ], "stateMutability": "view", @@ -763,6 +787,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "e3Id", + "type": "uint256" + } + ], + "name": "publicKeyHashes", + "outputs": [ + { + "internalType": "bytes32", + "name": "publicKeyHash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -960,5 +1003,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-d3db7173950582d145915864c28ba15e975a7c98" + "buildInfoId": "solc-0_8_28-a5c2ec0646a40f95c66bc21729b7a906479f7537" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json index 15f9047249..5ccec53a86 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json @@ -2093,5 +2093,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IEnclave.sol", - "buildInfoId": "solc-0_8_28-d3db7173950582d145915864c28ba15e975a7c98" + "buildInfoId": "solc-0_8_28-a5c2ec0646a40f95c66bc21729b7a906479f7537" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index 480e8ce6c7..e070e570fd 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -954,5 +954,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-d3db7173950582d145915864c28ba15e975a7c98" + "buildInfoId": "solc-0_8_28-a5c2ec0646a40f95c66bc21729b7a906479f7537" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json b/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json index c9b682e2dc..cb4abd89e4 100644 --- a/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json +++ b/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json @@ -796,6 +796,30 @@ "internalType": "address[]", "name": "nodes", "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "scores", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "e3Id", + "type": "uint256" + } + ], + "name": "getCommitteeStage", + "outputs": [ + { + "internalType": "enum ICiphernodeRegistry.CommitteeStage", + "name": "", + "type": "uint8" } ], "stateMutability": "view", @@ -1259,30 +1283,30 @@ "type": "function" } ], - "bytecode": "0x6080604052348015600e575f5ffd5b5060156019565b60c9565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161560685760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b039081161460c65780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b613e2d806100d65f395ff3fe608060405234801561000f575f5ffd5b506004361061023f575f3560e01c80639f0f874a11610135578063e59e4695116100b4578063f26ef74e11610079578063f26ef74e14610571578063f2fde38b14610584578063f379b0df14610597578063f52fd803146105d1578063f6fc05d514610643575f5ffd5b8063e59e469514610516578063e6745e1314610529578063e82f3b701461053c578063ebf0c7171461054f578063f165053614610557575f5ffd5b8063c3a0ec30116100fa578063c3a0ec30146104ae578063ca2869a0146104bf578063cd6dc687146104de578063da881e5a146104f1578063dbb06c9314610504575f5ffd5b80639f0f874a1461044d578063a016493014610456578063a8a4d69b14610469578063bff232c11461047c578063c2b40ae41461048f575f5ffd5b8063715018a6116101c15780638d1ddfb1116101865780638d1ddfb1146103cd5780638da5cb5b146103e35780638e5ce3ad146103eb5780639015d371146103fe5780639a7a2ffc14610411575f5ffd5b8063715018a6146103465780637c92f5241461034e578063858142431461037b5780638a78bb151461039b5780638cb89ecb146103ae575f5ffd5b80632e7b716d116102075780632e7b716d146102d85780634d6861a6146102eb57806350e6d94c146102fe5780635d5047761461032057806370e36bbe14610333575f5ffd5b8063096b810a146102435780630f3e34121461025857806317d611201461026b5780632800d82914610294578063291a691b146102b5575b5f5ffd5b61025661025136600461323c565b61064c565b005b610256610266366004613257565b610798565b61027e610279366004613257565b6107db565b60405161028b919061326e565b60405180910390f35b6102a76102a2366004613257565b61091d565b60405190815260200161028b565b6102c86102c33660046132b9565b610969565b604051901515815260200161028b565b6102c86102e636600461323c565b610b43565b6102c86102f9366004613257565b610bf6565b6102c861030c36600461323c565b60066020525f908152604090205460ff1681565b6102c861032e3660046132f2565b610c35565b61025661034136600461323c565b610c79565b610256610cef565b61036161035c366004613320565b610d02565b6040805192835263ffffffff90911660208301520161028b565b60015461038e906001600160a01b031681565b60405161028b9190613355565b6102566103a936600461323c565b610ea9565b6102a76103bc366004613257565b60096020525f908152604090205481565b600454600160281b900464ffffffffff166102a7565b61038e610fe7565b600b5461038e906001600160a01b031681565b6102c861040c36600461323c565b611015565b61043761041f36600461323c565b60076020525f908152604090205464ffffffffff1681565b60405164ffffffffff909116815260200161028b565b6102a760035481565b61027e610464366004613257565b611032565b6102c86104773660046132f2565b6110c8565b61025661048a36600461323c565b61110c565b6102a761049d366004613257565b60086020525f908152604090205481565b6001546001600160a01b031661038e565b6102a76104cd366004613257565b5f9081526008602052604090205490565b6102566104ec366004613369565b61115d565b6102c86104ff366004613257565b6112ba565b5f5461038e906001600160a01b031681565b61025661052436600461323c565b611594565b610256610537366004613393565b61160c565b6102a761054a366004613257565b6117cf565b6102a7611800565b61055f601481565b60405160ff909116815260200161028b565b61025661057f3660046133f7565b611812565b61025661059236600461323c565b611b0c565b6004546105b39064ffffffffff80821691600160281b90041682565b6040805164ffffffffff93841681529290911660208301520161028b565b6106146105df366004613257565b5f908152600a6020819052604090912090810154600590910154909163ffffffff80831692600160201b900416908284101590565b60405161028b949392919093845263ffffffff9283166020850152911660408301521515606082015260800190565b6102a760025481565b610654610fe7565b6001600160a01b0316336001600160a01b0316148061067d57506001546001600160a01b031633145b61069a57604051632864c4e160e01b815260040160405180910390fd5b6106a381611015565b81906106cc576040516381e5828960e01b81526004016106c39190613355565b60405180910390fd5b506001600160a01b0381165f9081526007602052604081205464ffffffffff16906106fa9060049083611b46565b6001600160a01b0382165f908152600660205260408120805460ff19169055600280549161072783613511565b90915550506002546004546040805164ffffffffff80861682526020820194909452600160281b909204909216918101919091526001600160a01b038316907f8c008e3835f6c79bfcdb89f0f6ca8705e0b01049ee84a90b0e4da1c7ba9405d5906060015b60405180910390a25050565b6107a0611de8565b60038190556040518181527fbe772dc189863d512fa01e489c8eac204975aef1a8662d8b5a333804b5207ab79060200160405180910390a150565b5f818152600a602081905260408220600681015491810154606093919291816001600160401b0381111561081157610811613526565b60405190808252806020026020018201604052801561083a578160200160208202803683370190505b5090505f805b84811015610911576001866009015f8860060184815481106108645761086461354e565b5f9182526020808320909101546001600160a01b0316835282019290925260400190205460ff16600281111561089c5761089c61353a565b03610909578560060181815481106108b6576108b661354e565b905f5260205f20015f9054906101000a90046001600160a01b03168383815181106108e3576108e361354e565b6001600160a01b03909216602092830291909101909101528161090581613562565b9250505b600101610840565b50909695505050505050565b5f818152600a6020526040812081815460ff1660038111156109415761094161353a565b0361095f57604051630d4c1d9760e41b815260040160405180910390fd5b6003015492915050565b5f80546001600160a01b031633146109945760405163e4c2a7eb60e01b815260040160405180910390fd5b5f848152600a6020526040812090815460ff1660038111156109b8576109b861353a565b146109d6576040516374ff462560e11b815260040160405180910390fd5b60015460408051630cc37d8f60e11b815290515f926001600160a01b031691631986fb1e9160048083019260209291908290030181865afa158015610a1d573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a41919061357a565b905080610a5460408601602087016135a4565b63ffffffff161115610a6c60408601602087016135a4565b829091610a9a576040516344ec930f60e01b815263ffffffff909216600483015260248201526044016106c3565b5050815460ff1916600190811783558201859055436002830155600354610ac190426135bd565b6003830155610ad560058301856002613173565b50610ade611800565b5f87815260086020526040908190209190915560028301546003840154915188927f381d281d32f95ef8fe4e5f3b263ea6a32d03d331e1a141ae1da996dc02a7a17092610b2f928a928a92916135d0565b60405180910390a250600195945050505050565b5f610b4d82611015565b610b5857505f919050565b6001546001600160a01b0316610b81576040516350ca893360e01b815260040160405180910390fd5b600154604051639f8a13d760e01b81526001600160a01b0390911690639f8a13d790610bb1908590600401613355565b602060405180830381865afa158015610bcc573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610bf0919061362f565b92915050565b5f818152600a602052604081206001815460ff166003811115610c1b57610c1b61353a565b14610c2857505f92915050565b6003015442111592915050565b5f60015f848152600a602090815260408083206001600160a01b038716845260090190915290205460ff166002811115610c7157610c7161353a565b149392505050565b610c81611de8565b6001600160a01b038116610ca85760405163d92e233d60e01b815260040160405180910390fd5b5f80546001600160a01b0319166001600160a01b038316908117825560405190917f2c8267accd82e977550ed2349c73311183cd22e306347be4453c8d130995e3c991a250565b610cf7611de8565b610d005f611e1a565b565b600b545f9081906001600160a01b03163314610d315760405163fcef374960e01b815260040160405180910390fd5b5f858152600a602052604090206002815460ff166003811115610d5657610d5661353a565b14610d7457604051634f4b461f60e11b815260040160405180910390fd5b60058101546001600160a01b0386165f90815260098301602052604090205463ffffffff909116925060019060ff166002811115610db457610db461353a565b14610dc457600a01549150610ea1565b6001600160a01b0385165f9081526009820160205260408120805460ff19166002179055600a8201805491610df883613511565b919050555080600a01549250846001600160a01b0316867f6c783b92374361b4d6efaf29673b89437ee969bb3c9d2d5d86b143ad5447b8498686604051610e49929190918252602082015260400190565b60405180910390a36040805184815263ffffffff84166020820181905285101591810182905287907f119cb11dd0a68c257d6dc9b06dcb37dd422ce276eb8bf3cd0b7079a116b8e2989060600160405180910390a250505b935093915050565b610eb1610fe7565b6001600160a01b0316336001600160a01b03161480610eda57506001546001600160a01b031633145b610ef757604051632864c4e160e01b815260040160405180910390fd5b610f0081611015565b610fe45760048054600160281b900464ffffffffff1690610f2a906001600160a01b038416611e8a565b6001600160a01b0382165f908152600660209081526040808320805460ff1916600117905560079091528120805464ffffffffff841664ffffffffff199091161790556002805491610f7b83613562565b90915550506002546004546040805164ffffffffff80861682526020820194909452600160281b909204909216918101919091526001600160a01b038316907f3318d261fe14a5761d2d1e21555652f623d2134c430a9883c9ad6e958bb0db539060600161078c565b50565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b03165f9081526006602052604090205460ff1690565b5f818152600a60205260409020600481015460609190611065576040516322e679e360e11b815260040160405180910390fd5b806006018054806020026020016040519081016040528092919081815260200182805480156110bb57602002820191905f5260205f20905b81546001600160a01b0316815260019091019060200180831161109d575b5050505050915050919050565b5f805f848152600a602090815260408083206001600160a01b038716845260090190915290205460ff1660028111156111035761110361353a565b14159392505050565b611114611de8565b6001600160a01b03811661113b5760405163d92e233d60e01b815260040160405180910390fd5b600b80546001600160a01b0319166001600160a01b0392909216919091179055565b5f611166612060565b805490915060ff600160401b82041615906001600160401b03165f8115801561118c5750825b90505f826001600160401b031660011480156111a75750303b155b9050811580156111b5575080155b156111d35760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156111fd57845460ff60401b1916600160401b1785555b6001600160a01b0387166112245760405163d92e233d60e01b815260040160405180910390fd5b61122d33612088565b61123960046014612099565b61124286610798565b61124a610fe7565b6001600160a01b0316876001600160a01b03161461126b5761126b87611b0c565b83156112b157845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050565b5f818152600a6020526040812081815460ff1660038111156112de576112de61353a565b036112fc57604051630d4c1d9760e41b815260040160405180910390fd5b6001815460ff1660038111156113145761131461353a565b1461133257604051631860f69960e31b815260040160405180910390fd5b8060030154421161135657604051632f021e8d60e11b815260040160405180910390fd5b60058101546006820154600160201b90910463ffffffff1611158061143b578154600360ff199091161782556006820154600583015460408051928352600160201b90910463ffffffff16602083015285917fecc4a9fb7e28d074cba7f5b227e9b5827823c850a385539b9a2f98a08f8c203d910160405180910390a25f54604051635d968dc160e11b815260048101869052600260248201526001600160a01b039091169063bb2d1b82906044015f604051808303815f87803b15801561141c575f5ffd5b505af115801561142e573d5f5f3e3d5ffd5b505f979650505050505050565b815460ff191660021782556006820154600a83018190555f816001600160401b0381111561146b5761146b613526565b604051908082528060200260200182016040528015611494578160200160208202803683370190505b5090505f5b8281101561150657846008015f8660060183815481106114bb576114bb61354e565b5f9182526020808320909101546001600160a01b0316835282019290925260400190205482518390839081106114f3576114f361354e565b6020908102919091010152600101611499565b505f54604051631f3ea75d60e21b8152600481018890526001600160a01b0390911690637cfa9d74906024015f604051808303815f87803b158015611549575f5ffd5b505af115801561155b573d5f5f3e3d5ffd5b50505050857f4f1f5b329c741a8ba15e9645e301061294d0c1fdd455448ffd5e76ff255929d78560060183604051610b2f929190613648565b61159c611de8565b6001600160a01b0381166115c35760405163d92e233d60e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b0383169081179091556040517fad4055f18cdad6f4bdd71afe3a72cbeee964217943e1bde38f138289e981a9a7905f90a250565b5f828152600a6020526040812090815460ff1660038111156116305761163061353a565b0361164e57604051630d4c1d9760e41b815260040160405180910390fd5b6001815460ff1660038111156116665761166661353a565b1461168457604051631860f69960e31b815260040160405180910390fd5b80600301544211156116a957604051639a19114d60e01b815260040160405180910390fd5b335f90815260078201602052604090205460ff16156116db5760405163257309f160e11b815260040160405180910390fd5b6116e433610b43565b6117015760405163149fbcfd60e11b815260040160405180910390fd5b61170c338385612118565b6001810154604080516bffffffffffffffffffffffff193360601b16602080830191909152603482018690526054820187905260748083019490945282518083039094018452609490910190915281519101205f90335f8181526007850160205260409020805460ff1916600117905590915061178b908390836122e9565b506040805184815260208101839052339186917f52999628fb1cb05707e842278833b22e511f11746202cecdf221968b0b89e8bd910160405180910390a350505050565b5f81815260096020526040902054806117fb576040516322e679e360e11b815260040160405180910390fd5b919050565b5f61180d600460146124ea565b905090565b61181a611de8565b5f898152600a602052604090206002815460ff16600381111561183f5761183f61353a565b1461185d57604051634f4b461f60e11b815260040160405180910390fd5b6004810154156118805760405163632a22bb60e01b815260040160405180910390fd5b600681015488146118c95760405162461bcd60e51b815260206004820152601360248201527209cdec8ca40c6deeadce840dad2e6dac2e8c6d606b1b60448201526064016106c3565b5f6118d6858701876137d2565b9150505f8151116119205760405162461bcd60e51b815260206004820152601460248201527343353a206e6f207075626c696320696e7075747360601b60448201526064016106c3565b5f81600183516119309190613875565b815181106119405761194061354e565b602002602001015190505f5f5f9054906101000a90046001600160a01b03166001600160a01b031663406ed35c8e6040518263ffffffff1660e01b815260040161198c91815260200190565b5f60405180830381865afa1580156119a6573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526119cd919081019061395a565b610120810151604051637bf41d7760e11b81529192506001600160a01b03169063f7e83aee90611a07908b908b908b908b90600401613af9565b602060405180830381865afa158015611a22573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a46919061357a565b5060048085018390555f8e815260096020526040808220859055905490516340a3b76160e11b81529182018f9052602482018490526001600160a01b0316906381476ec2906044015f604051808303815f87803b158015611aa5575f5ffd5b505af1158015611ab7573d5f5f3e3d5ffd5b505050508c7f49ac1dd411942113d1c5e6799c6379ce341afe85a4175fb562cf2a5fb886c27d8d8d8d8d8d8d604051611af596959493929190613b2a565b60405180910390a250505050505050505050505050565b611b14611de8565b6001600160a01b038116611b3d575f604051631e4fbdf760e01b81526004016106c39190613355565b610fe481611e1a565b7f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000018210611b855760405162461bcd60e51b81526004016106c390613ba3565b825464ffffffffff600160281b90910481169082168111611be35760405162461bcd60e51b815260206004820152601860248201527713185e9e5253550e881b195859881b5d5cdd08195e1a5cdd60421b60448201526064016106c3565b825f5b81866001015f611bf684886125e3565b64ffffffffff1681526020019081526020015f20819055505f816001611c1c9190613bed565b60ff168464ffffffffff16901c64ffffffffff16905060018564ffffffffff16901c64ffffffffff168111611c515750611de0565b600185165f03611d18575f611c7083611c6b886001613c06565b6125e3565b60408051808201825286815264ffffffffff83165f90815260018c0160209081529083902054908201529051632b0aac7f60e11b815291925073__$078c82ddf6c95d34ea184ef1dd6130d136$__9163561558fe91611cd191600401613c23565b602060405180830381865af4158015611cec573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d10919061357a565b935050611dcc565b5f611d2883611c6b600189613c53565b60408051808201825264ffffffffff83165f90815260018c0160209081529083902054825281018790529051632b0aac7f60e11b815291925073__$078c82ddf6c95d34ea184ef1dd6130d136$__9163561558fe91611d8991600401613c23565b602060405180830381865af4158015611da4573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611dc8919061357a565b9350505b50647fffffffff600194851c169301611be6565b505050505050565b33611df1610fe7565b6001600160a01b031614610d00573360405163118cdaa760e01b81526004016106c39190613355565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b8154600160281b900464ffffffffff167f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000018210611ed95760405162461bcd60e51b81526004016106c390613ba3565b825464ffffffffff90811690821610611f2c5760405162461bcd60e51b815260206004820152601560248201527413185e9e5253550e881d1c9959481a5cc8199d5b1b605a1b60448201526064016106c3565b611f37816001613c06565b835464ffffffffff91909116600160281b0269ffffffffff000000000019909116178355815f5b81856001015f611f6e84876125e3565b64ffffffffff16815260208101919091526040015f20556001831615612059575f611f9e82611c6b600187613c53565b60408051808201825264ffffffffff83165f90815260018a0160209081529083902054825281018690529051632b0aac7f60e11b815291925073__$078c82ddf6c95d34ea184ef1dd6130d136$__9163561558fe91611fff91600401613c23565b602060405180830381865af415801561201a573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061203e919061357a565b647fffffffff600195861c1694909350919091019050611f5e565b5050505050565b5f807ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00610bf0565b612090612600565b610fe481612625565b602060ff821611156120e75760405162461bcd60e51b81526020600482015260176024820152764c617a79494d543a205472656520746f6f206c6172676560481b60448201526064016106c3565b6120f8600160ff831681901b613875565b825469ffffffffffffffffffff191664ffffffffff919091161790915550565b5f82116121385760405163aeaddff160e01b815260040160405180910390fd5b6001546001600160a01b0316612161576040516350ca893360e01b815260040160405180910390fd5b5f818152600a602052604081206001805460028301549293926001600160a01b039091169163bb03bd7191889161219791613875565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401602060405180830381865afa1580156121de573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612202919061357a565b90505f60015f9054906101000a90046001600160a01b03166001600160a01b0316631209b1f66040518163ffffffff1660e01b8152600401602060405180830381865afa158015612255573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612279919061357a565b90505f811161229b5760405163aeaddff160e01b815260040160405180910390fd5b5f6122a68284613c70565b90505f81116122c85760405163149fbcfd60e11b815260040160405180910390fd5b808611156112b15760405163aeaddff160e01b815260040160405180910390fd5b60058301546006840180545f92600160201b900463ffffffff169081111561236757508054600180820183555f928352602080842090920180546001600160a01b0319166001600160a01b03881690811790915583526008870182526040808420869055600988019092529120805460ff19168217905590506124e3565b5f5f90505f876008015f855f815481106123835761238361354e565b5f9182526020808320909101546001600160a01b03168352820192909252604001902054905060015b845481101561240b575f896008015f8784815481106123cd576123cd61354e565b5f9182526020808320909101546001600160a01b03168352820192909252604001902054905082811115612402578092508193505b506001016123ac565b5080861061241f575f9450505050506124e3565b5f886009015f8685815481106124375761243761354e565b5f9182526020808320909101546001600160a01b031683528201929092526040019020805460ff191660018360028111156124745761247461353a565b02179055508684838154811061248c5761248c61354e565b5f91825260208083209190910180546001600160a01b0319166001600160a01b03948516179055918916815260088a018252604080822089905560098b0190925220805460ff191660019081179091559450505050505b9392505050565b5f5f8260ff161161253d5760405162461bcd60e51b815260206004820152601a60248201527f4c617a79494d543a206465707468206d757374206265203e203000000000000060448201526064016106c3565b602060ff831611156125615760405162461bcd60e51b81526004016106c390613c8f565b8254600160281b900464ffffffffff168061258060ff85166002613de0565b64ffffffffff1610156125d05760405162461bcd60e51b8152602060048201526018602482015277098c2f4f2929aa87440c2dac4d2ceeadeeae640c8cae0e8d60431b60448201526064016106c3565b6125db84828561262d565b949350505050565b5f816125f660ff851663ffffffff613df9565b6124e39190613c06565b6126086126f5565b610d0057604051631afcd79f60e31b815260040160405180910390fd5b611b14612600565b5f602060ff831611156126525760405162461bcd60e51b81526004016106c390613c8f565b8264ffffffffff165f03612670576126698261270e565b90506124e3565b5f61267c836001613bed565b60ff166001600160401b0381111561269657612696613526565b6040519080825280602002602001820160405280156126bf578160200160208202803683370190505b5090506126ce85858584612da8565b808360ff16815181106126e3576126e361354e565b60200260200101519150509392505050565b5f6126fe612060565b54600160401b900460ff16919050565b5f8160ff165f0361272057505f919050565b8160ff1660010361275257507f2098f5fb9e239eab3ceac3f27b81e481dc3124d55ffed523a839ee8446b64864919050565b8160ff1660020361278457507f1069673dcdb12263df301a6ff584a7ec261a44cb9dc68df067a4774460b1f1e1919050565b8160ff166003036127b657507f18f43331537ee2af2e3d758d50f72106467c6eea50371dd528d57eb2b856d238919050565b8160ff166004036127e857507f07f9d837cb17b0d36320ffe93ba52345f1b728571a568265caac97559dbc952a919050565b8160ff1660050361281a57507f2b94cf5e8746b3f5c9631f4c5df32907a699c58c94b2ad4d7b5cec1639183f55919050565b8160ff1660060361284c57507f2dee93c5a666459646ea7d22cca9e1bcfed71e6951b953611d11dda32ea09d78919050565b8160ff1660070361287e57507f078295e5a22b84e982cf601eb639597b8b0515a88cb5ac7fa8a4aabe3c87349d919050565b8160ff166008036128b057507f2fa5e5f18f6027a6501bec864564472a616b2e274a41211a444cbe3a99f3cc61919050565b8160ff166009036128e257507f0e884376d0d8fd21ecb780389e941f66e45e7acce3e228ab3e2156a614fcd747919050565b8160ff16600a0361291457507f1b7201da72494f1e28717ad1a52eb469f95892f957713533de6175e5da190af2919050565b8160ff16600b0361294657507f1f8d8822725e36385200c0b201249819a6e6e1e4650808b5bebc6bface7d7636919050565b8160ff16600c0361297857507f2c5d82f66c914bafb9701589ba8cfcfb6162b0a12acf88a8d0879a0471b5f85a919050565b8160ff16600d036129aa57507f14c54148a0940bb820957f5adf3fa1134ef5c4aaa113f4646458f270e0bfbfd0919050565b8160ff16600e036129dc57507f190d33b12f986f961e10c0ee44d8b9af11be25588cad89d416118e4bf4ebe80c919050565b8160ff16600f03612a0e57507f22f98aa9ce704152ac17354914ad73ed1167ae6596af510aa5b3649325e06c92919050565b8160ff16601003612a4057507f2a7c7c9b6ce5880b9f6f228d72bf6a575a526f29c66ecceef8b753d38bba7323919050565b8160ff16601103612a7257507f2e8186e558698ec1c67af9c14d463ffc470043c9c2988b954d75dd643f36b992919050565b8160ff16601203612aa457507f0f57c5571e9a4eab49e2c8cf050dae948aef6ead647392273546249d1c1ff10f919050565b8160ff16601303612ad657507f1830ee67b5fb554ad5f63d4388800e1cfe78e310697d46e43c9ce36134f72cca919050565b8160ff16601403612b0857507f2134e76ac5d21aab186c2be1dd8f84ee880a1e46eaf712f9d371b6df22191f3e919050565b8160ff16601503612b3a57507f19df90ec844ebc4ffeebd866f33859b0c051d8c958ee3aa88f8f8df3db91a5b1919050565b8160ff16601603612b6c57507f18cca2a66b5c0787981e69aefd84852d74af0e93ef4912b4648c05f722efe52b919050565b8160ff16601703612b9e57507f2388909415230d1b4d1304d2d54f473a628338f2efad83fadf05644549d2538d919050565b8160ff16601803612bd057507f27171fb4a97b6cc0e9e8f543b5294de866a2af2c9c8d0b1d96e673e4529ed540919050565b8160ff16601903612c0257507f2ff6650540f629fd5711a0bc74fc0d28dcb230b9392583e5f8d59696dde6ae21919050565b8160ff16601a03612c3457507f120c58f143d491e95902f7f5277778a2e0ad5168f6add75669932630ce611518919050565b8160ff16601b03612c6657507f1f21feb70d3f21b07bf853d5e5db03071ec495a0a565a21da2d665d279483795919050565b8160ff16601c03612c9857507f24be905fa71335e14c638cc0f66a8623a826e768068a9e968bb1a1dde18a72d2919050565b8160ff16601d03612cca57507f0f8666b62ed17491c50ceadead57d4cd597ef3821d65c328744c74e553dac26d919050565b8160ff16601e03612cfc57507f0918d46bf52d98b034413f4a1a1c41594e7a7a3f6ae08cb43d1a2a230e1959ef919050565b8160ff16601f03612d2e57507f1bbeb01b4c479ecde76917645e404dfa2e26f90d0afc5a65128513ad375c5ff2919050565b8160ff16602003612d6057507f2f68a1c58e257e42a17a6c61dff5551ed560b9922ab119d5ac8e184c9734ead9919050565b60405162461bcd60e51b815260206004820152601e60248201527f4c617a79494d543a2064656661756c745a65726f2062616420696e646578000060448201526064016106c3565b602060ff83161115612dcc5760405162461bcd60e51b81526004016106c390613c8f565b5f8364ffffffffff1611612e305760405162461bcd60e51b815260206004820152602560248201527f4c617a79494d543a206e756d626572206f66206c6561766573206d7573742062604482015264065203e20360dc1b60648201526084016106c3565b5f612e3c600185613c53565b9050600181165f03612e8f57846001015f612e575f846125e3565b64ffffffffff1681526020019081526020015f2054825f81518110612e7e57612e7e61354e565b602002602001018181525050612eb7565b612e985f61270e565b825f81518110612eaa57612eaa61354e565b6020026020010181815250505b5f5b8360ff168160ff161015611de057600182165f03612faf5773__$078c82ddf6c95d34ea184ef1dd6130d136$__63561558fe6040518060400160405280868560ff1681518110612f0b57612f0b61354e565b60200260200101518152602001612f218561270e565b8152506040518263ffffffff1660e01b8152600401612f409190613c23565b602060405180830381865af4158015612f5b573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612f7f919061357a565b83612f8b836001613bed565b60ff1681518110612f9e57612f9e61354e565b602002602001018181525050613160565b5f612fbb826001613bed565b60ff168664ffffffffff16901c64ffffffffff16905060018364ffffffffff16901c64ffffffffff1681111561305d575f876001015f6130128560016130019190613bed565b60018864ffffffffff16901c6125e3565b64ffffffffff1681526020019081526020015f2054905080858460016130389190613bed565b60ff168151811061304b5761304b61354e565b6020026020010181815250505061315e565b5f876001015f61307485600188611c6b9190613c53565b64ffffffffff1681526020019081526020015f2054905073__$078c82ddf6c95d34ea184ef1dd6130d136$__63561558fe6040518060400160405280848152602001888760ff16815181106130cb576130cb61354e565b60200260200101518152506040518263ffffffff1660e01b81526004016130f29190613c23565b602060405180830381865af415801561310d573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613131919061357a565b8561313d856001613bed565b60ff16815181106131505761315061354e565b602002602001018181525050505b505b647fffffffff600192831c169101612eb9565b600183019183908215613204579160200282015f5b838211156131d257833563ffffffff1683826101000a81548163ffffffff021916908363ffffffff1602179055509260200192600401602081600301049283019260010302613188565b80156132025782816101000a81549063ffffffff02191690556004016020816003010492830192600103026131d2565b505b50613210929150613214565b5090565b5b80821115613210575f8155600101613215565b6001600160a01b0381168114610fe4575f5ffd5b5f6020828403121561324c575f5ffd5b81356124e381613228565b5f60208284031215613267575f5ffd5b5035919050565b602080825282518282018190525f918401906040840190835b818110156132ae5783516001600160a01b0316835260209384019390920191600101613287565b509095945050505050565b5f5f5f608084860312156132cb575f5ffd5b8335925060208401359150608084018510156132e5575f5ffd5b6040840190509250925092565b5f5f60408385031215613303575f5ffd5b82359150602083013561331581613228565b809150509250929050565b5f5f5f60608486031215613332575f5ffd5b83359250602084013561334481613228565b929592945050506040919091013590565b6001600160a01b0391909116815260200190565b5f5f6040838503121561337a575f5ffd5b823561338581613228565b946020939093013593505050565b5f5f604083850312156133a4575f5ffd5b50508035926020909101359150565b5f5f83601f8401126133c3575f5ffd5b5081356001600160401b038111156133d9575f5ffd5b6020830191508360208285010111156133f0575f5ffd5b9250929050565b5f5f5f5f5f5f5f5f5f60a08a8c03121561340f575f5ffd5b8935985060208a01356001600160401b0381111561342b575f5ffd5b8a01601f81018c1361343b575f5ffd5b80356001600160401b03811115613450575f5ffd5b8c60208260051b8401011115613464575f5ffd5b6020919091019850965060408a01356001600160401b03811115613486575f5ffd5b6134928c828d016133b3565b90975095505060608a01356001600160401b038111156134b0575f5ffd5b6134bc8c828d016133b3565b90955093505060808a01356001600160401b038111156134da575f5ffd5b6134e68c828d016133b3565b915080935050809150509295985092959850929598565b634e487b7160e01b5f52601160045260245ffd5b5f8161351f5761351f6134fd565b505f190190565b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f60018201613573576135736134fd565b5060010190565b5f6020828403121561358a575f5ffd5b5051919050565b803563ffffffff811681146117fb575f5ffd5b5f602082840312156135b4575f5ffd5b6124e382613591565b80820180821115610bf057610bf06134fd565b84815260a0810160208201855f5b600281101561360b5763ffffffff6135f583613591565b16835260209283019291909101906001016135de565b50505060608201939093526080015292915050565b805180151581146117fb575f5ffd5b5f6020828403121561363f575f5ffd5b6124e382613620565b604080825283549082018190525f8481526020812090916060840190835b8181101561368d5783546001600160a01b0316835260019384019360209093019201613666565b5050838103602080860191909152855180835291810192508501905f5b818110156136c85782518452602093840193909201916001016136aa565b50919695505050505050565b6040516101e081016001600160401b03811182821017156136f7576136f7613526565b60405290565b604051601f8201601f191681016001600160401b038111828210171561372557613725613526565b604052919050565b5f6001600160401b0382111561374557613745613526565b50601f01601f191660200190565b5f82601f830112613762575f5ffd5b81356001600160401b0381111561377b5761377b613526565b8060051b61378b602082016136fd565b918252602081850181019290810190868411156137a6575f5ffd5b6020860192505b838310156137c85782358252602092830192909101906137ad565b9695505050505050565b5f5f604083850312156137e3575f5ffd5b82356001600160401b038111156137f8575f5ffd5b8301601f81018513613808575f5ffd5b803561381b6138168261372d565b6136fd565b81815286602083850101111561382f575f5ffd5b816020840160208301375f6020838301015280945050505060208301356001600160401b0381111561385f575f5ffd5b61386b85828601613753565b9150509250929050565b81810381811115610bf057610bf06134fd565b8051600481106117fb575f5ffd5b5f82601f8301126138a5575f5ffd5b604080519081016001600160401b03811182821017156138c7576138c7613526565b80604052508060408401858111156138dd575f5ffd5b845b818110156138f75780518352602092830192016138df565b509195945050505050565b80516117fb81613228565b5f82601f83011261391c575f5ffd5b815161392a6138168261372d565b81815284602083860101111561393e575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f6020828403121561396a575f5ffd5b81516001600160401b0381111561397f575f5ffd5b82016102008185031215613991575f5ffd5b6139996136d4565b815181526139a960208301613888565b6020820152604082810151908201526139c58560608401613896565b606082015260a082015160808201526139e060c08301613902565b60a082015260e08201516001600160401b038111156139fd575f5ffd5b613a098682850161390d565b60c0830152506101008201516001600160401b03811115613a28575f5ffd5b613a348682850161390d565b60e083015250613a476101208301613902565b610100820152613a5a6101408301613902565b61012082015261016082810151610140830152610180830151908201526101a08201516001600160401b03811115613a90575f5ffd5b613a9c8682850161390d565b61018083015250613ab06101c08301613902565b6101a0820152613ac36101e08301613620565b6101c0820152949350505050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b604081525f613b0c604083018688613ad1565b8281036020840152613b1f818587613ad1565b979650505050505050565b606080825281018690525f8760808301825b89811015613b6c578235613b4f81613228565b6001600160a01b0316825260209283019290910190600101613b3c565b508381036020850152613b8081888a613ad1565b9150508281036040840152613b96818587613ad1565b9998505050505050505050565b6020808252602a908201527f4c617a79494d543a206c656166206d757374206265203c20534e41524b5f53436040820152691053105497d19251531160b21b606082015260800190565b60ff8181168382160190811115610bf057610bf06134fd565b64ffffffffff8181168382160190811115610bf057610bf06134fd565b6040810181835f5b6002811015613c4a578151835260209283019290910190600101613c2b565b50505092915050565b64ffffffffff8281168282160390811115610bf057610bf06134fd565b5f82613c8a57634e487b7160e01b5f52601260045260245ffd5b500490565b60208082526023908201527f4c617a79494d543a206465707468206d757374206265203c3d204d41585f44456040820152620a0a8960eb1b606082015260800190565b6001815b6001841115610ea157808504811115613cf157613cf16134fd565b6001841615613cff57908102905b60019390931c928002613cd6565b5f82613d1b57506001610bf0565b81613d2757505f610bf0565b8160018114613d3d5760028114613d4757613d79565b6001915050610bf0565b60ff841115613d5857613d586134fd565b6001841b915064ffffffffff821115613d7357613d736134fd565b50610bf0565b5060208310610133831016604e8410600b8410161715613db1575081810a64ffffffffff811115613dac57613dac6134fd565b610bf0565b613dc164ffffffffff8484613cd2565b8064ffffffffff04821115613dd857613dd86134fd565b029392505050565b5f6124e364ffffffffff841664ffffffffff8416613d0d565b64ffffffffff8181168382160290811690818114613e1957613e196134fd565b509291505056fea164736f6c634300081c000a", - "deployedBytecode": "0x608060405234801561000f575f5ffd5b506004361061023f575f3560e01c80639f0f874a11610135578063e59e4695116100b4578063f26ef74e11610079578063f26ef74e14610571578063f2fde38b14610584578063f379b0df14610597578063f52fd803146105d1578063f6fc05d514610643575f5ffd5b8063e59e469514610516578063e6745e1314610529578063e82f3b701461053c578063ebf0c7171461054f578063f165053614610557575f5ffd5b8063c3a0ec30116100fa578063c3a0ec30146104ae578063ca2869a0146104bf578063cd6dc687146104de578063da881e5a146104f1578063dbb06c9314610504575f5ffd5b80639f0f874a1461044d578063a016493014610456578063a8a4d69b14610469578063bff232c11461047c578063c2b40ae41461048f575f5ffd5b8063715018a6116101c15780638d1ddfb1116101865780638d1ddfb1146103cd5780638da5cb5b146103e35780638e5ce3ad146103eb5780639015d371146103fe5780639a7a2ffc14610411575f5ffd5b8063715018a6146103465780637c92f5241461034e578063858142431461037b5780638a78bb151461039b5780638cb89ecb146103ae575f5ffd5b80632e7b716d116102075780632e7b716d146102d85780634d6861a6146102eb57806350e6d94c146102fe5780635d5047761461032057806370e36bbe14610333575f5ffd5b8063096b810a146102435780630f3e34121461025857806317d611201461026b5780632800d82914610294578063291a691b146102b5575b5f5ffd5b61025661025136600461323c565b61064c565b005b610256610266366004613257565b610798565b61027e610279366004613257565b6107db565b60405161028b919061326e565b60405180910390f35b6102a76102a2366004613257565b61091d565b60405190815260200161028b565b6102c86102c33660046132b9565b610969565b604051901515815260200161028b565b6102c86102e636600461323c565b610b43565b6102c86102f9366004613257565b610bf6565b6102c861030c36600461323c565b60066020525f908152604090205460ff1681565b6102c861032e3660046132f2565b610c35565b61025661034136600461323c565b610c79565b610256610cef565b61036161035c366004613320565b610d02565b6040805192835263ffffffff90911660208301520161028b565b60015461038e906001600160a01b031681565b60405161028b9190613355565b6102566103a936600461323c565b610ea9565b6102a76103bc366004613257565b60096020525f908152604090205481565b600454600160281b900464ffffffffff166102a7565b61038e610fe7565b600b5461038e906001600160a01b031681565b6102c861040c36600461323c565b611015565b61043761041f36600461323c565b60076020525f908152604090205464ffffffffff1681565b60405164ffffffffff909116815260200161028b565b6102a760035481565b61027e610464366004613257565b611032565b6102c86104773660046132f2565b6110c8565b61025661048a36600461323c565b61110c565b6102a761049d366004613257565b60086020525f908152604090205481565b6001546001600160a01b031661038e565b6102a76104cd366004613257565b5f9081526008602052604090205490565b6102566104ec366004613369565b61115d565b6102c86104ff366004613257565b6112ba565b5f5461038e906001600160a01b031681565b61025661052436600461323c565b611594565b610256610537366004613393565b61160c565b6102a761054a366004613257565b6117cf565b6102a7611800565b61055f601481565b60405160ff909116815260200161028b565b61025661057f3660046133f7565b611812565b61025661059236600461323c565b611b0c565b6004546105b39064ffffffffff80821691600160281b90041682565b6040805164ffffffffff93841681529290911660208301520161028b565b6106146105df366004613257565b5f908152600a6020819052604090912090810154600590910154909163ffffffff80831692600160201b900416908284101590565b60405161028b949392919093845263ffffffff9283166020850152911660408301521515606082015260800190565b6102a760025481565b610654610fe7565b6001600160a01b0316336001600160a01b0316148061067d57506001546001600160a01b031633145b61069a57604051632864c4e160e01b815260040160405180910390fd5b6106a381611015565b81906106cc576040516381e5828960e01b81526004016106c39190613355565b60405180910390fd5b506001600160a01b0381165f9081526007602052604081205464ffffffffff16906106fa9060049083611b46565b6001600160a01b0382165f908152600660205260408120805460ff19169055600280549161072783613511565b90915550506002546004546040805164ffffffffff80861682526020820194909452600160281b909204909216918101919091526001600160a01b038316907f8c008e3835f6c79bfcdb89f0f6ca8705e0b01049ee84a90b0e4da1c7ba9405d5906060015b60405180910390a25050565b6107a0611de8565b60038190556040518181527fbe772dc189863d512fa01e489c8eac204975aef1a8662d8b5a333804b5207ab79060200160405180910390a150565b5f818152600a602081905260408220600681015491810154606093919291816001600160401b0381111561081157610811613526565b60405190808252806020026020018201604052801561083a578160200160208202803683370190505b5090505f805b84811015610911576001866009015f8860060184815481106108645761086461354e565b5f9182526020808320909101546001600160a01b0316835282019290925260400190205460ff16600281111561089c5761089c61353a565b03610909578560060181815481106108b6576108b661354e565b905f5260205f20015f9054906101000a90046001600160a01b03168383815181106108e3576108e361354e565b6001600160a01b03909216602092830291909101909101528161090581613562565b9250505b600101610840565b50909695505050505050565b5f818152600a6020526040812081815460ff1660038111156109415761094161353a565b0361095f57604051630d4c1d9760e41b815260040160405180910390fd5b6003015492915050565b5f80546001600160a01b031633146109945760405163e4c2a7eb60e01b815260040160405180910390fd5b5f848152600a6020526040812090815460ff1660038111156109b8576109b861353a565b146109d6576040516374ff462560e11b815260040160405180910390fd5b60015460408051630cc37d8f60e11b815290515f926001600160a01b031691631986fb1e9160048083019260209291908290030181865afa158015610a1d573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a41919061357a565b905080610a5460408601602087016135a4565b63ffffffff161115610a6c60408601602087016135a4565b829091610a9a576040516344ec930f60e01b815263ffffffff909216600483015260248201526044016106c3565b5050815460ff1916600190811783558201859055436002830155600354610ac190426135bd565b6003830155610ad560058301856002613173565b50610ade611800565b5f87815260086020526040908190209190915560028301546003840154915188927f381d281d32f95ef8fe4e5f3b263ea6a32d03d331e1a141ae1da996dc02a7a17092610b2f928a928a92916135d0565b60405180910390a250600195945050505050565b5f610b4d82611015565b610b5857505f919050565b6001546001600160a01b0316610b81576040516350ca893360e01b815260040160405180910390fd5b600154604051639f8a13d760e01b81526001600160a01b0390911690639f8a13d790610bb1908590600401613355565b602060405180830381865afa158015610bcc573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610bf0919061362f565b92915050565b5f818152600a602052604081206001815460ff166003811115610c1b57610c1b61353a565b14610c2857505f92915050565b6003015442111592915050565b5f60015f848152600a602090815260408083206001600160a01b038716845260090190915290205460ff166002811115610c7157610c7161353a565b149392505050565b610c81611de8565b6001600160a01b038116610ca85760405163d92e233d60e01b815260040160405180910390fd5b5f80546001600160a01b0319166001600160a01b038316908117825560405190917f2c8267accd82e977550ed2349c73311183cd22e306347be4453c8d130995e3c991a250565b610cf7611de8565b610d005f611e1a565b565b600b545f9081906001600160a01b03163314610d315760405163fcef374960e01b815260040160405180910390fd5b5f858152600a602052604090206002815460ff166003811115610d5657610d5661353a565b14610d7457604051634f4b461f60e11b815260040160405180910390fd5b60058101546001600160a01b0386165f90815260098301602052604090205463ffffffff909116925060019060ff166002811115610db457610db461353a565b14610dc457600a01549150610ea1565b6001600160a01b0385165f9081526009820160205260408120805460ff19166002179055600a8201805491610df883613511565b919050555080600a01549250846001600160a01b0316867f6c783b92374361b4d6efaf29673b89437ee969bb3c9d2d5d86b143ad5447b8498686604051610e49929190918252602082015260400190565b60405180910390a36040805184815263ffffffff84166020820181905285101591810182905287907f119cb11dd0a68c257d6dc9b06dcb37dd422ce276eb8bf3cd0b7079a116b8e2989060600160405180910390a250505b935093915050565b610eb1610fe7565b6001600160a01b0316336001600160a01b03161480610eda57506001546001600160a01b031633145b610ef757604051632864c4e160e01b815260040160405180910390fd5b610f0081611015565b610fe45760048054600160281b900464ffffffffff1690610f2a906001600160a01b038416611e8a565b6001600160a01b0382165f908152600660209081526040808320805460ff1916600117905560079091528120805464ffffffffff841664ffffffffff199091161790556002805491610f7b83613562565b90915550506002546004546040805164ffffffffff80861682526020820194909452600160281b909204909216918101919091526001600160a01b038316907f3318d261fe14a5761d2d1e21555652f623d2134c430a9883c9ad6e958bb0db539060600161078c565b50565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b03165f9081526006602052604090205460ff1690565b5f818152600a60205260409020600481015460609190611065576040516322e679e360e11b815260040160405180910390fd5b806006018054806020026020016040519081016040528092919081815260200182805480156110bb57602002820191905f5260205f20905b81546001600160a01b0316815260019091019060200180831161109d575b5050505050915050919050565b5f805f848152600a602090815260408083206001600160a01b038716845260090190915290205460ff1660028111156111035761110361353a565b14159392505050565b611114611de8565b6001600160a01b03811661113b5760405163d92e233d60e01b815260040160405180910390fd5b600b80546001600160a01b0319166001600160a01b0392909216919091179055565b5f611166612060565b805490915060ff600160401b82041615906001600160401b03165f8115801561118c5750825b90505f826001600160401b031660011480156111a75750303b155b9050811580156111b5575080155b156111d35760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156111fd57845460ff60401b1916600160401b1785555b6001600160a01b0387166112245760405163d92e233d60e01b815260040160405180910390fd5b61122d33612088565b61123960046014612099565b61124286610798565b61124a610fe7565b6001600160a01b0316876001600160a01b03161461126b5761126b87611b0c565b83156112b157845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050565b5f818152600a6020526040812081815460ff1660038111156112de576112de61353a565b036112fc57604051630d4c1d9760e41b815260040160405180910390fd5b6001815460ff1660038111156113145761131461353a565b1461133257604051631860f69960e31b815260040160405180910390fd5b8060030154421161135657604051632f021e8d60e11b815260040160405180910390fd5b60058101546006820154600160201b90910463ffffffff1611158061143b578154600360ff199091161782556006820154600583015460408051928352600160201b90910463ffffffff16602083015285917fecc4a9fb7e28d074cba7f5b227e9b5827823c850a385539b9a2f98a08f8c203d910160405180910390a25f54604051635d968dc160e11b815260048101869052600260248201526001600160a01b039091169063bb2d1b82906044015f604051808303815f87803b15801561141c575f5ffd5b505af115801561142e573d5f5f3e3d5ffd5b505f979650505050505050565b815460ff191660021782556006820154600a83018190555f816001600160401b0381111561146b5761146b613526565b604051908082528060200260200182016040528015611494578160200160208202803683370190505b5090505f5b8281101561150657846008015f8660060183815481106114bb576114bb61354e565b5f9182526020808320909101546001600160a01b0316835282019290925260400190205482518390839081106114f3576114f361354e565b6020908102919091010152600101611499565b505f54604051631f3ea75d60e21b8152600481018890526001600160a01b0390911690637cfa9d74906024015f604051808303815f87803b158015611549575f5ffd5b505af115801561155b573d5f5f3e3d5ffd5b50505050857f4f1f5b329c741a8ba15e9645e301061294d0c1fdd455448ffd5e76ff255929d78560060183604051610b2f929190613648565b61159c611de8565b6001600160a01b0381166115c35760405163d92e233d60e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b0383169081179091556040517fad4055f18cdad6f4bdd71afe3a72cbeee964217943e1bde38f138289e981a9a7905f90a250565b5f828152600a6020526040812090815460ff1660038111156116305761163061353a565b0361164e57604051630d4c1d9760e41b815260040160405180910390fd5b6001815460ff1660038111156116665761166661353a565b1461168457604051631860f69960e31b815260040160405180910390fd5b80600301544211156116a957604051639a19114d60e01b815260040160405180910390fd5b335f90815260078201602052604090205460ff16156116db5760405163257309f160e11b815260040160405180910390fd5b6116e433610b43565b6117015760405163149fbcfd60e11b815260040160405180910390fd5b61170c338385612118565b6001810154604080516bffffffffffffffffffffffff193360601b16602080830191909152603482018690526054820187905260748083019490945282518083039094018452609490910190915281519101205f90335f8181526007850160205260409020805460ff1916600117905590915061178b908390836122e9565b506040805184815260208101839052339186917f52999628fb1cb05707e842278833b22e511f11746202cecdf221968b0b89e8bd910160405180910390a350505050565b5f81815260096020526040902054806117fb576040516322e679e360e11b815260040160405180910390fd5b919050565b5f61180d600460146124ea565b905090565b61181a611de8565b5f898152600a602052604090206002815460ff16600381111561183f5761183f61353a565b1461185d57604051634f4b461f60e11b815260040160405180910390fd5b6004810154156118805760405163632a22bb60e01b815260040160405180910390fd5b600681015488146118c95760405162461bcd60e51b815260206004820152601360248201527209cdec8ca40c6deeadce840dad2e6dac2e8c6d606b1b60448201526064016106c3565b5f6118d6858701876137d2565b9150505f8151116119205760405162461bcd60e51b815260206004820152601460248201527343353a206e6f207075626c696320696e7075747360601b60448201526064016106c3565b5f81600183516119309190613875565b815181106119405761194061354e565b602002602001015190505f5f5f9054906101000a90046001600160a01b03166001600160a01b031663406ed35c8e6040518263ffffffff1660e01b815260040161198c91815260200190565b5f60405180830381865afa1580156119a6573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526119cd919081019061395a565b610120810151604051637bf41d7760e11b81529192506001600160a01b03169063f7e83aee90611a07908b908b908b908b90600401613af9565b602060405180830381865afa158015611a22573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a46919061357a565b5060048085018390555f8e815260096020526040808220859055905490516340a3b76160e11b81529182018f9052602482018490526001600160a01b0316906381476ec2906044015f604051808303815f87803b158015611aa5575f5ffd5b505af1158015611ab7573d5f5f3e3d5ffd5b505050508c7f49ac1dd411942113d1c5e6799c6379ce341afe85a4175fb562cf2a5fb886c27d8d8d8d8d8d8d604051611af596959493929190613b2a565b60405180910390a250505050505050505050505050565b611b14611de8565b6001600160a01b038116611b3d575f604051631e4fbdf760e01b81526004016106c39190613355565b610fe481611e1a565b7f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000018210611b855760405162461bcd60e51b81526004016106c390613ba3565b825464ffffffffff600160281b90910481169082168111611be35760405162461bcd60e51b815260206004820152601860248201527713185e9e5253550e881b195859881b5d5cdd08195e1a5cdd60421b60448201526064016106c3565b825f5b81866001015f611bf684886125e3565b64ffffffffff1681526020019081526020015f20819055505f816001611c1c9190613bed565b60ff168464ffffffffff16901c64ffffffffff16905060018564ffffffffff16901c64ffffffffff168111611c515750611de0565b600185165f03611d18575f611c7083611c6b886001613c06565b6125e3565b60408051808201825286815264ffffffffff83165f90815260018c0160209081529083902054908201529051632b0aac7f60e11b815291925073__$078c82ddf6c95d34ea184ef1dd6130d136$__9163561558fe91611cd191600401613c23565b602060405180830381865af4158015611cec573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d10919061357a565b935050611dcc565b5f611d2883611c6b600189613c53565b60408051808201825264ffffffffff83165f90815260018c0160209081529083902054825281018790529051632b0aac7f60e11b815291925073__$078c82ddf6c95d34ea184ef1dd6130d136$__9163561558fe91611d8991600401613c23565b602060405180830381865af4158015611da4573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611dc8919061357a565b9350505b50647fffffffff600194851c169301611be6565b505050505050565b33611df1610fe7565b6001600160a01b031614610d00573360405163118cdaa760e01b81526004016106c39190613355565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b8154600160281b900464ffffffffff167f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000018210611ed95760405162461bcd60e51b81526004016106c390613ba3565b825464ffffffffff90811690821610611f2c5760405162461bcd60e51b815260206004820152601560248201527413185e9e5253550e881d1c9959481a5cc8199d5b1b605a1b60448201526064016106c3565b611f37816001613c06565b835464ffffffffff91909116600160281b0269ffffffffff000000000019909116178355815f5b81856001015f611f6e84876125e3565b64ffffffffff16815260208101919091526040015f20556001831615612059575f611f9e82611c6b600187613c53565b60408051808201825264ffffffffff83165f90815260018a0160209081529083902054825281018690529051632b0aac7f60e11b815291925073__$078c82ddf6c95d34ea184ef1dd6130d136$__9163561558fe91611fff91600401613c23565b602060405180830381865af415801561201a573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061203e919061357a565b647fffffffff600195861c1694909350919091019050611f5e565b5050505050565b5f807ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00610bf0565b612090612600565b610fe481612625565b602060ff821611156120e75760405162461bcd60e51b81526020600482015260176024820152764c617a79494d543a205472656520746f6f206c6172676560481b60448201526064016106c3565b6120f8600160ff831681901b613875565b825469ffffffffffffffffffff191664ffffffffff919091161790915550565b5f82116121385760405163aeaddff160e01b815260040160405180910390fd5b6001546001600160a01b0316612161576040516350ca893360e01b815260040160405180910390fd5b5f818152600a602052604081206001805460028301549293926001600160a01b039091169163bb03bd7191889161219791613875565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401602060405180830381865afa1580156121de573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612202919061357a565b90505f60015f9054906101000a90046001600160a01b03166001600160a01b0316631209b1f66040518163ffffffff1660e01b8152600401602060405180830381865afa158015612255573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612279919061357a565b90505f811161229b5760405163aeaddff160e01b815260040160405180910390fd5b5f6122a68284613c70565b90505f81116122c85760405163149fbcfd60e11b815260040160405180910390fd5b808611156112b15760405163aeaddff160e01b815260040160405180910390fd5b60058301546006840180545f92600160201b900463ffffffff169081111561236757508054600180820183555f928352602080842090920180546001600160a01b0319166001600160a01b03881690811790915583526008870182526040808420869055600988019092529120805460ff19168217905590506124e3565b5f5f90505f876008015f855f815481106123835761238361354e565b5f9182526020808320909101546001600160a01b03168352820192909252604001902054905060015b845481101561240b575f896008015f8784815481106123cd576123cd61354e565b5f9182526020808320909101546001600160a01b03168352820192909252604001902054905082811115612402578092508193505b506001016123ac565b5080861061241f575f9450505050506124e3565b5f886009015f8685815481106124375761243761354e565b5f9182526020808320909101546001600160a01b031683528201929092526040019020805460ff191660018360028111156124745761247461353a565b02179055508684838154811061248c5761248c61354e565b5f91825260208083209190910180546001600160a01b0319166001600160a01b03948516179055918916815260088a018252604080822089905560098b0190925220805460ff191660019081179091559450505050505b9392505050565b5f5f8260ff161161253d5760405162461bcd60e51b815260206004820152601a60248201527f4c617a79494d543a206465707468206d757374206265203e203000000000000060448201526064016106c3565b602060ff831611156125615760405162461bcd60e51b81526004016106c390613c8f565b8254600160281b900464ffffffffff168061258060ff85166002613de0565b64ffffffffff1610156125d05760405162461bcd60e51b8152602060048201526018602482015277098c2f4f2929aa87440c2dac4d2ceeadeeae640c8cae0e8d60431b60448201526064016106c3565b6125db84828561262d565b949350505050565b5f816125f660ff851663ffffffff613df9565b6124e39190613c06565b6126086126f5565b610d0057604051631afcd79f60e31b815260040160405180910390fd5b611b14612600565b5f602060ff831611156126525760405162461bcd60e51b81526004016106c390613c8f565b8264ffffffffff165f03612670576126698261270e565b90506124e3565b5f61267c836001613bed565b60ff166001600160401b0381111561269657612696613526565b6040519080825280602002602001820160405280156126bf578160200160208202803683370190505b5090506126ce85858584612da8565b808360ff16815181106126e3576126e361354e565b60200260200101519150509392505050565b5f6126fe612060565b54600160401b900460ff16919050565b5f8160ff165f0361272057505f919050565b8160ff1660010361275257507f2098f5fb9e239eab3ceac3f27b81e481dc3124d55ffed523a839ee8446b64864919050565b8160ff1660020361278457507f1069673dcdb12263df301a6ff584a7ec261a44cb9dc68df067a4774460b1f1e1919050565b8160ff166003036127b657507f18f43331537ee2af2e3d758d50f72106467c6eea50371dd528d57eb2b856d238919050565b8160ff166004036127e857507f07f9d837cb17b0d36320ffe93ba52345f1b728571a568265caac97559dbc952a919050565b8160ff1660050361281a57507f2b94cf5e8746b3f5c9631f4c5df32907a699c58c94b2ad4d7b5cec1639183f55919050565b8160ff1660060361284c57507f2dee93c5a666459646ea7d22cca9e1bcfed71e6951b953611d11dda32ea09d78919050565b8160ff1660070361287e57507f078295e5a22b84e982cf601eb639597b8b0515a88cb5ac7fa8a4aabe3c87349d919050565b8160ff166008036128b057507f2fa5e5f18f6027a6501bec864564472a616b2e274a41211a444cbe3a99f3cc61919050565b8160ff166009036128e257507f0e884376d0d8fd21ecb780389e941f66e45e7acce3e228ab3e2156a614fcd747919050565b8160ff16600a0361291457507f1b7201da72494f1e28717ad1a52eb469f95892f957713533de6175e5da190af2919050565b8160ff16600b0361294657507f1f8d8822725e36385200c0b201249819a6e6e1e4650808b5bebc6bface7d7636919050565b8160ff16600c0361297857507f2c5d82f66c914bafb9701589ba8cfcfb6162b0a12acf88a8d0879a0471b5f85a919050565b8160ff16600d036129aa57507f14c54148a0940bb820957f5adf3fa1134ef5c4aaa113f4646458f270e0bfbfd0919050565b8160ff16600e036129dc57507f190d33b12f986f961e10c0ee44d8b9af11be25588cad89d416118e4bf4ebe80c919050565b8160ff16600f03612a0e57507f22f98aa9ce704152ac17354914ad73ed1167ae6596af510aa5b3649325e06c92919050565b8160ff16601003612a4057507f2a7c7c9b6ce5880b9f6f228d72bf6a575a526f29c66ecceef8b753d38bba7323919050565b8160ff16601103612a7257507f2e8186e558698ec1c67af9c14d463ffc470043c9c2988b954d75dd643f36b992919050565b8160ff16601203612aa457507f0f57c5571e9a4eab49e2c8cf050dae948aef6ead647392273546249d1c1ff10f919050565b8160ff16601303612ad657507f1830ee67b5fb554ad5f63d4388800e1cfe78e310697d46e43c9ce36134f72cca919050565b8160ff16601403612b0857507f2134e76ac5d21aab186c2be1dd8f84ee880a1e46eaf712f9d371b6df22191f3e919050565b8160ff16601503612b3a57507f19df90ec844ebc4ffeebd866f33859b0c051d8c958ee3aa88f8f8df3db91a5b1919050565b8160ff16601603612b6c57507f18cca2a66b5c0787981e69aefd84852d74af0e93ef4912b4648c05f722efe52b919050565b8160ff16601703612b9e57507f2388909415230d1b4d1304d2d54f473a628338f2efad83fadf05644549d2538d919050565b8160ff16601803612bd057507f27171fb4a97b6cc0e9e8f543b5294de866a2af2c9c8d0b1d96e673e4529ed540919050565b8160ff16601903612c0257507f2ff6650540f629fd5711a0bc74fc0d28dcb230b9392583e5f8d59696dde6ae21919050565b8160ff16601a03612c3457507f120c58f143d491e95902f7f5277778a2e0ad5168f6add75669932630ce611518919050565b8160ff16601b03612c6657507f1f21feb70d3f21b07bf853d5e5db03071ec495a0a565a21da2d665d279483795919050565b8160ff16601c03612c9857507f24be905fa71335e14c638cc0f66a8623a826e768068a9e968bb1a1dde18a72d2919050565b8160ff16601d03612cca57507f0f8666b62ed17491c50ceadead57d4cd597ef3821d65c328744c74e553dac26d919050565b8160ff16601e03612cfc57507f0918d46bf52d98b034413f4a1a1c41594e7a7a3f6ae08cb43d1a2a230e1959ef919050565b8160ff16601f03612d2e57507f1bbeb01b4c479ecde76917645e404dfa2e26f90d0afc5a65128513ad375c5ff2919050565b8160ff16602003612d6057507f2f68a1c58e257e42a17a6c61dff5551ed560b9922ab119d5ac8e184c9734ead9919050565b60405162461bcd60e51b815260206004820152601e60248201527f4c617a79494d543a2064656661756c745a65726f2062616420696e646578000060448201526064016106c3565b602060ff83161115612dcc5760405162461bcd60e51b81526004016106c390613c8f565b5f8364ffffffffff1611612e305760405162461bcd60e51b815260206004820152602560248201527f4c617a79494d543a206e756d626572206f66206c6561766573206d7573742062604482015264065203e20360dc1b60648201526084016106c3565b5f612e3c600185613c53565b9050600181165f03612e8f57846001015f612e575f846125e3565b64ffffffffff1681526020019081526020015f2054825f81518110612e7e57612e7e61354e565b602002602001018181525050612eb7565b612e985f61270e565b825f81518110612eaa57612eaa61354e565b6020026020010181815250505b5f5b8360ff168160ff161015611de057600182165f03612faf5773__$078c82ddf6c95d34ea184ef1dd6130d136$__63561558fe6040518060400160405280868560ff1681518110612f0b57612f0b61354e565b60200260200101518152602001612f218561270e565b8152506040518263ffffffff1660e01b8152600401612f409190613c23565b602060405180830381865af4158015612f5b573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612f7f919061357a565b83612f8b836001613bed565b60ff1681518110612f9e57612f9e61354e565b602002602001018181525050613160565b5f612fbb826001613bed565b60ff168664ffffffffff16901c64ffffffffff16905060018364ffffffffff16901c64ffffffffff1681111561305d575f876001015f6130128560016130019190613bed565b60018864ffffffffff16901c6125e3565b64ffffffffff1681526020019081526020015f2054905080858460016130389190613bed565b60ff168151811061304b5761304b61354e565b6020026020010181815250505061315e565b5f876001015f61307485600188611c6b9190613c53565b64ffffffffff1681526020019081526020015f2054905073__$078c82ddf6c95d34ea184ef1dd6130d136$__63561558fe6040518060400160405280848152602001888760ff16815181106130cb576130cb61354e565b60200260200101518152506040518263ffffffff1660e01b81526004016130f29190613c23565b602060405180830381865af415801561310d573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613131919061357a565b8561313d856001613bed565b60ff16815181106131505761315061354e565b602002602001018181525050505b505b647fffffffff600192831c169101612eb9565b600183019183908215613204579160200282015f5b838211156131d257833563ffffffff1683826101000a81548163ffffffff021916908363ffffffff1602179055509260200192600401602081600301049283019260010302613188565b80156132025782816101000a81549063ffffffff02191690556004016020816003010492830192600103026131d2565b505b50613210929150613214565b5090565b5b80821115613210575f8155600101613215565b6001600160a01b0381168114610fe4575f5ffd5b5f6020828403121561324c575f5ffd5b81356124e381613228565b5f60208284031215613267575f5ffd5b5035919050565b602080825282518282018190525f918401906040840190835b818110156132ae5783516001600160a01b0316835260209384019390920191600101613287565b509095945050505050565b5f5f5f608084860312156132cb575f5ffd5b8335925060208401359150608084018510156132e5575f5ffd5b6040840190509250925092565b5f5f60408385031215613303575f5ffd5b82359150602083013561331581613228565b809150509250929050565b5f5f5f60608486031215613332575f5ffd5b83359250602084013561334481613228565b929592945050506040919091013590565b6001600160a01b0391909116815260200190565b5f5f6040838503121561337a575f5ffd5b823561338581613228565b946020939093013593505050565b5f5f604083850312156133a4575f5ffd5b50508035926020909101359150565b5f5f83601f8401126133c3575f5ffd5b5081356001600160401b038111156133d9575f5ffd5b6020830191508360208285010111156133f0575f5ffd5b9250929050565b5f5f5f5f5f5f5f5f5f60a08a8c03121561340f575f5ffd5b8935985060208a01356001600160401b0381111561342b575f5ffd5b8a01601f81018c1361343b575f5ffd5b80356001600160401b03811115613450575f5ffd5b8c60208260051b8401011115613464575f5ffd5b6020919091019850965060408a01356001600160401b03811115613486575f5ffd5b6134928c828d016133b3565b90975095505060608a01356001600160401b038111156134b0575f5ffd5b6134bc8c828d016133b3565b90955093505060808a01356001600160401b038111156134da575f5ffd5b6134e68c828d016133b3565b915080935050809150509295985092959850929598565b634e487b7160e01b5f52601160045260245ffd5b5f8161351f5761351f6134fd565b505f190190565b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f60018201613573576135736134fd565b5060010190565b5f6020828403121561358a575f5ffd5b5051919050565b803563ffffffff811681146117fb575f5ffd5b5f602082840312156135b4575f5ffd5b6124e382613591565b80820180821115610bf057610bf06134fd565b84815260a0810160208201855f5b600281101561360b5763ffffffff6135f583613591565b16835260209283019291909101906001016135de565b50505060608201939093526080015292915050565b805180151581146117fb575f5ffd5b5f6020828403121561363f575f5ffd5b6124e382613620565b604080825283549082018190525f8481526020812090916060840190835b8181101561368d5783546001600160a01b0316835260019384019360209093019201613666565b5050838103602080860191909152855180835291810192508501905f5b818110156136c85782518452602093840193909201916001016136aa565b50919695505050505050565b6040516101e081016001600160401b03811182821017156136f7576136f7613526565b60405290565b604051601f8201601f191681016001600160401b038111828210171561372557613725613526565b604052919050565b5f6001600160401b0382111561374557613745613526565b50601f01601f191660200190565b5f82601f830112613762575f5ffd5b81356001600160401b0381111561377b5761377b613526565b8060051b61378b602082016136fd565b918252602081850181019290810190868411156137a6575f5ffd5b6020860192505b838310156137c85782358252602092830192909101906137ad565b9695505050505050565b5f5f604083850312156137e3575f5ffd5b82356001600160401b038111156137f8575f5ffd5b8301601f81018513613808575f5ffd5b803561381b6138168261372d565b6136fd565b81815286602083850101111561382f575f5ffd5b816020840160208301375f6020838301015280945050505060208301356001600160401b0381111561385f575f5ffd5b61386b85828601613753565b9150509250929050565b81810381811115610bf057610bf06134fd565b8051600481106117fb575f5ffd5b5f82601f8301126138a5575f5ffd5b604080519081016001600160401b03811182821017156138c7576138c7613526565b80604052508060408401858111156138dd575f5ffd5b845b818110156138f75780518352602092830192016138df565b509195945050505050565b80516117fb81613228565b5f82601f83011261391c575f5ffd5b815161392a6138168261372d565b81815284602083860101111561393e575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f6020828403121561396a575f5ffd5b81516001600160401b0381111561397f575f5ffd5b82016102008185031215613991575f5ffd5b6139996136d4565b815181526139a960208301613888565b6020820152604082810151908201526139c58560608401613896565b606082015260a082015160808201526139e060c08301613902565b60a082015260e08201516001600160401b038111156139fd575f5ffd5b613a098682850161390d565b60c0830152506101008201516001600160401b03811115613a28575f5ffd5b613a348682850161390d565b60e083015250613a476101208301613902565b610100820152613a5a6101408301613902565b61012082015261016082810151610140830152610180830151908201526101a08201516001600160401b03811115613a90575f5ffd5b613a9c8682850161390d565b61018083015250613ab06101c08301613902565b6101a0820152613ac36101e08301613620565b6101c0820152949350505050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b604081525f613b0c604083018688613ad1565b8281036020840152613b1f818587613ad1565b979650505050505050565b606080825281018690525f8760808301825b89811015613b6c578235613b4f81613228565b6001600160a01b0316825260209283019290910190600101613b3c565b508381036020850152613b8081888a613ad1565b9150508281036040840152613b96818587613ad1565b9998505050505050505050565b6020808252602a908201527f4c617a79494d543a206c656166206d757374206265203c20534e41524b5f53436040820152691053105497d19251531160b21b606082015260800190565b60ff8181168382160190811115610bf057610bf06134fd565b64ffffffffff8181168382160190811115610bf057610bf06134fd565b6040810181835f5b6002811015613c4a578151835260209283019290910190600101613c2b565b50505092915050565b64ffffffffff8281168282160390811115610bf057610bf06134fd565b5f82613c8a57634e487b7160e01b5f52601260045260245ffd5b500490565b60208082526023908201527f4c617a79494d543a206465707468206d757374206265203c3d204d41585f44456040820152620a0a8960eb1b606082015260800190565b6001815b6001841115610ea157808504811115613cf157613cf16134fd565b6001841615613cff57908102905b60019390931c928002613cd6565b5f82613d1b57506001610bf0565b81613d2757505f610bf0565b8160018114613d3d5760028114613d4757613d79565b6001915050610bf0565b60ff841115613d5857613d586134fd565b6001841b915064ffffffffff821115613d7357613d736134fd565b50610bf0565b5060208310610133831016604e8410600b8410161715613db1575081810a64ffffffffff811115613dac57613dac6134fd565b610bf0565b613dc164ffffffffff8484613cd2565b8064ffffffffff04821115613dd857613dd86134fd565b029392505050565b5f6124e364ffffffffff841664ffffffffff8416613d0d565b64ffffffffff8181168382160290811690818114613e1957613e196134fd565b509291505056fea164736f6c634300081c000a", + "bytecode": "0x6080604052348015600e575f5ffd5b5060156019565b60c9565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161560685760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b039081161460c65780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b613f81806100d65f395ff3fe608060405234801561000f575f5ffd5b506004361061024a575f3560e01c80639f0f874a11610140578063dbb06c93116100bf578063f165053611610084578063f16505361461059f578063f26ef74e146105b9578063f2fde38b146105cc578063f379b0df146105df578063f52fd80314610619578063f6fc05d51461068b575f5ffd5b8063dbb06c931461054c578063e59e46951461055e578063e6745e1314610571578063e82f3b7014610584578063ebf0c71714610597575f5ffd5b8063c3a0ec3011610105578063c3a0ec30146104c7578063ca2869a0146104d8578063cd6dc687146104f7578063d4c22f6e1461050a578063da881e5a14610539575f5ffd5b80639f0f874a14610458578063a016493014610461578063a8a4d69b14610482578063bff232c114610495578063c2b40ae4146104a8575f5ffd5b8063715018a6116101cc5780638d1ddfb1116101915780638d1ddfb1146103d85780638da5cb5b146103ee5780638e5ce3ad146103f65780639015d371146104095780639a7a2ffc1461041c575f5ffd5b8063715018a6146103515780637c92f5241461035957806385814243146103865780638a78bb15146103a65780638cb89ecb146103b9575f5ffd5b80632e7b716d116102125780632e7b716d146102e35780634d6861a6146102f657806350e6d94c146103095780635d5047761461032b57806370e36bbe1461033e575f5ffd5b8063096b810a1461024e5780630f3e34121461026357806317d61120146102765780632800d8291461029f578063291a691b146102c0575b5f5ffd5b61026161025c366004613336565b610694565b005b610261610271366004613351565b6107e0565b610289610284366004613351565b610823565b60405161029691906133ab565b60405180910390f35b6102b26102ad366004613351565b610965565b604051908152602001610296565b6102d36102ce3660046133bd565b6109b1565b6040519015158152602001610296565b6102d36102f1366004613336565b610b8b565b6102d3610304366004613351565b610c3e565b6102d3610317366004613336565b60066020525f908152604090205460ff1681565b6102d36103393660046133f6565b610c7d565b61026161034c366004613336565b610cc1565b610261610d37565b61036c610367366004613424565b610d4a565b6040805192835263ffffffff909116602083015201610296565b600154610399906001600160a01b031681565b6040516102969190613459565b6102616103b4366004613336565b610ef1565b6102b26103c7366004613351565b60096020525f908152604090205481565b600454600160281b900464ffffffffff166102b2565b61039961102f565b600b54610399906001600160a01b031681565b6102d3610417366004613336565b61105d565b61044261042a366004613336565b60076020525f908152604090205464ffffffffff1681565b60405164ffffffffff9091168152602001610296565b6102b260035481565b61047461046f366004613351565b61107a565b60405161029692919061349d565b6102d36104903660046133f6565b6111c9565b6102616104a3366004613336565b61120d565b6102b26104b6366004613351565b60086020525f908152604090205481565b6001546001600160a01b0316610399565b6102b26104e6366004613351565b5f9081526008602052604090205490565b6102616105053660046134ca565b61125e565b61052c610518366004613351565b5f908152600a602052604090205460ff1690565b6040516102969190613508565b6102d3610547366004613351565b6113bb565b5f54610399906001600160a01b031681565b61026161056c366004613336565b611696565b61026161057f36600461352e565b61170e565b6102b2610592366004613351565b6118d1565b6102b2611902565b6105a7601481565b60405160ff9091168152602001610296565b6102616105c7366004613592565b611914565b6102616105da366004613336565b611c06565b6004546105fb9064ffffffffff80821691600160281b90041682565b6040805164ffffffffff938416815292909116602083015201610296565b61065c610627366004613351565b5f908152600a6020819052604090912090810154600590910154909163ffffffff80831692600160201b900416908284101590565b604051610296949392919093845263ffffffff9283166020850152911660408301521515606082015260800190565b6102b260025481565b61069c61102f565b6001600160a01b0316336001600160a01b031614806106c557506001546001600160a01b031633145b6106e257604051632864c4e160e01b815260040160405180910390fd5b6106eb8161105d565b8190610714576040516381e5828960e01b815260040161070b9190613459565b60405180910390fd5b506001600160a01b0381165f9081526007602052604081205464ffffffffff16906107429060049083611c40565b6001600160a01b0382165f908152600660205260408120805460ff19169055600280549161076f836136ac565b90915550506002546004546040805164ffffffffff80861682526020820194909452600160281b909204909216918101919091526001600160a01b038316907f8c008e3835f6c79bfcdb89f0f6ca8705e0b01049ee84a90b0e4da1c7ba9405d5906060015b60405180910390a25050565b6107e8611ee2565b60038190556040518181527fbe772dc189863d512fa01e489c8eac204975aef1a8662d8b5a333804b5207ab79060200160405180910390a150565b5f818152600a602081905260408220600681015491810154606093919291816001600160401b03811115610859576108596136c1565b604051908082528060200260200182016040528015610882578160200160208202803683370190505b5090505f805b84811015610959576001866009015f8860060184815481106108ac576108ac6136d5565b5f9182526020808320909101546001600160a01b0316835282019290925260400190205460ff1660028111156108e4576108e46134f4565b03610951578560060181815481106108fe576108fe6136d5565b905f5260205f20015f9054906101000a90046001600160a01b031683838151811061092b5761092b6136d5565b6001600160a01b03909216602092830291909101909101528161094d816136e9565b9250505b600101610888565b50909695505050505050565b5f818152600a6020526040812081815460ff166003811115610989576109896134f4565b036109a757604051630d4c1d9760e41b815260040160405180910390fd5b6003015492915050565b5f80546001600160a01b031633146109dc5760405163e4c2a7eb60e01b815260040160405180910390fd5b5f848152600a6020526040812090815460ff166003811115610a0057610a006134f4565b14610a1e576040516374ff462560e11b815260040160405180910390fd5b60015460408051630cc37d8f60e11b815290515f926001600160a01b031691631986fb1e9160048083019260209291908290030181865afa158015610a65573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a899190613701565b905080610a9c604086016020870161372b565b63ffffffff161115610ab4604086016020870161372b565b829091610ae2576040516344ec930f60e01b815263ffffffff9092166004830152602482015260440161070b565b5050815460ff1916600190811783558201859055436002830155600354610b099042613744565b6003830155610b1d6005830185600261326d565b50610b26611902565b5f87815260086020526040908190209190915560028301546003840154915188927f381d281d32f95ef8fe4e5f3b263ea6a32d03d331e1a141ae1da996dc02a7a17092610b77928a928a9291613757565b60405180910390a250600195945050505050565b5f610b958261105d565b610ba057505f919050565b6001546001600160a01b0316610bc9576040516350ca893360e01b815260040160405180910390fd5b600154604051639f8a13d760e01b81526001600160a01b0390911690639f8a13d790610bf9908590600401613459565b602060405180830381865afa158015610c14573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c3891906137b6565b92915050565b5f818152600a602052604081206001815460ff166003811115610c6357610c636134f4565b14610c7057505f92915050565b6003015442111592915050565b5f60015f848152600a602090815260408083206001600160a01b038716845260090190915290205460ff166002811115610cb957610cb96134f4565b149392505050565b610cc9611ee2565b6001600160a01b038116610cf05760405163d92e233d60e01b815260040160405180910390fd5b5f80546001600160a01b0319166001600160a01b038316908117825560405190917f2c8267accd82e977550ed2349c73311183cd22e306347be4453c8d130995e3c991a250565b610d3f611ee2565b610d485f611f14565b565b600b545f9081906001600160a01b03163314610d795760405163fcef374960e01b815260040160405180910390fd5b5f858152600a602052604090206002815460ff166003811115610d9e57610d9e6134f4565b14610dbc57604051634f4b461f60e11b815260040160405180910390fd5b60058101546001600160a01b0386165f90815260098301602052604090205463ffffffff909116925060019060ff166002811115610dfc57610dfc6134f4565b14610e0c57600a01549150610ee9565b6001600160a01b0385165f9081526009820160205260408120805460ff19166002179055600a8201805491610e40836136ac565b919050555080600a01549250846001600160a01b0316867f6c783b92374361b4d6efaf29673b89437ee969bb3c9d2d5d86b143ad5447b8498686604051610e91929190918252602082015260400190565b60405180910390a36040805184815263ffffffff84166020820181905285101591810182905287907f119cb11dd0a68c257d6dc9b06dcb37dd422ce276eb8bf3cd0b7079a116b8e2989060600160405180910390a250505b935093915050565b610ef961102f565b6001600160a01b0316336001600160a01b03161480610f2257506001546001600160a01b031633145b610f3f57604051632864c4e160e01b815260040160405180910390fd5b610f488161105d565b61102c5760048054600160281b900464ffffffffff1690610f72906001600160a01b038416611f84565b6001600160a01b0382165f908152600660209081526040808320805460ff1916600117905560079091528120805464ffffffffff841664ffffffffff199091161790556002805491610fc3836136e9565b90915550506002546004546040805164ffffffffff80861682526020820194909452600160281b909204909216918101919091526001600160a01b038316907f3318d261fe14a5761d2d1e21555652f623d2134c430a9883c9ad6e958bb0db53906060016107d4565b50565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b03165f9081526006602052604090205460ff1690565b5f818152600a60205260409020600481015460609182916110ae576040516322e679e360e11b815260040160405180910390fd5b8060060180548060200260200160405190810160405280929190818152602001828054801561110457602002820191905f5260205f20905b81546001600160a01b031681526001909101906020018083116110e6575b5050835193965083925050506001600160401b03811115611127576111276136c1565b604051908082528060200260200182016040528015611150578160200160208202803683370190505b5092505f5b818110156111c157826008015f868381518110611174576111746136d5565b60200260200101516001600160a01b03166001600160a01b031681526020019081526020015f20548482815181106111ae576111ae6136d5565b6020908102919091010152600101611155565b505050915091565b5f805f848152600a602090815260408083206001600160a01b038716845260090190915290205460ff166002811115611204576112046134f4565b14159392505050565b611215611ee2565b6001600160a01b03811661123c5760405163d92e233d60e01b815260040160405180910390fd5b600b80546001600160a01b0319166001600160a01b0392909216919091179055565b5f61126761215a565b805490915060ff600160401b82041615906001600160401b03165f8115801561128d5750825b90505f826001600160401b031660011480156112a85750303b155b9050811580156112b6575080155b156112d45760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156112fe57845460ff60401b1916600160401b1785555b6001600160a01b0387166113255760405163d92e233d60e01b815260040160405180910390fd5b61132e33612182565b61133a60046014612193565b611343866107e0565b61134b61102f565b6001600160a01b0316876001600160a01b03161461136c5761136c87611c06565b83156113b257845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050565b5f818152600a6020526040812081815460ff1660038111156113df576113df6134f4565b036113fd57604051630d4c1d9760e41b815260040160405180910390fd5b6001815460ff166003811115611415576114156134f4565b1461143357604051631860f69960e31b815260040160405180910390fd5b806003015442101561145857604051632f021e8d60e11b815260040160405180910390fd5b60058101546006820154600160201b90910463ffffffff1611158061153d578154600360ff199091161782556006820154600583015460408051928352600160201b90910463ffffffff16602083015285917fecc4a9fb7e28d074cba7f5b227e9b5827823c850a385539b9a2f98a08f8c203d910160405180910390a25f54604051635d968dc160e11b815260048101869052600260248201526001600160a01b039091169063bb2d1b82906044015f604051808303815f87803b15801561151e575f5ffd5b505af1158015611530573d5f5f3e3d5ffd5b505f979650505050505050565b815460ff191660021782556006820154600a83018190555f816001600160401b0381111561156d5761156d6136c1565b604051908082528060200260200182016040528015611596578160200160208202803683370190505b5090505f5b8281101561160857846008015f8660060183815481106115bd576115bd6136d5565b5f9182526020808320909101546001600160a01b0316835282019290925260400190205482518390839081106115f5576115f56136d5565b602090810291909101015260010161159b565b505f54604051631f3ea75d60e21b8152600481018890526001600160a01b0390911690637cfa9d74906024015f604051808303815f87803b15801561164b575f5ffd5b505af115801561165d573d5f5f3e3d5ffd5b50505050857f4f1f5b329c741a8ba15e9645e301061294d0c1fdd455448ffd5e76ff255929d78560060183604051610b779291906137cf565b61169e611ee2565b6001600160a01b0381166116c55760405163d92e233d60e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b0383169081179091556040517fad4055f18cdad6f4bdd71afe3a72cbeee964217943e1bde38f138289e981a9a7905f90a250565b5f828152600a6020526040812090815460ff166003811115611732576117326134f4565b0361175057604051630d4c1d9760e41b815260040160405180910390fd5b6001815460ff166003811115611768576117686134f4565b1461178657604051631860f69960e31b815260040160405180910390fd5b80600301544211156117ab57604051639a19114d60e01b815260040160405180910390fd5b335f90815260078201602052604090205460ff16156117dd5760405163257309f160e11b815260040160405180910390fd5b6117e633610b8b565b6118035760405163149fbcfd60e11b815260040160405180910390fd5b61180e338385612212565b6001810154604080516bffffffffffffffffffffffff193360601b16602080830191909152603482018690526054820187905260748083019490945282518083039094018452609490910190915281519101205f90335f8181526007850160205260409020805460ff1916600117905590915061188d908390836123e3565b506040805184815260208101839052339186917f52999628fb1cb05707e842278833b22e511f11746202cecdf221968b0b89e8bd910160405180910390a350505050565b5f81815260096020526040902054806118fd576040516322e679e360e11b815260040160405180910390fd5b919050565b5f61190f600460146125e4565b905090565b5f898152600a602052604090206002815460ff166003811115611939576119396134f4565b1461195757604051634f4b461f60e11b815260040160405180910390fd5b60048101541561197a5760405163632a22bb60e01b815260040160405180910390fd5b600681015488146119c35760405162461bcd60e51b815260206004820152601360248201527209cdec8ca40c6deeadce840dad2e6dac2e8c6d606b1b604482015260640161070b565b5f6119d085870187613926565b9150505f815111611a1a5760405162461bcd60e51b815260206004820152601460248201527343353a206e6f207075626c696320696e7075747360601b604482015260640161070b565b5f8160018351611a2a91906139c9565b81518110611a3a57611a3a6136d5565b602002602001015190505f5f5f9054906101000a90046001600160a01b03166001600160a01b031663406ed35c8e6040518263ffffffff1660e01b8152600401611a8691815260200190565b5f60405180830381865afa158015611aa0573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f19168201604052611ac79190810190613aae565b610120810151604051637bf41d7760e11b81529192506001600160a01b03169063f7e83aee90611b01908b908b908b908b90600401613c4d565b602060405180830381865afa158015611b1c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b409190613701565b5060048085018390555f8e815260096020526040808220859055905490516340a3b76160e11b81529182018f9052602482018490526001600160a01b0316906381476ec2906044015f604051808303815f87803b158015611b9f575f5ffd5b505af1158015611bb1573d5f5f3e3d5ffd5b505050508c7f49ac1dd411942113d1c5e6799c6379ce341afe85a4175fb562cf2a5fb886c27d8d8d8d8d8d8d604051611bef96959493929190613c7e565b60405180910390a250505050505050505050505050565b611c0e611ee2565b6001600160a01b038116611c37575f604051631e4fbdf760e01b815260040161070b9190613459565b61102c81611f14565b7f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000018210611c7f5760405162461bcd60e51b815260040161070b90613cf7565b825464ffffffffff600160281b90910481169082168111611cdd5760405162461bcd60e51b815260206004820152601860248201527713185e9e5253550e881b195859881b5d5cdd08195e1a5cdd60421b604482015260640161070b565b825f5b81866001015f611cf084886126dd565b64ffffffffff1681526020019081526020015f20819055505f816001611d169190613d41565b60ff168464ffffffffff16901c64ffffffffff16905060018564ffffffffff16901c64ffffffffff168111611d4b5750611eda565b600185165f03611e12575f611d6a83611d65886001613d5a565b6126dd565b60408051808201825286815264ffffffffff83165f90815260018c0160209081529083902054908201529051632b0aac7f60e11b815291925073__$078c82ddf6c95d34ea184ef1dd6130d136$__9163561558fe91611dcb91600401613d77565b602060405180830381865af4158015611de6573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e0a9190613701565b935050611ec6565b5f611e2283611d65600189613da7565b60408051808201825264ffffffffff83165f90815260018c0160209081529083902054825281018790529051632b0aac7f60e11b815291925073__$078c82ddf6c95d34ea184ef1dd6130d136$__9163561558fe91611e8391600401613d77565b602060405180830381865af4158015611e9e573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ec29190613701565b9350505b50647fffffffff600194851c169301611ce0565b505050505050565b33611eeb61102f565b6001600160a01b031614610d48573360405163118cdaa760e01b815260040161070b9190613459565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b8154600160281b900464ffffffffff167f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000018210611fd35760405162461bcd60e51b815260040161070b90613cf7565b825464ffffffffff908116908216106120265760405162461bcd60e51b815260206004820152601560248201527413185e9e5253550e881d1c9959481a5cc8199d5b1b605a1b604482015260640161070b565b612031816001613d5a565b835464ffffffffff91909116600160281b0269ffffffffff000000000019909116178355815f5b81856001015f61206884876126dd565b64ffffffffff16815260208101919091526040015f20556001831615612153575f61209882611d65600187613da7565b60408051808201825264ffffffffff83165f90815260018a0160209081529083902054825281018690529051632b0aac7f60e11b815291925073__$078c82ddf6c95d34ea184ef1dd6130d136$__9163561558fe916120f991600401613d77565b602060405180830381865af4158015612114573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906121389190613701565b647fffffffff600195861c1694909350919091019050612058565b5050505050565b5f807ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00610c38565b61218a6126fa565b61102c8161271f565b602060ff821611156121e15760405162461bcd60e51b81526020600482015260176024820152764c617a79494d543a205472656520746f6f206c6172676560481b604482015260640161070b565b6121f2600160ff831681901b6139c9565b825469ffffffffffffffffffff191664ffffffffff919091161790915550565b5f82116122325760405163aeaddff160e01b815260040160405180910390fd5b6001546001600160a01b031661225b576040516350ca893360e01b815260040160405180910390fd5b5f818152600a602052604081206001805460028301549293926001600160a01b039091169163bb03bd71918891612291916139c9565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401602060405180830381865afa1580156122d8573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122fc9190613701565b90505f60015f9054906101000a90046001600160a01b03166001600160a01b0316631209b1f66040518163ffffffff1660e01b8152600401602060405180830381865afa15801561234f573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906123739190613701565b90505f81116123955760405163aeaddff160e01b815260040160405180910390fd5b5f6123a08284613dc4565b90505f81116123c25760405163149fbcfd60e11b815260040160405180910390fd5b808611156113b25760405163aeaddff160e01b815260040160405180910390fd5b60058301546006840180545f92600160201b900463ffffffff169081111561246157508054600180820183555f928352602080842090920180546001600160a01b0319166001600160a01b03881690811790915583526008870182526040808420869055600988019092529120805460ff19168217905590506125dd565b5f5f90505f876008015f855f8154811061247d5761247d6136d5565b5f9182526020808320909101546001600160a01b03168352820192909252604001902054905060015b8454811015612505575f896008015f8784815481106124c7576124c76136d5565b5f9182526020808320909101546001600160a01b031683528201929092526040019020549050828111156124fc578092508193505b506001016124a6565b50808610612519575f9450505050506125dd565b5f886009015f868581548110612531576125316136d5565b5f9182526020808320909101546001600160a01b031683528201929092526040019020805460ff1916600183600281111561256e5761256e6134f4565b021790555086848381548110612586576125866136d5565b5f91825260208083209190910180546001600160a01b0319166001600160a01b03948516179055918916815260088a018252604080822089905560098b0190925220805460ff191660019081179091559450505050505b9392505050565b5f5f8260ff16116126375760405162461bcd60e51b815260206004820152601a60248201527f4c617a79494d543a206465707468206d757374206265203e2030000000000000604482015260640161070b565b602060ff8316111561265b5760405162461bcd60e51b815260040161070b90613de3565b8254600160281b900464ffffffffff168061267a60ff85166002613f34565b64ffffffffff1610156126ca5760405162461bcd60e51b8152602060048201526018602482015277098c2f4f2929aa87440c2dac4d2ceeadeeae640c8cae0e8d60431b604482015260640161070b565b6126d5848285612727565b949350505050565b5f816126f060ff851663ffffffff613f4d565b6125dd9190613d5a565b6127026127ef565b610d4857604051631afcd79f60e31b815260040160405180910390fd5b611c0e6126fa565b5f602060ff8316111561274c5760405162461bcd60e51b815260040161070b90613de3565b8264ffffffffff165f0361276a5761276382612808565b90506125dd565b5f612776836001613d41565b60ff166001600160401b03811115612790576127906136c1565b6040519080825280602002602001820160405280156127b9578160200160208202803683370190505b5090506127c885858584612ea2565b808360ff16815181106127dd576127dd6136d5565b60200260200101519150509392505050565b5f6127f861215a565b54600160401b900460ff16919050565b5f8160ff165f0361281a57505f919050565b8160ff1660010361284c57507f2098f5fb9e239eab3ceac3f27b81e481dc3124d55ffed523a839ee8446b64864919050565b8160ff1660020361287e57507f1069673dcdb12263df301a6ff584a7ec261a44cb9dc68df067a4774460b1f1e1919050565b8160ff166003036128b057507f18f43331537ee2af2e3d758d50f72106467c6eea50371dd528d57eb2b856d238919050565b8160ff166004036128e257507f07f9d837cb17b0d36320ffe93ba52345f1b728571a568265caac97559dbc952a919050565b8160ff1660050361291457507f2b94cf5e8746b3f5c9631f4c5df32907a699c58c94b2ad4d7b5cec1639183f55919050565b8160ff1660060361294657507f2dee93c5a666459646ea7d22cca9e1bcfed71e6951b953611d11dda32ea09d78919050565b8160ff1660070361297857507f078295e5a22b84e982cf601eb639597b8b0515a88cb5ac7fa8a4aabe3c87349d919050565b8160ff166008036129aa57507f2fa5e5f18f6027a6501bec864564472a616b2e274a41211a444cbe3a99f3cc61919050565b8160ff166009036129dc57507f0e884376d0d8fd21ecb780389e941f66e45e7acce3e228ab3e2156a614fcd747919050565b8160ff16600a03612a0e57507f1b7201da72494f1e28717ad1a52eb469f95892f957713533de6175e5da190af2919050565b8160ff16600b03612a4057507f1f8d8822725e36385200c0b201249819a6e6e1e4650808b5bebc6bface7d7636919050565b8160ff16600c03612a7257507f2c5d82f66c914bafb9701589ba8cfcfb6162b0a12acf88a8d0879a0471b5f85a919050565b8160ff16600d03612aa457507f14c54148a0940bb820957f5adf3fa1134ef5c4aaa113f4646458f270e0bfbfd0919050565b8160ff16600e03612ad657507f190d33b12f986f961e10c0ee44d8b9af11be25588cad89d416118e4bf4ebe80c919050565b8160ff16600f03612b0857507f22f98aa9ce704152ac17354914ad73ed1167ae6596af510aa5b3649325e06c92919050565b8160ff16601003612b3a57507f2a7c7c9b6ce5880b9f6f228d72bf6a575a526f29c66ecceef8b753d38bba7323919050565b8160ff16601103612b6c57507f2e8186e558698ec1c67af9c14d463ffc470043c9c2988b954d75dd643f36b992919050565b8160ff16601203612b9e57507f0f57c5571e9a4eab49e2c8cf050dae948aef6ead647392273546249d1c1ff10f919050565b8160ff16601303612bd057507f1830ee67b5fb554ad5f63d4388800e1cfe78e310697d46e43c9ce36134f72cca919050565b8160ff16601403612c0257507f2134e76ac5d21aab186c2be1dd8f84ee880a1e46eaf712f9d371b6df22191f3e919050565b8160ff16601503612c3457507f19df90ec844ebc4ffeebd866f33859b0c051d8c958ee3aa88f8f8df3db91a5b1919050565b8160ff16601603612c6657507f18cca2a66b5c0787981e69aefd84852d74af0e93ef4912b4648c05f722efe52b919050565b8160ff16601703612c9857507f2388909415230d1b4d1304d2d54f473a628338f2efad83fadf05644549d2538d919050565b8160ff16601803612cca57507f27171fb4a97b6cc0e9e8f543b5294de866a2af2c9c8d0b1d96e673e4529ed540919050565b8160ff16601903612cfc57507f2ff6650540f629fd5711a0bc74fc0d28dcb230b9392583e5f8d59696dde6ae21919050565b8160ff16601a03612d2e57507f120c58f143d491e95902f7f5277778a2e0ad5168f6add75669932630ce611518919050565b8160ff16601b03612d6057507f1f21feb70d3f21b07bf853d5e5db03071ec495a0a565a21da2d665d279483795919050565b8160ff16601c03612d9257507f24be905fa71335e14c638cc0f66a8623a826e768068a9e968bb1a1dde18a72d2919050565b8160ff16601d03612dc457507f0f8666b62ed17491c50ceadead57d4cd597ef3821d65c328744c74e553dac26d919050565b8160ff16601e03612df657507f0918d46bf52d98b034413f4a1a1c41594e7a7a3f6ae08cb43d1a2a230e1959ef919050565b8160ff16601f03612e2857507f1bbeb01b4c479ecde76917645e404dfa2e26f90d0afc5a65128513ad375c5ff2919050565b8160ff16602003612e5a57507f2f68a1c58e257e42a17a6c61dff5551ed560b9922ab119d5ac8e184c9734ead9919050565b60405162461bcd60e51b815260206004820152601e60248201527f4c617a79494d543a2064656661756c745a65726f2062616420696e6465780000604482015260640161070b565b602060ff83161115612ec65760405162461bcd60e51b815260040161070b90613de3565b5f8364ffffffffff1611612f2a5760405162461bcd60e51b815260206004820152602560248201527f4c617a79494d543a206e756d626572206f66206c6561766573206d7573742062604482015264065203e20360dc1b606482015260840161070b565b5f612f36600185613da7565b9050600181165f03612f8957846001015f612f515f846126dd565b64ffffffffff1681526020019081526020015f2054825f81518110612f7857612f786136d5565b602002602001018181525050612fb1565b612f925f612808565b825f81518110612fa457612fa46136d5565b6020026020010181815250505b5f5b8360ff168160ff161015611eda57600182165f036130a95773__$078c82ddf6c95d34ea184ef1dd6130d136$__63561558fe6040518060400160405280868560ff1681518110613005576130056136d5565b6020026020010151815260200161301b85612808565b8152506040518263ffffffff1660e01b815260040161303a9190613d77565b602060405180830381865af4158015613055573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906130799190613701565b83613085836001613d41565b60ff1681518110613098576130986136d5565b60200260200101818152505061325a565b5f6130b5826001613d41565b60ff168664ffffffffff16901c64ffffffffff16905060018364ffffffffff16901c64ffffffffff16811115613157575f876001015f61310c8560016130fb9190613d41565b60018864ffffffffff16901c6126dd565b64ffffffffff1681526020019081526020015f2054905080858460016131329190613d41565b60ff1681518110613145576131456136d5565b60200260200101818152505050613258565b5f876001015f61316e85600188611d659190613da7565b64ffffffffff1681526020019081526020015f2054905073__$078c82ddf6c95d34ea184ef1dd6130d136$__63561558fe6040518060400160405280848152602001888760ff16815181106131c5576131c56136d5565b60200260200101518152506040518263ffffffff1660e01b81526004016131ec9190613d77565b602060405180830381865af4158015613207573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061322b9190613701565b85613237856001613d41565b60ff168151811061324a5761324a6136d5565b602002602001018181525050505b505b647fffffffff600192831c169101612fb3565b6001830191839082156132fe579160200282015f5b838211156132cc57833563ffffffff1683826101000a81548163ffffffff021916908363ffffffff1602179055509260200192600401602081600301049283019260010302613282565b80156132fc5782816101000a81549063ffffffff02191690556004016020816003010492830192600103026132cc565b505b5061330a92915061330e565b5090565b5b8082111561330a575f815560010161330f565b6001600160a01b038116811461102c575f5ffd5b5f60208284031215613346575f5ffd5b81356125dd81613322565b5f60208284031215613361575f5ffd5b5035919050565b5f8151808452602084019350602083015f5b828110156133a15781516001600160a01b031686526020958601959091019060010161337a565b5093949350505050565b602081525f6125dd6020830184613368565b5f5f5f608084860312156133cf575f5ffd5b8335925060208401359150608084018510156133e9575f5ffd5b6040840190509250925092565b5f5f60408385031215613407575f5ffd5b82359150602083013561341981613322565b809150509250929050565b5f5f5f60608486031215613436575f5ffd5b83359250602084013561344881613322565b929592945050506040919091013590565b6001600160a01b0391909116815260200190565b5f8151808452602084019350602083015f5b828110156133a157815186526020958601959091019060010161347f565b604081525f6134af6040830185613368565b82810360208401526134c1818561346d565b95945050505050565b5f5f604083850312156134db575f5ffd5b82356134e681613322565b946020939093013593505050565b634e487b7160e01b5f52602160045260245ffd5b602081016004831061352857634e487b7160e01b5f52602160045260245ffd5b91905290565b5f5f6040838503121561353f575f5ffd5b50508035926020909101359150565b5f5f83601f84011261355e575f5ffd5b5081356001600160401b03811115613574575f5ffd5b60208301915083602082850101111561358b575f5ffd5b9250929050565b5f5f5f5f5f5f5f5f5f60a08a8c0312156135aa575f5ffd5b8935985060208a01356001600160401b038111156135c6575f5ffd5b8a01601f81018c136135d6575f5ffd5b80356001600160401b038111156135eb575f5ffd5b8c60208260051b84010111156135ff575f5ffd5b6020919091019850965060408a01356001600160401b03811115613621575f5ffd5b61362d8c828d0161354e565b90975095505060608a01356001600160401b0381111561364b575f5ffd5b6136578c828d0161354e565b90955093505060808a01356001600160401b03811115613675575f5ffd5b6136818c828d0161354e565b915080935050809150509295985092959850929598565b634e487b7160e01b5f52601160045260245ffd5b5f816136ba576136ba613698565b505f190190565b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f600182016136fa576136fa613698565b5060010190565b5f60208284031215613711575f5ffd5b5051919050565b803563ffffffff811681146118fd575f5ffd5b5f6020828403121561373b575f5ffd5b6125dd82613718565b80820180821115610c3857610c38613698565b84815260a0810160208201855f5b60028110156137925763ffffffff61377c83613718565b1683526020928301929190910190600101613765565b50505060608201939093526080015292915050565b805180151581146118fd575f5ffd5b5f602082840312156137c6575f5ffd5b6125dd826137a7565b604080825283549082018190525f8481526020812090916060840190835b818110156138145783546001600160a01b03168352600193840193602090930192016137ed565b50508381036020850152613828818661346d565b9695505050505050565b6040516101e081016001600160401b0381118282101715613855576138556136c1565b60405290565b604051601f8201601f191681016001600160401b0381118282101715613883576138836136c1565b604052919050565b5f6001600160401b038211156138a3576138a36136c1565b50601f01601f191660200190565b5f82601f8301126138c0575f5ffd5b81356001600160401b038111156138d9576138d96136c1565b8060051b6138e96020820161385b565b91825260208185018101929081019086841115613904575f5ffd5b6020860192505b8383101561382857823582526020928301929091019061390b565b5f5f60408385031215613937575f5ffd5b82356001600160401b0381111561394c575f5ffd5b8301601f8101851361395c575f5ffd5b803561396f61396a8261388b565b61385b565b818152866020838501011115613983575f5ffd5b816020840160208301375f6020838301015280945050505060208301356001600160401b038111156139b3575f5ffd5b6139bf858286016138b1565b9150509250929050565b81810381811115610c3857610c38613698565b8051600481106118fd575f5ffd5b5f82601f8301126139f9575f5ffd5b604080519081016001600160401b0381118282101715613a1b57613a1b6136c1565b8060405250806040840185811115613a31575f5ffd5b845b81811015613a4b578051835260209283019201613a33565b509195945050505050565b80516118fd81613322565b5f82601f830112613a70575f5ffd5b8151613a7e61396a8261388b565b818152846020838601011115613a92575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215613abe575f5ffd5b81516001600160401b03811115613ad3575f5ffd5b82016102008185031215613ae5575f5ffd5b613aed613832565b81518152613afd602083016139dc565b602082015260408281015190820152613b1985606084016139ea565b606082015260a08201516080820152613b3460c08301613a56565b60a082015260e08201516001600160401b03811115613b51575f5ffd5b613b5d86828501613a61565b60c0830152506101008201516001600160401b03811115613b7c575f5ffd5b613b8886828501613a61565b60e083015250613b9b6101208301613a56565b610100820152613bae6101408301613a56565b61012082015261016082810151610140830152610180830151908201526101a08201516001600160401b03811115613be4575f5ffd5b613bf086828501613a61565b61018083015250613c046101c08301613a56565b6101a0820152613c176101e083016137a7565b6101c0820152949350505050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b604081525f613c60604083018688613c25565b8281036020840152613c73818587613c25565b979650505050505050565b606080825281018690525f8760808301825b89811015613cc0578235613ca381613322565b6001600160a01b0316825260209283019290910190600101613c90565b508381036020850152613cd481888a613c25565b9150508281036040840152613cea818587613c25565b9998505050505050505050565b6020808252602a908201527f4c617a79494d543a206c656166206d757374206265203c20534e41524b5f53436040820152691053105497d19251531160b21b606082015260800190565b60ff8181168382160190811115610c3857610c38613698565b64ffffffffff8181168382160190811115610c3857610c38613698565b6040810181835f5b6002811015613d9e578151835260209283019290910190600101613d7f565b50505092915050565b64ffffffffff8281168282160390811115610c3857610c38613698565b5f82613dde57634e487b7160e01b5f52601260045260245ffd5b500490565b60208082526023908201527f4c617a79494d543a206465707468206d757374206265203c3d204d41585f44456040820152620a0a8960eb1b606082015260800190565b6001815b6001841115610ee957808504811115613e4557613e45613698565b6001841615613e5357908102905b60019390931c928002613e2a565b5f82613e6f57506001610c38565b81613e7b57505f610c38565b8160018114613e915760028114613e9b57613ecd565b6001915050610c38565b60ff841115613eac57613eac613698565b6001841b915064ffffffffff821115613ec757613ec7613698565b50610c38565b5060208310610133831016604e8410600b8410161715613f05575081810a64ffffffffff811115613f0057613f00613698565b610c38565b613f1564ffffffffff8484613e26565b8064ffffffffff04821115613f2c57613f2c613698565b029392505050565b5f6125dd64ffffffffff841664ffffffffff8416613e61565b64ffffffffff8181168382160290811690818114613f6d57613f6d613698565b509291505056fea164736f6c634300081c000a", + "deployedBytecode": "0x608060405234801561000f575f5ffd5b506004361061024a575f3560e01c80639f0f874a11610140578063dbb06c93116100bf578063f165053611610084578063f16505361461059f578063f26ef74e146105b9578063f2fde38b146105cc578063f379b0df146105df578063f52fd80314610619578063f6fc05d51461068b575f5ffd5b8063dbb06c931461054c578063e59e46951461055e578063e6745e1314610571578063e82f3b7014610584578063ebf0c71714610597575f5ffd5b8063c3a0ec3011610105578063c3a0ec30146104c7578063ca2869a0146104d8578063cd6dc687146104f7578063d4c22f6e1461050a578063da881e5a14610539575f5ffd5b80639f0f874a14610458578063a016493014610461578063a8a4d69b14610482578063bff232c114610495578063c2b40ae4146104a8575f5ffd5b8063715018a6116101cc5780638d1ddfb1116101915780638d1ddfb1146103d85780638da5cb5b146103ee5780638e5ce3ad146103f65780639015d371146104095780639a7a2ffc1461041c575f5ffd5b8063715018a6146103515780637c92f5241461035957806385814243146103865780638a78bb15146103a65780638cb89ecb146103b9575f5ffd5b80632e7b716d116102125780632e7b716d146102e35780634d6861a6146102f657806350e6d94c146103095780635d5047761461032b57806370e36bbe1461033e575f5ffd5b8063096b810a1461024e5780630f3e34121461026357806317d61120146102765780632800d8291461029f578063291a691b146102c0575b5f5ffd5b61026161025c366004613336565b610694565b005b610261610271366004613351565b6107e0565b610289610284366004613351565b610823565b60405161029691906133ab565b60405180910390f35b6102b26102ad366004613351565b610965565b604051908152602001610296565b6102d36102ce3660046133bd565b6109b1565b6040519015158152602001610296565b6102d36102f1366004613336565b610b8b565b6102d3610304366004613351565b610c3e565b6102d3610317366004613336565b60066020525f908152604090205460ff1681565b6102d36103393660046133f6565b610c7d565b61026161034c366004613336565b610cc1565b610261610d37565b61036c610367366004613424565b610d4a565b6040805192835263ffffffff909116602083015201610296565b600154610399906001600160a01b031681565b6040516102969190613459565b6102616103b4366004613336565b610ef1565b6102b26103c7366004613351565b60096020525f908152604090205481565b600454600160281b900464ffffffffff166102b2565b61039961102f565b600b54610399906001600160a01b031681565b6102d3610417366004613336565b61105d565b61044261042a366004613336565b60076020525f908152604090205464ffffffffff1681565b60405164ffffffffff9091168152602001610296565b6102b260035481565b61047461046f366004613351565b61107a565b60405161029692919061349d565b6102d36104903660046133f6565b6111c9565b6102616104a3366004613336565b61120d565b6102b26104b6366004613351565b60086020525f908152604090205481565b6001546001600160a01b0316610399565b6102b26104e6366004613351565b5f9081526008602052604090205490565b6102616105053660046134ca565b61125e565b61052c610518366004613351565b5f908152600a602052604090205460ff1690565b6040516102969190613508565b6102d3610547366004613351565b6113bb565b5f54610399906001600160a01b031681565b61026161056c366004613336565b611696565b61026161057f36600461352e565b61170e565b6102b2610592366004613351565b6118d1565b6102b2611902565b6105a7601481565b60405160ff9091168152602001610296565b6102616105c7366004613592565b611914565b6102616105da366004613336565b611c06565b6004546105fb9064ffffffffff80821691600160281b90041682565b6040805164ffffffffff938416815292909116602083015201610296565b61065c610627366004613351565b5f908152600a6020819052604090912090810154600590910154909163ffffffff80831692600160201b900416908284101590565b604051610296949392919093845263ffffffff9283166020850152911660408301521515606082015260800190565b6102b260025481565b61069c61102f565b6001600160a01b0316336001600160a01b031614806106c557506001546001600160a01b031633145b6106e257604051632864c4e160e01b815260040160405180910390fd5b6106eb8161105d565b8190610714576040516381e5828960e01b815260040161070b9190613459565b60405180910390fd5b506001600160a01b0381165f9081526007602052604081205464ffffffffff16906107429060049083611c40565b6001600160a01b0382165f908152600660205260408120805460ff19169055600280549161076f836136ac565b90915550506002546004546040805164ffffffffff80861682526020820194909452600160281b909204909216918101919091526001600160a01b038316907f8c008e3835f6c79bfcdb89f0f6ca8705e0b01049ee84a90b0e4da1c7ba9405d5906060015b60405180910390a25050565b6107e8611ee2565b60038190556040518181527fbe772dc189863d512fa01e489c8eac204975aef1a8662d8b5a333804b5207ab79060200160405180910390a150565b5f818152600a602081905260408220600681015491810154606093919291816001600160401b03811115610859576108596136c1565b604051908082528060200260200182016040528015610882578160200160208202803683370190505b5090505f805b84811015610959576001866009015f8860060184815481106108ac576108ac6136d5565b5f9182526020808320909101546001600160a01b0316835282019290925260400190205460ff1660028111156108e4576108e46134f4565b03610951578560060181815481106108fe576108fe6136d5565b905f5260205f20015f9054906101000a90046001600160a01b031683838151811061092b5761092b6136d5565b6001600160a01b03909216602092830291909101909101528161094d816136e9565b9250505b600101610888565b50909695505050505050565b5f818152600a6020526040812081815460ff166003811115610989576109896134f4565b036109a757604051630d4c1d9760e41b815260040160405180910390fd5b6003015492915050565b5f80546001600160a01b031633146109dc5760405163e4c2a7eb60e01b815260040160405180910390fd5b5f848152600a6020526040812090815460ff166003811115610a0057610a006134f4565b14610a1e576040516374ff462560e11b815260040160405180910390fd5b60015460408051630cc37d8f60e11b815290515f926001600160a01b031691631986fb1e9160048083019260209291908290030181865afa158015610a65573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a899190613701565b905080610a9c604086016020870161372b565b63ffffffff161115610ab4604086016020870161372b565b829091610ae2576040516344ec930f60e01b815263ffffffff9092166004830152602482015260440161070b565b5050815460ff1916600190811783558201859055436002830155600354610b099042613744565b6003830155610b1d6005830185600261326d565b50610b26611902565b5f87815260086020526040908190209190915560028301546003840154915188927f381d281d32f95ef8fe4e5f3b263ea6a32d03d331e1a141ae1da996dc02a7a17092610b77928a928a9291613757565b60405180910390a250600195945050505050565b5f610b958261105d565b610ba057505f919050565b6001546001600160a01b0316610bc9576040516350ca893360e01b815260040160405180910390fd5b600154604051639f8a13d760e01b81526001600160a01b0390911690639f8a13d790610bf9908590600401613459565b602060405180830381865afa158015610c14573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c3891906137b6565b92915050565b5f818152600a602052604081206001815460ff166003811115610c6357610c636134f4565b14610c7057505f92915050565b6003015442111592915050565b5f60015f848152600a602090815260408083206001600160a01b038716845260090190915290205460ff166002811115610cb957610cb96134f4565b149392505050565b610cc9611ee2565b6001600160a01b038116610cf05760405163d92e233d60e01b815260040160405180910390fd5b5f80546001600160a01b0319166001600160a01b038316908117825560405190917f2c8267accd82e977550ed2349c73311183cd22e306347be4453c8d130995e3c991a250565b610d3f611ee2565b610d485f611f14565b565b600b545f9081906001600160a01b03163314610d795760405163fcef374960e01b815260040160405180910390fd5b5f858152600a602052604090206002815460ff166003811115610d9e57610d9e6134f4565b14610dbc57604051634f4b461f60e11b815260040160405180910390fd5b60058101546001600160a01b0386165f90815260098301602052604090205463ffffffff909116925060019060ff166002811115610dfc57610dfc6134f4565b14610e0c57600a01549150610ee9565b6001600160a01b0385165f9081526009820160205260408120805460ff19166002179055600a8201805491610e40836136ac565b919050555080600a01549250846001600160a01b0316867f6c783b92374361b4d6efaf29673b89437ee969bb3c9d2d5d86b143ad5447b8498686604051610e91929190918252602082015260400190565b60405180910390a36040805184815263ffffffff84166020820181905285101591810182905287907f119cb11dd0a68c257d6dc9b06dcb37dd422ce276eb8bf3cd0b7079a116b8e2989060600160405180910390a250505b935093915050565b610ef961102f565b6001600160a01b0316336001600160a01b03161480610f2257506001546001600160a01b031633145b610f3f57604051632864c4e160e01b815260040160405180910390fd5b610f488161105d565b61102c5760048054600160281b900464ffffffffff1690610f72906001600160a01b038416611f84565b6001600160a01b0382165f908152600660209081526040808320805460ff1916600117905560079091528120805464ffffffffff841664ffffffffff199091161790556002805491610fc3836136e9565b90915550506002546004546040805164ffffffffff80861682526020820194909452600160281b909204909216918101919091526001600160a01b038316907f3318d261fe14a5761d2d1e21555652f623d2134c430a9883c9ad6e958bb0db53906060016107d4565b50565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b03165f9081526006602052604090205460ff1690565b5f818152600a60205260409020600481015460609182916110ae576040516322e679e360e11b815260040160405180910390fd5b8060060180548060200260200160405190810160405280929190818152602001828054801561110457602002820191905f5260205f20905b81546001600160a01b031681526001909101906020018083116110e6575b5050835193965083925050506001600160401b03811115611127576111276136c1565b604051908082528060200260200182016040528015611150578160200160208202803683370190505b5092505f5b818110156111c157826008015f868381518110611174576111746136d5565b60200260200101516001600160a01b03166001600160a01b031681526020019081526020015f20548482815181106111ae576111ae6136d5565b6020908102919091010152600101611155565b505050915091565b5f805f848152600a602090815260408083206001600160a01b038716845260090190915290205460ff166002811115611204576112046134f4565b14159392505050565b611215611ee2565b6001600160a01b03811661123c5760405163d92e233d60e01b815260040160405180910390fd5b600b80546001600160a01b0319166001600160a01b0392909216919091179055565b5f61126761215a565b805490915060ff600160401b82041615906001600160401b03165f8115801561128d5750825b90505f826001600160401b031660011480156112a85750303b155b9050811580156112b6575080155b156112d45760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156112fe57845460ff60401b1916600160401b1785555b6001600160a01b0387166113255760405163d92e233d60e01b815260040160405180910390fd5b61132e33612182565b61133a60046014612193565b611343866107e0565b61134b61102f565b6001600160a01b0316876001600160a01b03161461136c5761136c87611c06565b83156113b257845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050565b5f818152600a6020526040812081815460ff1660038111156113df576113df6134f4565b036113fd57604051630d4c1d9760e41b815260040160405180910390fd5b6001815460ff166003811115611415576114156134f4565b1461143357604051631860f69960e31b815260040160405180910390fd5b806003015442101561145857604051632f021e8d60e11b815260040160405180910390fd5b60058101546006820154600160201b90910463ffffffff1611158061153d578154600360ff199091161782556006820154600583015460408051928352600160201b90910463ffffffff16602083015285917fecc4a9fb7e28d074cba7f5b227e9b5827823c850a385539b9a2f98a08f8c203d910160405180910390a25f54604051635d968dc160e11b815260048101869052600260248201526001600160a01b039091169063bb2d1b82906044015f604051808303815f87803b15801561151e575f5ffd5b505af1158015611530573d5f5f3e3d5ffd5b505f979650505050505050565b815460ff191660021782556006820154600a83018190555f816001600160401b0381111561156d5761156d6136c1565b604051908082528060200260200182016040528015611596578160200160208202803683370190505b5090505f5b8281101561160857846008015f8660060183815481106115bd576115bd6136d5565b5f9182526020808320909101546001600160a01b0316835282019290925260400190205482518390839081106115f5576115f56136d5565b602090810291909101015260010161159b565b505f54604051631f3ea75d60e21b8152600481018890526001600160a01b0390911690637cfa9d74906024015f604051808303815f87803b15801561164b575f5ffd5b505af115801561165d573d5f5f3e3d5ffd5b50505050857f4f1f5b329c741a8ba15e9645e301061294d0c1fdd455448ffd5e76ff255929d78560060183604051610b779291906137cf565b61169e611ee2565b6001600160a01b0381166116c55760405163d92e233d60e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b0383169081179091556040517fad4055f18cdad6f4bdd71afe3a72cbeee964217943e1bde38f138289e981a9a7905f90a250565b5f828152600a6020526040812090815460ff166003811115611732576117326134f4565b0361175057604051630d4c1d9760e41b815260040160405180910390fd5b6001815460ff166003811115611768576117686134f4565b1461178657604051631860f69960e31b815260040160405180910390fd5b80600301544211156117ab57604051639a19114d60e01b815260040160405180910390fd5b335f90815260078201602052604090205460ff16156117dd5760405163257309f160e11b815260040160405180910390fd5b6117e633610b8b565b6118035760405163149fbcfd60e11b815260040160405180910390fd5b61180e338385612212565b6001810154604080516bffffffffffffffffffffffff193360601b16602080830191909152603482018690526054820187905260748083019490945282518083039094018452609490910190915281519101205f90335f8181526007850160205260409020805460ff1916600117905590915061188d908390836123e3565b506040805184815260208101839052339186917f52999628fb1cb05707e842278833b22e511f11746202cecdf221968b0b89e8bd910160405180910390a350505050565b5f81815260096020526040902054806118fd576040516322e679e360e11b815260040160405180910390fd5b919050565b5f61190f600460146125e4565b905090565b5f898152600a602052604090206002815460ff166003811115611939576119396134f4565b1461195757604051634f4b461f60e11b815260040160405180910390fd5b60048101541561197a5760405163632a22bb60e01b815260040160405180910390fd5b600681015488146119c35760405162461bcd60e51b815260206004820152601360248201527209cdec8ca40c6deeadce840dad2e6dac2e8c6d606b1b604482015260640161070b565b5f6119d085870187613926565b9150505f815111611a1a5760405162461bcd60e51b815260206004820152601460248201527343353a206e6f207075626c696320696e7075747360601b604482015260640161070b565b5f8160018351611a2a91906139c9565b81518110611a3a57611a3a6136d5565b602002602001015190505f5f5f9054906101000a90046001600160a01b03166001600160a01b031663406ed35c8e6040518263ffffffff1660e01b8152600401611a8691815260200190565b5f60405180830381865afa158015611aa0573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f19168201604052611ac79190810190613aae565b610120810151604051637bf41d7760e11b81529192506001600160a01b03169063f7e83aee90611b01908b908b908b908b90600401613c4d565b602060405180830381865afa158015611b1c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b409190613701565b5060048085018390555f8e815260096020526040808220859055905490516340a3b76160e11b81529182018f9052602482018490526001600160a01b0316906381476ec2906044015f604051808303815f87803b158015611b9f575f5ffd5b505af1158015611bb1573d5f5f3e3d5ffd5b505050508c7f49ac1dd411942113d1c5e6799c6379ce341afe85a4175fb562cf2a5fb886c27d8d8d8d8d8d8d604051611bef96959493929190613c7e565b60405180910390a250505050505050505050505050565b611c0e611ee2565b6001600160a01b038116611c37575f604051631e4fbdf760e01b815260040161070b9190613459565b61102c81611f14565b7f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000018210611c7f5760405162461bcd60e51b815260040161070b90613cf7565b825464ffffffffff600160281b90910481169082168111611cdd5760405162461bcd60e51b815260206004820152601860248201527713185e9e5253550e881b195859881b5d5cdd08195e1a5cdd60421b604482015260640161070b565b825f5b81866001015f611cf084886126dd565b64ffffffffff1681526020019081526020015f20819055505f816001611d169190613d41565b60ff168464ffffffffff16901c64ffffffffff16905060018564ffffffffff16901c64ffffffffff168111611d4b5750611eda565b600185165f03611e12575f611d6a83611d65886001613d5a565b6126dd565b60408051808201825286815264ffffffffff83165f90815260018c0160209081529083902054908201529051632b0aac7f60e11b815291925073__$078c82ddf6c95d34ea184ef1dd6130d136$__9163561558fe91611dcb91600401613d77565b602060405180830381865af4158015611de6573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e0a9190613701565b935050611ec6565b5f611e2283611d65600189613da7565b60408051808201825264ffffffffff83165f90815260018c0160209081529083902054825281018790529051632b0aac7f60e11b815291925073__$078c82ddf6c95d34ea184ef1dd6130d136$__9163561558fe91611e8391600401613d77565b602060405180830381865af4158015611e9e573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ec29190613701565b9350505b50647fffffffff600194851c169301611ce0565b505050505050565b33611eeb61102f565b6001600160a01b031614610d48573360405163118cdaa760e01b815260040161070b9190613459565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b8154600160281b900464ffffffffff167f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000018210611fd35760405162461bcd60e51b815260040161070b90613cf7565b825464ffffffffff908116908216106120265760405162461bcd60e51b815260206004820152601560248201527413185e9e5253550e881d1c9959481a5cc8199d5b1b605a1b604482015260640161070b565b612031816001613d5a565b835464ffffffffff91909116600160281b0269ffffffffff000000000019909116178355815f5b81856001015f61206884876126dd565b64ffffffffff16815260208101919091526040015f20556001831615612153575f61209882611d65600187613da7565b60408051808201825264ffffffffff83165f90815260018a0160209081529083902054825281018690529051632b0aac7f60e11b815291925073__$078c82ddf6c95d34ea184ef1dd6130d136$__9163561558fe916120f991600401613d77565b602060405180830381865af4158015612114573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906121389190613701565b647fffffffff600195861c1694909350919091019050612058565b5050505050565b5f807ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00610c38565b61218a6126fa565b61102c8161271f565b602060ff821611156121e15760405162461bcd60e51b81526020600482015260176024820152764c617a79494d543a205472656520746f6f206c6172676560481b604482015260640161070b565b6121f2600160ff831681901b6139c9565b825469ffffffffffffffffffff191664ffffffffff919091161790915550565b5f82116122325760405163aeaddff160e01b815260040160405180910390fd5b6001546001600160a01b031661225b576040516350ca893360e01b815260040160405180910390fd5b5f818152600a602052604081206001805460028301549293926001600160a01b039091169163bb03bd71918891612291916139c9565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401602060405180830381865afa1580156122d8573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122fc9190613701565b90505f60015f9054906101000a90046001600160a01b03166001600160a01b0316631209b1f66040518163ffffffff1660e01b8152600401602060405180830381865afa15801561234f573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906123739190613701565b90505f81116123955760405163aeaddff160e01b815260040160405180910390fd5b5f6123a08284613dc4565b90505f81116123c25760405163149fbcfd60e11b815260040160405180910390fd5b808611156113b25760405163aeaddff160e01b815260040160405180910390fd5b60058301546006840180545f92600160201b900463ffffffff169081111561246157508054600180820183555f928352602080842090920180546001600160a01b0319166001600160a01b03881690811790915583526008870182526040808420869055600988019092529120805460ff19168217905590506125dd565b5f5f90505f876008015f855f8154811061247d5761247d6136d5565b5f9182526020808320909101546001600160a01b03168352820192909252604001902054905060015b8454811015612505575f896008015f8784815481106124c7576124c76136d5565b5f9182526020808320909101546001600160a01b031683528201929092526040019020549050828111156124fc578092508193505b506001016124a6565b50808610612519575f9450505050506125dd565b5f886009015f868581548110612531576125316136d5565b5f9182526020808320909101546001600160a01b031683528201929092526040019020805460ff1916600183600281111561256e5761256e6134f4565b021790555086848381548110612586576125866136d5565b5f91825260208083209190910180546001600160a01b0319166001600160a01b03948516179055918916815260088a018252604080822089905560098b0190925220805460ff191660019081179091559450505050505b9392505050565b5f5f8260ff16116126375760405162461bcd60e51b815260206004820152601a60248201527f4c617a79494d543a206465707468206d757374206265203e2030000000000000604482015260640161070b565b602060ff8316111561265b5760405162461bcd60e51b815260040161070b90613de3565b8254600160281b900464ffffffffff168061267a60ff85166002613f34565b64ffffffffff1610156126ca5760405162461bcd60e51b8152602060048201526018602482015277098c2f4f2929aa87440c2dac4d2ceeadeeae640c8cae0e8d60431b604482015260640161070b565b6126d5848285612727565b949350505050565b5f816126f060ff851663ffffffff613f4d565b6125dd9190613d5a565b6127026127ef565b610d4857604051631afcd79f60e31b815260040160405180910390fd5b611c0e6126fa565b5f602060ff8316111561274c5760405162461bcd60e51b815260040161070b90613de3565b8264ffffffffff165f0361276a5761276382612808565b90506125dd565b5f612776836001613d41565b60ff166001600160401b03811115612790576127906136c1565b6040519080825280602002602001820160405280156127b9578160200160208202803683370190505b5090506127c885858584612ea2565b808360ff16815181106127dd576127dd6136d5565b60200260200101519150509392505050565b5f6127f861215a565b54600160401b900460ff16919050565b5f8160ff165f0361281a57505f919050565b8160ff1660010361284c57507f2098f5fb9e239eab3ceac3f27b81e481dc3124d55ffed523a839ee8446b64864919050565b8160ff1660020361287e57507f1069673dcdb12263df301a6ff584a7ec261a44cb9dc68df067a4774460b1f1e1919050565b8160ff166003036128b057507f18f43331537ee2af2e3d758d50f72106467c6eea50371dd528d57eb2b856d238919050565b8160ff166004036128e257507f07f9d837cb17b0d36320ffe93ba52345f1b728571a568265caac97559dbc952a919050565b8160ff1660050361291457507f2b94cf5e8746b3f5c9631f4c5df32907a699c58c94b2ad4d7b5cec1639183f55919050565b8160ff1660060361294657507f2dee93c5a666459646ea7d22cca9e1bcfed71e6951b953611d11dda32ea09d78919050565b8160ff1660070361297857507f078295e5a22b84e982cf601eb639597b8b0515a88cb5ac7fa8a4aabe3c87349d919050565b8160ff166008036129aa57507f2fa5e5f18f6027a6501bec864564472a616b2e274a41211a444cbe3a99f3cc61919050565b8160ff166009036129dc57507f0e884376d0d8fd21ecb780389e941f66e45e7acce3e228ab3e2156a614fcd747919050565b8160ff16600a03612a0e57507f1b7201da72494f1e28717ad1a52eb469f95892f957713533de6175e5da190af2919050565b8160ff16600b03612a4057507f1f8d8822725e36385200c0b201249819a6e6e1e4650808b5bebc6bface7d7636919050565b8160ff16600c03612a7257507f2c5d82f66c914bafb9701589ba8cfcfb6162b0a12acf88a8d0879a0471b5f85a919050565b8160ff16600d03612aa457507f14c54148a0940bb820957f5adf3fa1134ef5c4aaa113f4646458f270e0bfbfd0919050565b8160ff16600e03612ad657507f190d33b12f986f961e10c0ee44d8b9af11be25588cad89d416118e4bf4ebe80c919050565b8160ff16600f03612b0857507f22f98aa9ce704152ac17354914ad73ed1167ae6596af510aa5b3649325e06c92919050565b8160ff16601003612b3a57507f2a7c7c9b6ce5880b9f6f228d72bf6a575a526f29c66ecceef8b753d38bba7323919050565b8160ff16601103612b6c57507f2e8186e558698ec1c67af9c14d463ffc470043c9c2988b954d75dd643f36b992919050565b8160ff16601203612b9e57507f0f57c5571e9a4eab49e2c8cf050dae948aef6ead647392273546249d1c1ff10f919050565b8160ff16601303612bd057507f1830ee67b5fb554ad5f63d4388800e1cfe78e310697d46e43c9ce36134f72cca919050565b8160ff16601403612c0257507f2134e76ac5d21aab186c2be1dd8f84ee880a1e46eaf712f9d371b6df22191f3e919050565b8160ff16601503612c3457507f19df90ec844ebc4ffeebd866f33859b0c051d8c958ee3aa88f8f8df3db91a5b1919050565b8160ff16601603612c6657507f18cca2a66b5c0787981e69aefd84852d74af0e93ef4912b4648c05f722efe52b919050565b8160ff16601703612c9857507f2388909415230d1b4d1304d2d54f473a628338f2efad83fadf05644549d2538d919050565b8160ff16601803612cca57507f27171fb4a97b6cc0e9e8f543b5294de866a2af2c9c8d0b1d96e673e4529ed540919050565b8160ff16601903612cfc57507f2ff6650540f629fd5711a0bc74fc0d28dcb230b9392583e5f8d59696dde6ae21919050565b8160ff16601a03612d2e57507f120c58f143d491e95902f7f5277778a2e0ad5168f6add75669932630ce611518919050565b8160ff16601b03612d6057507f1f21feb70d3f21b07bf853d5e5db03071ec495a0a565a21da2d665d279483795919050565b8160ff16601c03612d9257507f24be905fa71335e14c638cc0f66a8623a826e768068a9e968bb1a1dde18a72d2919050565b8160ff16601d03612dc457507f0f8666b62ed17491c50ceadead57d4cd597ef3821d65c328744c74e553dac26d919050565b8160ff16601e03612df657507f0918d46bf52d98b034413f4a1a1c41594e7a7a3f6ae08cb43d1a2a230e1959ef919050565b8160ff16601f03612e2857507f1bbeb01b4c479ecde76917645e404dfa2e26f90d0afc5a65128513ad375c5ff2919050565b8160ff16602003612e5a57507f2f68a1c58e257e42a17a6c61dff5551ed560b9922ab119d5ac8e184c9734ead9919050565b60405162461bcd60e51b815260206004820152601e60248201527f4c617a79494d543a2064656661756c745a65726f2062616420696e6465780000604482015260640161070b565b602060ff83161115612ec65760405162461bcd60e51b815260040161070b90613de3565b5f8364ffffffffff1611612f2a5760405162461bcd60e51b815260206004820152602560248201527f4c617a79494d543a206e756d626572206f66206c6561766573206d7573742062604482015264065203e20360dc1b606482015260840161070b565b5f612f36600185613da7565b9050600181165f03612f8957846001015f612f515f846126dd565b64ffffffffff1681526020019081526020015f2054825f81518110612f7857612f786136d5565b602002602001018181525050612fb1565b612f925f612808565b825f81518110612fa457612fa46136d5565b6020026020010181815250505b5f5b8360ff168160ff161015611eda57600182165f036130a95773__$078c82ddf6c95d34ea184ef1dd6130d136$__63561558fe6040518060400160405280868560ff1681518110613005576130056136d5565b6020026020010151815260200161301b85612808565b8152506040518263ffffffff1660e01b815260040161303a9190613d77565b602060405180830381865af4158015613055573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906130799190613701565b83613085836001613d41565b60ff1681518110613098576130986136d5565b60200260200101818152505061325a565b5f6130b5826001613d41565b60ff168664ffffffffff16901c64ffffffffff16905060018364ffffffffff16901c64ffffffffff16811115613157575f876001015f61310c8560016130fb9190613d41565b60018864ffffffffff16901c6126dd565b64ffffffffff1681526020019081526020015f2054905080858460016131329190613d41565b60ff1681518110613145576131456136d5565b60200260200101818152505050613258565b5f876001015f61316e85600188611d659190613da7565b64ffffffffff1681526020019081526020015f2054905073__$078c82ddf6c95d34ea184ef1dd6130d136$__63561558fe6040518060400160405280848152602001888760ff16815181106131c5576131c56136d5565b60200260200101518152506040518263ffffffff1660e01b81526004016131ec9190613d77565b602060405180830381865af4158015613207573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061322b9190613701565b85613237856001613d41565b60ff168151811061324a5761324a6136d5565b602002602001018181525050505b505b647fffffffff600192831c169101612fb3565b6001830191839082156132fe579160200282015f5b838211156132cc57833563ffffffff1683826101000a81548163ffffffff021916908363ffffffff1602179055509260200192600401602081600301049283019260010302613282565b80156132fc5782816101000a81549063ffffffff02191690556004016020816003010492830192600103026132cc565b505b5061330a92915061330e565b5090565b5b8082111561330a575f815560010161330f565b6001600160a01b038116811461102c575f5ffd5b5f60208284031215613346575f5ffd5b81356125dd81613322565b5f60208284031215613361575f5ffd5b5035919050565b5f8151808452602084019350602083015f5b828110156133a15781516001600160a01b031686526020958601959091019060010161337a565b5093949350505050565b602081525f6125dd6020830184613368565b5f5f5f608084860312156133cf575f5ffd5b8335925060208401359150608084018510156133e9575f5ffd5b6040840190509250925092565b5f5f60408385031215613407575f5ffd5b82359150602083013561341981613322565b809150509250929050565b5f5f5f60608486031215613436575f5ffd5b83359250602084013561344881613322565b929592945050506040919091013590565b6001600160a01b0391909116815260200190565b5f8151808452602084019350602083015f5b828110156133a157815186526020958601959091019060010161347f565b604081525f6134af6040830185613368565b82810360208401526134c1818561346d565b95945050505050565b5f5f604083850312156134db575f5ffd5b82356134e681613322565b946020939093013593505050565b634e487b7160e01b5f52602160045260245ffd5b602081016004831061352857634e487b7160e01b5f52602160045260245ffd5b91905290565b5f5f6040838503121561353f575f5ffd5b50508035926020909101359150565b5f5f83601f84011261355e575f5ffd5b5081356001600160401b03811115613574575f5ffd5b60208301915083602082850101111561358b575f5ffd5b9250929050565b5f5f5f5f5f5f5f5f5f60a08a8c0312156135aa575f5ffd5b8935985060208a01356001600160401b038111156135c6575f5ffd5b8a01601f81018c136135d6575f5ffd5b80356001600160401b038111156135eb575f5ffd5b8c60208260051b84010111156135ff575f5ffd5b6020919091019850965060408a01356001600160401b03811115613621575f5ffd5b61362d8c828d0161354e565b90975095505060608a01356001600160401b0381111561364b575f5ffd5b6136578c828d0161354e565b90955093505060808a01356001600160401b03811115613675575f5ffd5b6136818c828d0161354e565b915080935050809150509295985092959850929598565b634e487b7160e01b5f52601160045260245ffd5b5f816136ba576136ba613698565b505f190190565b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f600182016136fa576136fa613698565b5060010190565b5f60208284031215613711575f5ffd5b5051919050565b803563ffffffff811681146118fd575f5ffd5b5f6020828403121561373b575f5ffd5b6125dd82613718565b80820180821115610c3857610c38613698565b84815260a0810160208201855f5b60028110156137925763ffffffff61377c83613718565b1683526020928301929190910190600101613765565b50505060608201939093526080015292915050565b805180151581146118fd575f5ffd5b5f602082840312156137c6575f5ffd5b6125dd826137a7565b604080825283549082018190525f8481526020812090916060840190835b818110156138145783546001600160a01b03168352600193840193602090930192016137ed565b50508381036020850152613828818661346d565b9695505050505050565b6040516101e081016001600160401b0381118282101715613855576138556136c1565b60405290565b604051601f8201601f191681016001600160401b0381118282101715613883576138836136c1565b604052919050565b5f6001600160401b038211156138a3576138a36136c1565b50601f01601f191660200190565b5f82601f8301126138c0575f5ffd5b81356001600160401b038111156138d9576138d96136c1565b8060051b6138e96020820161385b565b91825260208185018101929081019086841115613904575f5ffd5b6020860192505b8383101561382857823582526020928301929091019061390b565b5f5f60408385031215613937575f5ffd5b82356001600160401b0381111561394c575f5ffd5b8301601f8101851361395c575f5ffd5b803561396f61396a8261388b565b61385b565b818152866020838501011115613983575f5ffd5b816020840160208301375f6020838301015280945050505060208301356001600160401b038111156139b3575f5ffd5b6139bf858286016138b1565b9150509250929050565b81810381811115610c3857610c38613698565b8051600481106118fd575f5ffd5b5f82601f8301126139f9575f5ffd5b604080519081016001600160401b0381118282101715613a1b57613a1b6136c1565b8060405250806040840185811115613a31575f5ffd5b845b81811015613a4b578051835260209283019201613a33565b509195945050505050565b80516118fd81613322565b5f82601f830112613a70575f5ffd5b8151613a7e61396a8261388b565b818152846020838601011115613a92575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215613abe575f5ffd5b81516001600160401b03811115613ad3575f5ffd5b82016102008185031215613ae5575f5ffd5b613aed613832565b81518152613afd602083016139dc565b602082015260408281015190820152613b1985606084016139ea565b606082015260a08201516080820152613b3460c08301613a56565b60a082015260e08201516001600160401b03811115613b51575f5ffd5b613b5d86828501613a61565b60c0830152506101008201516001600160401b03811115613b7c575f5ffd5b613b8886828501613a61565b60e083015250613b9b6101208301613a56565b610100820152613bae6101408301613a56565b61012082015261016082810151610140830152610180830151908201526101a08201516001600160401b03811115613be4575f5ffd5b613bf086828501613a61565b61018083015250613c046101c08301613a56565b6101a0820152613c176101e083016137a7565b6101c0820152949350505050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b604081525f613c60604083018688613c25565b8281036020840152613c73818587613c25565b979650505050505050565b606080825281018690525f8760808301825b89811015613cc0578235613ca381613322565b6001600160a01b0316825260209283019290910190600101613c90565b508381036020850152613cd481888a613c25565b9150508281036040840152613cea818587613c25565b9998505050505050505050565b6020808252602a908201527f4c617a79494d543a206c656166206d757374206265203c20534e41524b5f53436040820152691053105497d19251531160b21b606082015260800190565b60ff8181168382160190811115610c3857610c38613698565b64ffffffffff8181168382160190811115610c3857610c38613698565b6040810181835f5b6002811015613d9e578151835260209283019290910190600101613d7f565b50505092915050565b64ffffffffff8281168282160390811115610c3857610c38613698565b5f82613dde57634e487b7160e01b5f52601260045260245ffd5b500490565b60208082526023908201527f4c617a79494d543a206465707468206d757374206265203c3d204d41585f44456040820152620a0a8960eb1b606082015260800190565b6001815b6001841115610ee957808504811115613e4557613e45613698565b6001841615613e5357908102905b60019390931c928002613e2a565b5f82613e6f57506001610c38565b81613e7b57505f610c38565b8160018114613e915760028114613e9b57613ecd565b6001915050610c38565b60ff841115613eac57613eac613698565b6001841b915064ffffffffff821115613ec757613ec7613698565b50610c38565b5060208310610133831016604e8410600b8410161715613f05575081810a64ffffffffff811115613f0057613f00613698565b610c38565b613f1564ffffffffff8484613e26565b8064ffffffffff04821115613f2c57613f2c613698565b029392505050565b5f6125dd64ffffffffff841664ffffffffff8416613e61565b64ffffffffff8181168382160290811690818114613f6d57613f6d613698565b509291505056fea164736f6c634300081c000a", "linkReferences": { "npm/poseidon-solidity@0.0.5/PoseidonT3.sol": { "PoseidonT3": [ { "length": 20, - "start": 7553 + "start": 7803 }, { "length": 20, - "start": 7737 + "start": 7987 }, { "length": 20, - "start": 8367 + "start": 8617 }, { "length": 20, - "start": 12201 + "start": 12451 }, { "length": 20, - "start": 12643 + "start": 12893 } ] } @@ -1292,28 +1316,28 @@ "PoseidonT3": [ { "length": 20, - "start": 7339 + "start": 7589 }, { "length": 20, - "start": 7523 + "start": 7773 }, { "length": 20, - "start": 8153 + "start": 8403 }, { "length": 20, - "start": 11987 + "start": 12237 }, { "length": 20, - "start": 12429 + "start": 12679 } ] } }, "immutableReferences": {}, "inputSourceName": "project/contracts/registry/CiphernodeRegistryOwnable.sol", - "buildInfoId": "solc-0_8_28-fd853fea4a7c18ebe6d1db07eac4a0f86f797607" + "buildInfoId": "solc-0_8_28-a5c2ec0646a40f95c66bc21729b7a906479f7537" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json b/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json index 65becc23d6..0c58e2877c 100644 --- a/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json +++ b/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json @@ -1223,5 +1223,5 @@ ] }, "inputSourceName": "project/contracts/token/EnclaveTicketToken.sol", - "buildInfoId": "solc-0_8_28-3f0df73226c7ff72a6c756321d5f230fe39ed6a0" + "buildInfoId": "solc-0_8_28-a5c2ec0646a40f95c66bc21729b7a906479f7537" } \ No newline at end of file diff --git a/packages/enclave-contracts/contracts/interfaces/ICiphernodeRegistry.sol b/packages/enclave-contracts/contracts/interfaces/ICiphernodeRegistry.sol index 6298a4df90..470a0487cc 100644 --- a/packages/enclave-contracts/contracts/interfaces/ICiphernodeRegistry.sol +++ b/packages/enclave-contracts/contracts/interfaces/ICiphernodeRegistry.sol @@ -296,7 +296,7 @@ interface ICiphernodeRegistry { ) external returns (bool success); /// @notice Publishes the public key resulting from the committee selection process. - /// @dev This function MUST revert if not called by the owner. + /// @dev Any caller may submit the first valid proof. Duplicate submissions must revert. /// @param e3Id ID of the E3 for which to select the committee. /// @param nodes Array of ciphernode addresses selected for the committee. /// @param publicKey The public key generated by the given committee. @@ -319,13 +319,27 @@ interface ICiphernodeRegistry { uint256 e3Id ) external view returns (bytes32 publicKeyHash); + /// @notice Read the published public key hash directly without reverting when it is unset. + /// @param e3Id ID of the E3. + /// @return publicKeyHash The currently stored committee public key hash, or zero when unpublished. + function publicKeyHashes( + uint256 e3Id + ) external view returns (bytes32 publicKeyHash); + /// @notice This function should be called by the Enclave contract to get the committee for a given E3. /// @dev This function MUST revert if no committee has been requested for the given E3. /// @param e3Id ID of the E3 for which to get the committee. /// @return committeeNodes The nodes in the committee for the given E3. + /// @return committeeScores The score of nodes in the committee for a given E3. function getCommitteeNodes( uint256 e3Id - ) external view returns (address[] memory committeeNodes); + ) + external + view + returns ( + address[] memory committeeNodes, + uint256[] memory committeeScores + ); /// @notice Returns the current root of the ciphernode IMT /// @return Current IMT root @@ -376,6 +390,13 @@ interface ICiphernodeRegistry { /// @return success True if committee formed successfully, false if threshold not met function finalizeCommittee(uint256 e3Id) external returns (bool success); + /// @notice Get the current lifecycle stage for an E3 committee. + /// @param e3Id ID of the E3 computation + /// @return stage The current committee stage + function getCommitteeStage( + uint256 e3Id + ) external view returns (CommitteeStage stage); + /// @notice Check if submission window is still open for an E3 /// @param e3Id ID of the E3 computation /// @return Whether the submission window is open diff --git a/packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol b/packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol index e67ae0c1af..f78727b6f6 100644 --- a/packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol +++ b/packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol @@ -188,8 +188,8 @@ contract CiphernodeRegistryOwnable is ICiphernodeRegistry, OwnableUpgradeable { success = true; } - /// @notice Publishes a committee for an E3 computation - /// @dev Only callable by owner. Verification of C5 proof is done in Enclave.onCommitteePublished. + /// @notice Publishes a committee for an E3 computation. + /// @dev Permissionless: any caller may submit the first valid C5 proof. /// @param e3Id ID of the E3 computation /// @param nodes Array of ciphernode addresses selected for the committee /// @param publicKey Aggregated public key of the committee @@ -201,7 +201,7 @@ contract CiphernodeRegistryOwnable is ICiphernodeRegistry, OwnableUpgradeable { bytes calldata publicKey, bytes calldata proof, bytes calldata foldProof - ) external onlyOwner { + ) external { Committee storage c = committees[e3Id]; require( @@ -326,7 +326,7 @@ contract CiphernodeRegistryOwnable is ICiphernodeRegistry, OwnableUpgradeable { CommitteeAlreadyFinalized() ); require( - block.timestamp > c.committeeDeadline, + block.timestamp >= c.committeeDeadline, SubmissionWindowNotClosed() ); bool thresholdMet = c.topNodes.length >= c.threshold[1]; @@ -459,10 +459,15 @@ contract CiphernodeRegistryOwnable is ICiphernodeRegistry, OwnableUpgradeable { /// @inheritdoc ICiphernodeRegistry function getCommitteeNodes( uint256 e3Id - ) public view returns (address[] memory nodes) { + ) public view returns (address[] memory nodes, uint256[] memory scores) { Committee storage c = committees[e3Id]; require(c.publicKey != bytes32(0), CommitteeNotPublished()); nodes = c.topNodes; + uint256 len = nodes.length; + scores = new uint256[](len); + for (uint256 i = 0; i < len; ++i) { + scores[i] = c.scoreOf[nodes[i]]; + } } /// @notice Returns the current size of the ciphernode IMT @@ -489,6 +494,13 @@ contract CiphernodeRegistryOwnable is ICiphernodeRegistry, OwnableUpgradeable { return c.committeeDeadline; } + /// @inheritdoc ICiphernodeRegistry + function getCommitteeStage( + uint256 e3Id + ) external view returns (ICiphernodeRegistry.CommitteeStage) { + return committees[e3Id].stage; + } + //////////////////////////////////////////////////////////// // // // Committee Expulsion Functions // diff --git a/packages/enclave-contracts/contracts/test/MockCiphernodeRegistry.sol b/packages/enclave-contracts/contracts/test/MockCiphernodeRegistry.sol index ee3a608b60..1badd0c9c3 100644 --- a/packages/enclave-contracts/contracts/test/MockCiphernodeRegistry.sol +++ b/packages/enclave-contracts/contracts/test/MockCiphernodeRegistry.sol @@ -56,6 +56,13 @@ contract MockCiphernodeRegistry is ICiphernodeRegistry { } } + function publicKeyHashes(uint256 e3Id) external pure returns (bytes32) { + if (e3Id == type(uint256).max) { + return bytes32(0); + } + return keccak256(abi.encode(e3Id)); + } + function isCiphernodeEligible(address) external pure returns (bool) { return false; } @@ -76,8 +83,12 @@ contract MockCiphernodeRegistry is ICiphernodeRegistry { function getCommitteeNodes( uint256 e3Id - ) external view returns (address[] memory) { - return _committeeNodes[e3Id]; + ) external view returns (address[] memory nodes, uint256[] memory scores) { + nodes = _committeeNodes[e3Id]; + scores = new uint256[](nodes.length); + for (uint256 i = 0; i < nodes.length; i++) { + scores[i] = uint256(keccak256(abi.encode(nodes[i]))); + } } function root() external pure returns (uint256) { @@ -110,6 +121,12 @@ contract MockCiphernodeRegistry is ICiphernodeRegistry { return true; } + function getCommitteeStage( + uint256 + ) external pure returns (ICiphernodeRegistry.CommitteeStage) { + return ICiphernodeRegistry.CommitteeStage.Finalized; + } + function sortitionSubmissionWindow() external pure returns (uint256) { return 0; } @@ -161,9 +178,9 @@ contract MockCiphernodeRegistry is ICiphernodeRegistry { } function getActiveCommitteeNodes( - uint256 - ) external pure returns (address[] memory) { - return new address[](0); + uint256 e3Id + ) external view returns (address[] memory nodes) { + nodes = _committeeNodes[e3Id]; } function getCommitteeViability( @@ -196,6 +213,10 @@ contract MockCiphernodeRegistryEmptyKey is ICiphernodeRegistry { revert CommitteeNotPublished(); } + function publicKeyHashes(uint256) external pure returns (bytes32) { + return bytes32(0); + } + function isCiphernodeEligible(address) external pure returns (bool) { return false; } @@ -216,9 +237,9 @@ contract MockCiphernodeRegistryEmptyKey is ICiphernodeRegistry { function getCommitteeNodes( uint256 - ) external pure returns (address[] memory) { - address[] memory nodes = new address[](0); - return nodes; + ) external pure returns (address[] memory nodes, uint256[] memory scores) { + nodes = new address[](0); + scores = new uint256[](0); } function root() external pure returns (uint256) { @@ -258,6 +279,12 @@ contract MockCiphernodeRegistryEmptyKey is ICiphernodeRegistry { return true; } + function getCommitteeStage( + uint256 + ) external pure returns (ICiphernodeRegistry.CommitteeStage) { + return ICiphernodeRegistry.CommitteeStage.Finalized; + } + function isOpen(uint256) external pure returns (bool) { return false; } diff --git a/packages/enclave-contracts/deployed_contracts.json b/packages/enclave-contracts/deployed_contracts.json index dfd3ccc67a..358966c089 100644 --- a/packages/enclave-contracts/deployed_contracts.json +++ b/packages/enclave-contracts/deployed_contracts.json @@ -151,21 +151,21 @@ }, "localhost": { "PoseidonT3": { - "blockNumber": 4, + "blockNumber": 6, "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" }, "MockUSDC": { "constructorArgs": { "initialSupply": "1000000" }, - "blockNumber": 5, + "blockNumber": 7, "address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" }, "EnclaveToken": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 6, + "blockNumber": 8, "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" }, "EnclaveTicketToken": { @@ -174,14 +174,14 @@ "registry": "0x0000000000000000000000000000000000000001", "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 8, + "blockNumber": 10, "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" }, "SlashingManager": { "constructorArgs": { "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 9, + "blockNumber": 11, "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" }, "CiphernodeRegistryOwnable": { @@ -196,7 +196,7 @@ "proxyAdminAddress": "0x9bd03768a7DCc129555dE410FF8E85528A4F88b5", "implementationAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F" }, - "blockNumber": 10, + "blockNumber": 12, "address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, "BondingRegistry": { @@ -218,7 +218,7 @@ "proxyAdminAddress": "0x8aCd85898458400f7Db866d53FCFF6f0D49741FF", "implementationAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" }, - "blockNumber": 11, + "blockNumber": 13, "address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" }, "Enclave": { @@ -241,7 +241,7 @@ "proxyAdminAddress": "0x8dAF17A20c9DBA35f005b6324F493785D239719d", "implementationAddress": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" }, - "blockNumber": 14, + "blockNumber": 16, "address": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" }, "E3RefundManager": { @@ -257,24 +257,24 @@ "proxyAdminAddress": "0x32467b43BFa67273FC7dDda0999Ee9A12F2AaA08", "implementationAddress": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" }, - "blockNumber": 16, + "blockNumber": 18, "address": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" }, "MockComputeProvider": { - "blockNumber": 18, - "address": "0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E" + "blockNumber": 20, + "address": "0xc3e53F4d16Ae77Db1c982e75a937B9f60FE63690" }, "MockDecryptionVerifier": { - "blockNumber": 19, - "address": "0xc3e53F4d16Ae77Db1c982e75a937B9f60FE63690" + "blockNumber": 21, + "address": "0x84eA74d481Ee0A5332c457a4d796187F6Ba67fEB" }, "MockPkVerifier": { - "blockNumber": 20, - "address": "0x84eA74d481Ee0A5332c457a4d796187F6Ba67fEB" + "blockNumber": 22, + "address": "0x9E545E3C0baAB3E08CdfD552C960A1050f373042" }, "MockE3Program": { - "blockNumber": 21, - "address": "0x9E545E3C0baAB3E08CdfD552C960A1050f373042" + "blockNumber": 23, + "address": "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9" } } } \ No newline at end of file diff --git a/packages/enclave-contracts/hardhat.config.ts b/packages/enclave-contracts/hardhat.config.ts index b0826c6d9a..268eaf91ad 100644 --- a/packages/enclave-contracts/hardhat.config.ts +++ b/packages/enclave-contracts/hardhat.config.ts @@ -20,6 +20,7 @@ import { updateSubmissionWindow, } from "./tasks/ciphernode"; import { + activeCommitteeNodes, enableE3, publishCiphertext, publishCommittee, @@ -92,6 +93,7 @@ const config: HardhatUserConfig = { ciphernodeMintTokens, ciphernodeRemove, requestCommittee, + activeCommitteeNodes, publishPlaintext, publishCiphertext, publishCommittee, diff --git a/packages/enclave-contracts/tasks/enclave.ts b/packages/enclave-contracts/tasks/enclave.ts index 5274de08c6..bbf406a040 100644 --- a/packages/enclave-contracts/tasks/enclave.ts +++ b/packages/enclave-contracts/tasks/enclave.ts @@ -315,6 +315,39 @@ export const publishCommittee = task( })) .build(); +export const activeCommitteeNodes = task( + "committee:active-nodes", + "Get the active finalized committee nodes for an E3", +) + .addOption({ + name: "e3Id", + description: "Id of the E3 program", + defaultValue: 0, + type: ArgumentType.INT, + }) + .setAction(async () => ({ + default: async ({ e3Id }, hre) => { + const { deployAndSaveCiphernodeRegistryOwnable } = await import( + "../scripts/deployAndSave/ciphernodeRegistryOwnable" + ); + const { deployAndSavePoseidonT3 } = await import( + "../scripts/deployAndSave/poseidonT3" + ); + + const poseidonT3 = await deployAndSavePoseidonT3({ hre }); + const { ciphernodeRegistry } = await deployAndSaveCiphernodeRegistryOwnable( + { + hre, + poseidonT3Address: poseidonT3, + }, + ); + + const nodes = await ciphernodeRegistry.getActiveCommitteeNodes(e3Id); + console.log(JSON.stringify(nodes.map((node) => node.toLowerCase()))); + }, + })) + .build(); + export const publishCiphertext = task( "e3:publishCiphertext", "Publish ciphertext output for an E3 program", diff --git a/packages/enclave-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts b/packages/enclave-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts index 858ce78925..cefd25abe9 100644 --- a/packages/enclave-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts +++ b/packages/enclave-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts @@ -408,7 +408,7 @@ describe("CiphernodeRegistryOwnable", function () { }); describe("publishCommittee()", function () { - it("reverts if the caller is not the owner", async function () { + it("allows any caller to publish the committee with a valid proof", async function () { const { registry, enclave, @@ -432,21 +432,21 @@ describe("CiphernodeRegistryOwnable", function () { await registry.connect(operator3).submitTicket(0, 1); await finalizeCommitteeAfterWindow(registry, 0); - await expect( - registry - .connect(notTheOwner) - .publishCommittee( - 0, - [ - await operator1.getAddress(), - await operator2.getAddress(), - await operator3.getAddress(), - ], - data, - c5Proof, - "0x", - ), - ).to.be.revertedWithCustomError(registry, "OwnableUnauthorizedAccount"); + await registry + .connect(notTheOwner) + .publishCommittee( + 0, + [ + await operator1.getAddress(), + await operator2.getAddress(), + await operator3.getAddress(), + ], + data, + c5Proof, + "0x", + ); + + expect(await registry.committeePublicKey(0)).to.equal(dataHash); }); it("stores the public key of the committee", async function () { const { diff --git a/templates/default/enclave.config.yaml b/templates/default/enclave.config.yaml index 143048a50a..6d6ce9fe18 100644 --- a/templates/default/enclave.config.yaml +++ b/templates/default/enclave.config.yaml @@ -53,11 +53,3 @@ nodes: ctrl_port: 50505 autonetkey: true autopassword: true - ag: - address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - quic_port: 9206 - ctrl_port: 50506 - autonetkey: true - autopassword: true - role: - type: "aggregator" diff --git a/templates/default/scripts/dev_ciphernodes.sh b/templates/default/scripts/dev_ciphernodes.sh index 97339c349f..37133c1dab 100755 --- a/templates/default/scripts/dev_ciphernodes.sh +++ b/templates/default/scripts/dev_ciphernodes.sh @@ -24,14 +24,12 @@ pnpm wait-on tcp:localhost:8545 rm -rf .enclave/data rm -rf .enclave/config -PRIVATE_KEY_AG="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" PRIVATE_KEY_CN1="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" PRIVATE_KEY_CN2="0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" PRIVATE_KEY_CN3="0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6" PRIVATE_KEY_CN4="0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a" PRIVATE_KEY_CN5="0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba" -enclave wallet set --name ag --private-key "$PRIVATE_KEY_AG" enclave wallet set --name cn1 --private-key "$PRIVATE_KEY_CN1" enclave wallet set --name cn2 --private-key "$PRIVATE_KEY_CN2" enclave wallet set --name cn3 --private-key "$PRIVATE_KEY_CN3" diff --git a/tests/integration/base.sh b/tests/integration/base.sh index 16b9c22548..cd82a80db6 100755 --- a/tests/integration/base.sh +++ b/tests/integration/base.sh @@ -20,8 +20,6 @@ done pnpm evm:clean pnpm evm:deploy --network localhost -# set wallet to ag specifically -enclave_wallet_set ag "$PRIVATE_KEY_AG" enclave_wallet_set cn1 "$PRIVATE_KEY_CN1" enclave_wallet_set cn2 "$PRIVATE_KEY_CN2" enclave_wallet_set cn3 "$PRIVATE_KEY_CN3" @@ -76,10 +74,17 @@ pnpm committee:new \ --e3-params "$ENCODED_PARAMS" \ --committee-size 0 -waiton "$SCRIPT_DIR/output/pubkey.bin" +# Wait for any node's pubkey to signal committee finalization + DKG completion +waiton_any_pubkey + +# Determine primary (rank=0) aggregator from on-chain committee ordering +PRIMARY_NODE=$(get_primary_committee_node 0) +echo "Primary committee node: $PRIMARY_NODE" +PRIMARY_PUBKEY_PATH=$(ciphernode_pubkey_path "$PRIMARY_NODE") +PRIMARY_PLAINTEXT_PATH=$(ciphernode_plaintext_path "$PRIMARY_NODE") heading "Mock encrypted plaintext" -$SCRIPT_DIR/lib/fake_encrypt.sh --input "$SCRIPT_DIR/output/pubkey.bin" --output "$SCRIPT_DIR/output/output.bin" --plaintext $PLAINTEXT --params "$ENCODED_PARAMS" +$SCRIPT_DIR/lib/fake_encrypt.sh --input "$PRIMARY_PUBKEY_PATH" --output "$SCRIPT_DIR/output/output.bin" --plaintext $PLAINTEXT --params "$ENCODED_PARAMS" heading "Mock publish input e3-id" pnpm e3-program:publishInput --network localhost --e3-id 0 --data 0x12345678 @@ -91,9 +96,9 @@ waiton "$SCRIPT_DIR/output/output.bin" heading "Publish ciphertext to EVM" pnpm e3:publishCiphertext --e3-id 0 --network localhost --data-file "$SCRIPT_DIR/output/output.bin" --proof 0x12345678 -waiton "$SCRIPT_DIR/output/plaintext.txt" +waiton "$PRIMARY_PLAINTEXT_PATH" -ACTUAL=$(cut -d',' -f1,2 $SCRIPT_DIR/output/plaintext.txt) +ACTUAL=$(cut -d',' -f1,2 "$PRIMARY_PLAINTEXT_PATH") # Assume plaintext is shorter echo "ACTUAL:" diff --git a/tests/integration/enclave.config.yaml b/tests/integration/enclave.config.yaml index 3dc7463498..b29fe1f5a6 100644 --- a/tests/integration/enclave.config.yaml +++ b/tests/integration/enclave.config.yaml @@ -3,7 +3,7 @@ chains: rpc_url: "ws://localhost:8545" contracts: e3_program: - address: "0x84eA74d481Ee0A5332c457a4d796187F6Ba67fEB" + address: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9" deploy_block: 1 # Set to actual deploy block enclave: address: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" @@ -39,37 +39,37 @@ nodes: ctrl_port: 50501 autonetkey: true autopassword: true + pubkey_write_path: "./output/cn1/pubkey.bin" + plaintext_write_path: "./output/cn1/plaintext.txt" cn2: address: "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" quic_port: 9202 ctrl_port: 50502 autonetkey: true autopassword: true + pubkey_write_path: "./output/cn2/pubkey.bin" + plaintext_write_path: "./output/cn2/plaintext.txt" cn3: address: "0x90F79bf6EB2c4f870365E785982E1f101E93b906" quic_port: 9203 ctrl_port: 50503 autonetkey: true autopassword: true + pubkey_write_path: "./output/cn3/pubkey.bin" + plaintext_write_path: "./output/cn3/plaintext.txt" cn4: address: "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65" quic_port: 9204 ctrl_port: 50504 autonetkey: true autopassword: true + pubkey_write_path: "./output/cn4/pubkey.bin" + plaintext_write_path: "./output/cn4/plaintext.txt" cn5: address: "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" quic_port: 9205 ctrl_port: 50505 autonetkey: true autopassword: true - ag: - address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - quic_port: 9206 - ctrl_port: 50506 - autonetkey: true - autopassword: true - role: - type: aggregator - pubkey_write_path: "./output/pubkey.bin" - plaintext_write_path: "./output/plaintext.txt" + pubkey_write_path: "./output/cn5/pubkey.bin" + plaintext_write_path: "./output/cn5/plaintext.txt" diff --git a/tests/integration/fns.sh b/tests/integration/fns.sh index 2b24b9c1de..e14832db91 100644 --- a/tests/integration/fns.sh +++ b/tests/integration/fns.sh @@ -15,7 +15,6 @@ fi # Environment variables RPC_URL="ws://localhost:8545" -PRIVATE_KEY_AG="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" PRIVATE_KEY_CN1="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" PRIVATE_KEY_CN2="0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" PRIVATE_KEY_CN3="0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6" @@ -99,6 +98,85 @@ waiton-files() { done } +normalize_address() { + echo "$1" | tr '[:upper:]' '[:lower:]' +} + +ciphernode_name_for_address() { + case "$(normalize_address "$1")" in + "$(normalize_address "$CIPHERNODE_ADDRESS_1")") echo "cn1" ;; + "$(normalize_address "$CIPHERNODE_ADDRESS_2")") echo "cn2" ;; + "$(normalize_address "$CIPHERNODE_ADDRESS_3")") echo "cn3" ;; + "$(normalize_address "$CIPHERNODE_ADDRESS_4")") echo "cn4" ;; + "$(normalize_address "$CIPHERNODE_ADDRESS_5")") echo "cn5" ;; + *) + echo "Unknown ciphernode address: $1" >&2 + return 1 + ;; + esac +} + +# One-shot lookup of the primary (rank=0) committee node. +# Call AFTER committee is finalized (e.g. after any pubkey.bin appears). +CIPHERNODE_REGISTRY=$(yq '.chains[0].contracts.ciphernode_registry.address' "$SCRIPT_DIR/enclave.config.yaml") + +get_primary_committee_node() { + local e3_id="${1:-0}" + local timeout="${2:-120}" + local start_time=$(date +%s) + + # Retry until publishCommittee tx is mined on-chain + local raw + while true; do + raw=$(cast call "$CIPHERNODE_REGISTRY" \ + "getCommitteeNodes(uint256)(address[],uint256[])" "$e3_id" \ + --rpc-url "$RPC_URL" 2>/dev/null) && break + + if [ $(($(date +%s) - start_time)) -ge "$timeout" ]; then + echo "Timeout after ${timeout}s waiting for committee to be published on-chain for e3_id=$e3_id" >&2 + return 1 + fi + sleep 1 + done + + local -a addrs scores + mapfile -t addrs < <(awk 'NR==1' <<< "$raw" | tr -d '[] ' | tr ',' '\n') + mapfile -t scores < <(awk 'NR==2' <<< "$raw" | tr -d '[] ' | tr ',' '\n') + + local best_addr + best_addr=$(paste <(printf '%s\n' "${addrs[@]}") <(printf '%s\n' "${scores[@]}") \ + | sort -t$'\t' -k2,2 -g | head -n1 | cut -f1) + + [[ -z "$best_addr" ]] && { echo "No committee nodes found for e3_id=$e3_id" >&2; return 1; } + ciphernode_name_for_address "$best_addr" +} + +ciphernode_pubkey_path() { + echo "$SCRIPT_DIR/output/$1/pubkey.bin" +} + +# Wait until ANY node's pubkey.bin appears (committee membership is non-deterministic) +waiton_any_pubkey() { + local timeout="${1:-1300}" + local start_time=$(date +%s) + while true; do + for name in cn1 cn2 cn3 cn4 cn5; do + if [ -f "$(ciphernode_pubkey_path "$name")" ]; then + return 0 + fi + done + if [ $(($(date +%s) - start_time)) -ge "$timeout" ]; then + echo "Timeout after ${timeout}s waiting for any pubkey.bin" >&2 + return 1 + fi + sleep 1 + done +} + +ciphernode_plaintext_path() { + echo "$SCRIPT_DIR/output/$1/plaintext.txt" +} + enclave_password_set() { local name="$1" local password="$2" diff --git a/tests/integration/persist.sh b/tests/integration/persist.sh index 20fce089cd..24f1b80bcf 100755 --- a/tests/integration/persist.sh +++ b/tests/integration/persist.sh @@ -20,8 +20,6 @@ done pnpm evm:clean pnpm evm:deploy --network localhost -# set wallet to ag specifically -enclave_wallet_set ag "$PRIVATE_KEY_AG" enclave_wallet_set cn1 "$PRIVATE_KEY_CN1" enclave_wallet_set cn2 "$PRIVATE_KEY_CN2" enclave_wallet_set cn3 "$PRIVATE_KEY_CN3" @@ -71,20 +69,26 @@ pnpm committee:new \ --committee-size 0 \ --proof-aggregation-enabled true -waiton "$SCRIPT_DIR/output/pubkey.bin" +# Wait for any node's pubkey to signal committee finalization + DKG completion +waiton_any_pubkey -# kill aggregator -enclave_nodes_stop ag +# Determine primary (rank=0) aggregator from on-chain committee ordering +PRIMARY_NODE=$(get_primary_committee_node 0) +echo "Primary committee node: $PRIMARY_NODE" +PRIMARY_PUBKEY_PATH=$(ciphernode_pubkey_path "$PRIMARY_NODE") +PRIMARY_PLAINTEXT_PATH=$(ciphernode_plaintext_path "$PRIMARY_NODE") + +# restart the current primary node to exercise persistence on a regular ciphernode +enclave_nodes_stop "$PRIMARY_NODE" sleep 8 -# relaunch the aggregator -enclave_nodes_start ag +enclave_nodes_start "$PRIMARY_NODE" sleep 8 heading "Mock encrypted plaintext" -$SCRIPT_DIR/lib/fake_encrypt.sh --input "$SCRIPT_DIR/output/pubkey.bin" --output "$SCRIPT_DIR/output/output.bin" --plaintext $PLAINTEXT --params "$ENCODED_PARAMS" +$SCRIPT_DIR/lib/fake_encrypt.sh --input "$PRIMARY_PUBKEY_PATH" --output "$SCRIPT_DIR/output/output.bin" --plaintext $PLAINTEXT --params "$ENCODED_PARAMS" heading "Mock publish input e3-id" pnpm e3-program:publishInput --network localhost --e3-id 0 --data 0x12345678 @@ -96,9 +100,9 @@ waiton "$SCRIPT_DIR/output/output.bin" heading "Publish ciphertext to EVM" pnpm e3:publishCiphertext --e3-id 0 --network localhost --data-file "$SCRIPT_DIR/output/output.bin" --proof 0x12345678 -waiton "$SCRIPT_DIR/output/plaintext.txt" +waiton "$PRIMARY_PLAINTEXT_PATH" -ACTUAL=$(cut -d',' -f1,2 $SCRIPT_DIR/output/plaintext.txt) +ACTUAL=$(cut -d',' -f1,2 "$PRIMARY_PLAINTEXT_PATH") # Assume plaintext is shorter From 04f4c485fe3b609ae11cbb1c2b4f2f561b3bc5da Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Sat, 28 Mar 2026 21:15:13 +0500 Subject: [PATCH 2/5] fix: add keyshare guard --- crates/keyshare/src/ext.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/keyshare/src/ext.rs b/crates/keyshare/src/ext.rs index e2c67e22d8..13b6fdff68 100644 --- a/crates/keyshare/src/ext.rs +++ b/crates/keyshare/src/ext.rs @@ -53,6 +53,11 @@ impl E3Extension for ThresholdKeyshareExtension { return; }; + // Don't start twice — a hydrated instance already exists after restart + if ctx.get_event_recipient("threshold_keyshare").is_some() { + return; + } + let e3_id = data.clone().e3_id; let party_id = data.clone().party_id; let Some(meta) = ctx.get_dependency(META_KEY) else { From 3bab99f48fc58812366b921302c3b51d02665bc1 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Sat, 28 Mar 2026 21:45:12 +0500 Subject: [PATCH 3/5] fix: review comments --- agent/flow-trace/04_DKG_AND_COMPUTATION.md | 14 +++++++------- .../ciphernode-builder/src/ciphernode_builder.rs | 4 +++- crates/evm/src/enclave_sol_writer.rs | 1 - tests/integration/fns.sh | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/agent/flow-trace/04_DKG_AND_COMPUTATION.md b/agent/flow-trace/04_DKG_AND_COMPUTATION.md index 2c143aa45a..c5ec83acb0 100644 --- a/agent/flow-trace/04_DKG_AND_COMPUTATION.md +++ b/agent/flow-trace/04_DKG_AND_COMPUTATION.md @@ -453,7 +453,7 @@ ThresholdKeyshare receives AllThresholdSharesCollected --- -## Phase 2: Public Key Aggregation (Eligible Finalized Committee Members, with C5 Proof) + ## Phase 2: Public Key Aggregation (Eligible Finalized Committee Members, with C5 Proof) ``` PublicKeyAggregator starts on each selected node and collects KeyshareCreated events @@ -504,16 +504,16 @@ ThresholdKeyshare receives AllThresholdSharesCollected ├─ Checks finalized committee rank via Sortition ├─ Reads on-chain committee stage / pk hash to avoid duplicate submission ├─ Waits rank-based delay (rank 0 first, later ranks staggered) - └─ Calls contract.publishCommittee(e3_id, nodes, publicKey, pkHash) + └─ Calls contract.publishCommittee(e3_id, nodes, publicKey, proof, foldProof) │ │ ┌─── ON-CHAIN (CiphernodeRegistryOwnable) ──────────┐ │ │ │ - │ │ publishCommittee(e3Id, nodes, pk, pkHash) { │ - │ │ 1. require(initialized && finalized) │ + │ │ publishCommittee(e3Id, nodes, pk, proof, foldProof) { + │ │ 1. require(finalized) │ │ │ 2. require(publicKeyHashes[e3Id] == 0) │ │ │ → Can only publish once │ - │ │ 3. verify C5 proof / committee metadata │ - │ │ 4. publicKeyHashes[e3Id] = pkHash │ + │ │ 3. verify C5 proof via pkVerifier │ + │ │ 4. publicKeyHashes[e3Id] = pkHash (from proof) │ │ │ 5. enclave.onCommitteePublished(e3Id, pkHash) │ │ │ │ │ │ │ │ ┌─ Enclave.sol ────────────────────────┐ │ @@ -634,7 +634,7 @@ EnclaveSolReader decodes CiphertextOutputPublished event --- - ## Phase 5: Plaintext Aggregation (Eligible Finalized Committee Members, with C6 Verification & C7 Proof) +## Phase 5: Plaintext Aggregation (Eligible Finalized Committee Members, with C6 Verification & C7 Proof) ``` ThresholdPlaintextAggregator runs on nodes inside the finalized fallback chain diff --git a/crates/ciphernode-builder/src/ciphernode_builder.rs b/crates/ciphernode-builder/src/ciphernode_builder.rs index 7c7b8765e8..dad1373955 100644 --- a/crates/ciphernode-builder/src/ciphernode_builder.rs +++ b/crates/ciphernode-builder/src/ciphernode_builder.rs @@ -685,6 +685,7 @@ async fn setup_evm_system( pubkey_agg: bool, ) -> Result { let mut evm_config = EvmEventConfig::new(); + let mut committee_finalizer_attached = false; for chain in chains.iter().filter(|chain| chain.enabled.unwrap_or(true)) { let provider = provider_cache.ensure_read_provider(chain).await?; let chain_id = provider.chain_id(); @@ -748,9 +749,10 @@ async fn setup_evm_system( ); info!("CiphernodeRegistrySolWriter attached for publishing committees"); - if pubkey_agg { + if pubkey_agg && !committee_finalizer_attached { info!("Attaching CommitteeFinalizer for score sortition"); CommitteeFinalizer::attach(&bus, sortition.clone()); + committee_finalizer_attached = true; } } Err(e) => error!( diff --git a/crates/evm/src/enclave_sol_writer.rs b/crates/evm/src/enclave_sol_writer.rs index 958fe7a88d..ef5801942e 100644 --- a/crates/evm/src/enclave_sol_writer.rs +++ b/crates/evm/src/enclave_sol_writer.rs @@ -236,7 +236,6 @@ impl Handler&2; return 1; } ciphernode_name_for_address "$best_addr" From 8b8c094d08af83daeac7bff55eca48ec8d15cb2f Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Sun, 29 Mar 2026 00:13:41 +0500 Subject: [PATCH 4/5] fix: remove threshold_m failure logic --- agent/flow-trace/00_INDEX.md | 17 +- .../flow-trace/03_E3_REQUEST_AND_COMMITTEE.md | 30 +- agent/flow-trace/04_DKG_AND_COMPUTATION.md | 46 ++-- crates/aggregator/src/committee_finalizer.rs | 2 +- crates/aggregator/src/ext.rs | 6 - crates/aggregator/src/publickey_aggregator.rs | 33 +-- .../src/threshold_plaintext_aggregator.rs | 144 +++++++--- crates/events/src/committee.rs | 17 -- .../src/enclave_event/aggregator_selected.rs | 26 ++ crates/events/src/enclave_event/mod.rs | 5 + crates/evm/src/ciphernode_registry_sol.rs | 52 +--- crates/evm/src/enclave_sol_writer.rs | 49 +--- crates/sortition/Readme.md | 2 +- crates/sortition/src/ciphernode_selector.rs | 38 ++- crates/sortition/src/sortition.rs | 260 ++++++++++++++++-- deploy/copy-secrets.sh | 4 +- tests/integration/fns.sh | 6 +- 17 files changed, 503 insertions(+), 234 deletions(-) create mode 100644 crates/events/src/enclave_event/aggregator_selected.rs diff --git a/agent/flow-trace/00_INDEX.md b/agent/flow-trace/00_INDEX.md index db5869e089..3863c14b05 100644 --- a/agent/flow-trace/00_INDEX.md +++ b/agent/flow-trace/00_INDEX.md @@ -6,7 +6,7 @@ | --- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | [01_REGISTRATION.md](01_REGISTRATION.md) | `setup`, `register`, `activate`, `status` CLI commands. On-chain registration into BondingRegistry → CiphernodeRegistry IMT. Rust-side event detection. | | 2 | [02_TOKENS_AND_ACTIVATION.md](02_TOKENS_AND_ACTIVATION.md) | ENCL license bonding, USDC→ETK ticket purchasing, unbonding, burning, exit queue, claiming. Activation thresholds and the `_updateOperatorStatus` formula. | -| 3 | [03_E3_REQUEST_AND_COMMITTEE.md](03_E3_REQUEST_AND_COMMITTEE.md) | E3 request on-chain flow, fee payment, committee request, IMT snapshot. Rust-side sortition (score-based), on-chain ticket submission, committee finalization, `CiphernodeSelected` event. | +| 3 | [03_E3_REQUEST_AND_COMMITTEE.md](03_E3_REQUEST_AND_COMMITTEE.md) | E3 request on-chain flow, fee payment, committee request, IMT snapshot. Rust-side sortition (score-based), buffered ticket submission candidates, on-chain committee finalization, `AggregatorSelected`, and `CiphernodeSelected`. | | 4 | [04_DKG_AND_COMPUTATION.md](04_DKG_AND_COMPUTATION.md) | Full DKG with ZK proof pipeline: BFV keygen → C0 proof → encryption key exchange → TrBFV share generation → C1/C2/C3 proofs → share verification → Shamir secret sharing → encrypted share broadcast → C4 proofs → decryption key reconstruction. C5 proof for PK aggregation. Ciphertext output → C6 proof for decryption shares → C7 proof for plaintext → rewards. | | 5 | [05_FAILURE_REFUND_SLASHING.md](05_FAILURE_REFUND_SLASHING.md) | Timeout-based failure detection, `markE3Failed`, `processE3Failure`. Refund calculation (work-value allocation). Off-chain AccusationManager quorum protocol (proof failure → accusation → voting → quorum). Lane A (attestation-based, atomic) and Lane B (evidence-based, with appeals) slashing. Ticket/license slashing. Slashed funds escrow and routing. | | 6 | [06_DEACTIVATION_AND_COMPLETION.md](06_DEACTIVATION_AND_COMPLETION.md) | Voluntary deactivation (ticket/license withdrawal), full deregistration (IMT removal), E3 happy-path completion, node shutdown, sync/restart, exit queue timing, ban/unban. | @@ -36,10 +36,12 @@ 6. E3 REQUEST Requester calls Enclave.request(params) → Fee paid, committee requested, IMT root snapshot -7. SORTITION Ciphernodes compute scores, submit tickets on-chain - → Top N lowest scores selected +7. SORTITION Ciphernodes compute scores, derive a buffered local candidate list, + and submit tickets on-chain + → Finalized committee is later sorted by score 8. FINALIZE finalizeCommittee() → committee locked in + → Sortition emits AggregatorSelected for the first active member 9. DKG Selected nodes perform distributed key generation: a. BFV keygen → C0 proof (proves keypair valid) @@ -51,8 +53,8 @@ g. Exchange DecryptionKeyShared → verify C4 proofs h. Publish KeyshareCreated → finalized committee aggregation candidates -10. PK AGG Primary finalized committee member aggregates pk_shares - → fallbacks are derived from later committee ranks +10. PK AGG Current aggregator aggregates pk_shares + → all finalized committee members are ordered fallbacks → C5 proof (proves aggregation correct) → publishCommittee() on-chain → KeyPublished stage @@ -63,8 +65,8 @@ → C6 proof per share (proves share correctly derived) → Broadcast to finalized committee aggregation candidates -13. AGGREGATE Primary finalized committee member combines M+1 shares → plaintext - → fallbacks submit later if higher-priority ranks miss their slot +13. AGGREGATE Current aggregator combines M+1 shares → plaintext + → active committee fallbacks submit later if higher-priority ranks miss their slot → C7 proof (proves reconstruction correct) 14. COMPLETE publishPlaintextOutput() → rewards distributed @@ -178,6 +180,7 @@ _Found during source-code cross-referencing of these trace documents._ | 8 | `CommitteePublished` event emits `(e3Id, nodes, publicKey, proof)` — full PK bytes and C5 proof, not just pkHash. | CiphernodeRegistryOwnable.sol | 04_DKG | | 9 | `_validateNodeEligibility` calls `bondingRegistry.getTicketBalanceAtBlock()` (not `ticketToken.getPastVotes()` directly). | CiphernodeRegistryOwnable.sol:668 | 03_E3_REQUEST | | 10 | Lane A slashing uses **attestation-based** verification (committee quorum votes), not direct ZK proof re-verification on-chain. `proposeSlash()` decodes voter addresses, agrees, data hashes, and ECDSA signatures — not ZK proofs. | SlashingManager.sol | 05_FAILURE | +| 11 | Aggregator failover is derived from the **ordered finalized committee**, not from `threshold_m`. All finalized committee members are ordered fallback candidates; `Sortition` emits `AggregatorSelected` for the first active member and advances it on expulsion. | Sortition + Aggregator writers | 03_E3_REQUEST + 04_DKG | ### Protocol Design Concerns diff --git a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md index 45aa13b13b..b949264f15 100644 --- a/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md +++ b/agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md @@ -128,12 +128,15 @@ EnclaveSolReader decodes IEnclave::E3Requested log │ └─ Stores as dependency in E3Context │ ├─ PublicKeyAggregatorExtension.on_event(): -│ └─ Spins up PublicKeyAggregator actor on each selected node -│ └─ State: Pending until CommitteeFinalized resolves finalized aggregation duty +│ └─ Spins up PublicKeyAggregator actor +│ └─ State: Pending until CommitteeFinalized resolves whether this node is in the finalized committee │ └─ Sortition actor receives E3Requested: │ ├─ Calculates buffer = calculate_buffer_size(M, N) + │ → Produces a LOCAL selected list of N + buffer candidates + │ → This same ordered candidate list drives both ticket submission and + │ fallback `finalizeCommittee()` scheduling │ ├─ ScoreBackend.get_committee(): │ │ @@ -150,12 +153,12 @@ EnclaveSolReader decodes IEnclave::E3Requested log │ │ │ ├─ Sort ALL nodes by their best score (ascending) │ │ - │ └─ Select top N nodes (lowest scores win) - │ → Returns committee list with party indices + │ └─ Select top N + buffer nodes (lowest scores win) + │ → Returns the ordered local candidate list used before finalization │ └─ Sends WithSortitionTicket to CiphernodeSelector │ - ├─ If THIS node is in the selected committee: + ├─ If THIS node is in the selected candidate list: │ ticket_id = Some(TicketId::Score(best_ticket_number)) │ party_index = Some(index_in_committee) │ @@ -235,11 +238,11 @@ CiphernodeRegistrySolWriter receives TicketGenerated event ### 3a. Deadline Timer (Rust-Side, Selected Ciphernodes) -``` +```text CommitteeFinalizer actor receives CommitteeRequested event on each selected node │ -├─ Resolves this node's local score rank from sortition -│ └─ If the node is outside the provisional top-N, no timer is scheduled +├─ Resolves this node's local score rank from the same buffered sortition list +│ └─ If the node is outside the local selected candidate list, no timer is scheduled │ ├─ Calculates wait time: │ wait = committeeDeadline - currentTimestamp + buffer + local_rank_stagger @@ -312,9 +315,16 @@ CiphernodeRegistrySolReader decodes CommitteeFinalized event ├─ Sortition actor: │ └─ Stores finalized committee as a `Committee` struct in persistent map │ → Provides O(1) address→party_id lookup for later expulsion handling +│ └─ Emits AggregatorSelected { +│ e3_id, party_id, node, committee +│ } +│ → Initial selection is the first member in the score-sorted finalized committee +│ → If that member is later expelled, Sortition emits another AggregatorSelected +│ for the next active committee member in order │ ├─ CiphernodeSelector: -│ ├─ Checks if this node's address is in the committee list +│ ├─ Receives the FIRST AggregatorSelected for this E3 +│ ├─ Checks if this node's address is in the finalized committee list │ ├─ If YES: │ │ party_id = index of this node in committee array │ │ Publishes CiphernodeSelected { @@ -322,6 +332,8 @@ CiphernodeRegistrySolReader decodes CommitteeFinalized event │ │ seed, party_id, ...all E3 metadata │ │ } │ └─ If NO: does nothing for this E3 +│ → Later AggregatorSelected failover events do NOT re-emit CiphernodeSelected +│ because committee membership did not change │ └─ KeyshareCreatedFilterBuffer: └─ Stores committee set diff --git a/agent/flow-trace/04_DKG_AND_COMPUTATION.md b/agent/flow-trace/04_DKG_AND_COMPUTATION.md index c5ec83acb0..1c2fc68a4e 100644 --- a/agent/flow-trace/04_DKG_AND_COMPUTATION.md +++ b/agent/flow-trace/04_DKG_AND_COMPUTATION.md @@ -453,19 +453,20 @@ ThresholdKeyshare receives AllThresholdSharesCollected --- - ## Phase 2: Public Key Aggregation (Eligible Finalized Committee Members, with C5 Proof) +## Phase 2: Public Key Aggregation (Finalized Committee Members, with C5 Proof) -``` - PublicKeyAggregator starts on each selected node and collects KeyshareCreated events +```text +PublicKeyAggregator starts on each node and collects KeyshareCreated events │ ├─ KeyshareCreatedFilterBuffer gates events: │ └─ Only forwards KeyshareCreated from verified committee members │ └─ Buffers until CommitteeFinalized is known │ - ├─ When CommitteeFinalized resolves this node into the finalized fallback chain: - │ └─ State becomes Eligible(rank) + ├─ When CommitteeFinalized shows this node is in the finalized committee: + │ └─ State becomes active for aggregation on this node + │ └─ Every finalized committee member is an ordered fallback candidate │ - ├─ When threshold_n keyshares collected on an eligible node: + ├─ When threshold_n keyshares collected on a finalized-committee node: │ │ │ ├─ 1. Aggregate public key shares: │ │ aggregate_pk = Fhe::get_aggregate_public_key( @@ -500,10 +501,12 @@ ThresholdKeyshare receives AllThresholdSharesCollected │ e3_id, aggregate_pk, pk_hash, node_list │ } │ -└─ CiphernodeRegistrySolWriter on each eligible node receives PublicKeyAggregated: - ├─ Checks finalized committee rank via Sortition +└─ CiphernodeRegistrySolWriter on each finalized-committee node receives PublicKeyAggregated: + ├─ Queries Sortition for this node's ACTIVE submission rank + │ → rank 0 = current aggregator from AggregatorSelected + │ → later active committee members are staggered fallbacks ├─ Reads on-chain committee stage / pk hash to avoid duplicate submission - ├─ Waits rank-based delay (rank 0 first, later ranks staggered) + ├─ Waits rank-based delay (rank 0 first, later active ranks staggered) └─ Calls contract.publishCommittee(e3_id, nodes, publicKey, proof, foldProof) │ │ ┌─── ON-CHAIN (CiphernodeRegistryOwnable) ──────────┐ @@ -634,23 +637,24 @@ EnclaveSolReader decodes CiphertextOutputPublished event --- -## Phase 5: Plaintext Aggregation (Eligible Finalized Committee Members, with C6 Verification & C7 Proof) +## Phase 5: Plaintext Aggregation (Finalized Committee Members, with C6 Verification & C7 Proof) -``` - ThresholdPlaintextAggregator runs on nodes inside the finalized fallback chain +```text + ThresholdPlaintextAggregator runs on finalized committee nodes that are still active │ - ├─ On startup / CommitteeFinalized: - │ └─ Resolves whether this node is eligible to aggregate plaintext for this e3_id + ├─ On startup: + │ └─ Queries Sortition for this node's active aggregation rank for this e3_id + │ └─ Stops immediately if the node is no longer an active committee member │ ThresholdPlaintextAggregator receives DecryptionshareCreated events │ ├─ For each share: -│ ├─ Verify sender is in committee: +│ ├─ Verify sender is in the ACTIVE committee: │ │ └─ Query Sortition via E3CommitteeContainsRequest │ ├─ If verified: add_share(party_id, decryption_share) │ └─ If not: ignore │ - ├─ C6 VERIFICATION (per-share, on eligible aggregation node): + ├─ C6 VERIFICATION (per-share, on active aggregation node): │ ShareVerificationActor receives C6 signed proofs │ ├─ ECDSA recovery + ZK verification (same 2-phase as C2/C3) │ ├─ On failure: SignedProofFailed → accusation pipeline @@ -699,15 +703,15 @@ EnclaveSolReader decodes CiphertextOutputPublished event │ │ │ └─ Publish PlaintextAggregated { e3_id, decrypted_output } │ -└─ EnclaveSolWriter on each eligible node receives PlaintextAggregated: - ├─ Checks finalized committee rank via Sortition +└─ EnclaveSolWriter on each active committee node receives PlaintextAggregated: + ├─ Queries Sortition for this node's ACTIVE submission rank ├─ Reads on-chain E3 stage before submitting - ├─ Waits rank-based delay (rank 0 first, later ranks staggered) - └─ Calls contract.publishPlaintextOutput(e3Id, output, proof) + ├─ Waits rank-based delay (current aggregator first, later active ranks staggered) + └─ Calls contract.publishPlaintextOutput(e3Id, output, proof, foldProof) │ │ ┌─── ON-CHAIN (Enclave.sol) ─────────────────────────┐ │ │ │ - │ │ publishPlaintextOutput(e3Id, output, proof) { │ + │ │ publishPlaintextOutput(e3Id, output, proof, foldProof) { │ │ 1. require(stage == CiphertextReady) │ │ │ 2. require(now <= decryptionDeadline) │ │ │ 3. e3.plaintextOutput = output │ diff --git a/crates/aggregator/src/committee_finalizer.rs b/crates/aggregator/src/committee_finalizer.rs index a91369ded8..98698d7a2b 100644 --- a/crates/aggregator/src/committee_finalizer.rs +++ b/crates/aggregator/src/committee_finalizer.rs @@ -108,7 +108,7 @@ impl Handler> for CommitteeFinalizer { let local_rank_request = GetLocalNodeSortitionRank { e3_id: msg.e3_id.clone(), seed: msg.seed, - size: msg.threshold[1], + threshold: msg.threshold, chain_id: msg.chain_id, }; let sortition = self.sortition.clone(); diff --git a/crates/aggregator/src/ext.rs b/crates/aggregator/src/ext.rs index 81c0509ef9..baecdf0fbf 100644 --- a/crates/aggregator/src/ext.rs +++ b/crates/aggregator/src/ext.rs @@ -80,7 +80,6 @@ impl E3Extension for PublicKeyAggregatorExtension { sync_state, self.params_preset.clone(), &self.node_address, - meta.threshold_m, ); ctx.set_event_recipient("publickey", Some(value)); @@ -116,9 +115,6 @@ impl E3Extension for PublicKeyAggregatorExtension { sync_state, self.params_preset.clone(), &self.node_address, - ctx.get_dependency(META_KEY) - .ok_or_else(|| anyhow!(ERROR_PUBKEY_META_MISSING))? - .threshold_m, ); // send to context @@ -135,7 +131,6 @@ fn create_publickey_aggregator( sync_state: Persistable, params_preset: BfvPreset, node_address: &str, - threshold_m: usize, ) -> Recipient { KeyshareCreatedFilterBuffer::new( PublicKeyAggregator::new( @@ -145,7 +140,6 @@ fn create_publickey_aggregator( e3_id, params_preset, node_address: node_address.to_owned(), - threshold_m, }, sync_state, ) diff --git a/crates/aggregator/src/publickey_aggregator.rs b/crates/aggregator/src/publickey_aggregator.rs index 54e9bb534b..a48952e8c9 100644 --- a/crates/aggregator/src/publickey_aggregator.rs +++ b/crates/aggregator/src/publickey_aggregator.rs @@ -13,9 +13,9 @@ use e3_events::{ DKGRecursiveAggregationComplete, Die, E3Failed, E3Stage, E3id, EnclaveEvent, EnclaveEventData, EventContext, FailureReason, KeyshareCreated, OrderedSet, PartyProofsToVerify, PkAggregationProofPending, PkAggregationProofRequest, PkAggregationProofSigned, Proof, - ProofType, ProofVerificationPassed, PublicKeyAggregated, Seed, Sequenced, - ShareVerificationComplete, ShareVerificationDispatched, SignedProofFailed, SignedProofPayload, - TypedEvent, VerificationKind, ZkResponse, + ProofType, PublicKeyAggregated, Seed, Sequenced, ShareVerificationComplete, + ShareVerificationDispatched, SignedProofFailed, SignedProofPayload, TypedEvent, + VerificationKind, ZkResponse, }; use e3_events::{trap, EType}; use e3_fhe::{Fhe, GetAggregatePublicKey}; @@ -87,7 +87,7 @@ impl PublicKeyAggregatorState { #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum AggregationDuty { PendingCommittee, - Eligible { rank: usize }, + CommitteeMember { party_id: u64 }, Inactive, } @@ -98,7 +98,6 @@ pub struct PublicKeyAggregator { state: Persistable, params_preset: BfvPreset, node_address: String, - threshold_m: usize, duty: AggregationDuty, /// DKG recursive aggregation events received before entering GeneratingC5Proof. early_dkg_proofs: Vec>, @@ -110,7 +109,6 @@ pub struct PublicKeyAggregatorParams { pub e3_id: E3id, pub params_preset: BfvPreset, pub node_address: String, - pub threshold_m: usize, } /// Aggregate PublicKey for a committee of nodes. This actor listens for KeyshareCreated events @@ -128,7 +126,6 @@ impl PublicKeyAggregator { state, params_preset: params.params_preset, node_address: params.node_address, - threshold_m: params.threshold_m, duty: AggregationDuty::PendingCommittee, early_dkg_proofs: Vec::new(), } @@ -140,28 +137,28 @@ impl PublicKeyAggregator { } let committee = e3_events::Committee::new(msg.committee); - self.duty = match committee.aggregation_rank_for(&self.node_address, self.threshold_m) { - Some(rank) => { + self.duty = match committee.party_id_for(&self.node_address) { + Some(party_id) => { info!( e3_id = %self.e3_id, node = %self.node_address, - rank = rank, - "Node is in the finalized public-key aggregation priority chain" + party_id = party_id, + "Node is in the finalized public-key aggregation chain" ); - if rank == 0 { + if party_id == 0 { info!( e3_id = %self.e3_id, node = %self.node_address, "[AGGREGATOR] Node is the current primary public-key aggregator" ); } - AggregationDuty::Eligible { rank } + AggregationDuty::CommitteeMember { party_id } } None => { info!( e3_id = %self.e3_id, node = %self.node_address, - "Node is outside the finalized public-key aggregation priority chain" + "Node is outside the finalized public-key aggregation chain" ); AggregationDuty::Inactive } @@ -687,7 +684,7 @@ impl PublicKeyAggregator { /// Publish `PublicKeyAggregated` when both C5 and cross-node fold are complete. fn try_publish_complete(&mut self) -> Result<()> { - if !matches!(self.duty, AggregationDuty::Eligible { .. }) { + if !matches!(self.duty, AggregationDuty::CommitteeMember { .. }) { return Ok(()); } @@ -940,6 +937,10 @@ impl Handler for PublicKeyAggregator { node_addr, data.e3_id ); trap(EType::PublickeyAggregation, &self.bus.with_ec(&ec), || { + if node_addr.eq_ignore_ascii_case(&self.node_address) { + self.duty = AggregationDuty::Inactive; + } + let was_collecting = matches!( self.state.get(), Some(PublicKeyAggregatorState::Collecting { .. }) @@ -988,7 +989,7 @@ impl Handler> for PublicKeyAggregator { ) -> Self::Result { let (event, ec) = event.into_components(); trap(EType::PublickeyAggregation, &self.bus.with_ec(&ec), || { - if !matches!(self.duty, AggregationDuty::Eligible { .. }) { + if !matches!(self.duty, AggregationDuty::CommitteeMember { .. }) { return Ok(()); } diff --git a/crates/aggregator/src/threshold_plaintext_aggregator.rs b/crates/aggregator/src/threshold_plaintext_aggregator.rs index 6cf2813364..73be616e36 100644 --- a/crates/aggregator/src/threshold_plaintext_aggregator.rs +++ b/crates/aggregator/src/threshold_plaintext_aggregator.rs @@ -11,15 +11,16 @@ use actix::prelude::*; use anyhow::{anyhow, bail, ensure, Result}; use e3_data::Persistable; use e3_events::{ - prelude::*, trap, AggregationProofPending, AggregationProofSigned, BusHandle, ComputeRequest, - ComputeResponse, ComputeResponseKind, CorrelationId, DecryptedSharesAggregationProofRequest, - DecryptionshareCreated, Die, E3id, EType, EnclaveEvent, EnclaveEventData, EventContext, - PartyProofsToVerify, PlaintextAggregated, Proof, Seed, Sequenced, ShareVerificationComplete, - ShareVerificationDispatched, SignedProofPayload, TypedEvent, VerificationKind, ZkResponse, + prelude::*, trap, AggregationProofPending, AggregationProofSigned, BusHandle, + CommitteeMemberExpelled, ComputeRequest, ComputeResponse, ComputeResponseKind, CorrelationId, + DecryptedSharesAggregationProofRequest, DecryptionshareCreated, Die, E3id, EType, EnclaveEvent, + EnclaveEventData, EventContext, PartyProofsToVerify, PlaintextAggregated, Proof, Seed, + Sequenced, ShareVerificationComplete, ShareVerificationDispatched, SignedProofPayload, + TypedEvent, VerificationKind, ZkResponse, }; use e3_fhe_params::BfvPreset; use e3_sortition::{ - E3CommitteeContainsRequest, E3CommitteeContainsResponse, GetFinalizedCommittee, Sortition, + E3CommitteeContainsRequest, E3CommitteeContainsResponse, GetAggregatorSubmissionRank, Sortition, }; use e3_trbfv::{ calculate_threshold_decryption::CalculateThresholdDecryptionRequest, TrBFVConfig, TrBFVRequest, @@ -173,7 +174,7 @@ impl ThresholdPlaintextAggregatorState { #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum AggregationDuty { PendingCommittee, - Eligible { rank: usize }, + CommitteeMember, Inactive, } @@ -222,53 +223,75 @@ impl ThresholdPlaintextAggregator { } } - fn resolve_aggregation_duty(&mut self, committee: e3_events::Committee) { - self.duty = match committee.aggregation_rank_for(&self.node_address, self.threshold_m()) { + fn resolve_aggregation_duty(&mut self, submission_rank: Option) { + self.duty = match submission_rank { Some(rank) => { info!( e3_id = %self.e3_id, node = %self.node_address, rank = rank, - "Node is in the finalized plaintext aggregation priority chain" + "Node is in the active plaintext aggregation chain" ); - AggregationDuty::Eligible { rank } + AggregationDuty::CommitteeMember } None => { info!( e3_id = %self.e3_id, node = %self.node_address, - "Node is outside the finalized plaintext aggregation priority chain" + "Node is outside the active plaintext aggregation chain" ); AggregationDuty::Inactive } }; } - fn threshold_m(&self) -> usize { - self.state - .get() - .and_then(|state| match state { - ThresholdPlaintextAggregatorState::Collecting(ref data) => { - Some(data.threshold_m as usize) + fn flush_pending_shares(&mut self, ctx: &mut Context) { + for event in std::mem::take(&mut self.pending_shares) { + ctx.notify(event); + } + } + + fn handle_member_expelled( + &mut self, + party_id: u64, + node: &str, + ec: &EventContext, + ) -> Result<()> { + self.state.try_mutate(ec, |state| { + Ok(match state { + ThresholdPlaintextAggregatorState::Collecting(mut data) => { + data.shares.remove(&party_id); + data.c6_proofs.remove(&party_id); + data.c6_wrapped_proofs.remove(&party_id); + ThresholdPlaintextAggregatorState::Collecting(data) } - ThresholdPlaintextAggregatorState::VerifyingC6(ref data) => { - Some(data.threshold_m as usize) + ThresholdPlaintextAggregatorState::VerifyingC6(mut data) => { + data.shares.remove(&party_id); + data.c6_proofs.remove(&party_id); + data.c6_wrapped_proofs.remove(&party_id); + ThresholdPlaintextAggregatorState::VerifyingC6(data) } - ThresholdPlaintextAggregatorState::Computing(ref data) => { - Some(data.threshold_m as usize) + ThresholdPlaintextAggregatorState::Computing(mut data) => { + data.shares.retain(|(id, _)| *id != party_id); + ThresholdPlaintextAggregatorState::Computing(data) } - ThresholdPlaintextAggregatorState::GeneratingC7Proof(ref data) => { - Some(data.threshold_m as usize) + ThresholdPlaintextAggregatorState::GeneratingC7Proof(mut data) => { + data.shares.retain(|(id, _)| *id != party_id); + ThresholdPlaintextAggregatorState::GeneratingC7Proof(data) + } + ThresholdPlaintextAggregatorState::Complete(mut data) => { + data.shares.retain(|(id, _)| *id != party_id); + ThresholdPlaintextAggregatorState::Complete(data) } - ThresholdPlaintextAggregatorState::Complete(_) => None, }) - .unwrap_or_default() - } + })?; - fn flush_pending_shares(&mut self, ctx: &mut Context) { - for event in std::mem::take(&mut self.pending_shares) { - ctx.notify(event); + if node.eq_ignore_ascii_case(&self.node_address) { + self.duty = AggregationDuty::Inactive; + self.pending_shares.clear(); } + + Ok(()) } pub fn add_share( @@ -667,26 +690,29 @@ impl Actor for ThresholdPlaintextAggregator { let sortition = self.sortition.clone(); let e3_id = self.e3_id.clone(); + let node_address = self.node_address.clone(); ctx.spawn( - async move { sortition.send(GetFinalizedCommittee { e3_id }).await } + async move { + sortition + .send(GetAggregatorSubmissionRank { + e3_id, + node: node_address, + }) + .await + } .into_actor(self) .map(|result, act, ctx| match result { - Ok(Some(committee)) => { - act.resolve_aggregation_duty(committee); - if matches!(act.duty, AggregationDuty::Eligible { .. }) { + Ok(submission_rank) => { + act.resolve_aggregation_duty(submission_rank); + if matches!(act.duty, AggregationDuty::CommitteeMember) { act.flush_pending_shares(ctx); } else { act.pending_shares.clear(); ctx.stop(); } } - Ok(None) => { - warn!(e3_id = %act.e3_id, "No finalized committee available for plaintext aggregation; stopping actor"); - act.pending_shares.clear(); - ctx.stop(); - } Err(err) => { - warn!(e3_id = %act.e3_id, error = %err, "Failed to resolve finalized committee for plaintext aggregation; stopping actor"); + warn!(e3_id = %act.e3_id, error = %err, "Failed to resolve active plaintext aggregation rank; stopping actor"); act.pending_shares.clear(); ctx.stop(); } @@ -711,6 +737,9 @@ impl Handler for ThresholdPlaintextAggregator { EnclaveEventData::AggregationProofSigned(data) => { self.notify_sync(ctx, TypedEvent::new(data, ec)) } + EnclaveEventData::CommitteeMemberExpelled(data) => { + self.notify_sync(ctx, TypedEvent::new(data, ec)) + } _ => (), } } @@ -724,7 +753,7 @@ impl Handler> for ThresholdPlaintextAggregato ctx: &mut Self::Context, ) -> Self::Result { trap( - EType::PublickeyAggregation, + EType::PlaintextAggregation, &self.bus.with_ec(msg.get_ctx()), || { let Some(ThresholdPlaintextAggregatorState::Collecting(Collecting { .. })) = @@ -740,7 +769,7 @@ impl Handler> for ThresholdPlaintextAggregato return Ok(()); } AggregationDuty::Inactive => return Ok(()), - AggregationDuty::Eligible { .. } => {} + AggregationDuty::CommitteeMember => {} } let node = msg.node.clone(); @@ -763,7 +792,7 @@ impl Handler>> _ctx: &mut Self::Context, ) -> Self::Result { trap( - EType::PublickeyAggregation, + EType::PlaintextAggregation, &self.bus.with_ec(msg.get_ctx()), || { let e3_id = &msg.e3_id; @@ -852,6 +881,35 @@ impl Handler> for ThresholdPlaintextAggregato } } +impl Handler> for ThresholdPlaintextAggregator { + type Result = (); + + fn handle( + &mut self, + msg: TypedEvent, + ctx: &mut Self::Context, + ) -> Self::Result { + let (msg, ec) = msg.into_components(); + let Some(party_id) = msg.party_id else { + return; + }; + + if msg.e3_id != self.e3_id { + return; + } + + trap(EType::PlaintextAggregation, &self.bus.with_ec(&ec), || { + self.handle_member_expelled(party_id, &msg.node.to_string(), &ec)?; + + if matches!(self.duty, AggregationDuty::Inactive) { + ctx.stop(); + } + + Ok(()) + }) + } +} + impl Handler for ThresholdPlaintextAggregator { type Result = (); fn handle(&mut self, _: Die, ctx: &mut Self::Context) -> Self::Result { diff --git a/crates/events/src/committee.rs b/crates/events/src/committee.rs index 54d6987f0b..b55a2a04e9 100644 --- a/crates/events/src/committee.rs +++ b/crates/events/src/committee.rs @@ -70,23 +70,6 @@ impl Committee { &self.members } - /// Number of committee members eligible to act as aggregators for this E3. - /// - /// This is the committee's failure budget, derived from the existing M/N - /// threshold. Rank 0 is the primary aggregator, rank 1 the first fallback, - /// and so on up to `len - threshold_m - 1`. - pub fn aggregator_count(&self, threshold_m: usize) -> usize { - self.len().saturating_sub(threshold_m) - } - - /// Resolve the current node's aggregation rank from the finalized committee - /// ordering. Returns `None` when the node is outside the fallback chain. - pub fn aggregation_rank_for(&self, addr: &str, threshold_m: usize) -> Option { - let party_id = self.party_id_for(addr)? as usize; - let aggregator_count = self.aggregator_count(threshold_m); - (party_id < aggregator_count).then_some(party_id) - } - pub fn len(&self) -> usize { self.members.len() } diff --git a/crates/events/src/enclave_event/aggregator_selected.rs b/crates/events/src/enclave_event/aggregator_selected.rs new file mode 100644 index 0000000000..b0b42360a9 --- /dev/null +++ b/crates/events/src/enclave_event/aggregator_selected.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use crate::E3id; +use actix::Message; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +#[derive(Message, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[rtype(result = "()")] +pub struct AggregatorSelected { + pub e3_id: E3id, + pub party_id: u64, + pub node: String, + pub committee: Vec, + pub chain_id: u64, +} + +impl Display for AggregatorSelected { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/crates/events/src/enclave_event/mod.rs b/crates/events/src/enclave_event/mod.rs index c931eec527..756a82b093 100644 --- a/crates/events/src/enclave_event/mod.rs +++ b/crates/events/src/enclave_event/mod.rs @@ -8,6 +8,7 @@ mod accusation_quorum_reached; mod accusation_vote; mod aggregation_proof_pending; mod aggregation_proof_signed; +mod aggregator_selected; mod ciphernode_added; mod ciphernode_removed; mod ciphernode_selected; @@ -73,6 +74,7 @@ pub use accusation_quorum_reached::*; pub use accusation_vote::*; pub use aggregation_proof_pending::*; pub use aggregation_proof_signed::*; +pub use aggregator_selected::*; pub use ciphernode_added::*; pub use ciphernode_removed::*; pub use ciphernode_selected::*; @@ -232,6 +234,7 @@ macro_rules! impl_event_types { #[derive(Clone, Debug, PartialEq, Eq, Hash, IntoStaticStr, Serialize, Deserialize)] pub enum EnclaveEventData { + AggregatorSelected(AggregatorSelected), AccusationQuorumReached(AccusationQuorumReached), AccusationVote(AccusationVote), ProofFailureAccusation(ProofFailureAccusation), @@ -543,6 +546,7 @@ impl EnclaveEventData { EnclaveEventData::DecryptionKeyShared(ref data) => Some(data.e3_id.clone()), EnclaveEventData::DecryptionshareCreated(ref data) => Some(data.e3_id.clone()), EnclaveEventData::PlaintextAggregated(ref data) => Some(data.e3_id.clone()), + EnclaveEventData::AggregatorSelected(ref data) => Some(data.e3_id.clone()), EnclaveEventData::PkGenerationProofSigned(ref data) => Some(data.e3_id.clone()), EnclaveEventData::DkgProofSigned(ref data) => Some(data.e3_id.clone()), EnclaveEventData::CiphernodeSelected(ref data) => Some(data.e3_id.clone()), @@ -608,6 +612,7 @@ impl WithAggregateId for EnclaveEvent { } impl_event_types!( + AggregatorSelected, AccusationQuorumReached, AccusationVote, ProofFailureAccusation, diff --git a/crates/evm/src/ciphernode_registry_sol.rs b/crates/evm/src/ciphernode_registry_sol.rs index 2236e09363..55bce8153e 100644 --- a/crates/evm/src/ciphernode_registry_sol.rs +++ b/crates/evm/src/ciphernode_registry_sol.rs @@ -24,7 +24,7 @@ use e3_events::{ EffectsEnabled, EnclaveEvent, EnclaveEventData, EventSubscriber, EventType, OrderedSet, Proof, PublicKeyAggregated, Seed, Shutdown, TicketGenerated, TicketId, }; -use e3_sortition::{GetFinalizedCommittee, Sortition}; +use e3_sortition::{GetAggregatorSubmissionRank, Sortition}; use e3_utils::{ArcBytes, NotifySync, MAILBOX_LIMIT}; use std::time::Duration; use tokio::time::sleep; @@ -485,24 +485,17 @@ impl Handler rank, - Err(err) => { - bus.err(EType::Evm, err); - return; - } - }; + let rank = + match aggregator_submission_rank_for_e3(&sortition, &e3_id, &my_address).await { + Ok(rank) => rank, + Err(err) => { + bus.err(EType::Evm, err); + return; + } + }; let Some(rank) = rank else { - info!(e3_id = %e3_id, node = %my_address, "Node is outside the finalized aggregation priority chain, skipping committee publication"); + info!(e3_id = %e3_id, node = %my_address, "Node is outside the active committee aggregation chain, skipping committee publication"); return; }; @@ -659,32 +652,17 @@ async fn should_submit_committee_public_key( - provider: EthProvider

, - contract_address: Address, - e3_id: &E3id, -) -> Result { - let e3_id_u256: U256 = e3_id.clone().try_into()?; - let contract = ICiphernodeRegistry::new(contract_address, provider.provider()); - let viability = contract.getCommitteeViability(e3_id_u256).call().await?; - Ok(viability.thresholdM as usize) -} - -async fn aggregation_rank_for_e3( +async fn aggregator_submission_rank_for_e3( sortition: &Addr, - provider: EthProvider

, - contract_address: Address, e3_id: &E3id, node_address: &str, ) -> Result> { - let committee = sortition - .send(GetFinalizedCommittee { + Ok(sortition + .send(GetAggregatorSubmissionRank { e3_id: e3_id.clone(), + node: node_address.to_owned(), }) - .await? - .ok_or_else(|| anyhow::anyhow!("No finalized committee available for {}", e3_id))?; - let threshold_m = committee_threshold_m(provider, contract_address, e3_id).await?; - Ok(committee.aggregation_rank_for(node_address, threshold_m)) + .await?) } pub async fn publish_committee_to_registry( diff --git a/crates/evm/src/enclave_sol_writer.rs b/crates/evm/src/enclave_sol_writer.rs index ef5801942e..580490a1c7 100644 --- a/crates/evm/src/enclave_sol_writer.rs +++ b/crates/evm/src/enclave_sol_writer.rs @@ -17,7 +17,7 @@ use alloy::{ primitives::{Bytes, U256}, rpc::types::TransactionReceipt, }; -use anyhow::{bail, Result}; +use anyhow::Result; use e3_events::BusHandle; use e3_events::EnclaveEventData; use e3_events::EventType; @@ -26,10 +26,9 @@ use e3_events::{prelude::*, EffectsEnabled}; use e3_events::{run_once, EnclaveEvent}; use e3_events::{E3Stage, E3StageChanged}; use e3_events::{E3id, EType, PlaintextAggregated, Proof}; -use e3_sortition::{GetFinalizedCommittee, Sortition}; +use e3_sortition::{GetAggregatorSubmissionRank, Sortition}; use e3_utils::NotifySync; use e3_utils::MAILBOX_LIMIT; -use e3_zk_helpers::CiphernodesCommitteeSize; use std::time::Duration; use tokio::time::sleep; use tracing::info; @@ -173,14 +172,8 @@ impl Handler rank, Err(err) => { @@ -190,7 +183,7 @@ impl Handler( Ok(stage == 4u8) } -async fn plaintext_threshold_m( - provider: EthProvider

, - contract_address: Address, - e3_id: &E3id, -) -> Result { - let e3_id_u256: U256 = e3_id.clone().try_into()?; - let contract = IEnclave::new(contract_address, provider.provider()); - let e3 = contract.getE3(e3_id_u256).call().await?; - let committee = match e3.committeeSize { - 0 => CiphernodesCommitteeSize::Micro, - 1 => CiphernodesCommitteeSize::Small, - 2 => CiphernodesCommitteeSize::Medium, - 3 => CiphernodesCommitteeSize::Large, - _ => bail!("Unknown committee size in E3"), - }; - Ok(committee.values().threshold) -} - -async fn plaintext_aggregation_rank_for_e3( +async fn aggregator_submission_rank_for_e3( sortition: &Addr, - provider: EthProvider

, - contract_address: Address, e3_id: &E3id, node_address: &str, ) -> Result> { - let committee = sortition - .send(GetFinalizedCommittee { + Ok(sortition + .send(GetAggregatorSubmissionRank { e3_id: e3_id.clone(), + node: node_address.to_owned(), }) - .await? - .ok_or_else(|| anyhow::anyhow!("No finalized committee available for {}", e3_id))?; - let threshold_m = plaintext_threshold_m(provider, contract_address, e3_id).await?; - Ok(committee.aggregation_rank_for(node_address, threshold_m)) + .await?) } async fn process_e3_failure( diff --git a/crates/sortition/Readme.md b/crates/sortition/Readme.md index ce92fbf9fa..47e7c97413 100644 --- a/crates/sortition/Readme.md +++ b/crates/sortition/Readme.md @@ -114,7 +114,7 @@ sequenceDiagram PublicKeyAggregator->>PublicKeyAggregator: fhe.get_aggregate_public_key(keyshares) PublicKeyAggregator->>PublicKeyAggregator: Aggregate public key shares PublicKeyAggregator->>EventBus: PublicKeyAggregated(e3Id, pubkey, nodes, chainId) - PublicKeyAggregator->>CiphernodeRegistry: publishPublicKey(e3Id, pubkey, nodes) after rank-based delay + PublicKeyAggregator->>CiphernodeRegistry: publishCommittee(e3Id, pubkey, nodes) after rank-based delay end Note over Operator,PlaintextAggregator: Phase 8: Encryption & Computation diff --git a/crates/sortition/src/ciphernode_selector.rs b/crates/sortition/src/ciphernode_selector.rs index 296d2c06b9..85d828b49e 100644 --- a/crates/sortition/src/ciphernode_selector.rs +++ b/crates/sortition/src/ciphernode_selector.rs @@ -12,13 +12,13 @@ use e3_data::{AutoPersist, Persistable, Repository}; use e3_events::E3RequestComplete; use e3_events::TypedEvent; use e3_events::{ - prelude::*, trap, BusHandle, CiphernodeSelected, CommitteeFinalized, E3Requested, E3id, EType, + prelude::*, trap, AggregatorSelected, BusHandle, CiphernodeSelected, E3Requested, E3id, EType, EnclaveEvent, EnclaveEventData, EventType, Shutdown, TicketGenerated, TicketId, }; use e3_request::E3Meta; use e3_utils::NotifySync; use e3_utils::MAILBOX_LIMIT; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use tracing::info; /// Build an `E3Meta` from an `E3Requested` event's fields. @@ -40,6 +40,7 @@ pub struct CiphernodeSelector { bus: BusHandle, address: String, e3_cache: Persistable>, + selected_e3s: HashSet, } impl Actor for CiphernodeSelector { @@ -59,6 +60,7 @@ impl CiphernodeSelector { bus: bus.clone(), e3_cache, address: address.to_owned(), + selected_e3s: HashSet::new(), } } @@ -72,7 +74,7 @@ impl CiphernodeSelector { bus.subscribe(EventType::E3Requested, addr.clone().recipient()); bus.subscribe(EventType::E3RequestComplete, addr.clone().recipient()); - bus.subscribe(EventType::CommitteeFinalized, addr.clone().recipient()); + bus.subscribe(EventType::AggregatorSelected, addr.clone().recipient()); bus.subscribe(EventType::Shutdown, addr.clone().recipient()); info!("CiphernodeSelector listening!"); @@ -89,7 +91,7 @@ impl Handler for CiphernodeSelector { EnclaveEventData::E3RequestComplete(data) => { self.notify_sync(ctx, TypedEvent::new(data, ec)) } - EnclaveEventData::CommitteeFinalized(data) => { + EnclaveEventData::AggregatorSelected(data) => { self.notify_sync(ctx, TypedEvent::new(data, ec)) } EnclaveEventData::Shutdown(data) => self.notify_sync(ctx, data), @@ -103,7 +105,7 @@ impl Handler for CiphernodeSelector { /// This handler populates `e3_cache` during sync replay, when `Sortition` gates its /// `E3Requested` subscription behind `EffectsEnabled` and therefore does NOT forward /// `WithSortitionTicket` messages to us. Without this handler the cache would be empty -/// when `CommitteeFinalized` arrives during replay, causing a missing-meta error. +/// when `AggregatorSelected` arrives during replay, causing a missing-meta error. /// /// During live operation both this handler AND the `WithSortitionTicket` handler fire for /// the same E3. `or_insert` ensures the first write wins; the `WithSortitionTicket` @@ -180,18 +182,22 @@ impl Handler> for CiphernodeSelector { self.e3_cache.try_mutate(msg.get_ctx(), |mut cache| { cache.remove(&msg.e3_id); Ok(cache) - }) + })?; + + self.selected_e3s.remove(&msg.e3_id); + + Ok(()) }, ) } } -impl Handler> for CiphernodeSelector { +impl Handler> for CiphernodeSelector { type Result = (); fn handle( &mut self, - msg: TypedEvent, + msg: TypedEvent, _ctx: &mut Self::Context, ) -> Self::Result { trap( @@ -199,14 +205,17 @@ impl Handler> for CiphernodeSelector { &self.bus.with_ec(msg.get_ctx()), move || { let (msg, ec) = msg.into_components(); - info!("CiphernodeSelector received CommitteeFinalized."); + info!("CiphernodeSelector received AggregatorSelected."); let bus = self.bus.clone(); - info!("Getting e3_cache..."); + + if self.selected_e3s.contains(&msg.e3_id) { + return Ok(()); + } + let Some(e3_cache) = self.e3_cache.get() else { bail!("Could not get cache"); }; - info!("Getting e3_meta..."); let Some(e3_meta) = e3_cache.get(&msg.e3_id) else { bail!( "Could not find E3Meta on CiphernodeSelector for {}", @@ -230,16 +239,19 @@ impl Handler> for CiphernodeSelector { return Ok(()); }; + self.selected_e3s.insert(msg.e3_id.clone()); + info!( node = self.address, party_id = party_id, "Node is in finalized committee, emitting CiphernodeSelected" ); - if party_id == 0 { + if msg.node == self.address { info!( node = self.address, e3_id = %msg.e3_id, - "[SORTITION] Node is the finalized committee primary (party_id=0)" + aggregator_party_id = msg.party_id, + "[SORTITION] Node is the currently selected aggregator" ); } diff --git a/crates/sortition/src/sortition.rs b/crates/sortition/src/sortition.rs index 70f0e817a5..281c7a4614 100644 --- a/crates/sortition/src/sortition.rs +++ b/crates/sortition/src/sortition.rs @@ -12,16 +12,16 @@ use alloy::primitives::U256; use anyhow::{anyhow, Result}; use e3_data::{AutoPersist, Persistable, Repository}; use e3_events::{ - prelude::*, trap, CiphernodeAdded, CiphernodeRemoved, Committee, CommitteeFinalized, - CommitteeMemberExpelled, CommitteePublished, ConfigurationUpdated, E3Failed, E3Requested, - E3Stage, E3StageChanged, EType, EnclaveEvent, EventContext, EventType, + prelude::*, trap, AggregatorSelected, CiphernodeAdded, CiphernodeRemoved, Committee, + CommitteeFinalized, CommitteeMemberExpelled, CommitteePublished, ConfigurationUpdated, + E3Failed, E3Requested, E3Stage, E3StageChanged, EType, EnclaveEvent, EventContext, EventType, OperatorActivationChanged, PlaintextOutputPublished, Seed, Sequenced, TicketBalanceUpdated, TypedEvent, }; use e3_events::{BusHandle, E3id, EnclaveEventData}; use e3_utils::{NotifySync, MAILBOX_LIMIT}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use std::ops::Deref; use tracing::{info, instrument, warn}; @@ -209,7 +209,7 @@ impl Deref for E3CommitteeContainsResponse { pub struct GetLocalNodeSortitionRank { pub e3_id: E3id, pub seed: Seed, - pub size: usize, + pub threshold: [usize; 2], pub chain_id: u64, } @@ -219,6 +219,64 @@ pub struct GetFinalizedCommittee { pub e3_id: E3id, } +#[derive(Message, Clone, Debug, PartialEq, Eq)] +#[rtype(result = "Option")] +pub struct GetAggregatorSubmissionRank { + pub e3_id: E3id, + pub node: String, +} + +fn active_party_id( + committee: &Committee, + expelled: Option<&BTreeSet>, + node: &str, +) -> Option { + let party_id = committee.party_id_for(node)?; + (!expelled.is_some_and(|members| members.contains(&party_id))).then_some(party_id) +} + +fn committee_contains_active( + committee: &Committee, + expelled: Option<&BTreeSet>, + node: &str, +) -> bool { + active_party_id(committee, expelled, node).is_some() +} + +fn current_aggregator( + committee: &Committee, + expelled: Option<&BTreeSet>, +) -> Option<(u64, String)> { + committee + .members() + .iter() + .enumerate() + .find(|(index, _)| !expelled.is_some_and(|members| members.contains(&(*index as u64)))) + .map(|(index, node)| (index as u64, node.clone())) +} + +fn active_submission_rank_for( + committee: &Committee, + expelled: Option<&BTreeSet>, + node: &str, +) -> Option { + let target_party_id = active_party_id(committee, expelled, node)?; + + committee + .members() + .iter() + .enumerate() + .filter(|(index, _)| !expelled.is_some_and(|members| members.contains(&(*index as u64)))) + .position(|(index, _)| index as u64 == target_party_id) +} + +fn local_sortition_selection_size(threshold: [usize; 2]) -> usize { + let threshold_m = threshold[0]; + let threshold_n = threshold[1]; + let buffer = ticket_sortition::calculate_buffer_size(threshold_m, threshold_n); + threshold_n + buffer +} + /// Sortition actor that manages the sortition algorithm and the node state. pub struct Sortition { /// Persistent map of `chain_id -> SortitionBackend`. @@ -229,6 +287,8 @@ pub struct Sortition { bus: BusHandle, /// Persistent map of finalized committees per E3 finalized_committees: Persistable>, + /// In-memory expelled party ids for finalized committees. + expelled_members: HashMap>, /// Address for the CiphernodeSelector ciphernode_selector: Addr, /// Address for the current node @@ -246,6 +306,8 @@ pub struct SortitionParams { pub node_state: Persistable>, /// Persistent map of finalized committees per E3 pub finalized_committees: Persistable>, + /// In-memory expelled party ids for finalized committees. + expelled_members: HashMap>, /// Address for the CiphernodeSelector pub ciphernode_selector: Addr, /// Address for the current node @@ -259,6 +321,7 @@ impl Sortition { node_state: params.node_state, bus: params.bus, finalized_committees: params.finalized_committees, + expelled_members: params.expelled_members, ciphernode_selector: params.ciphernode_selector, address: params.address, } @@ -288,6 +351,7 @@ impl Sortition { backends, node_state, finalized_committees, + expelled_members: HashMap::new(), ciphernode_selector, address: address.to_owned(), }) @@ -368,7 +432,7 @@ impl Sortition { .and_then(|committees| committees.get(e3_id).cloned()) } - fn committee_contains(&mut self, e3_id: E3id, node: String) -> bool { + fn committee_contains(&self, e3_id: E3id, node: String) -> bool { let Some(committee) = self.get_committee(&e3_id) else { // Non blocking error self.bus.err( @@ -378,7 +442,33 @@ impl Sortition { return false; }; - committee.contains(&node) + committee_contains_active(&committee, self.expelled_members.get(&e3_id), &node) + } + + fn aggregator_submission_rank(&self, e3_id: &E3id, node: &str) -> Option { + let committee = self.get_committee(e3_id)?; + active_submission_rank_for(&committee, self.expelled_members.get(e3_id), node) + } + + fn publish_aggregator_selected( + &self, + e3_id: &E3id, + chain_id: u64, + party_id: u64, + node: String, + committee: Vec, + ec: EventContext, + ) -> Result<()> { + self.bus.publish( + AggregatorSelected { + e3_id: e3_id.clone(), + party_id, + node, + committee, + chain_id, + }, + ec, + ) } /// Helper method to decrement active jobs for an E3's committee fn decrement_jobs_for_e3( @@ -425,7 +515,11 @@ impl Sortition { } Ok(state_map) - }) + })?; + + self.expelled_members.remove(e3_id); + + Ok(()) } } @@ -485,15 +579,14 @@ impl Handler> for Sortition { let e3_id = msg.e3_id.clone(); let chain_id = msg.e3_id.chain_id(); let seed = msg.seed; - let threshold_m = msg.threshold_m; - let threshold_n = msg.threshold_n; - let buffer = ticket_sortition::calculate_buffer_size(threshold_m, threshold_n); - let total_selection_size = threshold_n + buffer; + let total_selection_size = + local_sortition_selection_size([msg.threshold_m, msg.threshold_n]); + let buffer = total_selection_size.saturating_sub(msg.threshold_n); info!( e3_id = %e3_id, - threshold_m = threshold_m, - threshold_n = threshold_n, + threshold_m = msg.threshold_m, + threshold_n = msg.threshold_n, buffer = buffer, total_selection_size = total_selection_size, "Performing Sortition with buffer" @@ -756,8 +849,9 @@ impl Handler for Sortition { type Result = MessageResult; fn handle(&mut self, msg: GetLocalNodeSortitionRank, _: &mut Self::Context) -> Self::Result { + let size = local_sortition_selection_size(msg.threshold); MessageResult( - self.get_node_index(msg.e3_id, msg.seed, msg.size, msg.chain_id) + self.get_node_index(msg.e3_id, msg.seed, size, msg.chain_id) .map(|(party_index, _)| party_index), ) } @@ -771,6 +865,14 @@ impl Handler for Sortition { } } +impl Handler for Sortition { + type Result = MessageResult; + + fn handle(&mut self, msg: GetAggregatorSubmissionRank, _: &mut Self::Context) -> Self::Result { + MessageResult(self.aggregator_submission_rank(&msg.e3_id, &msg.node)) + } +} + impl Handler> for Sortition { type Result = (); @@ -832,16 +934,34 @@ impl Handler> for Sortition { ) -> Self::Result { let (msg, ec) = msg.into_components(); trap(EType::Sortition, &self.bus.with_ec(&ec), || { + let committee = Committee::new(msg.committee.clone()); + info!( e3_id = %msg.e3_id, committee_size = msg.committee.len(), "Storing finalized committee" ); - self.finalized_committees.try_mutate(&ec, |mut committees| { - committees.insert(msg.e3_id.clone(), Committee::new(msg.committee.clone())); - Ok(committees) - }) + self.finalized_committees + .try_mutate(&ec, |mut committees| { + committees.insert(msg.e3_id.clone(), committee.clone()); + Ok(committees) + })?; + + self.expelled_members.remove(&msg.e3_id); + + if let Some((party_id, node)) = current_aggregator(&committee, None) { + self.publish_aggregator_selected( + &msg.e3_id, + msg.chain_id, + party_id, + node, + msg.committee, + ec, + )?; + } + + Ok(()) }) } } @@ -882,6 +1002,20 @@ impl Handler> for Sortition { return Ok(()); }; + let previous_aggregator = + current_aggregator(&committee, self.expelled_members.get(&data.e3_id)); + + self.expelled_members + .entry(data.e3_id.clone()) + .or_default() + .insert(party_id); + + let next_aggregator = + current_aggregator(&committee, self.expelled_members.get(&data.e3_id)); + let ordered_committee = committee.members().to_vec(); + let chain_id = data.e3_id.chain_id(); + let e3_id = data.e3_id.clone(); + info!( "Sortition: resolved expelled node {} to party_id={} for e3_id={}, re-publishing enriched event", node_addr, party_id, data.e3_id @@ -893,10 +1027,96 @@ impl Handler> for Sortition { party_id: Some(party_id), ..data }, - ec, + ec.clone(), )?; + if next_aggregator != previous_aggregator { + if let Some((party_id, node)) = next_aggregator { + self.publish_aggregator_selected( + &e3_id, + chain_id, + party_id, + node, + ordered_committee, + ec, + )?; + } + } + Ok(()) }) } } + +#[cfg(test)] +mod tests { + use super::{active_submission_rank_for, current_aggregator}; + use e3_events::Committee; + use std::collections::BTreeSet; + + #[test] + fn active_submission_rank_tracks_ordered_failover_chain() { + let committee = Committee::new(vec![ + "0x0000000000000000000000000000000000000001".to_string(), + "0x0000000000000000000000000000000000000002".to_string(), + "0x0000000000000000000000000000000000000003".to_string(), + "0x0000000000000000000000000000000000000004".to_string(), + ]); + let mut expelled = BTreeSet::new(); + + assert_eq!( + current_aggregator(&committee, Some(&expelled)).map(|(id, _)| id), + Some(0) + ); + assert_eq!( + active_submission_rank_for( + &committee, + Some(&expelled), + "0x0000000000000000000000000000000000000001", + ), + Some(0) + ); + assert_eq!( + active_submission_rank_for( + &committee, + Some(&expelled), + "0x0000000000000000000000000000000000000004", + ), + Some(3) + ); + + expelled.insert(0); + + assert_eq!( + current_aggregator(&committee, Some(&expelled)).map(|(id, _)| id), + Some(1) + ); + assert_eq!( + active_submission_rank_for( + &committee, + Some(&expelled), + "0x0000000000000000000000000000000000000002", + ), + Some(0) + ); + assert_eq!( + active_submission_rank_for( + &committee, + Some(&expelled), + "0x0000000000000000000000000000000000000004", + ), + Some(2) + ); + + expelled.insert(2); + + assert_eq!( + active_submission_rank_for( + &committee, + Some(&expelled), + "0x0000000000000000000000000000000000000004", + ), + Some(1) + ); + } +} diff --git a/deploy/copy-secrets.sh b/deploy/copy-secrets.sh index c8010794e3..0948e8eccf 100755 --- a/deploy/copy-secrets.sh +++ b/deploy/copy-secrets.sh @@ -39,10 +39,10 @@ for target in "${TARGETS[@]}"; do echo "Skipping ${target}.secrets.json - file already exists" else cp "$SOURCE" "${target}.secrets.json" - set_network_private_key "${target}" "${NET_KEYS[${i:-0}]}" - ((i++)) + set_network_private_key "${target}" "${NET_KEYS[$i]}" echo "Created ${target}.secrets.json" fi + ((i++)) done echo "Copy operation completed!" diff --git a/tests/integration/fns.sh b/tests/integration/fns.sh index dcbefc362f..ad37a13faa 100644 --- a/tests/integration/fns.sh +++ b/tests/integration/fns.sh @@ -123,7 +123,8 @@ CIPHERNODE_REGISTRY=$(yq '.chains[0].contracts.ciphernode_registry.address' "$SC get_primary_committee_node() { local e3_id="${1:-0}" local timeout="${2:-120}" - local start_time=$(date +%s) + local start_time + start_time=$(date +%s) # Retry until publishCommittee tx is mined on-chain local raw @@ -158,7 +159,8 @@ ciphernode_pubkey_path() { # Wait until ANY node's pubkey.bin appears (committee membership is non-deterministic) waiton_any_pubkey() { local timeout="${1:-1300}" - local start_time=$(date +%s) + local start_time + start_time=$(date +%s) while true; do for name in cn1 cn2 cn3 cn4 cn5; do if [ -f "$(ciphernode_pubkey_path "$name")" ]; then From 0761f3ac6543f4349c5c41080b7daacf32e898d6 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Sun, 29 Mar 2026 10:25:39 +0500 Subject: [PATCH 5/5] fix: review comments --- crates/aggregator/src/committee_finalizer.rs | 224 +++++++++++------- .../src/threshold_plaintext_aggregator.rs | 34 ++- crates/evm/src/ciphernode_registry_sol.rs | 44 ++-- crates/evm/src/enclave_sol_writer.rs | 17 +- deploy/copy-secrets.sh | 21 +- .../contracts/test/MockCiphernodeRegistry.sol | 95 ++++++-- 6 files changed, 288 insertions(+), 147 deletions(-) diff --git a/crates/aggregator/src/committee_finalizer.rs b/crates/aggregator/src/committee_finalizer.rs index 98698d7a2b..ba0cd35417 100644 --- a/crates/aggregator/src/committee_finalizer.rs +++ b/crates/aggregator/src/committee_finalizer.rs @@ -14,10 +14,19 @@ use e3_sortition::{GetLocalNodeSortitionRank, Sortition}; use e3_utils::{NotifySync, MAILBOX_LIMIT}; use std::collections::HashMap; use std::time::Duration; -use tracing::{error, info}; +use tracing::{error, info, warn}; const FINALIZATION_BUFFER_SECONDS: u64 = 1; const FINALIZATION_INTERVAL_SECONDS: u64 = 1; +const FINALIZATION_SCHEDULE_RETRY_SECONDS: u64 = 1; +const MAX_FINALIZATION_SCHEDULE_ATTEMPTS: u8 = 3; + +#[derive(Message, Clone, Debug, PartialEq, Eq)] +#[rtype(result = "()")] +struct RetryCommitteeScheduling { + event: TypedEvent, + attempt: u8, +} /// CommitteeFinalizer is an actor that listens to CommitteeRequested events and dispatches /// CommitteeFinalizeRequested events after the submission deadline has passed. @@ -66,42 +75,13 @@ impl CommitteeFinalizer { addr } -} - -impl Actor for CommitteeFinalizer { - type Context = Context; - fn started(&mut self, ctx: &mut Self::Context) { - ctx.set_mailbox_capacity(MAILBOX_LIMIT); - } -} - -impl Handler for CommitteeFinalizer { - type Result = (); - fn handle(&mut self, msg: EnclaveEvent, ctx: &mut Self::Context) -> Self::Result { - let (msg, ec) = msg.into_components(); - match msg { - EnclaveEventData::CommitteeRequested(data) => { - self.notify_sync(ctx, TypedEvent::new(data, ec)) - } - EnclaveEventData::Shutdown(data) => self.notify_sync(ctx, data), - EnclaveEventData::E3Failed(data) => self.notify_sync(ctx, TypedEvent::new(data, ec)), - EnclaveEventData::E3StageChanged(data) => { - self.notify_sync(ctx, TypedEvent::new(data, ec)) - } - _ => (), - } - } -} - -impl Handler> for CommitteeFinalizer { - type Result = (); - // TODO: Remove all async from this function. Remove reliance on e3_evm package. Add unit test. - fn handle( + fn schedule_committee_finalization( &mut self, msg: TypedEvent, - ctx: &mut Self::Context, - ) -> Self::Result { + attempt: u8, + ctx: &mut Context, + ) { let ec = msg.get_ctx().clone(); let e3_id = msg.e3_id.clone(); let committee_deadline = msg.committee_deadline; @@ -113,18 +93,15 @@ impl Handler> for CommitteeFinalizer { }; let sortition = self.sortition.clone(); let e3_id_for_log = e3_id.clone(); + let msg_for_retry = msg.clone(); let fut = async move { - // TODO: we should have no dependencies on e3_evm here. Reason being that this is core - // functionality and evm is shell. Shell can depend on core but core MUST not depend on - // shell. This means we should not hold an address to a shell actor even and should use - // the eventbus to communicate. - // see https://github.com/gnosisguild/enclave/issues/989 let current_timestamp = match e3_evm::helpers::get_current_timestamp().await { Ok(timestamp) => timestamp, Err(e) => { error!( e3_id = %e3_id_for_log, error = %e, + attempt = attempt, "Failed to get current timestamp from RPC" ); return None; @@ -137,6 +114,7 @@ impl Handler> for CommitteeFinalizer { error!( e3_id = %e3_id_for_log, error = %e, + attempt = attempt, "Failed to get local sortition rank for committee finalization" ); return None; @@ -146,71 +124,93 @@ impl Handler> for CommitteeFinalizer { Some((current_timestamp, local_rank)) }; - let e3_id_for_async = e3_id; ctx.spawn( fut.into_actor(self) .then(move |result, act, ctx| { - if let Some((current_timestamp, local_rank)) = result { - if let Some(rank) = local_rank { - let base_delay = if committee_deadline > current_timestamp { - (committee_deadline - current_timestamp) - + FINALIZATION_BUFFER_SECONDS - } else { - info!( - e3_id = %e3_id_for_async, - committee_deadline = committee_deadline, - current_timestamp = current_timestamp, - "Submission deadline already passed, finalizing with fallback buffer" - ); - FINALIZATION_BUFFER_SECONDS - }; - let seconds_to_wait = - base_delay + (rank * FINALIZATION_INTERVAL_SECONDS); - + if let Some((current_timestamp, Some(rank))) = result { + let base_delay = if committee_deadline > current_timestamp { + (committee_deadline - current_timestamp) + FINALIZATION_BUFFER_SECONDS + } else { info!( - e3_id = %e3_id_for_async, + e3_id = %e3_id, committee_deadline = committee_deadline, current_timestamp = current_timestamp, - rank = rank, - seconds_to_wait = seconds_to_wait, - "Scheduling committee finalization" + "Submission deadline already passed, finalizing with fallback buffer" ); + FINALIZATION_BUFFER_SECONDS + }; + let seconds_to_wait = + base_delay + (rank * FINALIZATION_INTERVAL_SECONDS); + + info!( + e3_id = %e3_id, + committee_deadline = committee_deadline, + current_timestamp = current_timestamp, + rank = rank, + seconds_to_wait = seconds_to_wait, + "Scheduling committee finalization" + ); - let bus = act.bus.clone(); - let e3_id_clone = e3_id_for_async.clone(); + let bus = act.bus.clone(); + let e3_id_clone = e3_id.clone(); - let handle = ctx.run_later( - Duration::from_secs(seconds_to_wait), - move |act, _ctx| { - info!(e3_id = %e3_id_clone, rank = rank, "Dispatching CommitteeFinalizeRequested event"); + let handle = ctx.run_later( + Duration::from_secs(seconds_to_wait), + move |act, _ctx| { + info!(e3_id = %e3_id_clone, rank = rank, "Dispatching CommitteeFinalizeRequested event"); - trap(EType::Sortition, &act.bus.with_ec(&ec), || { - bus.publish(CommitteeFinalizeRequested { + trap(EType::Sortition, &act.bus.with_ec(&ec), || { + bus.publish( + CommitteeFinalizeRequested { e3_id: e3_id_clone.clone(), - },ec)?; - Ok(()) - }); + }, + ec, + )?; + Ok(()) + }); - act.pending_committees.remove(&e3_id_clone.to_string()); - }, - ); + act.pending_committees.remove(&e3_id_clone.to_string()); + }, + ); - if let Some(existing) = act - .pending_committees - .insert(e3_id_for_async.to_string(), handle) - { - ctx.cancel_future(existing); - } - } else { - info!( - e3_id = %e3_id_for_async, - "Node is outside the local pre-finalization committee ranking, skipping finalize scheduling" - ); + if let Some(existing) = + act.pending_committees.insert(e3_id.to_string(), handle) + { + ctx.cancel_future(existing); + } + } else if attempt < MAX_FINALIZATION_SCHEDULE_ATTEMPTS { + let next_attempt = attempt + 1; + let retry_event = msg_for_retry.clone(); + let retry_e3_id = e3_id.clone(); + + info!( + e3_id = %retry_e3_id, + attempt = next_attempt, + retry_after_secs = FINALIZATION_SCHEDULE_RETRY_SECONDS, + "Could not resolve local finalization schedule yet, retrying" + ); + + let handle = ctx.run_later( + Duration::from_secs(FINALIZATION_SCHEDULE_RETRY_SECONDS), + move |act, ctx| { + act.pending_committees.remove(&retry_e3_id.to_string()); + ctx.notify(RetryCommitteeScheduling { + event: retry_event.clone(), + attempt: next_attempt, + }); + }, + ); + + if let Some(existing) = + act.pending_committees.insert(e3_id.to_string(), handle) + { + ctx.cancel_future(existing); } } else { - error!( - e3_id = %e3_id_for_async, - "Skipping committee finalization due to timestamp or sortition lookup failure" + warn!( + e3_id = %e3_id, + attempts = attempt, + "Unable to resolve local committee finalization rank after retries; skipping local scheduling" ); } @@ -220,6 +220,52 @@ impl Handler> for CommitteeFinalizer { } } +impl Actor for CommitteeFinalizer { + type Context = Context; + fn started(&mut self, ctx: &mut Self::Context) { + ctx.set_mailbox_capacity(MAILBOX_LIMIT); + } +} + +impl Handler for CommitteeFinalizer { + type Result = (); + fn handle(&mut self, msg: EnclaveEvent, ctx: &mut Self::Context) -> Self::Result { + let (msg, ec) = msg.into_components(); + match msg { + EnclaveEventData::CommitteeRequested(data) => { + self.notify_sync(ctx, TypedEvent::new(data, ec)) + } + EnclaveEventData::Shutdown(data) => self.notify_sync(ctx, data), + EnclaveEventData::E3Failed(data) => self.notify_sync(ctx, TypedEvent::new(data, ec)), + EnclaveEventData::E3StageChanged(data) => { + self.notify_sync(ctx, TypedEvent::new(data, ec)) + } + _ => (), + } + } +} + +impl Handler> for CommitteeFinalizer { + type Result = (); + + // TODO: Remove all async from this function. Remove reliance on e3_evm package. Add unit test. + fn handle( + &mut self, + msg: TypedEvent, + ctx: &mut Self::Context, + ) -> Self::Result { + self.schedule_committee_finalization(msg, 0, ctx); + } +} + +impl Handler for CommitteeFinalizer { + type Result = (); + + fn handle(&mut self, msg: RetryCommitteeScheduling, ctx: &mut Self::Context) -> Self::Result { + self.schedule_committee_finalization(msg.event, msg.attempt, ctx); + } +} + impl Handler for CommitteeFinalizer { type Result = (); fn handle(&mut self, _msg: Shutdown, ctx: &mut Self::Context) -> Self::Result { diff --git a/crates/aggregator/src/threshold_plaintext_aggregator.rs b/crates/aggregator/src/threshold_plaintext_aggregator.rs index 73be616e36..73bd0aaf94 100644 --- a/crates/aggregator/src/threshold_plaintext_aggregator.rs +++ b/crates/aggregator/src/threshold_plaintext_aggregator.rs @@ -254,6 +254,7 @@ impl ThresholdPlaintextAggregator { fn handle_member_expelled( &mut self, party_id: u64, + active_count_after: u64, node: &str, ec: &EventContext, ) -> Result<()> { @@ -263,20 +264,44 @@ impl ThresholdPlaintextAggregator { data.shares.remove(&party_id); data.c6_proofs.remove(&party_id); data.c6_wrapped_proofs.remove(&party_id); - ThresholdPlaintextAggregatorState::Collecting(data) + data.threshold_n = active_count_after; + + if active_count_after > 0 && (data.shares.len() as u64) >= active_count_after { + info!( + e3_id = %self.e3_id, + party_id = party_id, + active_count_after = active_count_after, + "Collected all remaining active plaintext shares after expulsion, transitioning to VerifyingC6" + ); + + ThresholdPlaintextAggregatorState::VerifyingC6(VerifyingC6 { + threshold_m: data.threshold_m, + threshold_n: active_count_after, + shares: data.shares, + c6_proofs: data.c6_proofs, + c6_wrapped_proofs: data.c6_wrapped_proofs, + ciphertext_output: data.ciphertext_output, + params: data.params, + }) + } else { + ThresholdPlaintextAggregatorState::Collecting(data) + } } ThresholdPlaintextAggregatorState::VerifyingC6(mut data) => { data.shares.remove(&party_id); data.c6_proofs.remove(&party_id); data.c6_wrapped_proofs.remove(&party_id); + data.threshold_n = active_count_after; ThresholdPlaintextAggregatorState::VerifyingC6(data) } ThresholdPlaintextAggregatorState::Computing(mut data) => { data.shares.retain(|(id, _)| *id != party_id); + data.threshold_n = active_count_after; ThresholdPlaintextAggregatorState::Computing(data) } ThresholdPlaintextAggregatorState::GeneratingC7Proof(mut data) => { data.shares.retain(|(id, _)| *id != party_id); + data.threshold_n = active_count_after; ThresholdPlaintextAggregatorState::GeneratingC7Proof(data) } ThresholdPlaintextAggregatorState::Complete(mut data) => { @@ -899,7 +924,12 @@ impl Handler> for ThresholdPlaintextAggregat } trap(EType::PlaintextAggregation, &self.bus.with_ec(&ec), || { - self.handle_member_expelled(party_id, &msg.node.to_string(), &ec)?; + self.handle_member_expelled( + party_id, + msg.active_count_after, + &msg.node.to_string(), + &ec, + )?; if matches!(self.duty, AggregationDuty::Inactive) { ctx.stop(); diff --git a/crates/evm/src/ciphernode_registry_sol.rs b/crates/evm/src/ciphernode_registry_sol.rs index 55bce8153e..6cebb752e9 100644 --- a/crates/evm/src/ciphernode_registry_sol.rs +++ b/crates/evm/src/ciphernode_registry_sol.rs @@ -425,17 +425,21 @@ impl Handler { - info!(e3_id = %e3_id, "Committee already finalized or no longer requested, skipping finalizeCommittee submission"); + Ok(true) => { + info!(e3_id = %e3_id, "Committee is already terminal on-chain, skipping finalizeCommittee submission"); return; } Err(err) => { - bus.err(EType::Evm, err); - return; + info!(e3_id = %e3_id, error = %err, "Committee finalize preflight unavailable, proceeding to tx path"); } - Ok(true) => {} + Ok(false) => {} } info!("Finalizing committee for E3 {:?}", e3_id); @@ -505,22 +509,21 @@ impl Handler { - info!(e3_id = %e3_id, node = %my_address, rank = rank, "Public key already published or committee not finalizable, skipping committee publication"); + Ok(true) => { + info!(e3_id = %e3_id, node = %my_address, rank = rank, "Committee public key is already published on-chain, skipping committee publication"); return; } Err(err) => { - bus.err(EType::Evm, err); - return; + info!(e3_id = %e3_id, node = %my_address, rank = rank, error = %err, "Committee publication preflight unavailable, proceeding to tx path"); } - Ok(true) => {} + Ok(false) => {} } let result = publish_committee_to_registry( @@ -625,7 +628,9 @@ pub async fn finalize_committee_on_registry( +async fn should_skip_finalize_committee_submission< + P: Provider + WalletProvider + Clone + 'static, +>( provider: EthProvider

, contract_address: Address, e3_id: E3id, @@ -633,23 +638,20 @@ async fn should_finalize_committee( +async fn should_skip_committee_public_key_submission< + P: Provider + WalletProvider + Clone + 'static, +>( provider: EthProvider

, contract_address: Address, e3_id: E3id, ) -> Result { let e3_id_u256: U256 = e3_id.try_into()?; let contract = ICiphernodeRegistry::new(contract_address, provider.provider()); - let stage = contract.getCommitteeStage(e3_id_u256).call().await?; - if stage != 2u8 { - return Ok(false); - } - let public_key_hash = contract.publicKeyHashes(e3_id_u256).call().await?; - Ok(public_key_hash == B256::ZERO) + Ok(public_key_hash != B256::ZERO) } async fn aggregator_submission_rank_for_e3( diff --git a/crates/evm/src/enclave_sol_writer.rs b/crates/evm/src/enclave_sol_writer.rs index 580490a1c7..4d25d6af70 100644 --- a/crates/evm/src/enclave_sol_writer.rs +++ b/crates/evm/src/enclave_sol_writer.rs @@ -194,22 +194,21 @@ impl Handler { - info!(e3_id = %e3_id, node = %my_address, rank = rank, "Plaintext already published or E3 no longer in CiphertextReady, skipping plaintext submission"); + Ok(true) => { + info!(e3_id = %e3_id, node = %my_address, rank = rank, "Plaintext submission is already terminal on-chain, skipping plaintext submission"); return; } Err(err) => { - bus.err(EType::Evm, err); - return; + info!(e3_id = %e3_id, node = %my_address, rank = rank, error = %err, "Plaintext submission preflight unavailable, proceeding to tx path"); } - Ok(true) => {} + Ok(false) => {} } let result = publish_plaintext_output( @@ -311,7 +310,7 @@ async fn publish_plaintext_output( send_tx_with_retry( "publishPlaintextOutput", - &["CiphertextOutputNotPublished"], + &["CiphertextOutputNotPublished", "InvalidStage"], || { info!("publishPlaintextOutput() e3_id={:?}", e3_id); let decrypted_output = Bytes::from(decrypted_output.clone()); @@ -331,7 +330,7 @@ async fn publish_plaintext_output( .await } -async fn should_submit_plaintext_output( +async fn should_skip_plaintext_output_submission( provider: EthProvider

, contract_address: Address, e3_id: E3id, @@ -339,7 +338,7 @@ async fn should_submit_plaintext_output( let e3_id_u256: U256 = e3_id.try_into()?; let contract = IEnclave::new(contract_address, provider.provider()); let stage = contract.getE3Stage(e3_id_u256).call().await?; - Ok(stage == 4u8) + Ok(matches!(stage, 5u8 | 6u8)) } async fn aggregator_submission_rank_for_e3( diff --git a/deploy/copy-secrets.sh b/deploy/copy-secrets.sh index 0948e8eccf..d3584fe3c8 100755 --- a/deploy/copy-secrets.sh +++ b/deploy/copy-secrets.sh @@ -24,7 +24,16 @@ NETWORK_KEY_CN1="0x11a1e500a548b70d88184a1e042900c0ed6c57f8710bcc35dc8c85fa33d3f NETWORK_KEY_CN2="0x21a1e500a548b70d88184a1e042900c0ed6c57f8710bcc35dc8c85fa33d3f580" NETWORK_KEY_CN3="0x31a1e500a548b70d88184a1e042900c0ed6c57f8710bcc35dc8c85fa33d3f580" NETWORK_KEY_CN4="0x41a1e500a548b70d88184a1e042900c0ed6c57f8710bcc35dc8c85fa33d3f580" -NET_KEYS=($NETWORK_KEY_CN1 $NETWORK_KEY_CN2 $NETWORK_KEY_CN3 $NETWORK_KEY_CN4) + +get_network_private_key() { + case "$1" in + cn1) printf '%s\n' "$NETWORK_KEY_CN1" ;; + cn2) printf '%s\n' "$NETWORK_KEY_CN2" ;; + cn3) printf '%s\n' "$NETWORK_KEY_CN3" ;; + cn4) printf '%s\n' "$NETWORK_KEY_CN4" ;; + *) printf '%s\n' "" ;; + esac +} # Check if source file exists if [ ! -f "$SOURCE" ]; then @@ -32,17 +41,21 @@ if [ ! -f "$SOURCE" ]; then exit 1 fi -i=0 # Copy file to each target, skipping if exists for target in "${TARGETS[@]}"; do + key="$(get_network_private_key "$target")" + if [ -z "$key" ]; then + echo "Error: No network private key configured for $target" + exit 1 + fi + if [ -f "${target}.secrets.json" ]; then echo "Skipping ${target}.secrets.json - file already exists" else cp "$SOURCE" "${target}.secrets.json" - set_network_private_key "${target}" "${NET_KEYS[$i]}" + set_network_private_key "${target}" "$key" echo "Created ${target}.secrets.json" fi - ((i++)) done echo "Copy operation completed!" diff --git a/packages/enclave-contracts/contracts/test/MockCiphernodeRegistry.sol b/packages/enclave-contracts/contracts/test/MockCiphernodeRegistry.sol index 1badd0c9c3..0f06b2059e 100644 --- a/packages/enclave-contracts/contracts/test/MockCiphernodeRegistry.sol +++ b/packages/enclave-contracts/contracts/test/MockCiphernodeRegistry.sol @@ -16,6 +16,21 @@ contract MockCiphernodeRegistry is ICiphernodeRegistry { /// @notice Configurable threshold M per E3 for testing mapping(uint256 e3Id => uint32 threshold) private _thresholdM; + /// @notice Configurable lifecycle stage per E3 for testing + mapping(uint256 e3Id => CommitteeStage stage) private _committeeStage; + + /// @notice Tracks whether a stage was explicitly configured for an E3 + mapping(uint256 e3Id => bool configured) private _stageConfigured; + + /// @notice Configurable committee deadline per E3 for testing + mapping(uint256 e3Id => uint256 deadline) private _committeeDeadline; + + /// @notice Configurable public key hash per E3 for testing + mapping(uint256 e3Id => bytes32 hash) private _publicKeyHash; + + /// @notice Tracks whether a public key hash was explicitly configured for an E3 + mapping(uint256 e3Id => bool configured) private _publicKeyHashConfigured; + /// @notice Set committee members for an E3 (test helper) function setCommitteeNodes( uint256 e3Id, @@ -32,35 +47,61 @@ contract MockCiphernodeRegistry is ICiphernodeRegistry { _thresholdM[e3Id] = m; } + /// @notice Set the committee stage for an E3 (test helper) + function setCommitteeStage(uint256 e3Id, CommitteeStage stage) external { + _committeeStage[e3Id] = stage; + _stageConfigured[e3Id] = true; + } + + /// @notice Set the committee deadline for an E3 (test helper) + function setCommitteeDeadline(uint256 e3Id, uint256 deadline) external { + _committeeDeadline[e3Id] = deadline; + } + + /// @notice Set the published public key hash for an E3 (test helper) + function setPublicKeyHash(uint256 e3Id, bytes32 publicKeyHash) external { + _publicKeyHash[e3Id] = publicKeyHash; + _publicKeyHashConfigured[e3Id] = true; + } + function requestCommittee( + uint256 e3Id, uint256, - uint256, - uint32[2] calldata - ) external pure returns (bool success) { + uint32[2] calldata threshold + ) external returns (bool success) { + _committeeStage[e3Id] = CommitteeStage.Requested; + _stageConfigured[e3Id] = true; + _committeeDeadline[e3Id] = block.timestamp + 10; + _thresholdM[e3Id] = threshold[0]; + _publicKeyHash[e3Id] = bytes32(0); + _publicKeyHashConfigured[e3Id] = true; success = true; } - function getCommitteeDeadline(uint256) external view returns (uint256) { - return block.timestamp + 10; + function getCommitteeDeadline( + uint256 e3Id + ) external view returns (uint256) { + uint256 deadline = _committeeDeadline[e3Id]; + return deadline == 0 ? block.timestamp + 10 : deadline; } function isEnabled(address) external pure returns (bool) { return true; } - function committeePublicKey(uint256 e3Id) external pure returns (bytes32) { - if (e3Id == type(uint256).max) { - return bytes32(0); - } else { - return keccak256(abi.encode(e3Id)); + function committeePublicKey(uint256 e3Id) external view returns (bytes32) { + bytes32 publicKeyHash = _publicKeyHashConfigured[e3Id] + ? _publicKeyHash[e3Id] + : bytes32(0); + if (publicKeyHash == bytes32(0)) { + revert CommitteeNotPublished(); } + return publicKeyHash; } - function publicKeyHashes(uint256 e3Id) external pure returns (bytes32) { - if (e3Id == type(uint256).max) { - return bytes32(0); - } - return keccak256(abi.encode(e3Id)); + function publicKeyHashes(uint256 e3Id) external view returns (bytes32) { + return + _publicKeyHashConfigured[e3Id] ? _publicKeyHash[e3Id] : bytes32(0); } function isCiphernodeEligible(address) external pure returns (bool) { @@ -74,12 +115,17 @@ contract MockCiphernodeRegistry is ICiphernodeRegistry { function removeCiphernode(address) external pure {} function publishCommittee( - uint256, + uint256 e3Id, address[] calldata, - bytes calldata, + bytes calldata publicKey, bytes calldata, bytes calldata - ) external pure {} // solhint-disable-line no-empty-blocks + ) external { + _committeeStage[e3Id] = CommitteeStage.Finalized; + _stageConfigured[e3Id] = true; + _publicKeyHash[e3Id] = keccak256(publicKey); + _publicKeyHashConfigured[e3Id] = true; + } function getCommitteeNodes( uint256 e3Id @@ -117,14 +163,19 @@ contract MockCiphernodeRegistry is ICiphernodeRegistry { function submitTicket(uint256, uint256) external pure {} // solhint-disable-next-line no-empty-blocks - function finalizeCommittee(uint256) external pure returns (bool) { + function finalizeCommittee(uint256 e3Id) external returns (bool) { + _committeeStage[e3Id] = CommitteeStage.Finalized; + _stageConfigured[e3Id] = true; return true; } function getCommitteeStage( - uint256 - ) external pure returns (ICiphernodeRegistry.CommitteeStage) { - return ICiphernodeRegistry.CommitteeStage.Finalized; + uint256 e3Id + ) external view returns (ICiphernodeRegistry.CommitteeStage) { + return + _stageConfigured[e3Id] + ? _committeeStage[e3Id] + : ICiphernodeRegistry.CommitteeStage.None; } function sortitionSubmissionWindow() external pure returns (uint256) {