Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ANS (NEP#0006) #1193

Merged
merged 5 commits into from
Aug 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
257 changes: 254 additions & 3 deletions core/primitives/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use crate::types::{AccountId, ShardId};
use chrono::{DateTime, NaiveDateTime, Utc};

pub const ACCOUNT_DATA_SEPARATOR: &[u8; 1] = b",";
pub const MIN_ACCOUNT_ID_LEN: usize = 2;
pub const MAX_ACCOUNT_ID_LEN: usize = 64;

/// Number of nano seconds in a second.
const NS_IN_SECOND: u64 = 1_000_000_000;
Expand Down Expand Up @@ -113,7 +115,15 @@ pub fn account_to_shard_id(account_id: &AccountId) -> ShardId {
}

lazy_static! {
static ref VALID_ACCOUNT_ID: Regex = Regex::new(r"^[a-z0-9@._\-]{5,32}$").unwrap();
/// See NEP#0006
static ref VALID_ACCOUNT_ID: Regex =
Regex::new(r"^(([a-z\d]+[\-_])*[a-z\d]+[\.@])*([a-z\d]+[\-_])*[a-z\d]+$").unwrap();
/// Represents a part of an account ID with a suffix of as a separator `.` or `@`.
static ref VALID_ACCOUNT_PART_ID_WITH_TAIL_SEPARATOR: Regex =
Regex::new(r"^([a-z\d]+[\-_])*[a-z\d]+[\.@]$").unwrap();
/// Represents a top level account ID.
static ref VALID_TOP_LEVEL_ACCOUNT_ID: Regex =
Regex::new(r"^([a-z\d]+[\-_])*[a-z\d]+$").unwrap();
}

/// const does not allow function call, so have to resort to this
Expand All @@ -122,10 +132,34 @@ pub fn system_account() -> AccountId {
}

pub fn is_valid_account_id(account_id: &AccountId) -> bool {
if *account_id == system_account() {
account_id.len() >= MIN_ACCOUNT_ID_LEN
&& account_id.len() <= MAX_ACCOUNT_ID_LEN
&& VALID_ACCOUNT_ID.is_match(account_id)
}

pub fn is_valid_top_level_account_id(account_id: &AccountId) -> bool {
account_id.len() >= MIN_ACCOUNT_ID_LEN
&& account_id.len() <= MAX_ACCOUNT_ID_LEN
&& account_id != &system_account()
&& VALID_TOP_LEVEL_ACCOUNT_ID.is_match(account_id)
}

/// Returns true if the signer_id can create a direct sub-account with the given account Id.
/// It assumes the signer_id is a valid account_id
pub fn is_valid_sub_account_id(signer_id: &AccountId, sub_account_id: &AccountId) -> bool {
if !is_valid_account_id(sub_account_id) {
return false;
}
VALID_ACCOUNT_ID.is_match(account_id)
if signer_id.len() >= sub_account_id.len() {
return false;
}
// Will not panic, since valid account id is utf-8 only and the length is checked above.
// e.g. when `near` creates `aa.near`, it splits into `aa.` and `near`
let (prefix, suffix) = sub_account_id.split_at(sub_account_id.len() - signer_id.len());
if suffix != signer_id {
return false;
}
VALID_ACCOUNT_PART_ID_WITH_TAIL_SEPARATOR.is_match(prefix)
}

/// A wrapper around Option<T> that provides native Display trait.
Expand Down Expand Up @@ -184,3 +218,220 @@ pub fn from_timestamp(timestamp: u64) -> DateTime<Utc> {
pub fn to_timestamp(time: DateTime<Utc>) -> u64 {
time.timestamp_nanos() as u64
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_is_valid_account_id() {
let ok_account_ids = vec![
"aa",
"a-a",
"a-aa",
"100",
"0o",
"com",
"near",
"bowen",
"b-o_w_e-n",
"b.owen",
"bro.wen",
"a.ha",
"a.b-a.ra",
"system",
"some-complex-address@gmail.com",
"sub.buy_d1gitz@atata@b0-rg.c_0_m",
"over.9000",
"google.com",
"illia.cheapaccounts.near",
"0o0ooo00oo00o",
"alex-skidanov",
"10-4.8-2",
"b-o_w_e-n",
"no_lols",
"0123456789012345678901234567890123456789012345678901234567890123",
// Valid, but can't be created
"near.a",
];
for account_id in ok_account_ids {
assert!(
is_valid_account_id(&account_id.to_string()),
"Valid account id {:?} marked invalid",
account_id
);
}

let bad_account_ids = vec![
"a",
"A",
"Abc",
"-near",
"near-",
"-near-",
"near.",
".near",
"near@",
"@near",
"неар",
"@@@@@",
"0__0",
"0_-_0",
"0_-_0",
"..",
"a..near",
"nEar",
"_bowen",
"hello world",
"abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz",
"01234567890123456789012345678901234567890123456789012345678901234",
];
for account_id in bad_account_ids {
assert!(
!is_valid_account_id(&account_id.to_string()),
"Invalid account id {:?} marked valid",
account_id
);
}
}

#[test]
fn test_is_valid_top_level_account_id() {
let ok_top_level_account_ids = vec![
"aa",
"a-a",
"a-aa",
"100",
"0o",
"com",
"near",
"bowen",
"b-o_w_e-n",
"0o0ooo00oo00o",
"alex-skidanov",
"b-o_w_e-n",
"no_lols",
"0123456789012345678901234567890123456789012345678901234567890123",
];
for account_id in ok_top_level_account_ids {
assert!(
is_valid_top_level_account_id(&account_id.to_string()),
"Valid top level account id {:?} marked invalid",
account_id
);
}

let bad_top_level_account_ids = vec![
"near.a",
"b.owen",
"bro.wen",
"a.ha",
"a.b-a.ra",
"some-complex-address@gmail.com",
"sub.buy_d1gitz@atata@b0-rg.c_0_m",
"over.9000",
"google.com",
"illia.cheapaccounts.near",
"10-4.8-2",
"a",
"A",
"Abc",
"-near",
"near-",
"-near-",
"near.",
".near",
"near@",
"@near",
"неар",
"@@@@@",
"0__0",
"0_-_0",
"0_-_0",
"..",
"a..near",
"nEar",
"_bowen",
"hello world",
"abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz",
"01234567890123456789012345678901234567890123456789012345678901234",
// Valid regex and length, but reserved
"system",
];
for account_id in bad_top_level_account_ids {
assert!(
!is_valid_top_level_account_id(&account_id.to_string()),
"Invalid top level account id {:?} marked valid",
account_id
);
}
}

#[test]
fn test_is_valid_sub_account_id() {
let ok_pairs = vec![
("test", "a.test"),
("test", "a@test"),
("test-me", "abc.test-me"),
("test_me", "abc@test_me"),
("gmail.com", "abc@gmail.com"),
("gmail@com", "abc.gmail@com"),
("gmail.com", "abc-lol@gmail.com"),
("gmail@com", "abc_lol.gmail@com"),
("gmail@com", "bro-abc_lol.gmail@com"),
("g0", "0g.g0"),
("1g", "1g.1g"),
("5-3", "4_2.5-3"),
];
for (signer_id, sub_account_id) in ok_pairs {
assert!(
is_valid_sub_account_id(&signer_id.to_string(), &sub_account_id.to_string()),
"Failed to create sub-account {:?} by account {:?}",
sub_account_id,
signer_id
);
}

let bad_pairs = vec![
("test", ".test"),
("test", "test"),
("test", "est"),
("test", ""),
("test", "st"),
("test5", "ббб"),
("test", "a-test"),
("test", "etest"),
("test", "a.etest"),
("test", "retest"),
("test-me", "abc-.test-me"),
("test-me", "Abc.test-me"),
("test-me", "-abc.test-me"),
("test-me", "a--c.test-me"),
("test-me", "a_-c.test-me"),
("test-me", "a-_c.test-me"),
("test-me", "_abc.test-me"),
("test-me", "abc_.test-me"),
("test-me", "..test-me"),
("test-me", "a..test-me"),
("gmail.com", "a.abc@gmail.com"),
("gmail.com", ".abc@gmail.com"),
("gmail.com", ".abc@gmail@com"),
("gmail.com", "abc@gmail@com"),
("gmail.com", "123456789012345678901234567890123456789012345678901234567890@gmail.com"),
(
"123456789012345678901234567890123456789012345678901234567890",
"1234567890.123456789012345678901234567890123456789012345678901234567890",
),
("aa", "ъ@aa"),
("aa", "ъ.aa"),
];
for (signer_id, sub_account_id) in bad_pairs {
assert!(
!is_valid_sub_account_id(&signer_id.to_string(), &sub_account_id.to_string()),
"Invalid sub-account {:?} created by account {:?}",
sub_account_id,
signer_id
);
}
}
}
49 changes: 39 additions & 10 deletions runtime/runtime/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ use near_primitives::transaction::{
Action, AddKeyAction, DeleteAccountAction, DeleteKeyAction, DeployContractAction,
FunctionCallAction, StakeAction, TransferAction,
};
use near_primitives::types::{AccountId, BlockIndex, ValidatorStake};
use near_primitives::utils::key_for_access_key;
use near_primitives::types::{AccountId, Balance, BlockIndex, ValidatorStake};
use near_primitives::utils::{
is_valid_sub_account_id, is_valid_top_level_account_id, key_for_access_key,
};
use near_store::{
get_access_key, get_code, remove_account, set_access_key, set_code, total_account_storage,
TrieUpdate,
Expand All @@ -22,12 +24,30 @@ use std::sync::Arc;
/// Number of epochs it takes to unstake.
const NUM_UNSTAKING_EPOCHS: BlockIndex = 3;

fn cost_per_block(
account_id: &AccountId,
account: &Account,
runtime_config: &RuntimeConfig,
) -> Balance {
let account_length_cost_per_block = if account_id.len() > 10 {
0
} else {
runtime_config.account_length_baseline_cost_per_block
/ 3_u128.pow(account_id.len() as u32 - 2)
};

let storage_cost_per_block = (total_account_storage(account_id, account) as u128)
* runtime_config.storage_cost_byte_per_block;

account_length_cost_per_block + storage_cost_per_block
}

/// Returns true if the account has enough balance to pay storage rent for at least required number of blocks.
/// Validators must have at least enough for `NUM_UNSTAKING_EPOCHS` * epoch_length of blocks,
/// regular users - `poke_threshold` blocks.
pub(crate) fn check_rent(
account_id: &AccountId,
account: &mut Account,
account: &Account,
runtime_config: &RuntimeConfig,
epoch_length: BlockIndex,
) -> bool {
Expand All @@ -36,9 +56,8 @@ pub(crate) fn check_rent(
} else {
runtime_config.poke_threshold
};
let buffer_amount = (buffer_length as u128)
* (total_account_storage(account_id, account) as u128)
* runtime_config.storage_cost_byte_per_block;
let buffer_amount =
(buffer_length as u128) * cost_per_block(account_id, account, runtime_config);
account.amount >= buffer_amount
}

Expand All @@ -47,11 +66,10 @@ pub(crate) fn apply_rent(
account_id: &AccountId,
account: &mut Account,
block_index: BlockIndex,
config: &RuntimeConfig,
runtime_config: &RuntimeConfig,
) {
let charge = ((block_index - account.storage_paid_at) as u128)
* (total_account_storage(account_id, account) as u128)
* config.storage_cost_byte_per_block;
* cost_per_block(account_id, account, runtime_config);
account.amount = account.amount.saturating_sub(charge);
account.storage_paid_at = block_index;
}
Expand Down Expand Up @@ -192,8 +210,19 @@ pub(crate) fn action_create_account(
account: &mut Option<Account>,
actor_id: &mut AccountId,
receipt: &Receipt,
result: &mut ActionResult,
) {
// TODO(#968): Validate new name according to ANS
let account_id = &receipt.receiver_id;
if !is_valid_top_level_account_id(account_id)
&& !is_valid_sub_account_id(&receipt.predecessor_id, account_id)
{
result.result = Err(format!(
"The new account_id {:?} can't be created by {:?}",
account_id, &receipt.predecessor_id
)
.into());
return;
}
*actor_id = receipt.receiver_id.clone();
*account = Some(Account::new(0, CryptoHash::default(), apply_state.block_index));
}
Expand Down
4 changes: 4 additions & 0 deletions runtime/runtime/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ pub struct RuntimeConfig {
pub transaction_costs: RuntimeFeesConfig,
/// Config of wasm operations.
pub wasm_config: Config,
/// The baseline cost to store account_id of short length per block.
/// The original formula in NEP#0006 is `1,000 / (3 ^ (account_id.length - 2))` for cost per year.
/// This value represents `1,000` above adjusted to use per block.
pub account_length_baseline_cost_per_block: Balance,
}

pub fn safe_gas_to_balance(
Expand Down
2 changes: 1 addition & 1 deletion runtime/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ impl Runtime {
}
match action {
Action::CreateAccount(_) => {
action_create_account(apply_state, account, actor_id, receipt);
action_create_account(apply_state, account, actor_id, receipt, &mut result);
}
Action::DeployContract(deploy_contract) => {
action_deploy_contract(state_update, account, &account_id, deploy_contract);
Expand Down
Loading