Skip to content

Commit

Permalink
Automatically create proposals groups for new SNSes (#4528)
Browse files Browse the repository at this point in the history
  • Loading branch information
hpeebles authored Oct 9, 2023
1 parent 6f6fc21 commit 02836bb
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 23 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion backend/canister_installer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ async fn install_service_canisters_impl(
service_owner_principals: vec![principal],
user_index_canister_id: canister_ids.user_index,
group_index_canister_id: canister_ids.group_index,
local_user_index_canister_id: canister_ids.local_user_index,
nns_governance_canister_id: canister_ids.nns_governance,
sns_wasm_canister_id: canister_ids.nns_sns_wasm,
cycles_dispenser_canister_id: canister_ids.cycles_dispenser,
wasm_version: version,
test_mode,
Expand Down
1 change: 1 addition & 0 deletions backend/canisters/proposals_bot/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

- Support submitting proposals from within OpenChat ([#4486](https://github.com/open-chat-labs/open-chat/pull/4486))
- Make ProposalsBot able to stake neurons for submitting proposals ([#4493](https://github.com/open-chat-labs/open-chat/pull/4493))
- Automatically create proposals groups for new SNSes ([#4528](https://github.com/open-chat-labs/open-chat/pull/4528))

### Changed

Expand Down
2 changes: 1 addition & 1 deletion backend/canisters/proposals_bot/api/src/lifecycle/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ pub struct Args {
pub service_owner_principals: Vec<Principal>,
pub user_index_canister_id: CanisterId,
pub group_index_canister_id: CanisterId,
pub local_user_index_canister_id: CanisterId,
pub nns_governance_canister_id: CanisterId,
pub sns_wasm_canister_id: CanisterId,
pub cycles_dispenser_canister_id: CanisterId,
pub wasm_version: BuildVersion,
pub test_mode: bool,
Expand Down
2 changes: 2 additions & 0 deletions backend/canisters/proposals_bot/impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ serializer = { path = "../../../libraries/serializer" }
sha2 = { workspace = true }
sns_governance_canister = { path = "../../../external_canisters/sns_governance/api" }
sns_governance_canister_c2c_client = { path = "../../../external_canisters/sns_governance/c2c_client" }
sns_swap_canister_c2c_client = { path = "../../../external_canisters/sns_swap/c2c_client" }
sns_wasm_canister_c2c_client = { path = "../../../external_canisters/sns_wasm/c2c_client" }
tracing = { workspace = true }
types = { path = "../../../libraries/types" }
user_index_canister_c2c_client = { path = "../../user_index/c2c_client" }
Expand Down
124 changes: 124 additions & 0 deletions backend/canisters/proposals_bot/impl/src/jobs/check_for_new_snses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use crate::{mutate_state, read_state};
use std::fmt::Write;
use std::time::Duration;
use tracing::{error, info};
use types::{
CanisterId, Empty, GovernanceProposalsSubtype, GroupPermissionRole, GroupPermissions, GroupSubtype, MultiUserChat, Rules,
};
use utils::time::HOUR_IN_MS;

const LIFECYCLE_COMMITTED: i32 = 3;
const LIFECYCLE_ABORTED: i32 = 4;

pub fn start_job() {
ic_cdk_timers::set_timer_interval(Duration::from_millis(HOUR_IN_MS), run);
ic_cdk_timers::set_timer(Duration::ZERO, run);
}

fn run() {
ic_cdk::spawn(run_async());
}

async fn run_async() {
let sns_wasm_canister_id = read_state(|state| state.data.sns_wasm_canister_id);

if let Ok(response) = sns_wasm_canister_c2c_client::list_deployed_snses(sns_wasm_canister_id, &Empty {}).await {
let new_snses: Vec<_> = read_state(|state| {
response
.instances
.into_iter()
.filter(|sns| {
!state.data.failed_sns_launches.contains(&sns.root_canister_id.unwrap())
&& !state.data.nervous_systems.exists(&sns.governance_canister_id.unwrap())
})
.collect()
});

for sns in new_snses {
let root_canister_id = sns.root_canister_id.unwrap();
info!(%root_canister_id, "Getting details of unknown SNS");
if let Some(success) = is_successfully_launched(sns.swap_canister_id.unwrap()).await {
if success {
let governance_canister_id = sns.governance_canister_id.unwrap();
if let Ok(metadata) =
sns_governance_canister_c2c_client::get_metadata(governance_canister_id, &Empty {}).await
{
let name = metadata.name.unwrap();
ic_cdk::spawn(create_group(governance_canister_id, name));
}
} else {
info!(%root_canister_id, "Recording failed SNS launch");
mutate_state(|state| state.data.failed_sns_launches.insert(root_canister_id));
}
}
}
}
}

async fn is_successfully_launched(sns_swap_canister_id: CanisterId) -> Option<bool> {
let response = sns_swap_canister_c2c_client::get_lifecycle(sns_swap_canister_id, &Empty {})
.await
.ok()?;

match response.lifecycle? {
LIFECYCLE_COMMITTED => Some(true),
LIFECYCLE_ABORTED => Some(false),
_ => None,
}
}

async fn create_group(governance_canister_id: CanisterId, name: String) {
let (group_index_canister_id, is_nns) = read_state(|state| {
(
state.data.group_index_canister_id,
governance_canister_id == state.data.nns_governance_canister_id,
)
});

let create_group_args = group_index_canister::c2c_create_group::Args {
is_public: true,
name: format!("{} Proposals", name),
description: default_description(&name),
rules: Rules::default(),
subtype: Some(GroupSubtype::GovernanceProposals(GovernanceProposalsSubtype {
governance_canister_id,
is_nns,
})),
avatar: None,
history_visible_to_new_joiners: true,
permissions: Some(GroupPermissions {
create_polls: GroupPermissionRole::Admins,
send_messages: GroupPermissionRole::Admins,
..Default::default()
}),
events_ttl: None,
gate: None,
};

match group_index_canister_c2c_client::c2c_create_group(group_index_canister_id, &create_group_args).await {
Ok(group_index_canister::c2c_create_group::Response::Success(result)) => {
mutate_state(|state| {
state
.data
.nervous_systems
.add(governance_canister_id, MultiUserChat::Group(result.chat_id));
});
info!(%governance_canister_id, name = name.as_str(), "Proposals group created");
}
response => error!(?response, %governance_canister_id, name = name.as_str(), "Failed to create proposals group"),
}
}

fn default_description(name: &str) -> String {
let mut description = String::new();
writeln!(&mut description, "Join this group to view and vote on {name} proposals.").unwrap();
writeln!(&mut description).unwrap();
writeln!(
&mut description,
"To vote on proposals you must add your user id as a hotkey to any {name} neurons you wish to vote with."
)
.unwrap();
writeln!(&mut description).unwrap();
writeln!(&mut description, "Your OpenChat user id is {{userId}}.").unwrap();
description
}
2 changes: 2 additions & 0 deletions backend/canisters/proposals_bot/impl/src/jobs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use crate::RuntimeState;

mod check_for_new_snses;
mod push_proposals;
mod retrieve_proposals;
mod update_finished_proposals;
mod update_proposals;

pub(crate) fn start(state: &RuntimeState) {
check_for_new_snses::start_job();
push_proposals::start_job_if_required(state);
retrieve_proposals::start_job();
update_finished_proposals::start_job_if_required(state);
Expand Down
19 changes: 11 additions & 8 deletions backend/canisters/proposals_bot/impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ impl RuntimeState {
canister_ids: CanisterIds {
user_index: self.data.user_index_canister_id,
group_index: self.data.group_index_canister_id,
local_user_index: self.data.local_user_index_canister_id,
cycles_dispenser: self.data.cycles_dispenser_canister_id,
nns_governance: self.data.nns_governance_canister_id,
sns_wasm: self.data.sns_wasm_canister_id,
},
}
}
Expand All @@ -68,40 +68,43 @@ struct Data {
pub governance_principals: HashSet<Principal>,
pub user_index_canister_id: CanisterId,
pub group_index_canister_id: CanisterId,
#[serde(default = "local_user_index_canister_id")]
pub local_user_index_canister_id: CanisterId,
pub cycles_dispenser_canister_id: CanisterId,
pub nns_governance_canister_id: CanisterId,
#[serde(default = "sns_wasm_canister_id")]
pub sns_wasm_canister_id: CanisterId,
pub finished_proposals_to_process: VecDeque<(CanisterId, ProposalId)>,
#[serde(default)]
pub timer_jobs: TimerJobs<TimerJob>,
#[serde(default)]
pub failed_sns_launches: HashSet<CanisterId>,
pub test_mode: bool,
}

fn local_user_index_canister_id() -> CanisterId {
CanisterId::from_text("nq4qv-wqaaa-aaaaf-bhdgq-cai").unwrap()
fn sns_wasm_canister_id() -> CanisterId {
CanisterId::from_text("qaa6y-5yaaa-aaaaa-aaafa-cai").unwrap()
}

impl Data {
pub fn new(
governance_principals: HashSet<Principal>,
user_index_canister_id: CanisterId,
group_index_canister_id: CanisterId,
local_user_index_canister_id: CanisterId,
cycles_dispenser_canister_id: CanisterId,
nns_governance_canister_id: CanisterId,
sns_wasm_canister_id: CanisterId,
test_mode: bool,
) -> Data {
Data {
nervous_systems: NervousSystems::default(),
governance_principals,
user_index_canister_id,
group_index_canister_id,
local_user_index_canister_id,
cycles_dispenser_canister_id,
nns_governance_canister_id,
sns_wasm_canister_id,
finished_proposals_to_process: VecDeque::new(),
timer_jobs: TimerJobs::default(),
failed_sns_launches: HashSet::default(),
test_mode,
}
}
Expand Down Expand Up @@ -137,9 +140,9 @@ pub struct NervousSystemMetrics {
pub struct CanisterIds {
pub user_index: CanisterId,
pub group_index: CanisterId,
pub local_user_index: CanisterId,
pub cycles_dispenser: CanisterId,
pub nns_governance: CanisterId,
pub sns_wasm: CanisterId,
}

// Deterministically generate each MessageId so that there is never any chance of a proposal
Expand Down
2 changes: 1 addition & 1 deletion backend/canisters/proposals_bot/impl/src/lifecycle/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ fn init(args: Args) {
args.service_owner_principals.into_iter().collect(),
args.user_index_canister_id,
args.group_index_canister_id,
args.local_user_index_canister_id,
args.cycles_dispenser_canister_id,
args.nns_governance_canister_id,
args.sns_wasm_canister_id,
args.test_mode,
);

Expand Down
16 changes: 8 additions & 8 deletions backend/canisters/registry/impl/src/jobs/check_for_new_snses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ async fn run_async() {
}

async fn is_successfully_launched(sns_swap_canister_id: CanisterId) -> Option<bool> {
if let Ok(response) = sns_swap_canister_c2c_client::get_lifecycle(sns_swap_canister_id, &Empty {}).await {
match response.lifecycle {
Some(LIFECYCLE_COMMITTED) => Some(true),
Some(LIFECYCLE_ABORTED) => Some(false),
_ => None,
}
} else {
None
let response = sns_swap_canister_c2c_client::get_lifecycle(sns_swap_canister_id, &Empty {})
.await
.ok()?;

match response.lifecycle? {
LIFECYCLE_COMMITTED => Some(true),
LIFECYCLE_ABORTED => Some(false),
_ => None,
}
}
1 change: 0 additions & 1 deletion backend/integration_tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,4 @@ pub struct CanisterIds {
}

const T: Cycles = 1_000_000_000_000;
const NNS_GOVERNANCE_CANISTER_ID: CanisterId = Principal::from_slice(&[0, 0, 0, 0, 0, 0, 0, 1, 1, 1]);
const NNS_INTERNET_IDENTITY_CANISTER_ID: CanisterId = Principal::from_slice(&[0, 0, 0, 0, 0, 0, 0, 10, 1, 1]);
6 changes: 3 additions & 3 deletions backend/integration_tests/src/setup.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::client::{create_canister, install_canister};
use crate::rng::random_principal;
use crate::utils::{local_bin, tick_many};
use crate::{client, wasms, CanisterIds, TestEnv, NNS_GOVERNANCE_CANISTER_ID, NNS_INTERNET_IDENTITY_CANISTER_ID, T};
use crate::{client, wasms, CanisterIds, TestEnv, NNS_INTERNET_IDENTITY_CANISTER_ID, T};
use candid::{CandidType, Principal};
use ic_ledger_types::{AccountIdentifier, BlockIndex, Tokens, DEFAULT_SUBACCOUNT};
use ic_test_state_machine_client::StateMachine;
Expand Down Expand Up @@ -141,8 +141,8 @@ fn install_canisters(env: &mut StateMachine, controller: Principal) -> CanisterI
service_owner_principals: vec![controller],
user_index_canister_id,
group_index_canister_id,
local_user_index_canister_id,
nns_governance_canister_id: NNS_GOVERNANCE_CANISTER_ID,
nns_governance_canister_id,
sns_wasm_canister_id,
cycles_dispenser_canister_id,
wasm_version: BuildVersion::min(),
test_mode: true,
Expand Down

0 comments on commit 02836bb

Please sign in to comment.