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

More parsing #4

Merged
merged 4 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
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
25 changes: 25 additions & 0 deletions common/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub struct NeonTxInfo {

/// Event kinds can be logged to solana transaction logs.
#[derive(Debug, Clone, Copy)]
#[repr(u32)]
pub enum EventKind {
Log = 1,

Expand All @@ -101,6 +102,30 @@ pub enum EventKind {
Cancel = 301,
}

impl EventKind {
pub fn is_exit(&self) -> bool {
matches!(
self,
EventKind::ExitStop
| EventKind::ExitReturn
| EventKind::ExitSelfDestruct
| EventKind::ExitRevert
)
}

pub fn is_start(&self) -> bool {
matches!(
self,
EventKind::EnterCall
| EventKind::EnterCallCode
| EventKind::EnterStaticCall
| EventKind::EnterDelegateCall
| EventKind::EnterCreate
| EventKind::EnterCreate2
)
}
}

// ===== Alternative Event =====
// #[derive(Debug, Clone)]
// #[repr(u8)]
Expand Down
232 changes: 225 additions & 7 deletions parse/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use common::solana_sdk::transaction::VersionedTransaction;
use thiserror::Error;

use common::evm_loader::types::{Address, Transaction};
use common::solana_sdk::signature::Signature;
use common::types::{NeonTxInfo, SolanaTransaction};

use self::log::NeonLogInfo;

mod log;
mod transaction;

Expand All @@ -29,23 +33,237 @@
pub ident: SolTxSigSlotInfo,
}

fn parse_transactions(tx: VersionedTransaction) -> Result<Vec<(usize, Transaction)>, Error> {
let mut txs = Vec::new();
for (idx, ix) in tx.message.instructions().iter().enumerate() {
let neon_tx = transaction::parse(&ix.data)?;
tracing::info!("neon tx {:?}", neon_tx);
if let Some(neon_tx) = neon_tx {
txs.push((idx, neon_tx));
}
}
Ok(txs)
}

fn merge_logs_transactions(
txs: Vec<(usize, Transaction)>,
log_info: NeonLogInfo,
slot: u64,
tx_idx: u64,
) -> Vec<NeonTxInfo> {
let mut tx_infos = Vec::new();
for (idx, tx) in txs {
let canceled = log_info
.ret
.as_ref()
.map(|r| r.is_canceled)
.unwrap_or_default(); // TODO

let tx_info = NeonTxInfo {
tx_type: 0, // TODO
neon_signature: log_info.sig.map(hex::encode).unwrap_or_default(), // TODO
from: Address::default(), // TODO
contract: None, // TODO
transaction: tx,
events: log_info.event_list.clone(), // TODO
gas_used: log_info
.ret
.as_ref()
.map(|r| r.gas_used)
.unwrap_or_default(), // TODO
sum_gas_used: log_info
.ix
.as_ref()
.map(|i| i.total_gas_used)
.unwrap_or_default(), // TODO: unclear what this is
sol_signature: String::default(), // TODO: should be in input?
sol_slot: slot,
sol_tx_idx: tx_idx,
sol_ix_idx: idx as u64,
sol_ix_inner_idx: 0, // TODO: what is this?
status: log_info.ret.as_ref().map(|r| r.status).unwrap_or_default(), // TODO
is_cancelled: canceled,
is_completed: !canceled, // TODO: ???
};
tx_infos.push(tx_info);
}
tx_infos
}

pub fn parse(transaction: SolanaTransaction) -> Result<Vec<NeonTxInfo>, Error> {
let SolanaTransaction { slot, tx, .. } = transaction;
let SolanaTransaction {
slot, tx, tx_idx, ..
} = transaction;
let sig_slot_info = SolTxSigSlotInfo {
signature: tx.signatures[0],
block_slot: slot,
};
let _meta_info = SolTxMetaInfo {
ident: sig_slot_info,
};
for ix in tx.message.instructions() {
let neon_tx = transaction::parse(&ix.data)?;
tracing::info!("neon tx {:?}", neon_tx);
}
let neon_txs = parse_transactions(tx)?;

let log_info = match log::parse(transaction.log_messages) {
Ok(log) => log,
Err(err) => panic!("log parsing error {:?}", err),
};
tracing::info!("log info {:?}", log_info);
Ok(Vec::new())
let tx_infos = merge_logs_transactions(neon_txs, log_info, slot, tx_idx);
Ok(tx_infos)
}

#[cfg(test)]
mod tests {
use super::*;
use common::solana_transaction_status::EncodedTransactionWithStatusMeta;
use serde::Deserialize;
use std::collections::HashMap;
use test_log::test;

#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct ReferenceRow {
neon_sig: String,
tx_type: u8,
from_addr: String,
sol_sig: String,
sol_ix_idx: u64,
sol_ix_inner_idx: Option<u64>,
block_slot: u64,
tx_idx: u64,
nonce: String,
gas_price: String,
gas_limit: String,
value: String,
gas_used: String,
sum_gas_used: String,
to_addr: String,
contract: Option<String>,
status: String,
is_canceled: bool,
is_completed: bool,
v: String,
r: String,
s: String,
calldata: String,
logs: Vec<ReferenceEvent>,
}

#[derive(Debug, Deserialize)]
struct ReferenceEvent {
event_type: u32,
is_hidden: bool,
address: String,
topic_list: Vec<String>,
data: String,
sol_sig: String,

Check warning on line 158 in parse/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite

multiple fields are never read

Check warning on line 158 in parse/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite

multiple fields are never read
idx: u64,
inner_idx: Option<u64>,
total_gas_used: u64,
is_reverted: bool,
event_level: u64,
event_order: u64,
neon_sig: String,
block_hash: String,
block_slot: u64,
neon_tx_idx: u64,
block_log_idx: Option<u64>,
neon_tx_log_idx: Option<u64>,
}

type Reference = HashMap<String, Vec<ReferenceRow>>;

fn check_events(ref_logs: &[ReferenceEvent], my_logs: &[common::types::EventLog]) {
for (ref_log, my_log) in ref_logs.iter().zip(my_logs.iter()) {
println!("event type {:?} {}", my_log.event_type, ref_log.event_type);
assert_eq!(ref_log.event_type, my_log.event_type as u32);
assert_eq!(ref_log.is_hidden, my_log.is_hidden);
assert_eq!(ref_log.event_level, my_log.level);
assert_eq!(ref_log.event_order, my_log.order);

assert_eq!(
ref_log.address,
my_log.address.map(|a| a.to_string()).unwrap_or_default()
);
assert_eq!(
ref_log.topic_list,
my_log
.topic_list
.iter()
.map(|t| format!("0x{}", t))
.collect::<Vec<_>>()
);
assert_eq!(ref_log.data, format!("0x{}", hex::encode(&my_log.data)));
}
}

#[test]
fn parse_2f() {
let transaction_path = "tests/data/2FSmsnCJYenPWsbrK1vFpkhgeFGKXC13gB3wDwqqyUfEnZmWpEJ6iUSjtLrtNn5QZh54bz5brWMonccG7WHA4Wp5.json";
let reference_path = "tests/data/reference/2FSmsnCJYenPWsbrK1vFpkhgeFGKXC13gB3wDwqqyUfEnZmWpEJ6iUSjtLrtNn5QZh54bz5brWMonccG7WHA4Wp5.json";

let encoded: EncodedTransactionWithStatusMeta =
serde_json::from_str(&std::fs::read_to_string(transaction_path).unwrap()).unwrap();
let tx = encoded.transaction.decode().unwrap();
let neon_txs = parse_transactions(tx).unwrap();
let logs = match encoded.meta.unwrap().log_messages {
common::solana_transaction_status::option_serializer::OptionSerializer::Some(logs) => {
logs
}
_ => panic!("no logs"),
};
let logs = log::parse(logs).unwrap();
let neon_tx_infos = merge_logs_transactions(neon_txs, logs, 276140928, 3);
let references: Vec<_> =
serde_json::from_str::<Reference>(&std::fs::read_to_string(reference_path).unwrap())
.unwrap()
.into_values()
.flatten()
.collect();
assert_eq!(neon_tx_infos.len(), references.len());
for (info, refr) in neon_tx_infos.iter().zip(references) {
let neon_sig = format!("0x{}", info.neon_signature);
assert_eq!(refr.neon_sig, neon_sig);
assert_eq!(refr.tx_type, info.tx_type);
// fails as we don't set from address
//assert_eq!(refr.from_addr, format!("0x{}", info.from));
// fails as we don't set signature
//assert_eq!(refr.sol_sig, info.sol_signature);
assert_eq!(refr.sol_ix_idx, info.sol_ix_idx);
// fails as we don't set inner index
//assert_eq!(refr.sol_ix_inner_idx, Some(info.sol_ix_inner_idx));
assert_eq!(refr.block_slot, info.sol_slot);
assert_eq!(refr.tx_idx, info.sol_tx_idx);
assert_eq!(refr.nonce, format!("{:#0x}", info.transaction.nonce()));
assert_eq!(
refr.gas_price,
format!("{:#0x}", info.transaction.gas_price())
);
assert_eq!(
refr.gas_limit,
format!("{:#0x}", info.transaction.gas_limit())
);
assert_eq!(refr.value, format!("{:#0x}", info.transaction.value()));
assert_eq!(refr.gas_used, format!("{:#0x}", info.gas_used));
// fails for unknown reason
//assert_eq!(refr.sum_gas_used, format!("{:#0x}", info.sum_gas_used));
// fails for unknown reason
// assert_eq!(
// refr.to_addr,
// format!("0x{}", info.transaction.target().unwrap())
// );
assert_eq!(refr.contract, info.contract.map(|c| format!("0x{}", c)));
// fails for unknown reason
//assert_eq!(refr.status, format!("{:#0x}", info.status));
assert_eq!(refr.is_canceled, info.is_cancelled);
assert_eq!(refr.is_completed, info.is_completed);

// TODO: we don't have v,r,s fields for some reason?

assert_eq!(
refr.calldata,
format!("0x{}", hex::encode(info.transaction.call_data()))
);
check_events(&refr.logs, &info.events);
}
}
}
48 changes: 47 additions & 1 deletion parse/src/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ pub struct NeonLogTxIx {

#[derive(Debug, Copy, Clone)]
enum Mnemonic {
Miner,
Hash,
Return,
Log(u8),
Expand Down Expand Up @@ -271,6 +272,11 @@ impl Mnemonic {
is_canceled: false,
})
}

// TODO
fn decode_miner(_input: &str) {
warn!("miner opcode not implemented yet")
}
}

#[derive(Debug, Error)]
Expand All @@ -283,6 +289,7 @@ impl FromStr for Mnemonic {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"HASH" => Ok(Mnemonic::Hash),
"MINER" => Ok(Mnemonic::Miner),
"RETURN" => Ok(Mnemonic::Return),
"ENTER" => Ok(Mnemonic::Enter),
"EXIT" => Ok(Mnemonic::Exit),
Expand All @@ -292,7 +299,10 @@ impl FromStr for Mnemonic {
let n = n.parse().map_err(|_| BadMnemonic)?;
Ok(Mnemonic::Log(n))
}
_ => Err(BadMnemonic),
s => {
println!("Invalid mnemonic {}", s);
Err(BadMnemonic)
}
}
}
}
Expand Down Expand Up @@ -363,9 +373,45 @@ pub fn parse(lines: impl IntoIterator<Item = impl AsRef<str>>) -> Result<NeonLog
}
neon_tx_ix = Some(Mnemonic::decode_tx_gas(rest)?);
}
Mnemonic::Miner => {
// TODO
Mnemonic::decode_miner(rest);
}
}
}
}

// complete event list
let mut current_level = 0;
let mut current_order = 0;
let mut addr_stack = Vec::new();
let mut event_level;
let mut event_addr;

for event in &mut event_list {
if event.event_type.is_start() {
current_level += 1;
event_level = current_level;
event_addr = event.address;
addr_stack.push(event.address.unwrap());
} else if event.event_type.is_exit() {
event_level = current_level;
current_level -= 1;
event_addr = addr_stack.pop();
} else {
event_level = current_level;
if addr_stack.is_empty() {
event_addr = None;
} else {
event_addr = Some(*addr_stack.last().unwrap());
}
}
current_order += 1;
event.level = event_level;
event.order = current_order;
event.address = event_addr;
}

let log_info = NeonLogInfo {
sig: neon_tx_sig,
ix: neon_tx_ix,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"blockTime":1706592175,"meta":{"computeUnitsConsumed":92092,"err":null,"fee":5000,"innerInstructions":[{"index":2,"instructions":[{"accounts":[0,2],"data":"3Bxs4PckVVt51W8w","programIdIndex":5,"stackHeight":2}]}],"loadedAddresses":{"readonly":[],"writable":[]},"logMessages":["Program ComputeBudget111111111111111111111111111111 invoke [1]","Program ComputeBudget111111111111111111111111111111 success","Program ComputeBudget111111111111111111111111111111 invoke [1]","Program ComputeBudget111111111111111111111111111111 success","Program eeLSJgWzzxrqKv1UxtRVVH8FX3qCQWUs9QuAjJpETGU invoke [1]","Program log: Instruction: Execute Transaction from Instruction","Program data: SEFTSA== xqDxMPJzt3SxclsFig+b4rJIaBZULPU6fkJiIPe2E9E=","Program data: TUlORVI= 9ohQV7C9v4wbdLR91d1fK/hkAcc=","Program data: RU5URVI= Q0FMTA== BUOlMRMZ6nO4Z8OcM8TZul1uUXs=","Program data: RVhJVA== U1RPUA==","Program 11111111111111111111111111111111 invoke [2]","Program 11111111111111111111111111111111 success","Program data: R0FT ECcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= ECcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","Program log: exit_status=0x11","Program data: UkVUVVJO EQ==","Program eeLSJgWzzxrqKv1UxtRVVH8FX3qCQWUs9QuAjJpETGU consumed 91736 of 1399700 compute units","Program eeLSJgWzzxrqKv1UxtRVVH8FX3qCQWUs9QuAjJpETGU success"],"postBalances":[2134989240,1378080,757150880,1378080,1378080,1,1,1141440,34911360],"postTokenBalances":[],"preBalances":[2134999240,1378080,757145880,1378080,1378080,1,1,1141440,34911360],"preTokenBalances":[],"rewards":[],"status":{"Ok":null}},"slot":276140928,"transaction":["AT55KMj0PTzjD72PR0klsjwEwLR6SkQUAZirH14uALMtK0mygcBVZAlMB//XgU1/yaDhoX/2hJCVRpTNCJSO6wYBAAQJJalKmpZXwhy8e2gAB88mdD30or9ZZsQtrDlpBXTN8GI3RouEAA9+9KzHJY/DnxVVJXnngVcLLFi6OTvKqofNiUbCxNc83ByGAxR105h8w4WCERGlm9eDmfZmrxBhYG2Vj+13Ha4gxD8sJ6wYsJYlIqWtlU1HWI/QCVFQ0pEFvVTBt9dhN7H2HVrOVuLT47hD/kz0xB/uquJRkmfKrZy3+QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAJpLRy2fLFNxdeUmvu7aq2doyAgA7b9ztEEPSKkdZRwbj9ty/ydffLtYK4hD2iPCOeBGETp5viUn4/4P9qunOImVIwMtA7CeK9oxfWJ9yL64zwdIATQewgHxBVnFJw1XQDBgAFAQAABAAGAAUCwFwVAAcHAAIEBQEDCJcBMkYAAAD4kIIOPoUSLhjZRYJhqJQFQ6UxExnqc7hnw5wzxNm6XW5Re4YGlbGg0ACg/h4KcO3DJZnJxj5v/hsMT6og4Nr7TlVXb6xMEJtKe9CEHTWBv6DQtHFMt5OWY5DB9S7kWzMRoOYEsFElJUojvxX8zRL3s6ByMS3wgQtmWvhZAaqKDjoPG6v8rTdyYFlA7Fe+gKl/Ow==","base64"]}
Loading
Loading