Skip to content

Commit

Permalink
fix(mirror): map receipts correctly in the genesis state (#10929)
Browse files Browse the repository at this point in the history
Before this change, we are only changing the `predecessor_id` and
`receiver_id` of delayed and postponed receipts in the genesis state.
But there are many more references to keys and account IDs in receipts,
which also need to be changed. Fix it by adding a `map_receipt()`
function that goes through the receipt to map any such references, and
make sure to use it in the `fork-network` command as well.
  • Loading branch information
marcelo-gonzalez committed May 14, 2024
1 parent 69c4bb1 commit 4396b6a
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 66 deletions.
54 changes: 14 additions & 40 deletions tools/fork-network/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use near_primitives::account::id::AccountType;
use near_primitives::account::{AccessKey, AccessKeyPermission, Account};
use near_primitives::borsh;
use near_primitives::hash::CryptoHash;
use near_primitives::receipt::Receipt;
use near_primitives::serialize::dec_format;
use near_primitives::shard_layout::ShardUId;
use near_primitives::state::FlatStateValue;
Expand Down Expand Up @@ -483,6 +482,8 @@ impl ForkNetworkCommand {
tracing::info!(?shard_uid);
let mut storage_mutator: SingleShardStorageMutator = make_storage_mutator(prev_state_root)?;

// TODO: allow mutating the state with a secret, so this can be used to prepare a public test network
let default_key = near_mirror::key_mapping::default_extra_key(None).public_key();
// Keeps track of accounts that have a full access key.
let mut has_full_key = HashSet::new();
// Lets us lookup large values in the `State` columns.
Expand All @@ -498,7 +499,6 @@ impl ForkNetworkCommand {
let mut contract_data_updated = 0;
let mut contract_code_updated = 0;
let mut postponed_receipts_updated = 0;
let mut delayed_receipts_updated = 0;
let mut received_data_updated = 0;
let mut fake_block_height = block_height + 1;
for item in store_helper::iter_flat_state_entries(shard_uid, &store, None, None) {
Expand Down Expand Up @@ -557,23 +557,11 @@ impl ForkNetworkCommand {
contract_code_updated += 1;
}
}
StateRecord::PostponedReceipt(receipt) => {
// TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported.
if receipt.predecessor_id.get_account_type()
== AccountType::NearImplicitAccount
|| receipt.receiver_id.get_account_type()
== AccountType::NearImplicitAccount
{
let new_receipt = Receipt {
predecessor_id: map_account(&receipt.predecessor_id, None),
receiver_id: map_account(&receipt.receiver_id, None),
receipt_id: receipt.receipt_id,
receipt: receipt.receipt.clone(),
};
storage_mutator.delete_postponed_receipt(receipt)?;
storage_mutator.set_postponed_receipt(&new_receipt)?;
postponed_receipts_updated += 1;
}
StateRecord::PostponedReceipt(mut receipt) => {
storage_mutator.delete_postponed_receipt(&receipt)?;
near_mirror::genesis::map_receipt(&mut receipt, None, &default_key);
storage_mutator.set_postponed_receipt(&receipt)?;
postponed_receipts_updated += 1;
}
StateRecord::ReceivedData { account_id, data_id, data } => {
// TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported.
Expand All @@ -584,24 +572,10 @@ impl ForkNetworkCommand {
received_data_updated += 1;
}
}
StateRecord::DelayedReceipt(receipt) => {
// TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported.
if receipt.predecessor_id.get_account_type()
== AccountType::NearImplicitAccount
|| receipt.receiver_id.get_account_type()
== AccountType::NearImplicitAccount
{
let new_receipt = Receipt {
predecessor_id: map_account(&receipt.predecessor_id, None),
receiver_id: map_account(&receipt.receiver_id, None),
receipt_id: receipt.receipt_id,
receipt: receipt.receipt,
};
storage_mutator.delete_delayed_receipt(index_delayed_receipt)?;
storage_mutator
.set_delayed_receipt(index_delayed_receipt, &new_receipt)?;
delayed_receipts_updated += 1;
}
StateRecord::DelayedReceipt(mut receipt) => {
storage_mutator.delete_delayed_receipt(index_delayed_receipt)?;
near_mirror::genesis::map_receipt(&mut receipt, None, &default_key);
storage_mutator.set_delayed_receipt(index_delayed_receipt, &receipt)?;
index_delayed_receipt += 1;
}
}
Expand All @@ -619,7 +593,7 @@ impl ForkNetworkCommand {
+ contract_data_updated
+ contract_code_updated
+ postponed_receipts_updated
+ delayed_receipts_updated
+ index_delayed_receipt
+ received_data_updated,
);
let state_root = storage_mutator.commit(&shard_uid, fake_block_height)?;
Expand All @@ -638,7 +612,7 @@ impl ForkNetworkCommand {
contract_code_updated,
contract_data_updated,
postponed_receipts_updated,
delayed_receipts_updated,
delayed_receipts_updated = index_delayed_receipt,
received_data_updated,
num_has_full_key = has_full_key.len(),
"Pass 1 done"
Expand Down Expand Up @@ -669,7 +643,7 @@ impl ForkNetworkCommand {
}
storage_mutator.set_access_key(
account_id,
near_mirror::key_mapping::default_extra_key(None).public_key(),
default_key.clone(),
AccessKey::full_access(),
)?;
num_added += 1;
Expand Down
4 changes: 2 additions & 2 deletions tools/fork-network/src/single_shard_storage_mutator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ impl SingleShardStorageMutator {
)
}

pub(crate) fn delete_postponed_receipt(&mut self, receipt: Box<Receipt>) -> anyhow::Result<()> {
pub(crate) fn delete_postponed_receipt(&mut self, receipt: &Receipt) -> anyhow::Result<()> {
self.remove(TrieKey::PostponedReceipt {
receiver_id: receipt.receiver_id,
receiver_id: receipt.receiver_id.clone(),
receipt_id: receipt.receipt_id,
})
}
Expand Down
105 changes: 84 additions & 21 deletions tools/mirror/src/genesis.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use near_crypto::PublicKey;
use near_primitives::receipt::{ActionReceipt, Receipt, ReceiptEnum};
use near_primitives::state_record::StateRecord;
use near_primitives::transaction::{Action, AddKeyAction, DeleteAccountAction, DeleteKeyAction};
use near_primitives_core::account::id::AccountType;
use near_primitives_core::account::{AccessKey, AccessKeyPermission};
use serde::ser::{SerializeSeq, Serializer};
Expand All @@ -7,10 +10,85 @@ use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::path::Path;

// map all the account IDs and keys in this receipt and its actions, and skip any stake actions
fn map_action_receipt(
receipt: &mut ActionReceipt,
secret: Option<&[u8; crate::secret::SECRET_LEN]>,
default_key: &PublicKey,
) {
receipt.signer_id = crate::key_mapping::map_account(&receipt.signer_id, secret);
receipt.signer_public_key =
crate::key_mapping::map_key(&receipt.signer_public_key, secret).public_key();
for receiver in receipt.output_data_receivers.iter_mut() {
receiver.receiver_id = crate::key_mapping::map_account(&receiver.receiver_id, secret);
}

let mut actions = Vec::with_capacity(receipt.actions.len());
let mut account_created = false;
let mut full_key_added = false;
for action in receipt.actions.iter() {
match action {
Action::AddKey(add_key) => {
if add_key.access_key.permission == AccessKeyPermission::FullAccess {
full_key_added = true;
}
let public_key =
crate::key_mapping::map_key(&add_key.public_key, secret).public_key();

actions.push(Action::AddKey(Box::new(AddKeyAction {
public_key,
access_key: add_key.access_key.clone(),
})));
}
Action::DeleteKey(delete_key) => {
let public_key =
crate::key_mapping::map_key(&delete_key.public_key, secret).public_key();

actions.push(Action::DeleteKey(Box::new(DeleteKeyAction { public_key })));
}
Action::DeleteAccount(delete_account) => {
let beneficiary_id =
crate::key_mapping::map_account(&delete_account.beneficiary_id, secret);
actions.push(Action::DeleteAccount(DeleteAccountAction { beneficiary_id }));
}
// We don't want to mess with the set of validators in the target chain
Action::Stake(_) => {}
Action::CreateAccount(_) => {
account_created = true;
actions.push(action.clone());
}
_ => actions.push(action.clone()),
};
}
if account_created && !full_key_added {
actions.push(Action::AddKey(Box::new(AddKeyAction {
public_key: default_key.clone(),
access_key: AccessKey::full_access(),
})));
}
receipt.actions = actions;
}

// map any account IDs or keys referenced in the receipt
pub fn map_receipt(
receipt: &mut Receipt,
secret: Option<&[u8; crate::secret::SECRET_LEN]>,
default_key: &PublicKey,
) {
receipt.predecessor_id = crate::key_mapping::map_account(&receipt.predecessor_id, secret);
receipt.receiver_id = crate::key_mapping::map_account(&receipt.receiver_id, secret);
match &mut receipt.receipt {
ReceiptEnum::Action(r) => {
map_action_receipt(r, secret, default_key);
}
_ => {}
}
}

/// Reads records, makes changes to them and writes them to a new file.
/// `records_file_in` must be different from `records_file_out`.
/// Writes a secret to `secret_file_out`.
pub fn map_records<P: AsRef<Path>>(
pub(crate) fn map_records<P: AsRef<Path>>(
records_file_in: P,
records_file_out: P,
no_secret: bool,
Expand All @@ -30,6 +108,7 @@ pub fn map_records<P: AsRef<Path>>(
let mut has_full_key = HashSet::new();
let mut accounts = HashSet::new();

let default_key = crate::key_mapping::default_extra_key(secret.as_ref()).public_key();
near_chain_configs::stream_records_from_file(reader, |mut r| {
match &mut r {
StateRecord::AccessKey { account_id, public_key, access_key } => {
Expand Down Expand Up @@ -73,15 +152,7 @@ pub fn map_records<P: AsRef<Path>>(
records_seq.serialize_element(&r).unwrap();
}
StateRecord::PostponedReceipt(receipt) => {
// TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported.
if receipt.predecessor_id.get_account_type() == AccountType::NearImplicitAccount
|| receipt.receiver_id.get_account_type() == AccountType::NearImplicitAccount
{
receipt.predecessor_id =
crate::key_mapping::map_account(&receipt.predecessor_id, secret.as_ref());
receipt.receiver_id =
crate::key_mapping::map_account(&receipt.receiver_id, secret.as_ref());
}
map_receipt(receipt, secret.as_ref(), &default_key);
records_seq.serialize_element(&r).unwrap();
}
StateRecord::ReceivedData { account_id, .. } => {
Expand All @@ -92,25 +163,17 @@ pub fn map_records<P: AsRef<Path>>(
records_seq.serialize_element(&r).unwrap();
}
StateRecord::DelayedReceipt(receipt) => {
// TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported.
if receipt.predecessor_id.get_account_type() == AccountType::NearImplicitAccount
|| receipt.receiver_id.get_account_type() == AccountType::NearImplicitAccount
{
receipt.predecessor_id =
crate::key_mapping::map_account(&receipt.predecessor_id, secret.as_ref());
receipt.receiver_id =
crate::key_mapping::map_account(&receipt.receiver_id, secret.as_ref());
}
map_receipt(receipt, secret.as_ref(), &default_key);
records_seq.serialize_element(&r).unwrap();
}
};
})?;
let default_key = crate::key_mapping::default_extra_key(secret.as_ref());

for account_id in accounts {
if !has_full_key.contains(&account_id) {
records_seq.serialize_element(&StateRecord::AccessKey {
account_id,
public_key: default_key.public_key(),
public_key: default_key.clone(),
access_key: AccessKey::full_access(),
})?;
}
Expand Down
15 changes: 12 additions & 3 deletions tools/mirror/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use near_o11y::WithSpanContextExt;
use near_primitives::hash::CryptoHash;
use near_primitives::receipt::{Receipt, ReceiptEnum};
use near_primitives::transaction::{
Action, AddKeyAction, CreateAccountAction, DeleteKeyAction, SignedTransaction, StakeAction,
Transaction,
Action, AddKeyAction, CreateAccountAction, DeleteAccountAction, DeleteKeyAction,
SignedTransaction, StakeAction, Transaction,
};
use near_primitives::types::{
AccountId, BlockHeight, BlockReference, Finality, TransactionOrReceiptId,
Expand All @@ -41,7 +41,7 @@ use tokio::sync::mpsc;

mod chain_tracker;
pub mod cli;
mod genesis;
pub mod genesis;
pub mod key_mapping;
mod metrics;
mod offline;
Expand Down Expand Up @@ -1032,6 +1032,15 @@ impl<T: ChainAccess> TxMirror<T> {
account_created = true;
actions.push(action.clone());
}
Action::DeleteAccount(d) => {
actions.push(Action::DeleteAccount(DeleteAccountAction {
beneficiary_id: crate::key_mapping::map_account(
&d.beneficiary_id,
self.secret.as_ref(),
),
}));
}
// TODO: handle delegate actions
_ => actions.push(action.clone()),
};
}
Expand Down

0 comments on commit 4396b6a

Please sign in to comment.