-
Notifications
You must be signed in to change notification settings - Fork 332
/
Copy pathcanister.rs
757 lines (675 loc) · 26.8 KB
/
canister.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
use async_trait::async_trait;
use ic_base_types::{CanisterId, PrincipalId};
use ic_canister_log::log;
use ic_canister_profiler::{measure_span, measure_span_async};
use ic_canisters_http_types::{HttpRequest, HttpResponse, HttpResponseBuilder};
use ic_cdk::{caller as cdk_caller, init, post_upgrade, pre_upgrade, query, update};
use ic_cdk_timers::TimerId;
use ic_nervous_system_canisters::{cmc::CMCCanister, ledger::IcpLedgerCanister};
use ic_nervous_system_clients::{
canister_status::CanisterStatusResultV2, ledger_client::LedgerCanister,
};
use ic_nervous_system_common::{
dfn_core_stable_mem_utils::{BufferedStableMemReader, BufferedStableMemWriter},
serve_logs, serve_logs_v2, serve_metrics,
};
use ic_nervous_system_proto::pb::v1::{
GetTimersRequest, GetTimersResponse, ResetTimersRequest, ResetTimersResponse, Timers,
};
use ic_nervous_system_runtime::CdkRuntime;
use ic_nns_constants::LEDGER_CANISTER_ID as NNS_LEDGER_CANISTER_ID;
use ic_sns_governance::{
governance::{
log_prefix, Governance, TimeWarp, ValidGovernanceProto, MATURITY_DISBURSEMENT_DELAY_SECONDS,
},
logs::{ERROR, INFO},
pb::v1 as sns_gov_pb,
types::{Environment, HeapGrowthPotential},
upgrade_journal::serve_journal,
};
use ic_sns_governance_api::pb::v1::{
get_running_sns_version_response::UpgradeInProgress, governance::Version,
ClaimSwapNeuronsRequest, ClaimSwapNeuronsResponse, FailStuckUpgradeInProgressRequest,
FailStuckUpgradeInProgressResponse, GetMaturityModulationRequest,
GetMaturityModulationResponse, GetMetadataRequest, GetMetadataResponse, GetMode,
GetModeResponse, GetNeuron, GetNeuronResponse, GetProposal, GetProposalResponse,
GetRunningSnsVersionRequest, GetRunningSnsVersionResponse,
GetSnsInitializationParametersRequest, GetSnsInitializationParametersResponse,
GetUpgradeJournalRequest, GetUpgradeJournalResponse, Governance as GovernanceProto,
ListNervousSystemFunctionsResponse, ListNeurons, ListNeuronsResponse, ListProposals,
ListProposalsResponse, ManageNeuron, ManageNeuronResponse, NervousSystemParameters,
RewardEvent, SetMode, SetModeResponse,
};
#[cfg(feature = "test")]
use ic_sns_governance_api::pb::v1::{
AddMaturityRequest, AddMaturityResponse, AdvanceTargetVersionRequest,
AdvanceTargetVersionResponse, GovernanceError, MintTokensRequest, MintTokensResponse, Neuron,
RefreshCachedUpgradeStepsRequest, RefreshCachedUpgradeStepsResponse,
};
use prost::Message;
use rand::{RngCore, SeedableRng};
use rand_chacha::ChaCha20Rng;
use std::{
boxed::Box,
cell::RefCell,
convert::TryFrom,
time::{Duration, SystemTime},
};
/// Size of the buffer for stable memory reads and writes.
///
/// Smaller buffer size means more stable_write and stable_read calls. With
/// 100MiB buffer size, when the heap is near full, we need ~40 system calls.
/// Larger buffer size means we may not be able to serialize the heap fully in
/// some cases.
const STABLE_MEM_BUFFER_SIZE: u32 = 100 * 1024 * 1024; // 100MiB
static mut GOVERNANCE: Option<Governance> = None;
thread_local! {
static TIMER_ID: RefCell<Option<TimerId>> = RefCell::new(Default::default());
}
/// This guarantees that timers cannot be restarted more often than once every 60 intervals.
const RESET_TIMERS_COOL_DOWN_INTERVAL: Duration = Duration::from_secs(600);
const RUN_PERIODIC_TASKS_INTERVAL: Duration = Duration::from_secs(10);
/// Returns an immutable reference to the governance's global state.
///
/// This should only be called once the global state has been initialized, which
/// happens in `canister_init` or `canister_post_upgrade`.
fn governance() -> &'static Governance {
unsafe { GOVERNANCE.as_ref().expect("Canister not initialized!") }
}
/// Returns a mutable reference to the governance's global state.
///
/// This should only be called once the global state has been initialized, which
/// happens in `canister_init` or `canister_post_upgrade`.
fn governance_mut() -> &'static mut Governance {
unsafe { GOVERNANCE.as_mut().expect("Canister not initialized!") }
}
struct CanisterEnv {
rng: ChaCha20Rng,
time_warp: TimeWarp,
}
impl CanisterEnv {
fn new() -> Self {
CanisterEnv {
// Seed the pseudo-random number generator (PRNG) with the current time.
//
// All replicas are guaranteed to see the same result of now() and the resulting
// number isn't easily predictable from the outside.
//
// Why we don't use raw_rand from the ic00 api instead: this is an asynchronous
// call so can't really be used to generate random numbers for most cases.
// It could be used to seed the PRNG, but that wouldn't add any security regarding
// unpredictability since the pseudo-random numbers could still be predicted after
// inception.
rng: {
let now_nanos = now_nanoseconds() as u128;
let mut seed = [0u8; 32];
seed[..16].copy_from_slice(&now_nanos.to_be_bytes());
seed[16..32].copy_from_slice(&now_nanos.to_be_bytes());
ChaCha20Rng::from_seed(seed)
},
time_warp: TimeWarp { delta_s: 0 },
}
}
}
#[async_trait]
impl Environment for CanisterEnv {
fn now(&self) -> u64 {
self.time_warp.apply(now_seconds())
}
fn set_time_warp(&mut self, new_time_warp: TimeWarp) {
self.time_warp = new_time_warp;
}
// Returns a random u64.
fn insecure_random_u64(&mut self) -> u64 {
self.rng.next_u64()
}
// Calls an external method (i.e., on a canister outside the nervous system) to execute a
// proposal as a result of the proposal being adopted.
//
// The method returns either a success or error.
async fn call_canister(
&self,
canister_id: CanisterId,
method_name: &str,
arg: Vec<u8>,
) -> Result<
/* reply: */ Vec<u8>,
(
/* error_code: */ Option<i32>,
/* message: */ String,
),
> {
// Due to object safety constraints in Rust, call_canister sends and returns bytes, so we are using
// call_raw here instead of call, which requires known candid types.
ic_cdk::api::call::call_raw(canister_id.get().0, method_name, &arg, 0)
.await
.map_err(|(rejection_code, message)| (Some(rejection_code as i32), message))
}
#[cfg(target_arch = "wasm32")]
fn heap_growth_potential(&self) -> HeapGrowthPotential {
if core::arch::wasm32::memory_size(0)
< ic_sns_governance::governance::HEAP_SIZE_SOFT_LIMIT_IN_WASM32_PAGES
{
HeapGrowthPotential::NoIssue
} else {
HeapGrowthPotential::LimitedAvailability
}
}
/// Returns how much the heap can still grow.
#[cfg(not(target_arch = "wasm32"))]
fn heap_growth_potential(&self) -> HeapGrowthPotential {
unimplemented!("CanisterEnv can only be used with wasm32 environment.");
}
/// Return the canister's ID.
fn canister_id(&self) -> CanisterId {
CanisterId::unchecked_from_principal(PrincipalId::from(ic_cdk::id()))
}
/// Return the canister version.
fn canister_version(&self) -> Option<u64> {
Some(ic_cdk::api::canister_version())
}
}
fn now_nanoseconds() -> u64 {
if cfg!(target_arch = "wasm32") {
ic_cdk::api::time()
} else {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("Failed to get time since epoch")
.as_nanos()
.try_into()
.expect("Failed to convert time to u64")
}
}
fn now_seconds() -> u64 {
Duration::from_nanos(now_nanoseconds()).as_secs()
}
fn caller() -> PrincipalId {
PrincipalId::from(cdk_caller())
}
/// In contrast to canister_init(), this method does not do deserialization.
/// In addition to canister_init, this method is called by canister_post_upgrade.
#[init]
fn canister_init_(init_payload: GovernanceProto) {
let init_payload = sns_gov_pb::Governance::from(init_payload);
let init_payload = ValidGovernanceProto::try_from(init_payload).expect(
"Cannot start canister, because the deserialized \
GovernanceProto is invalid in some way",
);
log!(
INFO,
"canister_init_: Initializing with: {}",
init_payload.summary(),
);
let ledger_canister_id = init_payload.ledger_canister_id();
unsafe {
assert!(
GOVERNANCE.is_none(),
"{}Trying to initialize an already-initialized governance canister!",
log_prefix()
);
let governance = Governance::new(
init_payload,
Box::new(CanisterEnv::new()),
Box::new(LedgerCanister::new(ledger_canister_id)),
Box::new(IcpLedgerCanister::<CdkRuntime>::new(NNS_LEDGER_CANISTER_ID)),
Box::new(CMCCanister::<CdkRuntime>::new()),
);
let governance = if cfg!(feature = "test") {
governance.enable_test_features()
} else {
governance
};
GOVERNANCE = Some(governance);
}
init_timers();
}
/// Executes some logic before executing an upgrade, including serializing and writing the
/// governance's state to stable memory so that it is preserved during the upgrade and can
/// be deserialized again in canister_post_upgrade. That is, the stable memory allows
/// saving the state and restoring it after the upgrade.
#[pre_upgrade]
fn canister_pre_upgrade() {
log!(INFO, "Executing pre upgrade");
let mut writer = BufferedStableMemWriter::new(STABLE_MEM_BUFFER_SIZE);
governance()
.proto
.encode(&mut writer)
.expect("Error. Couldn't serialize canister pre-upgrade.");
writer.flush(); // or `drop(writer)`
log!(INFO, "Completed pre upgrade");
}
/// Executes some logic after executing an upgrade, including deserializing what has been written
/// to stable memory in canister_pre_upgrade and initializing the governance's state with it.
#[post_upgrade]
fn canister_post_upgrade() {
log!(INFO, "Executing post upgrade");
let reader = BufferedStableMemReader::new(STABLE_MEM_BUFFER_SIZE);
match GovernanceProto::decode(reader) {
Err(err) => {
log!(
ERROR,
"Error deserializing canister state post-upgrade. \
CANISTER MIGHT HAVE BROKEN STATE!!!!. Error: {:?}",
err
);
Err(err)
}
Ok(mut governance_proto) => {
// Post-process GovernanceProto
// TODO: Delete this once it's been released.
populate_finalize_disbursement_timestamp_seconds(&mut governance_proto);
canister_init_(governance_proto);
Ok(())
}
}
.expect("Couldn't upgrade canister.");
init_timers();
log!(INFO, "Completed post upgrade");
}
fn populate_finalize_disbursement_timestamp_seconds(governance_proto: &mut GovernanceProto) {
for neuron in governance_proto.neurons.values_mut() {
for disbursement in neuron.disburse_maturity_in_progress.iter_mut() {
disbursement.finalize_disbursement_timestamp_seconds = Some(
disbursement.timestamp_of_disbursement_seconds
+ MATURITY_DISBURSEMENT_DELAY_SECONDS,
);
}
}
}
/// Test only feature. Internal method for calling set_time_warp.
#[cfg(feature = "test")]
#[update(hidden = true)]
fn set_time_warp(new_time_warp: TimeWarp) {
governance_mut().env.set_time_warp(new_time_warp);
}
/// Returns the governance's NervousSystemParameters
#[query]
fn get_nervous_system_parameters(_: ()) -> NervousSystemParameters {
log!(INFO, "get_nervous_system_parameters");
NervousSystemParameters::from(
governance()
.proto
.parameters
.clone()
.expect("NervousSystemParameters are not set"),
)
}
/// Returns metadata describing the SNS.
#[query]
fn get_metadata(request: GetMetadataRequest) -> GetMetadataResponse {
log!(INFO, "get_metadata");
GetMetadataResponse::from(
governance().get_metadata(&sns_gov_pb::GetMetadataRequest::from(request)),
)
}
/// Returns the initialization parameters used to spawn an SNS
#[query]
fn get_sns_initialization_parameters(
request: GetSnsInitializationParametersRequest,
) -> GetSnsInitializationParametersResponse {
log!(INFO, "get_sns_initialization_parameters");
GetSnsInitializationParametersResponse::from(governance().get_sns_initialization_parameters(
&sns_gov_pb::GetSnsInitializationParametersRequest::from(request),
))
}
/// Performs a command on a neuron if the caller is authorized to do so.
/// The possible neuron commands are (for details, see the SNS's governance.proto):
/// - configuring the neuron (increasing or setting its dissolve delay or changing the
/// dissolve state),
/// - disbursing the neuron's stake to a ledger account
/// - following a set of neurons for proposals of a certain action
/// - make a proposal in the name of the neuron
/// - register a vote for the neuron
/// - split the neuron
/// - claim or refresh the neuron
/// - merge the neuron's maturity into the neuron's stake
#[update]
async fn manage_neuron(request: ManageNeuron) -> ManageNeuronResponse {
log!(INFO, "manage_neuron");
let governance = governance_mut();
let result = measure_span_async(
governance.profiling_information,
"manage_neuron",
governance.manage_neuron(&sns_gov_pb::ManageNeuron::from(request), &caller()),
)
.await;
ManageNeuronResponse::from(result)
}
#[cfg(feature = "test")]
#[update]
/// Test only feature. Update neuron parameters.
fn update_neuron(neuron: Neuron) -> Option<GovernanceError> {
log!(INFO, "update_neuron");
let governance = governance_mut();
measure_span(governance.profiling_information, "update_neuron", || {
governance
.update_neuron(sns_gov_pb::Neuron::from(neuron))
.map_err(GovernanceError::from)
.err()
})
}
/// Returns the full neuron corresponding to the neuron with ID `neuron_id`.
#[query]
fn get_neuron(request: GetNeuron) -> GetNeuronResponse {
log!(INFO, "get_neuron");
GetNeuronResponse::from(governance().get_neuron(sns_gov_pb::GetNeuron::from(request)))
}
/// Returns a list of neurons of size `limit` using `start_page_at` to
/// indicate the start of the list. Specifying `of_principal` will return
/// Neurons of which the given PrincipalId has permissions.
///
/// To paginate through the all neurons, `start_page_at` should be set to
/// the last neuron of the previously returned page and will not be included
/// in the next page. If not set, i.e. in the first call to list_neurons,
/// list_neurons will return a page of size `limit` starting at the neuron
/// with the smallest ID. Neurons are not kept in any specific order, but their
/// ordering is deterministic, so this can be used to return all the neurons one
/// page at a time.
///
/// If this method is called as a query call, the returned list is not certified.
#[query]
fn list_neurons(request: ListNeurons) -> ListNeuronsResponse {
log!(INFO, "list_neurons");
ListNeuronsResponse::from(governance().list_neurons(&sns_gov_pb::ListNeurons::from(request)))
}
/// Returns the full proposal corresponding to the `proposal_id`.
#[query]
fn get_proposal(request: GetProposal) -> GetProposalResponse {
GetProposalResponse::from(governance().get_proposal(&sns_gov_pb::GetProposal::from(request)))
}
/// Returns a list of proposals of size `limit` using `before_proposal` to
/// indicate the start of the list. Additional filter parameters can be set on the
/// request.
///
/// Proposals are stored in increasing order of ids, where the most recent proposals
/// have the highest ids. ListProposals paginates in reverse, where the first proposals
/// returned are the most recent. To paginate through the all proposals, `before_proposal`
/// should be set to the last proposal of the previously returned page and will not be
/// included in the next page. If not set i.e. in the first call to list_proposals,
/// list_proposals will return a page of size `limit` starting at the most recent proposal.
///
/// If this method is called as a query call, the returned list is not certified.
#[query]
fn list_proposals(request: ListProposals) -> ListProposalsResponse {
log!(INFO, "list_proposals");
ListProposalsResponse::from(
governance().list_proposals(&sns_gov_pb::ListProposals::from(request), &caller()),
)
}
/// Returns the current list of available NervousSystemFunctions.
#[query]
fn list_nervous_system_functions() -> ListNervousSystemFunctionsResponse {
log!(INFO, "list_nervous_system_functions");
ListNervousSystemFunctionsResponse::from(governance().list_nervous_system_functions())
}
/// Returns the latest reward event.
#[query]
fn get_latest_reward_event() -> RewardEvent {
log!(INFO, "get_latest_reward_event");
RewardEvent::from(governance().latest_reward_event())
}
/// Deprecated method. Previously returned the root canister's status.
/// No longer necessary now that canisters can get their own status.
#[update]
#[allow(clippy::let_unit_value)] // clippy false positive
async fn get_root_canister_status(_: ()) -> CanisterStatusResultV2 {
panic!("This method is deprecated and should not be used. Please use the root canister's `get_sns_canisters_summary` method.")
}
/// Gets the current SNS version, as understood by Governance. This is useful
/// for diagnosing upgrade problems, such as if multiple ledger archives are not
/// running the same version.
#[query]
fn get_running_sns_version(_: GetRunningSnsVersionRequest) -> GetRunningSnsVersionResponse {
log!(INFO, "get_running_sns_version");
let pending_version = governance().proto.pending_version.clone();
let upgrade_in_progress = pending_version.map(|upgrade_in_progress| UpgradeInProgress {
target_version: upgrade_in_progress
.target_version
.clone()
.map(Version::from),
mark_failed_at_seconds: upgrade_in_progress.mark_failed_at_seconds,
checking_upgrade_lock: upgrade_in_progress.checking_upgrade_lock,
proposal_id: upgrade_in_progress.proposal_id.unwrap_or(0),
});
GetRunningSnsVersionResponse {
deployed_version: governance()
.proto
.deployed_version
.clone()
.map(Version::from),
pending_version: upgrade_in_progress,
}
}
/// Marks an in progress upgrade that has passed its deadline as failed.
#[update]
fn fail_stuck_upgrade_in_progress(
request: FailStuckUpgradeInProgressRequest,
) -> FailStuckUpgradeInProgressResponse {
log!(INFO, "fail_stuck_upgrade_in_progress");
FailStuckUpgradeInProgressResponse::from(governance_mut().fail_stuck_upgrade_in_progress(
sns_gov_pb::FailStuckUpgradeInProgressRequest::from(request),
))
}
/// Sets the mode. Only the swap canister is allowed to call this.
///
/// In practice, the only mode that the swap canister would ever choose is
/// Normal. Also, in practice, the current value of mode should be
/// PreInitializationSwap. whenever the swap canister calls this.
#[update]
fn set_mode(request: SetMode) -> SetModeResponse {
log!(INFO, "set_mode");
governance_mut().set_mode(request.mode, caller());
SetModeResponse {}
}
#[query]
fn get_mode(request: GetMode) -> GetModeResponse {
log!(INFO, "get_mode");
GetModeResponse::from(governance().get_mode(sns_gov_pb::GetMode::from(request)))
}
/// Claims a batch of neurons requested by the SNS Swap canister. This method is
/// only callable by the Swap canister that was deployed along with this
/// SNS Governance canister.
///
/// This API takes a request of multiple `NeuronRecipes` that provide
/// the configurable parameters of the to-be-created neurons. Since these neurons
/// are responsible for the decentralization of an SNS during the Swap, there are
/// a few differences in neuron creation that occur in comparison to the normal
/// `ManageNeuron::ClaimOrRefresh` API. See `Governance::claim_swap_neurons` for
/// more details.
///
/// This method is idempotent. If called with a `NeuronRecipes` of an already
/// created Neuron, the `ClaimSwapNeuronsResponse.skipped_claims` field will be
/// incremented and execution will continue.
#[update]
fn claim_swap_neurons(
claim_swap_neurons_request: ClaimSwapNeuronsRequest,
) -> ClaimSwapNeuronsResponse {
log!(INFO, "claim_swap_neurons");
let governance = governance_mut();
measure_span(
governance.profiling_information,
"claim_swap_neurons",
|| {
ClaimSwapNeuronsResponse::from(governance.claim_swap_neurons(
sns_gov_pb::ClaimSwapNeuronsRequest::from(claim_swap_neurons_request),
caller(),
))
},
)
}
/// This is not really useful to the public. It is, however, useful to integration tests.
#[update]
fn get_maturity_modulation(request: GetMaturityModulationRequest) -> GetMaturityModulationResponse {
log!(INFO, "get_maturity_modulation");
let governance = governance_mut();
measure_span(
governance.profiling_information,
"get_maturity_modulation",
|| {
GetMaturityModulationResponse::from(
governance.get_maturity_modulation(sns_gov_pb::GetMaturityModulationRequest::from(
request,
)),
)
},
)
}
async fn run_periodic_tasks() {
if let Some(ref mut timers) = governance_mut().proto.timers {
timers.last_spawned_timestamp_seconds.replace(now_seconds());
};
governance_mut().run_periodic_tasks().await;
}
/// Test only feature. Internal method for calling run_periodic_tasks.
#[cfg(feature = "test")]
#[update(hidden = true)]
async fn run_periodic_tasks_now(_request: ()) {
governance_mut().run_periodic_tasks().await;
}
#[query]
fn get_timers(_arg: GetTimersRequest) -> GetTimersResponse {
let timers = governance().proto.timers;
GetTimersResponse { timers }
}
fn init_timers() {
governance_mut().proto.timers.replace(Timers {
last_reset_timestamp_seconds: Some(now_seconds()),
..Default::default()
});
let new_timer_id = ic_cdk_timers::set_timer_interval(RUN_PERIODIC_TASKS_INTERVAL, || {
ic_cdk::spawn(run_periodic_tasks())
});
TIMER_ID.with(|saved_timer_id| {
let mut saved_timer_id = saved_timer_id.borrow_mut();
if let Some(saved_timer_id) = *saved_timer_id {
ic_cdk_timers::clear_timer(saved_timer_id);
}
saved_timer_id.replace(new_timer_id);
});
}
#[update]
fn reset_timers(_request: ResetTimersRequest) -> ResetTimersResponse {
let reset_timers_cool_down_interval_seconds = RESET_TIMERS_COOL_DOWN_INTERVAL.as_secs();
if let Some(timers) = governance_mut().proto.timers {
if let Some(last_reset_timestamp_seconds) = timers.last_reset_timestamp_seconds {
assert!(
now_seconds().saturating_sub(last_reset_timestamp_seconds)
>= reset_timers_cool_down_interval_seconds,
"Reset has already been called within the past {:?} seconds",
reset_timers_cool_down_interval_seconds
);
}
}
init_timers();
ResetTimersResponse {}
}
ic_nervous_system_common_build_metadata::define_get_build_metadata_candid_method_cdk! {}
/// Serve an HttpRequest made to this canister
#[query(hidden = true, decoding_quota = 10000)]
pub fn http_request(request: HttpRequest) -> HttpResponse {
match request.path() {
"/journal/json" => {
let journal = governance()
.proto
.upgrade_journal
.clone()
.expect("The upgrade journal is not initialized for this SNS.");
serve_journal(&journal)
}
"/metrics" => serve_metrics(encode_metrics),
"/logs" => serve_logs_v2(request, &INFO, &ERROR),
// These are obsolete.
"/log/info" => serve_logs(&INFO),
"/log/error" => serve_logs(&ERROR),
_ => HttpResponseBuilder::not_found().build(),
}
}
/// Encode the metrics in a format that can be understood by Prometheus.
fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>) -> std::io::Result<()> {
let governance = governance();
w.encode_gauge(
"sns_governance_neurons_total",
governance.proto.neurons.len() as f64,
"Total number of neurons.",
)?;
match w.histogram_vec(
"sns_governance_performance_metrics",
"Performance data of the SNS governance canister.",
) {
Ok(instrumentation_builder) => {
let recording_result = governance
.profiling_information
.with(|r| r.borrow().record_metrics(instrumentation_builder));
match recording_result {
Ok(_) => {}
Err(e) => {
log!(ERROR, "Unable to record instrumentation metrics: {}", e);
}
}
}
Err(e) => {
log!(
ERROR,
"Unable to create instrumentation histogram builder: {}",
e
);
}
}
Ok(())
}
/// Adds maturity to a neuron for testing
#[cfg(feature = "test")]
#[update]
fn add_maturity(request: AddMaturityRequest) -> AddMaturityResponse {
AddMaturityResponse::from(
governance_mut().add_maturity(sns_gov_pb::AddMaturityRequest::from(request)),
)
}
#[query]
fn get_upgrade_journal(arg: GetUpgradeJournalRequest) -> GetUpgradeJournalResponse {
GetUpgradeJournalResponse::from(
governance().get_upgrade_journal(sns_gov_pb::GetUpgradeJournalRequest::from(arg)),
)
}
/// Mints tokens for testing
#[cfg(feature = "test")]
#[update]
async fn mint_tokens(request: MintTokensRequest) -> MintTokensResponse {
MintTokensResponse::from(
governance_mut()
.mint_tokens(sns_gov_pb::MintTokensRequest::from(request))
.await,
)
}
// Test-only API that advances the target version of the SNS.
#[cfg(feature = "test")]
#[update]
fn advance_target_version(request: AdvanceTargetVersionRequest) -> AdvanceTargetVersionResponse {
AdvanceTargetVersionResponse::from(
governance_mut()
.advance_target_version(sns_gov_pb::AdvanceTargetVersionRequest::from(request)),
)
}
/// Test only feature. Immediately refreshes the cached upgrade steps.
#[cfg(feature = "test")]
#[update]
async fn refresh_cached_upgrade_steps(
_: RefreshCachedUpgradeStepsRequest,
) -> RefreshCachedUpgradeStepsResponse {
let goverance = governance_mut();
let deployed_version = goverance
.try_temporarily_lock_refresh_cached_upgrade_steps()
.unwrap();
goverance
.refresh_cached_upgrade_steps(deployed_version)
.await;
RefreshCachedUpgradeStepsResponse {}
}
fn main() {
// This block is intentionally left blank.
}
// In order for some of the test(s) within this mod to work,
// this MUST occur at the end.
#[cfg(test)]
mod tests;