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

Setup for ETH-implicit accounts #10020

Merged
merged 10 commits into from
Nov 6, 2023
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);
staffik marked this conversation as resolved.
Show resolved Hide resolved
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)
}
}
}
26 changes: 19 additions & 7 deletions core/primitives/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,24 +555,36 @@ 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 if account_id == eth_implicit_test_account() {
InMemorySigner::from_secret_key(account_id, eth_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()
}

/// A fixed ETH-implicit account for which tests can know the private key.
pub fn eth_implicit_test_account() -> AccountId {
"0x96791e923f8cf697ad9c3290f2c9059f0231b24c".parse().unwrap()
}

/// Private key for the fixed ETH-implicit test account.
pub fn eth_implicit_test_account_secret() -> SecretKey {
"secp256k1:X4ETFKtQkSGVoZEnkn7bZ3LyajJaK2b3eweXaKmynGx".parse().unwrap()
}

impl FinalExecutionOutcomeView {
#[track_caller]
/// Check transaction and all transitive receipts for success status.
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
Loading