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(evm-block-recovery): Add account-ledger routine #471

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions evm-utils/evm-block-recovery/Cargo.toml
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
solana-client = { path = "../../client" }
solana-evm-loader-program = { path = "../programs/evm_loader" }
solana-program = { path = "../../sdk/program" }
solana-sdk = { path = "../../sdk", version = "=1.10.41" }
solana-storage-bigtable = { path = "../../storage-bigtable", version = "=1.10.41" }
solana-transaction-status = { path = "../../transaction-status", version = "=1.10.41" }
55 changes: 51 additions & 4 deletions evm-utils/evm-block-recovery/src/cli.rs
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ pub enum Command {
/// Checks content of Native Block
CheckNative(CheckNativeArgs),

/// Checks content of Evm Block
/// Checks content of EVM Block
CheckEvm(CheckEvmArgs),

/// Compares difference of Native Block sets
@@ -51,7 +51,11 @@ pub enum Command {

ScanEvmStateRoots(ScanEvmStateRootsArgs),

ScratchPad,
/// Gets Ledger for the provided Native Address
AccountLedger(AccountLedgerArgs),

/// Gets underlying Native Transaction for provided EVM transaction
NativeByEvm(NativeByEvmArgs),

/// Generetes Shell Completions for this Utility
Completion(CompletionArgs),
@@ -257,10 +261,53 @@ pub struct ScanEvmStateRootsArgs {
pub rangemap_json: PathBuf,
}

#[derive(clap::Args)]
pub struct AccountLedgerArgs {
/// Address to search for
#[clap(short, long, value_name = "PUBKEY")]
pub address: String,

/// Start with the first signature older than this one
#[clap(short, long, value_name = "SIGNATURE")]
pub before_signature: Option<String>,

/// End with the last signature more recent than this one
#[clap(short, long, value_name = "SIGNATURE")]
pub until_signature: Option<String>,

/// Stop after this many signatures
#[clap(short, long, value_name = "NUM")]
pub limit: usize,

/// Export CSV results to file
#[clap(short, long, value_hint = clap::ValueHint::FilePath, value_name = "FILEPATH")]
pub output: PathBuf,

/// CSV column separator
#[clap(short, long, value_name = "STRING", value_enum, default_value_t = CsvSeparator::Semicolon)]
pub separator: CsvSeparator,
}

#[derive(Clone, clap::ValueEnum)]
pub enum CsvSeparator {
/// Use Comma as separator in CSV output
Comma,
/// Use Semicolon as separator in CSV output
Semicolon,
/// Use Tabulation as separator in CSV output
Tab,
}

#[derive(clap::Args)]
pub struct NativeByEvmArgs {
/// EVM Transaction Hash
#[clap(long, value_enum, value_name = "H256")]
pub hash: String,
}

#[derive(clap::Args)]
pub struct CompletionArgs {
/// Which shell completions to generate
#[arg(value_enum)]
#[clap(long, value_name = "STRING")]
#[clap(long, value_enum, value_name = "STRING")]
pub shell: clap_complete::Shell,
}
7 changes: 7 additions & 0 deletions evm-utils/evm-block-recovery/src/error.rs
Original file line number Diff line number Diff line change
@@ -85,6 +85,12 @@ pub enum AppError {
limit: usize,
},

#[error("Unable to fetch Signatures")]
GetSignatures {
#[source]
source: solana_storage_bigtable::Error,
},

#[error("Unable to write block to bigtable")]
UploadEvmBlock(#[source] solana_storage_bigtable::Error),

@@ -144,6 +150,7 @@ impl AppError {
AppError::TokioTaskJoin(_) => 1021,
AppError::IO(_) => 1022,
AppError::Storage(_) => 1023,
AppError::GetSignatures { .. } => 1024,
}
}
}
12 changes: 4 additions & 8 deletions evm-utils/evm-block-recovery/src/main.rs
Original file line number Diff line number Diff line change
@@ -34,13 +34,8 @@ async fn main() {
builder.target(Target::Stderr);
builder.init();

match dotenv {
Ok(path) => {
log::info!(r#""{}" successfully loaded"#, path.display())
}
Err(e) => {
log::warn!(r#"".env" file not found: {e:?}""#)
}
if let Ok(path) = dotenv {
log::info!(r#""{}" successfully loaded"#, path.display())
}

let execution_result = match cli.subcommand {
@@ -54,7 +49,8 @@ async fn main() {
RepeatEvm(args) => repeat_evm(args).await,
RepeatNative(args) => repeat_native(args).await,
ScanEvmStateRoots(ref args) => scan_evm_state_roots::command(args).await,
ScratchPad => scratchpad::command().await,
AccountLedger(args) => account_ledger(cli.creds, cli.instance, args).await,
NativeByEvm(args) => native_by_evm(cli.creds, cli.instance, args).await,
Completion(args) => completion(args),
};

8 changes: 5 additions & 3 deletions evm-utils/evm-block-recovery/src/routines.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
pub(crate) mod account_ledger;
pub(crate) mod check_evm;
pub(crate) mod check_native;
pub(crate) mod compare;
pub(crate) mod completion;
pub(crate) mod find;
pub(crate) mod native_by_evm;
pub(crate) mod repeat;
pub(crate) mod restore_chain;
pub(crate) mod scan_evm_state_roots;
pub(crate) mod upload;
// a tmp placeholder to test out various new commands or approaches
// it's convenient, as all potentially required dependencies have been imported
pub(crate) mod scratchpad;

use crate::error::AppError;

pub use {
account_ledger::account_ledger,
check_evm::check_evm,
check_native::check_native,
compare::compare_native,
completion::completion,
find::{find_evm, find_native},
native_by_evm::native_by_evm,
repeat::{repeat_evm, repeat_native},
restore_chain::restore_chain,
upload::upload,
151 changes: 151 additions & 0 deletions evm-utils/evm-block-recovery/src/routines/account_ledger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
use std::str::FromStr;

use solana_sdk::{message::VersionedMessage, pubkey::Pubkey, signature::Signature, system_program};
use solana_transaction_status::TransactionWithStatusMeta;

use crate::{
cli::{AccountLedgerArgs, CsvSeparator},
error::{AppError, RoutineResult},
ledger,
};

// NOTE: remove panics if routine as service embedding is required

pub async fn account_ledger(
creds: Option<String>,
instance: String,
args: AccountLedgerArgs,
) -> RoutineResult {
let AccountLedgerArgs {
address,
before_signature,
until_signature,
limit,
output,
separator,
} = args;

let before_signature = before_signature.map(|x| {
solana_sdk::signature::Signature::from_str(&x).expect(&format!("Invalid signature: {}", x))
});

let until_signature = until_signature.map(|x| {
solana_sdk::signature::Signature::from_str(&x).expect(&format!("Invalid signature: {}", x))
});

let ledger = ledger::with_params(creds, instance).await?;

let address = Pubkey::from_str(&address).unwrap();

let signatures = ledger
.get_confirmed_signatures_for_address(
&address,
before_signature.as_ref(),
until_signature.as_ref(),
limit,
)
.await
.map_err(|source| AppError::GetSignatures { source })?;

log::info!("{} signatures discovered", signatures.len());

struct LedgerEntry {
signature: Signature,
is_transfer: bool,
counterparty: Pubkey,
block_time: i64,
pre_balance: u64,
post_balance: u64,
}

let mut result_ledger: Vec<LedgerEntry> = Vec::with_capacity(signatures.len());
let mut txs_count = 0;

for (status, _tx_slot_pos) in signatures {
let signature = status.signature;
let block_time = status.block_time.expect("Block has no timestamp");

if txs_count % 100 == 0 {
log::info!("{txs_count} transactions processed...");
}

let transaction = ledger
.get_confirmed_transaction(&signature)
.await
.unwrap()
.unwrap();

txs_count += 1;

let tx_with_meta = match &transaction.tx_with_meta {
TransactionWithStatusMeta::Complete(complete) => complete.clone(),
TransactionWithStatusMeta::MissingMetadata(_) => {
panic!("Transaction {} has no metadata!", &signature)
}
};

let message = match transaction.get_transaction().message {
VersionedMessage::Legacy(legacy) => legacy,
VersionedMessage::V0(_v0) => panic!(
"Unsupported `message` version for transaction {}",
&signature
),
};

let mut account_keys = message.account_keys.clone();

let balance_idx = account_keys
.iter()
.position(|a| a == &address)
.expect("Account should be present in account_keys");

account_keys.retain(|e| e != &system_program::ID && e != &address);

let pre_balance = tx_with_meta.meta.pre_balances[balance_idx];
let post_balance = tx_with_meta.meta.post_balances[balance_idx];

let is_transfer = account_keys.len() == 1;
let counterparty = account_keys[0];

result_ledger.push(LedgerEntry {
signature,
is_transfer,
counterparty,
block_time,
pre_balance,
post_balance,
});
}

let separator = match separator {
CsvSeparator::Comma => ',',
CsvSeparator::Semicolon => ';',
CsvSeparator::Tab => '\t',
};

let mut output_buffer = String::new();

output_buffer.push_str(&format!(
"Signature{separator}Is Transfer{separator}Counterparty{separator}Unix Timestamp (s){separator}Change (VLX){separator}Post Balance (VLX)\n"
));

for entry in result_ledger {
let balance_change =
(entry.post_balance as i64 - entry.pre_balance as i64) as f64 / 1_000_000_000.;
let post_balance = entry.post_balance as f64 / 1_000_000_000.;

output_buffer.push_str(&format!(
"{}{separator}{}{separator}{}{separator}{}{separator}{}{separator}{}\n",
entry.signature,
entry.is_transfer,
entry.counterparty,
entry.block_time,
balance_change,
post_balance
));
}

std::fs::write(output, output_buffer).expect("Write output ledger file failed");

Ok(())
}
78 changes: 78 additions & 0 deletions evm-utils/evm-block-recovery/src/routines/native_by_evm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use evm_state::H256;
use solana_sdk::{evm_loader::check_id as is_evm_loader, evm_state::check_id as is_evm_state};
use solana_transaction_status::TransactionWithStatusMeta;
use std::str::FromStr;

use crate::{cli::NativeByEvmArgs, error::RoutineResult, ledger};

// NOTE: remove panics if routine as service embedding is required

pub async fn native_by_evm(
creds: Option<String>,
instance: String,
args: NativeByEvmArgs,
) -> RoutineResult {
let NativeByEvmArgs { hash } = args;

let hash = H256::from_str(&hash).unwrap();

let ledger = ledger::with_params(creds, instance).await?;

let receipt = ledger.get_evm_confirmed_receipt(&hash).await.unwrap();

let (block_number, index) = match receipt {
Some(receipt) => (receipt.block_number, receipt.index),
None => {
panic!("Receipt for transaction {hash} does not exist");
}
};

let evm_header = ledger
.get_evm_confirmed_block_header(block_number)
.await
.unwrap();

let native_block = ledger
.get_confirmed_block(evm_header.native_chain_slot)
.await
.unwrap();

let evm_related_txs = native_block
.transactions
.into_iter()
.filter(is_evm_related_tx)
.collect::<Vec<_>>();

let result_tx = &evm_related_txs[index as usize - 1];

let logs = &result_tx.get_status_meta().unwrap().log_messages.unwrap();

let mut evm_hash_found = false;

for log in logs {
if log.contains(&format!("{hash:?}")) {
evm_hash_found = true;
break;
}
}

let signature = result_tx.transaction_signature();

log::info!("EVM Transaction Hash: {hash:?}");
log::info!("Native Transaction Signature: {signature}");
if !evm_hash_found {
log::warn!("Logs do NOT contain EVM Transaction Hash: {hash:?}");
log::debug!("{logs:?}");
}

Ok(())
}

fn is_evm_related_tx(tx: &TransactionWithStatusMeta) -> bool {
let tx_keys = tx.account_keys();

let contains_evm_state_key = tx_keys.iter().find(|key| is_evm_state(key)).is_some();
let contains_evm_loadr_key = tx_keys.iter().find(|key| is_evm_loader(key)).is_some();

contains_evm_state_key && contains_evm_loadr_key
}
6 changes: 0 additions & 6 deletions evm-utils/evm-block-recovery/src/routines/scratchpad.rs

This file was deleted.


Unchanged files with check annotations Beta

#[cfg(test)]
mod tests {
use solana_sdk::{
instruction::CompiledInstruction,

Check warning on line 1329 in program-runtime/src/invoke_context.rs

GitHub Actions / test

unused imports: `SanitizedMessage`, `instruction::CompiledInstruction`, `v0::Message`
message::{v0::Message, SanitizedMessage},
};
) -> io::Result<()> {
if let Some(block_time) = block_time {
let block_time_output = match timezone {
CliTimezone::Local => format!("{:?}", Local.timestamp(block_time, 0)),

Check warning on line 317 in cli-output/src/display.rs

GitHub Actions / test

use of deprecated associated function `chrono::TimeZone::timestamp`: use `timestamp_opt()` instead
CliTimezone::Utc => format!("{:?}", Utc.timestamp(block_time, 0)),

Check warning on line 318 in cli-output/src/display.rs

GitHub Actions / test

use of deprecated associated function `chrono::TimeZone::timestamp`: use `timestamp_opt()` instead
};
writeln!(w, "{}Block Time: {}", prefix, block_time_output,)?;
}
shrink_paths: Option<Vec<PathBuf>>,
snapshot_config: &SnapshotConfig,
process_options: &ProcessOptions,
evm_block_recorder_sender: Option<&EvmRecorderSender>,

Check warning on line 244 in ledger/src/bank_forks_utils.rs

GitHub Actions / test

unused variable: `evm_block_recorder_sender`
evm_state_recorder_sender: Option<&EvmStateRecorderSender>,

Check warning on line 245 in ledger/src/bank_forks_utils.rs

GitHub Actions / test

unused variable: `evm_state_recorder_sender`
accounts_update_notifier: Option<AccountsUpdateNotifier>,
) -> (BankForks, Option<StartingSnapshotHashes>) {
// Fail hard here if snapshot fails to load, don't silently continue
}
}
fn add_shred_type_to_shred_seed(shred_slot: Slot, bank: &Bank) -> bool {

Check warning on line 1164 in ledger/src/shred.rs

GitHub Actions / test

function `add_shred_type_to_shred_seed` is never used
let feature_slot = bank
.feature_set
.activated_slot(&feature_set::add_shred_type_to_shred_seed::id());