Skip to content

Commit

Permalink
Update the check in and add a "memo" field to the check in payload
Browse files Browse the repository at this point in the history
  • Loading branch information
yanliu38 committed Dec 7, 2024
1 parent dfee46d commit edf12f5
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 97 deletions.
8 changes: 4 additions & 4 deletions cli/src/ledger_canister_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl LedgerCanister {

pub fn list_functions_queries(&self) -> Vec<String> {
vec![
"get_np_check_in_nonce".to_string(),
"get_check_in_nonce".to_string(),
"data_fetch".to_string(),
"metadata".to_string(),
"get_logs_debug".to_string(),
Expand Down Expand Up @@ -126,12 +126,12 @@ impl LedgerCanister {
Decode!(response.as_slice(), ResultString).map_err(|e| e.to_string())?
}

pub async fn get_np_check_in_nonce(&self) -> Vec<u8> {
pub async fn get_check_in_nonce(&self) -> Vec<u8> {
let args = Encode!(&()).expect("Failed to encode args");
let response = self
.call_query("get_np_check_in_nonce", &args)
.call_query("get_check_in_nonce", &args)
.await
.expect("Failed to call get_np_check_in_nonce");
.expect("Failed to call get_check_in_nonce");
Decode!(response.as_slice(), Vec<u8>).expect("Failed to decode response")
}

Expand Down
7 changes: 6 additions & 1 deletion common/src/dcc_identity.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::{IcrcCompatibleAccount, MAX_PUBKEY_BYTES, MINTING_ACCOUNT_PRINCIPAL};
use crate::{
IcrcCompatibleAccount, ED25519_SIGNATURE_LENGTH, MAX_PUBKEY_BYTES, MINTING_ACCOUNT_PRINCIPAL,
};
use ed25519_dalek::ed25519::Error as DalekError;
use ed25519_dalek::pkcs8::spki::der::pem::LineEnding;
use ed25519_dalek::pkcs8::{EncodePrivateKey, EncodePublicKey};
Expand Down Expand Up @@ -164,6 +166,9 @@ impl DccIdentity {
}

pub fn verify_bytes(&self, data: &[u8], signature_bytes: &[u8]) -> Result<(), CryptoError> {
if signature_bytes.len() != ED25519_SIGNATURE_LENGTH {
return Err("Invalid signature".into());
}
let signature = Signature::from_bytes(slice_to_64_bytes_array(signature_bytes)?);
self.verify(data, &signature)
}
Expand Down
7 changes: 2 additions & 5 deletions common/src/profiles.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
amount_as_string, charge_fees_to_account_no_bump_reputation, info, reputation_get,
reward_e9s_per_block, Balance, DccIdentity, ED25519_SIGNATURE_LENGTH, LABEL_NP_PROFILE,
MAX_NP_PROFILE_BYTES, MAX_PUBKEY_BYTES,
reward_e9s_per_block, Balance, DccIdentity, LABEL_NP_PROFILE, MAX_NP_PROFILE_BYTES,
MAX_PUBKEY_BYTES,
};
use borsh::{BorshDeserialize, BorshSerialize};
use candid::Principal;
Expand Down Expand Up @@ -40,9 +40,6 @@ impl UpdateProfilePayload {
pub fn verify_signature(&self, dcc_id: &DccIdentity) -> Result<(), String> {
match self {
UpdateProfilePayload::V1(payload) => {
if payload.signature.len() != ED25519_SIGNATURE_LENGTH {
return Err("Invalid signature".to_string());
}
if payload.profile_bytes.len() > MAX_NP_PROFILE_BYTES {
return Err("Profile payload too long".to_string());
}
Expand Down
123 changes: 75 additions & 48 deletions common/src/rewards.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,59 @@
use crate::platform_specific::get_timestamp_ns;
use crate::MAX_PUBKEY_BYTES;
use crate::{
account_balance_get, account_registration_fee_e9s, account_transfers::FundsTransfer,
amount_as_string, charge_fees_to_account_no_bump_reputation, get_account_from_pubkey, info,
account_balance_get, account_transfers::FundsTransfer, amount_as_string,
charge_fees_to_account_no_bump_reputation, get_account_from_pubkey, info,
ledger_funds_transfer, Balance, DccIdentity, TransferError, BLOCK_INTERVAL_SECS,
DC_TOKEN_DECIMALS_DIV, FIRST_BLOCK_TIMESTAMP_NS, KEY_LAST_REWARD_DISTRIBUTION_TS,
LABEL_NP_CHECK_IN, LABEL_NP_REGISTER, LABEL_REWARD_DISTRIBUTION, MINTING_ACCOUNT,
REWARD_HALVING_AFTER_BLOCKS,
LABEL_NP_CHECK_IN, LABEL_NP_REGISTER, LABEL_REWARD_DISTRIBUTION, MEMO_BYTES_MAX,
MINTING_ACCOUNT, REWARD_HALVING_AFTER_BLOCKS,
};
use candid::Principal;
use ed25519_dalek::Signature;
use borsh::{BorshDeserialize, BorshSerialize};
#[cfg(target_arch = "wasm32")]
#[allow(unused_imports)]
use ic_cdk::println;
use ledger_map::LedgerMap;
use std::cell::RefCell;

pub fn check_in_fee_e9s() -> Balance {
reward_e9s_per_block() / 100
}

#[derive(BorshSerialize, BorshDeserialize)]
pub struct CheckInPayloadV1 {
memo: String, // Memo can for example be shown on a dashboard, as an arbitrary personal message
nonce_signature: Vec<u8>,
}

#[derive(BorshSerialize, BorshDeserialize)]
pub enum CheckInPayload {
V1(CheckInPayloadV1),
}

impl CheckInPayload {
pub fn new(memo: String, nonce_signature: Vec<u8>) -> CheckInPayload {
CheckInPayload::V1(CheckInPayloadV1 {
memo,
nonce_signature,
})
}

pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
borsh::to_vec(self)
}

pub fn memo(&self) -> &str {
match self {
CheckInPayload::V1(record) => &record.memo,
}
}

pub fn nonce_signature(&self) -> &[u8] {
match self {
CheckInPayload::V1(record) => &record.nonce_signature,
}
}
}

fn calc_token_rewards_e9_since_timestamp_ns(last_reward_distribution_ts_ns: u64) -> Balance {
let elapsed_secs_since_reward_distribution =
(get_timestamp_ns().saturating_sub(last_reward_distribution_ts_ns)) / 1_000_000_000;
Expand Down Expand Up @@ -170,62 +208,51 @@ pub fn rewards_distribute(ledger: &mut LedgerMap) -> Result<String, TransferErro

pub fn do_node_provider_check_in(
ledger: &mut LedgerMap,
caller: Principal,
pubkey_bytes: Vec<u8>,
memo: String,
nonce_signature: Vec<u8>,
) -> Result<String, String> {
info!("[do_node_provider_check_in]: caller: {}", caller);
let dcc_id = DccIdentity::new_verifying_from_bytes(&pubkey_bytes)?;
info!("[do_node_provider_check_in]: {}", dcc_id);

if pubkey_bytes.len() > MAX_PUBKEY_BYTES {
return Err("Node provider unique id too long".to_string());
// Check the max length of the memo
if memo.len() > MEMO_BYTES_MAX {
return Err(format!(
"Memo too long, max length is {} bytes",
MEMO_BYTES_MAX
));
}
if nonce_signature.len() != 64 {
return Err("Invalid signature".to_string());
}
// Ensure the NP is registered
ledger
.get(LABEL_NP_REGISTER, &pubkey_bytes)
.map_err(|e| e.to_string())?;
let dcc_identity =
DccIdentity::new_verifying_from_bytes(&pubkey_bytes).map_err(|e| e.to_string())?;
info!(
"Check-in of {}, account: {}",
dcc_identity,
get_account_from_pubkey(&pubkey_bytes)
);
let latest_nonce = ledger.get_latest_block_hash();
let signature = Signature::from_slice(&nonce_signature).map_err(|e| e.to_string())?;
info!(
"Checking signature {} against latest nonce: {}",
signature,
hex::encode(&latest_nonce)
);
dcc_identity
.verify(&latest_nonce, &signature)
.expect("Signature didn't verify");

// Check that the NP is already registered
ledger.get(LABEL_NP_REGISTER, &pubkey_bytes)?;

// Verify the signature
dcc_id.verify_bytes(&ledger.get_latest_block_hash(), &nonce_signature)?;

let fees = if ledger.get_blocks_count() > 0 {
let amount = account_registration_fee_e9s();
let amount = check_in_fee_e9s();
info!(
"Charging {} tokens {} for NP check in",
"Charging {} tokens {} for the check in",
amount_as_string(amount as Balance),
dcc_identity.to_ic_principal()
dcc_id.to_ic_principal()
);
charge_fees_to_account_no_bump_reputation(ledger, &dcc_identity, amount as Balance)?;
charge_fees_to_account_no_bump_reputation(ledger, &dcc_id, amount as Balance)?;
amount
} else {
0
};

ledger
.upsert(LABEL_NP_CHECK_IN, pubkey_bytes, nonce_signature)
.map(|_| "ok".to_string())
.map_err(|e| format!("{:?}", e))?;

Ok(format!(
"Signature verified, check in successful. You have been charged {} tokens",
amount_as_string(fees)
))
let payload = CheckInPayload::new(memo, nonce_signature);
let payload_bytes = payload.to_bytes().unwrap();

Ok(ledger
.upsert(LABEL_NP_CHECK_IN, pubkey_bytes, &payload_bytes)
.map(|_| {
format!(
"Signature verified, check in successful. You have been charged {} tokens",
amount_as_string(fees)
)
})?)
}

pub fn rewards_applied_np_count(ledger: &LedgerMap) -> usize {
Expand Down
26 changes: 16 additions & 10 deletions ic-canister/decent_cloud.did
Original file line number Diff line number Diff line change
Expand Up @@ -360,31 +360,37 @@ type OfferingRentReply = record {
rent_success: bool; // Short boolean to mark whether the renting was accepted or rejected by the provider
response_text: text; // Thank you note, or similar on success. Reason the request failed on failure.
response_details: text; // Instructions or a link to the detailed instructions: describing next steps, further information, etc.
signature: vec nat8; // Signature of the response, to simplify verification
};

type RefundRequest = record { // TODO
requester_pubkey_bytes: vec nat8; // Who is making this request?
instance_id: text; // instance id for which a refund is requested
signature: vec nat8; // The whole request needs to be signed by the requester's private key
crypto_sig: vec nat8; // The whole request needs to be signed by the requester's private key
};

service : {
// Node Provider (NP) management operations
node_provider_register: (pubkey_bytes: vec nat8, signature: vec nat8) -> (ResultString);
node_provider_check_in: (pubkey_bytes: vec nat8, signature: vec nat8) -> (ResultString);
node_provider_update_profile: (pubkey_bytes: vec nat8, update_profile_payload: vec nat8, signature: vec nat8) -> (ResultString);
node_provider_update_offering: (pubkey_bytes: vec nat8, update_offering_payload: vec nat8, signature: vec nat8) -> (ResultString);
node_provider_register: (pubkey_bytes: vec nat8, crypto_sig: vec nat8) -> (ResultString);
node_provider_update_profile: (pubkey_bytes: vec nat8, update_profile_payload: vec nat8, crypto_sig: vec nat8) -> (ResultString);
node_provider_update_offering: (pubkey_bytes: vec nat8, update_offering_payload: vec nat8, crypto_sig: vec nat8) -> (ResultString);
node_provider_list_checked_in: () -> (ResultString) query;
node_provider_get_profile_by_pubkey_bytes: (vec nat8) -> (opt text) query;
node_provider_get_profile_by_principal: (principal) -> (opt text) query;
get_np_check_in_nonce: () -> (vec nat8) query;
offering_search: (search_query: text) -> (vec OfferingEntry) query;
offering_request: (offering_rent_request_payload: vec nat8, signature: vec nat8) -> (ResultString);
offering_reply: (offering_rent_reply_payload: vec nat8, signature: vec nat8) -> (ResultString);
offering_request: (offering_rent_request_payload: vec nat8, crypto_sig: vec nat8) -> (ResultString);
offering_reply: (offering_rent_reply_payload: vec nat8, crypto_sig: vec nat8) -> (ResultString);

// User management operations
user_register: (pubkey_bytes: vec nat8, signature: vec nat8) -> (ResultString);
user_register: (pubkey_bytes: vec nat8, crypto_sig: vec nat8) -> (ResultString);

// Check-in nonce that needs to be signed and sent for the check in
// Provided here as a reference only, since client will instead use the nonce of the local ledger
get_check_in_nonce: () -> (vec nat8) query;
// Expected arguments:
// - public key of the account (32B)
// - memo, arbitrary text, up to 32B
// - nonce_crypto_sig, cryptographic signature of the latest blockchain nonce
node_provider_check_in: (pubkey_bytes: vec nat8, memo: text, nonce_crypto_sig: vec nat8) -> (ResultString);

// Common NP and user management operations
get_identity_reputation: (pubkey_bytes: vec nat8) -> (nat64) query;
Expand Down
33 changes: 17 additions & 16 deletions ic-canister/src/canister_backend/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,22 @@ pub(crate) fn _user_register(
})
}

pub(crate) fn _node_provider_check_in(
pubkey_bytes: Vec<u8>,
memo: String,
nonce_signature: Vec<u8>,
) -> Result<String, String> {
// To prevent DOS attacks, a fee is charged for executing this operation
LEDGER_MAP.with(|ledger| {
dcc_common::do_node_provider_check_in(
&mut ledger.borrow_mut(),
pubkey_bytes,
memo,
nonce_signature,
)
})
}

pub(crate) fn _node_provider_update_profile(
pubkey_bytes: Vec<u8>,
update_profile_payload: Vec<u8>,
Expand Down Expand Up @@ -214,22 +230,7 @@ pub(crate) fn _node_provider_get_profile_by_principal(principal: Principal) -> O
_node_provider_get_profile_by_pubkey_bytes(pubkey_bytes)
}

pub(crate) fn _node_provider_check_in(
pubkey_bytes: Vec<u8>,
nonce_signature: Vec<u8>,
) -> Result<String, String> {
// To prevent DOS attacks, a fee is charged for executing this operation
LEDGER_MAP.with(|ledger| {
dcc_common::do_node_provider_check_in(
&mut ledger.borrow_mut(),
ic_cdk::api::caller(),
pubkey_bytes,
nonce_signature,
)
})
}

pub(crate) fn _get_np_check_in_nonce() -> Vec<u8> {
pub(crate) fn _get_check_in_nonce() -> Vec<u8> {
LEDGER_MAP.with(|ledger| ledger.borrow().get_latest_block_hash())
}

Expand Down
21 changes: 11 additions & 10 deletions ic-canister/src/canister_endpoints/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ fn user_register(pubkey_bytes: Vec<u8>, signature: Vec<u8>) -> Result<String, St
_user_register(pubkey_bytes, signature)
}

#[ic_cdk::update]
fn node_provider_check_in(
pubkey_bytes: Vec<u8>,
memo: String,
nonce_signature: Vec<u8>,
) -> Result<String, String> {
_node_provider_check_in(pubkey_bytes, memo, nonce_signature)
}

#[ic_cdk::update]
fn node_provider_update_profile(
pubkey_bytes: Vec<u8>,
Expand Down Expand Up @@ -66,17 +75,9 @@ fn node_provider_get_profile_by_principal(principal: Principal) -> Option<String
_node_provider_get_profile_by_principal(principal)
}

#[ic_cdk::update]
fn node_provider_check_in(
pubkey_bytes: Vec<u8>,
nonce_signature: Vec<u8>,
) -> Result<String, String> {
_node_provider_check_in(pubkey_bytes, nonce_signature)
}

#[ic_cdk::query]
fn get_np_check_in_nonce() -> Vec<u8> {
_get_np_check_in_nonce()
fn get_check_in_nonce() -> Vec<u8> {
_get_check_in_nonce()
}

#[ic_cdk::query]
Expand Down
7 changes: 4 additions & 3 deletions ic-canister/tests/test_canister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ fn np_check_in(
dcc_identity: &DccIdentity,
) -> Result<String, String> {
let no_args = encode_one(()).expect("failed to encode");
let nonce_bytes = query_check_and_decode!(pic, can, "get_np_check_in_nonce", no_args, Vec<u8>);
let nonce_bytes = query_check_and_decode!(pic, can, "get_check_in_nonce", no_args, Vec<u8>);
let nonce_string = hex::encode(&nonce_bytes);
println!(
"Checking-in NP {}, using nonce: {} ({} bytes)",
Expand All @@ -339,7 +339,7 @@ fn np_check_in(
nonce_bytes.len()
);

let payload = dcc_identity.sign(&nonce_bytes).unwrap().to_bytes();
let crypto_sig = dcc_identity.sign(&nonce_bytes).unwrap().to_bytes();

update_check_and_decode!(
pic,
Expand All @@ -348,7 +348,8 @@ fn np_check_in(
"node_provider_check_in",
Encode!(
&dcc_identity.to_bytes_verifying(),
&payload
&String::from("Just a test memo!"),
&crypto_sig
)
.unwrap(),
Result<String, String>
Expand Down

0 comments on commit edf12f5

Please sign in to comment.