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

feat: Implicit account creation #3251

Merged
merged 14 commits into from
Sep 2, 2020
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions chain/chain/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ impl RuntimeAdapter for KeyValueRuntime {
_state_update: Option<StateRoot>,
_transaction: &SignedTransaction,
_verify_signature: bool,
_current_protocol_version: ProtocolVersion,
) -> Result<Option<InvalidTxError>, Error> {
Ok(None)
}
Expand All @@ -474,6 +475,7 @@ impl RuntimeAdapter for KeyValueRuntime {
_state_root: StateRoot,
transactions: &mut dyn PoolIterator,
_chain_validate: &mut dyn FnMut(&SignedTransaction) -> bool,
_current_protocol_version: ProtocolVersion,
) -> Result<Vec<SignedTransaction>, Error> {
let mut res = vec![];
while let Some(iter) = transactions.next() {
Expand Down
2 changes: 2 additions & 0 deletions chain/chain/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ pub trait RuntimeAdapter: Send + Sync {
state_root: Option<StateRoot>,
transaction: &SignedTransaction,
verify_signature: bool,
current_protocol_version: ProtocolVersion,
) -> Result<Option<InvalidTxError>, Error>;

/// Returns an ordered list of valid transactions from the pool up the given limits.
Expand All @@ -272,6 +273,7 @@ pub trait RuntimeAdapter: Send + Sync {
state_root: StateRoot,
pool_iterator: &mut dyn PoolIterator,
chain_validate: &mut dyn FnMut(&SignedTransaction) -> bool,
current_protocol_version: ProtocolVersion,
) -> Result<Vec<SignedTransaction>, Error>;

/// Verify validator signature for the given epoch.
Expand Down
58 changes: 33 additions & 25 deletions chain/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ impl Client {
.clone();

let prev_block_header = self.chain.get_block_header(&prev_block_hash)?.clone();
let transactions = self.prepare_transactions(shard_id, &chunk_extra, &prev_block_header);
let transactions = self.prepare_transactions(shard_id, &chunk_extra, &prev_block_header)?;
let num_filtered_transactions = transactions.len();
let (tx_root, _) = merklize(&transactions);
let ReceiptResponse(_, outgoing_receipts) = self.chain.get_outgoing_receipts_for_shard(
Expand Down Expand Up @@ -556,36 +556,40 @@ impl Client {
shard_id: ShardId,
chunk_extra: &ChunkExtra,
prev_block_header: &BlockHeader,
) -> Vec<SignedTransaction> {
) -> Result<Vec<SignedTransaction>, Error> {
let Self { chain, shards_mgr, runtime_adapter, .. } = self;

let next_epoch_id =
runtime_adapter.get_epoch_id_from_prev_block(&prev_block_header.hash())?;
let protocol_version = runtime_adapter.get_epoch_protocol_version(&next_epoch_id)?;

let transactions = if let Some(mut iter) = shards_mgr.get_pool_iterator(shard_id) {
let transaction_validity_period = chain.transaction_validity_period;
runtime_adapter
.prepare_transactions(
prev_block_header.gas_price(),
chunk_extra.gas_limit,
shard_id,
chunk_extra.state_root.clone(),
&mut iter,
&mut |tx: &SignedTransaction| -> bool {
chain
.mut_store()
.check_transaction_validity_period(
&prev_block_header,
&tx.transaction.block_hash,
transaction_validity_period,
)
.is_ok()
},
)
.expect("no StorageError please")
runtime_adapter.prepare_transactions(
prev_block_header.gas_price(),
chunk_extra.gas_limit,
shard_id,
chunk_extra.state_root.clone(),
&mut iter,
&mut |tx: &SignedTransaction| -> bool {
chain
.mut_store()
.check_transaction_validity_period(
&prev_block_header,
&tx.transaction.block_hash,
transaction_validity_period,
)
.is_ok()
},
protocol_version,
)?
} else {
vec![]
};
// Reintroduce valid transactions back to the pool. They will be removed when the chunk is
// included into the block.
shards_mgr.reintroduce_transactions(shard_id, &transactions);
transactions
Ok(transactions)
}

pub fn send_challenges(&mut self, challenges: Arc<RwLock<Vec<ChallengeBody>>>) {
Expand Down Expand Up @@ -1225,8 +1229,12 @@ impl Client {
let gas_price = cur_block_header.gas_price();
let epoch_id = self.runtime_adapter.get_epoch_id_from_prev_block(&head.last_block_hash)?;

if let Some(err) =
self.runtime_adapter.validate_tx(gas_price, None, &tx, true).expect("no storage errors")
let protocol_version = self.runtime_adapter.get_epoch_protocol_version(&epoch_id)?;

if let Some(err) = self
.runtime_adapter
.validate_tx(gas_price, None, &tx, true, protocol_version)
.expect("no storage errors")
{
debug!(target: "client", "Invalid tx during basic validation: {:?}", err);
return Ok(NetworkClientResponses::InvalidTx(err));
Expand All @@ -1252,7 +1260,7 @@ impl Client {
};
if let Some(err) = self
.runtime_adapter
.validate_tx(gas_price, Some(state_root), &tx, false)
.validate_tx(gas_price, Some(state_root), &tx, false, protocol_version)
.expect("no storage errors")
{
debug!(target: "client", "Invalid tx: {:?}", err);
Expand Down
6 changes: 5 additions & 1 deletion core/primitives/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@ pub enum ActionErrorKind {
/// Error occurs when a new `ActionReceipt` created by the `FunctionCall` action fails
/// receipt validation.
NewReceiptValidationError(ReceiptValidationError),
/// Error occurs when a `CreateAccount` action is called on hex-characters account of length 64.
/// See implicit account creation NEP: https://github.com/nearprotocol/NEPs/pull/71
OnlyImplicitAccountCreationAllowed { account_id: AccountId },
}

impl From<ActionErrorKind> for ActionError {
Expand Down Expand Up @@ -670,7 +673,8 @@ impl Display for ActionErrorKind {
ActionErrorKind::NewReceiptValidationError(e) => {
write!(f, "An new action receipt created during a FunctionCall is not valid: {}", e)
}
ActionErrorKind::InsufficientStake { account_id, stake, minimum_stake } => write!(f, "Account {} tries to stake {} but minimum required stake is {}", account_id, stake, minimum_stake)
ActionErrorKind::InsufficientStake { account_id, stake, minimum_stake } => write!(f, "Account {} tries to stake {} but minimum required stake is {}", account_id, stake, minimum_stake),
ActionErrorKind::OnlyImplicitAccountCreationAllowed { account_id } => write!(f, "CreateAccount action is called on hex-characters account of length 64 {}", account_id)
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions core/primitives/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ pub fn is_valid_top_level_account_id(account_id: &AccountId) -> bool {
&& VALID_TOP_LEVEL_ACCOUNT_ID.is_match(account_id)
}

/// Returns true if the account ID length is 64 characters and it's a hex representation.
pub fn is_account_id_64_len_hex(account_id: &AccountId) -> bool {
account_id.len() == 64
&& account_id.as_bytes().iter().all(|&b| match b {
evgenykuzyakov marked this conversation as resolved.
Show resolved Hide resolved
b'a'..=b'f' | b'0'..=b'9' => true,
_ => false,
})
}

/// 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 {
Expand Down
5 changes: 4 additions & 1 deletion core/primitives/src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub const DB_VERSION: DbVersion = 7;
pub type ProtocolVersion = u32;

/// Current latest version of the protocol.
pub const PROTOCOL_VERSION: ProtocolVersion = 34;
pub const PROTOCOL_VERSION: ProtocolVersion = 35;
/// Oldest supported version by this client.
pub const OLDEST_BACKWARD_COMPATIBLE_PROTOCOL_VERSION: ProtocolVersion = 29;

Expand All @@ -31,3 +31,6 @@ pub const MIN_GAS_PRICE_NEP_92_FIX: Balance = 100_000_000;
pub const MIN_PROTOCOL_VERSION_NEP_92_FIX: ProtocolVersion = 32;

pub const CORRECT_RANDOM_VALUE_PROTOCOL_VERSION: ProtocolVersion = 33;

/// See [NEP 71](https://github.com/nearprotocol/NEPs/pull/71)
pub const IMPLICIT_ACCOUNT_CREATION_PROTOCOL_VERSION: ProtocolVersion = 35;
4 changes: 2 additions & 2 deletions neard/res/genesis_config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"protocol_version": 34,
"protocol_version": 35,
"genesis_time": "1970-01-01T00:00:00.000000000Z",
"chain_id": "sample",
"genesis_height": 0,
Expand Down Expand Up @@ -236,4 +236,4 @@
"fishermen_threshold": "10000000000000000000000000",
"minimum_stake_divisor": 10,
"records": []
}
}
14 changes: 12 additions & 2 deletions neard/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ impl RuntimeAdapter for NightshadeRuntime {
state_root: Option<StateRoot>,
transaction: &SignedTransaction,
verify_signature: bool,
current_protocol_version: ProtocolVersion,
) -> Result<Option<InvalidTxError>, Error> {
if let Some(state_root) = state_root {
let shard_id = self.account_id_to_shard_id(&transaction.transaction.signer_id);
Expand All @@ -539,6 +540,7 @@ impl RuntimeAdapter for NightshadeRuntime {
gas_price,
&transaction,
verify_signature,
current_protocol_version,
) {
Ok(_) => Ok(None),
Err(RuntimeError::InvalidTxError(err)) => {
Expand All @@ -557,6 +559,7 @@ impl RuntimeAdapter for NightshadeRuntime {
gas_price,
&transaction,
verify_signature,
current_protocol_version,
) {
Ok(_) => Ok(None),
Err(RuntimeError::InvalidTxError(err)) => {
Expand All @@ -579,6 +582,7 @@ impl RuntimeAdapter for NightshadeRuntime {
state_root: StateRoot,
pool_iterator: &mut dyn PoolIterator,
chain_validate: &mut dyn FnMut(&SignedTransaction) -> bool,
current_protocol_version: ProtocolVersion,
) -> Result<Vec<SignedTransaction>, Error> {
let mut state_update = self.get_tries().new_trie_update(shard_id, state_root);

Expand All @@ -602,6 +606,7 @@ impl RuntimeAdapter for NightshadeRuntime {
gas_price,
&tx,
false,
current_protocol_version,
) {
Ok(verification_result) => {
state_update.commit(StateChangeCause::NotWritableToDisk);
Expand Down Expand Up @@ -1065,11 +1070,13 @@ impl RuntimeAdapter for NightshadeRuntime {
}
QueryRequest::CallFunction { account_id, method_name, args } => {
let mut logs = vec![];
let epoch_height = {
let (epoch_height, current_protocol_version) = {
let mut epoch_manager =
self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR);
epoch_manager.get_epoch_info(&epoch_id)?.epoch_height
let epoch_info = epoch_manager.get_epoch_info(&epoch_id)?;
(epoch_info.epoch_height, epoch_info.protocol_version)
};

match self.call_function(
shard_id,
*state_root,
Expand All @@ -1083,6 +1090,7 @@ impl RuntimeAdapter for NightshadeRuntime {
args.as_ref(),
&mut logs,
&self.epoch_manager,
current_protocol_version,
) {
Ok(result) => Ok(QueryResponse {
kind: QueryResponseKind::CallResult(CallResult { result, logs }),
Expand Down Expand Up @@ -1304,6 +1312,7 @@ impl node_runtime::adapter::ViewRuntimeAdapter for NightshadeRuntime {
args: &[u8],
logs: &mut Vec<String>,
epoch_info_provider: &dyn EpochInfoProvider,
current_protocol_version: ProtocolVersion,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let state_update = self.get_tries().new_trie_update(shard_id, state_root);
self.trie_viewer.call_function(
Expand All @@ -1318,6 +1327,7 @@ impl node_runtime::adapter::ViewRuntimeAdapter for NightshadeRuntime {
args,
logs,
epoch_info_provider,
current_protocol_version,
)
}

Expand Down
58 changes: 54 additions & 4 deletions runtime/near-vm-logic/src/logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use crate::context::VMContext;
use crate::dependencies::{External, MemoryLike};
use crate::gas_counter::GasCounter;
use crate::types::{
AccountId, Balance, EpochHeight, Gas, ProfileData, PromiseIndex, PromiseResult, ReceiptIndex,
ReturnData, StorageUsage,
AccountId, Balance, EpochHeight, Gas, ProfileData, PromiseIndex, PromiseResult,
ProtocolVersion, ReceiptIndex, ReturnData, StorageUsage,
};
use crate::utils::split_method_names;
use crate::utils::{is_account_id_64_len_hex, split_method_names};
use crate::{ExtCosts, HostError, VMLogicError, ValuePtr};
use byteorder::ByteOrder;
use near_runtime_fees::RuntimeFeesConfig;
Expand All @@ -19,6 +19,9 @@ use std::mem::size_of;

type Result<T> = ::std::result::Result<T, VMLogicError>;

// const LAST_DEFAULT_PROTOCOL_VERSION: ProtocolVersion = 34;
const IMPLICIT_ACCOUNT_CREATION_PROTOCOL_VERSION: ProtocolVersion = 35;

pub struct VMLogic<'a> {
/// Provides access to the components outside the Wasm runtime for operations on the trie and
/// receipts creation.
Expand Down Expand Up @@ -59,6 +62,9 @@ pub struct VMLogic<'a> {

/// Tracks the total log length. The sum of length of all logs.
total_log_length: u64,

/// Current protocol version that is used for the function call.
current_protocol_version: ProtocolVersion,
}

/// Promises API allows to create a DAG-structure that defines dependencies between smart contract
Expand Down Expand Up @@ -92,14 +98,15 @@ macro_rules! memory_set {
}

impl<'a> VMLogic<'a> {
pub fn new(
pub fn new_with_protocol_version(
ext: &'a mut dyn External,
context: VMContext,
config: &'a VMConfig,
fees_config: &'a RuntimeFeesConfig,
promise_results: &'a [PromiseResult],
memory: &'a mut dyn MemoryLike,
profile: Option<ProfileData>,
current_protocol_version: ProtocolVersion,
) -> Self {
ext.reset_touched_nodes_counter();
// Overflow should be checked before calling VMLogic.
Expand Down Expand Up @@ -135,9 +142,33 @@ impl<'a> VMLogic<'a> {
promises: vec![],
receipt_to_account: HashMap::new(),
total_log_length: 0,
current_protocol_version,
}
}

// /// Legacy initialization method that doesn't pass the protocol version and uses the last
// /// protocol version before the change was introduced.
// pub fn new(
// ext: &'a mut dyn External,
// context: VMContext,
// config: &'a VMConfig,
// fees_config: &'a RuntimeFeesConfig,
// promise_results: &'a [PromiseResult],
// memory: &'a mut dyn MemoryLike,
// profile: Option<ProfileData>,
// ) -> Self {
// Self::new_with_protocol_version(
// ext,
// context,
// config,
// fees_config,
// promise_results,
// memory,
// profile,
// LAST_DEFAULT_PROTOCOL_VERSION,
// )
// }

// ###########################
// # Memory helper functions #
// ###########################
Expand Down Expand Up @@ -1358,6 +1389,25 @@ impl<'a> VMLogic<'a> {

let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?;

if self.current_protocol_version >= IMPLICIT_ACCOUNT_CREATION_PROTOCOL_VERSION {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We charge more base fees for transfer on 64len hex account under new protocol

// Need to check if the receiver_id can be an implicit account.
let account_id = self
.receipt_to_account
.get(&receipt_idx)
.expect("promises and receipt_to_account should be consistent.");
if is_account_id_64_len_hex(&account_id) {
self.gas_counter.pay_action_base(
&self.fees_config.action_creation_config.create_account_cost,
sir,
ActionCosts::transfer,
)?;
self.gas_counter.pay_action_base(
&self.fees_config.action_creation_config.add_key_cost.full_access_cost,
sir,
ActionCosts::transfer,
)?;
}
}
self.gas_counter.pay_action_base(
&self.fees_config.action_creation_config.transfer_cost,
sir,
Expand Down
Loading