Skip to content

Commit

Permalink
Setup for ETH-implicit accounts (#10020)
Browse files Browse the repository at this point in the history
Part of #10018.

This PR introduces some changes from
#9969 laying groundwork for real
protocol changes to be done in a separate PR.
Some changes might look redundant (especially `match
receiver_id.get_account_type() { ...`) but they will make it easier to
read changes done in the next PR.

Changes done in [near-account-id
1.0.0-alpha.2](near/near-account-id-rs#14):
- Add `AccountType` enum: `NamedAccount`, `NearImplicitAccount`, or
`EthImplicitAccount`.
- Parse 40 characters long hexadecimal addresses prefixed with `'0x'` as
`EthImplicitAccount`.
- `AccountType::is_implicit()` returns true for both
`NearImplicitAccount` and `EthImplicitAccount`.

Summary of this PR:
- Bump version of `near-account-id` to `1.0.0-alpha.2`.
- Do not use `is_implicit()` for now, as we do not want to treat
`EthImplicitAccount` as implicit as for now.
- Add `derive_account_id_from_public_key` function that currently only
supports `ED25519` key type and returns hex-encoded copy of the key.
- Refactor/rename tests a bit.
  • Loading branch information
staffik authored Nov 6, 2023
1 parent a26635a commit 00ca2f3
Show file tree
Hide file tree
Showing 18 changed files with 309 additions and 151 deletions.
4 changes: 2 additions & 2 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ lru = "0.7.2"
memmap2 = "0.5"
memoffset = "0.8"
more-asserts = "0.2"
near-account-id = { version = "1.0.0-alpha.1", features = ["internal_unstable", "serde", "borsh"] }
near-account-id = { version = "1.0.0-alpha.2", features = ["internal_unstable", "serde", "borsh"] }
near-actix-test-utils = { path = "test-utils/actix-test-utils" }
near-amend-genesis = { path = "tools/amend-genesis" }
near-database-tool = { path = "tools/database" }
Expand Down
4 changes: 2 additions & 2 deletions core/crypto/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ impl From<ParseKeyTypeError> for ParseSignatureError {

#[derive(Debug, Clone, thiserror::Error)]
pub enum ImplicitPublicKeyError {
#[error("'{account_id}' is not an implicit account")]
AccountIsNotImplicit { account_id: AccountId },
#[error("'{account_id}' is not a NEAR-implicit account")]
AccountIsNotNearImplicit { account_id: AccountId },
}
16 changes: 9 additions & 7 deletions core/crypto/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use curve25519_dalek::traits::VartimeMultiscalarMul;
pub use curve25519_dalek::ristretto::RistrettoPoint as Point;
pub use curve25519_dalek::scalar::Scalar;

use near_account_id::AccountType;

pub fn vmul2(s1: Scalar, p1: &Point, s2: Scalar, p2: &Point) -> Point {
Point::vartime_multiscalar_mul(&[s1, s2], [p1, p2].iter().copied())
}
Expand Down Expand Up @@ -96,16 +98,16 @@ impl<
}

impl PublicKey {
/// Create the implicit public key from an implicit account ID.
/// Create the implicit public key from an NEAR-implicit account ID.
///
/// Returns `ImplicitPublicKeyError::AccountIsNotImplicit` if the given
/// account id is not a valid implicit account ID.
/// See [`near_account_id::AccountId#is_implicit`] for the definition.
pub fn from_implicit_account(
/// Returns `ImplicitPublicKeyError::AccountIsNotNearImplicit` if the given
/// account id is not a valid NEAR-implicit account ID.
/// See [`near_account_id::AccountId#is_near_implicit`] for the definition.
pub fn from_near_implicit_account(
account_id: &near_account_id::AccountId,
) -> Result<Self, ImplicitPublicKeyError> {
if !account_id.is_implicit() {
return Err(ImplicitPublicKeyError::AccountIsNotImplicit {
if account_id.get_account_type() != AccountType::NearImplicitAccount {
return Err(ImplicitPublicKeyError::AccountIsNotNearImplicit {
account_id: account_id.clone(),
});
}
Expand Down
52 changes: 37 additions & 15 deletions core/primitives-core/src/runtime/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::config::ActionCosts;
use crate::num_rational::Rational32;
use crate::types::{Balance, Gas};
use enum_map::EnumMap;
use near_account_id::AccountType;

/// Costs associated with an object that can only be sent over the network (and executed
/// by the receiver).
Expand Down Expand Up @@ -203,28 +204,49 @@ impl StorageUsageConfig {

/// Helper functions for computing Transfer fees.
/// In case of implicit account creation they always include extra fees for the CreateAccount and
/// AddFullAccessKey actions that are implicit.
/// AddFullAccessKey (for NEAR-implicit account only) actions that are implicit.
/// We can assume that no overflow will happen here.
pub fn transfer_exec_fee(cfg: &RuntimeFeesConfig, is_receiver_implicit: bool) -> Gas {
if is_receiver_implicit {
cfg.fee(ActionCosts::create_account).exec_fee()
+ cfg.fee(ActionCosts::add_full_access_key).exec_fee()
+ cfg.fee(ActionCosts::transfer).exec_fee()
} else {
cfg.fee(ActionCosts::transfer).exec_fee()
pub fn transfer_exec_fee(
cfg: &RuntimeFeesConfig,
implicit_account_creation_allowed: bool,
receiver_account_type: AccountType,
) -> Gas {
let transfer_fee = cfg.fee(ActionCosts::transfer).exec_fee();
match (implicit_account_creation_allowed, receiver_account_type) {
// Regular transfer to a named account.
(_, AccountType::NamedAccount) => transfer_fee,
// No account will be created, just a regular transfer.
(false, _) => transfer_fee,
// Currently, no account is created on transfer to ETH-implicit account, just a regular transfer.
(true, AccountType::EthImplicitAccount) => transfer_fee,
// Extra fees for the CreateAccount and AddFullAccessKey.
(true, AccountType::NearImplicitAccount) => {
transfer_fee
+ cfg.fee(ActionCosts::create_account).exec_fee()
+ cfg.fee(ActionCosts::add_full_access_key).exec_fee()
}
}
}

pub fn transfer_send_fee(
cfg: &RuntimeFeesConfig,
sender_is_receiver: bool,
is_receiver_implicit: bool,
implicit_account_creation_allowed: bool,
receiver_account_type: AccountType,
) -> Gas {
if is_receiver_implicit {
cfg.fee(ActionCosts::create_account).send_fee(sender_is_receiver)
+ cfg.fee(ActionCosts::add_full_access_key).send_fee(sender_is_receiver)
+ cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver)
} else {
cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver)
let transfer_fee = cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver);
match (implicit_account_creation_allowed, receiver_account_type) {
// Regular transfer to a named account.
(_, AccountType::NamedAccount) => transfer_fee,
// No account will be created, just a regular transfer.
(false, _) => transfer_fee,
// Currently, no account is created on transfer to ETH-implicit account, just a regular transfer.
(true, AccountType::EthImplicitAccount) => transfer_fee,
// Extra fees for the CreateAccount and AddFullAccessKey.
(true, AccountType::NearImplicitAccount) => {
transfer_fee
+ cfg.fee(ActionCosts::create_account).send_fee(sender_is_receiver)
+ cfg.fee(ActionCosts::add_full_access_key).send_fee(sender_is_receiver)
}
}
}
14 changes: 7 additions & 7 deletions core/primitives/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,21 +555,21 @@ pub fn create_test_signer(account_name: &str) -> InMemoryValidatorSigner {
///
/// Should be used only in tests.
pub fn create_user_test_signer(account_name: &AccountIdRef) -> InMemorySigner {
let account_id: AccountId = account_name.to_owned();
if account_id == implicit_test_account() {
InMemorySigner::from_secret_key(account_id, implicit_test_account_secret())
let account_id = account_name.to_owned();
if account_id == near_implicit_test_account() {
InMemorySigner::from_secret_key(account_id, near_implicit_test_account_secret())
} else {
InMemorySigner::from_seed(account_id, KeyType::ED25519, account_name.as_str())
}
}

/// A fixed implicit account for which tests can know the private key.
pub fn implicit_test_account() -> AccountId {
/// A fixed NEAR-implicit account for which tests can know the private key.
pub fn near_implicit_test_account() -> AccountId {
"061b1dd17603213b00e1a1e53ba060ad427cef4887bd34a5e0ef09010af23b0a".parse().unwrap()
}

/// Private key for the fixed implicit test account.
pub fn implicit_test_account_secret() -> SecretKey {
/// Private key for the fixed NEAR-implicit test account.
pub fn near_implicit_test_account_secret() -> SecretKey {
"ed25519:5roj6k68kvZu3UEJFyXSfjdKGrodgZUfFLZFpzYXWtESNsLWhYrq3JGi4YpqeVKuw1m9R2TEHjfgWT1fjUqB1DNy".parse().unwrap()
}

Expand Down
20 changes: 20 additions & 0 deletions core/primitives/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ use crate::version::{
ProtocolVersion, CORRECT_RANDOM_VALUE_PROTOCOL_VERSION, CREATE_HASH_PROTOCOL_VERSION,
CREATE_RECEIPT_ID_SWITCH_TO_CURRENT_BLOCK_VERSION,
};

use near_crypto::ED25519PublicKey;
use near_primitives_core::account::id::AccountId;

use std::mem::size_of;
use std::ops::Deref;

Expand Down Expand Up @@ -465,9 +469,25 @@ where
Serializable(object)
}

/// Derives `AccountId` from `PublicKey``.
/// If the key type is ED25519, returns hex-encoded copy of the key.
pub fn derive_near_implicit_account_id(public_key: &ED25519PublicKey) -> AccountId {
hex::encode(public_key).parse().unwrap()
}

#[cfg(test)]
mod tests {
use super::*;
use near_crypto::{KeyType, PublicKey};

#[test]
fn test_derive_account_id_from_ed25519_public_key() {
let public_key = PublicKey::from_seed(KeyType::ED25519, "test");
let expected: AccountId =
"bb4dc639b212e075a751685b26bdcea5920a504181ff2910e8549742127092a0".parse().unwrap();
let account_id = derive_near_implicit_account_id(public_key.unwrap_as_ed25519());
assert_eq!(account_id, expected);
}

#[test]
fn test_num_chunk_producers() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use near_chain_configs::Genesis;
use near_chunks::metrics::PARTIAL_ENCODED_CHUNK_FORWARD_CACHED_WITHOUT_HEADER;
use near_client::test_utils::{create_chunk_with_transactions, TestEnv};
use near_client::ProcessTxResponse;
use near_crypto::{InMemorySigner, KeyType, Signer};
use near_crypto::{InMemorySigner, KeyType, SecretKey, Signer};
use near_network::shards_manager::ShardsManagerRequestFromNetwork;
use near_network::types::{NetworkRequests, PeerManagerMessageRequest};
use near_o11y::testonly::init_test_logger;
Expand All @@ -18,6 +18,7 @@ use near_primitives::shard_layout::ShardLayout;
use near_primitives::sharding::ChunkHash;
use near_primitives::transaction::SignedTransaction;
use near_primitives::types::{AccountId, BlockHeight};
use near_primitives::utils::derive_near_implicit_account_id;
use near_primitives::version::{ProtocolFeature, ProtocolVersion};
use near_primitives::views::FinalExecutionStatus;
use nearcore::config::GenesisExt;
Expand Down Expand Up @@ -118,6 +119,7 @@ fn test_transaction_hash_collision() {
/// should fail since the protocol upgrade.
fn get_status_of_tx_hash_collision_for_implicit_account(
protocol_version: ProtocolVersion,
implicit_account_signer: InMemorySigner,
) -> ProcessTxResponse {
let epoch_length = 100;
let mut genesis = Genesis::test(vec!["test0".parse().unwrap(), "test1".parse().unwrap()], 1);
Expand All @@ -128,17 +130,11 @@ fn get_status_of_tx_hash_collision_for_implicit_account(
.nightshade_runtimes(&genesis)
.build();
let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap();

let signer1 = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1");

let public_key = &signer1.public_key;
let raw_public_key = public_key.unwrap_as_ed25519().0.to_vec();
let implicit_account_id = AccountId::try_from(hex::encode(&raw_public_key)).unwrap();
let implicit_account_signer =
InMemorySigner::from_secret_key(implicit_account_id.clone(), signer1.secret_key.clone());
let deposit_for_account_creation = 10u128.pow(23);
let mut height = 1;
let blocks_number = 5;
let signer1 = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1");
let implicit_account_id = implicit_account_signer.account_id.clone();

// Send money to implicit account, invoking its creation.
let send_money_tx = SignedTransaction::send_money(
Expand Down Expand Up @@ -202,23 +198,37 @@ fn get_status_of_tx_hash_collision_for_implicit_account(
response
}

/// Test that duplicate transactions from implicit accounts are properly rejected.
/// Test that duplicate transactions from NEAR-implicit accounts are properly rejected.
#[test]
fn test_transaction_hash_collision_for_implicit_account_fail() {
let protocol_version = ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version();
let secret_key = SecretKey::from_seed(KeyType::ED25519, "test");
let implicit_account_id =
derive_near_implicit_account_id(secret_key.public_key().unwrap_as_ed25519());
let implicit_account_signer = InMemorySigner::from_secret_key(implicit_account_id, secret_key);
assert_matches!(
get_status_of_tx_hash_collision_for_implicit_account(protocol_version),
get_status_of_tx_hash_collision_for_implicit_account(
protocol_version,
implicit_account_signer
),
ProcessTxResponse::InvalidTx(InvalidTxError::InvalidNonce { .. })
);
}

/// Test that duplicate transactions from implicit accounts are not rejected until protocol upgrade.
/// Test that duplicate transactions from NEAR-implicit accounts are not rejected until protocol upgrade.
#[test]
fn test_transaction_hash_collision_for_implicit_account_ok() {
let protocol_version =
ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version() - 1;
let secret_key = SecretKey::from_seed(KeyType::ED25519, "test");
let implicit_account_id =
derive_near_implicit_account_id(secret_key.public_key().unwrap_as_ed25519());
let implicit_account_signer = InMemorySigner::from_secret_key(implicit_account_id, secret_key);
assert_matches!(
get_status_of_tx_hash_collision_for_implicit_account(protocol_version),
get_status_of_tx_hash_collision_for_implicit_account(
protocol_version,
implicit_account_signer
),
ProcessTxResponse::ValidTx
);
}
Expand Down
Loading

0 comments on commit 00ca2f3

Please sign in to comment.