use crate::{
error::PlerkleSerializationError,
solana_geyser_plugin_interface_shims::{
ReplicaAccountInfoV2, ReplicaBlockInfoV2, ReplicaTransactionInfoV2,
ReplicaTransactionInfoV3, SlotStatus,
},
AccountInfo, AccountInfoArgs, BlockInfo, BlockInfoArgs, CompiledInnerInstruction,
CompiledInnerInstructionArgs, CompiledInnerInstructions, CompiledInnerInstructionsArgs,
CompiledInstruction, CompiledInstructionArgs, Pubkey as FBPubkey, Pubkey, SlotStatusInfo,
SlotStatusInfoArgs, Status as FBSlotStatus, TransactionInfo, TransactionInfoArgs,
TransactionVersion,
};
use chrono::Utc;
use flatbuffers::{FlatBufferBuilder, WIPOffset};
use solana_sdk::{
message::{SanitizedMessage, VersionedMessage},
transaction::VersionedTransaction,
};
use solana_transaction_status::{
option_serializer::OptionSerializer, EncodedConfirmedTransactionWithStatusMeta, UiInstruction,
UiTransactionStatusMeta,
};
pub fn serialize_account<'a>(
mut builder: FlatBufferBuilder<'a>,
account: &ReplicaAccountInfoV2,
slot: u64,
is_startup: bool,
) -> FlatBufferBuilder<'a> {
let pubkey: Pubkey = account.pubkey.into();
let owner: Pubkey = account.owner.into();
let data = builder.create_vector(account.data);
let seen_at = Utc::now();
let account_info = AccountInfo::create(
&mut builder,
&AccountInfoArgs {
pubkey: Some(&pubkey),
lamports: account.lamports,
owner: Some(&owner),
executable: account.executable,
rent_epoch: account.rent_epoch,
data: Some(data),
write_version: account.write_version,
slot,
is_startup,
seen_at: seen_at.timestamp_millis(),
},
);
builder.finish(account_info, None);
builder
}
pub fn serialize_slot_status<'a>(
mut builder: FlatBufferBuilder<'a>,
slot: u64,
parent: Option<u64>,
status: SlotStatus,
) -> FlatBufferBuilder<'a> {
let status = match status {
SlotStatus::Confirmed => FBSlotStatus::Confirmed,
SlotStatus::Processed => FBSlotStatus::Processed,
SlotStatus::Rooted => FBSlotStatus::Rooted,
};
let seen_at = Utc::now();
let slot_status = SlotStatusInfo::create(
&mut builder,
&SlotStatusInfoArgs {
slot,
parent,
status,
seen_at: seen_at.timestamp_millis(),
},
);
builder.finish(slot_status, None);
builder
}
pub fn serialize_transaction<'a>(
mut builder: FlatBufferBuilder<'a>,
transaction_info: &ReplicaTransactionInfoV2,
slot: u64,
) -> FlatBufferBuilder<'a> {
let account_keys = transaction_info.transaction.message().account_keys();
let atl_keys = &transaction_info.transaction_status_meta.loaded_addresses;
let account_keys = {
let mut account_keys_fb_vec = vec![];
for key in account_keys.iter() {
account_keys_fb_vec.push(FBPubkey(key.to_bytes()));
}
for i in &atl_keys.writable {
let pubkey = FBPubkey(i.to_bytes());
account_keys_fb_vec.push(pubkey);
}
for i in &atl_keys.readonly {
let pubkey = FBPubkey(i.to_bytes());
account_keys_fb_vec.push(pubkey);
}
if !account_keys_fb_vec.is_empty() {
Some(builder.create_vector(&account_keys_fb_vec))
} else {
None
}
};
let log_messages =
if let Some(log_messages) = &transaction_info.transaction_status_meta.log_messages {
let mut log_messages_fb_vec = Vec::with_capacity(log_messages.len());
for message in log_messages {
log_messages_fb_vec.push(builder.create_string(message));
}
Some(builder.create_vector(&log_messages_fb_vec))
} else {
None
};
let inner_instructions = if let Some(inner_instructions_vec) = transaction_info
.transaction_status_meta
.inner_instructions
.as_ref()
{
let mut overall_fb_vec = Vec::with_capacity(inner_instructions_vec.len());
for inner_instructions in inner_instructions_vec.iter() {
let index = inner_instructions.index;
let mut instructions_fb_vec = Vec::with_capacity(inner_instructions.instructions.len());
for compiled_instruction in inner_instructions.instructions.iter() {
let program_id_index = compiled_instruction.instruction.program_id_index;
let accounts =
Some(builder.create_vector(&compiled_instruction.instruction.accounts));
let data = Some(builder.create_vector(&compiled_instruction.instruction.data));
let compiled = CompiledInstruction::create(
&mut builder,
&CompiledInstructionArgs {
program_id_index,
accounts,
data,
},
);
instructions_fb_vec.push(CompiledInnerInstruction::create(
&mut builder,
&CompiledInnerInstructionArgs {
compiled_instruction: Some(compiled),
stack_height: 0, },
));
}
let instructions = Some(builder.create_vector(&instructions_fb_vec));
overall_fb_vec.push(CompiledInnerInstructions::create(
&mut builder,
&CompiledInnerInstructionsArgs {
index,
instructions,
},
))
}
Some(builder.create_vector(&overall_fb_vec))
} else {
None
};
let message = transaction_info.transaction.message();
let version = match message {
SanitizedMessage::Legacy(_) => TransactionVersion::Legacy,
SanitizedMessage::V0(_) => TransactionVersion::V0,
};
let outer_instructions = message.instructions();
let outer_instructions = if !outer_instructions.is_empty() {
let mut instructions_fb_vec = Vec::with_capacity(outer_instructions.len());
for compiled_instruction in outer_instructions.iter() {
let program_id_index = compiled_instruction.program_id_index;
let accounts = Some(builder.create_vector(&compiled_instruction.accounts));
let data = Some(builder.create_vector(&compiled_instruction.data));
instructions_fb_vec.push(CompiledInstruction::create(
&mut builder,
&CompiledInstructionArgs {
program_id_index,
accounts,
data,
},
));
}
Some(builder.create_vector(&instructions_fb_vec))
} else {
None
};
let seen_at = Utc::now();
let txn_sig = transaction_info.signature.to_string();
let signature_offset = builder.create_string(&txn_sig);
let slot_idx = format!("{}_{}", slot, transaction_info.index);
let slot_index_offset = builder.create_string(&slot_idx);
let transaction_info_ser = TransactionInfo::create(
&mut builder,
&TransactionInfoArgs {
is_vote: transaction_info.is_vote,
account_keys,
log_messages,
inner_instructions: None,
outer_instructions,
slot,
slot_index: Some(slot_index_offset),
seen_at: seen_at.timestamp_millis(),
signature: Some(signature_offset),
compiled_inner_instructions: inner_instructions,
version,
},
);
builder.finish(transaction_info_ser, None);
builder
}
pub fn serialize_transaction_v3<'a>(
mut builder: FlatBufferBuilder<'a>,
transaction_info: &ReplicaTransactionInfoV3,
slot: u64,
) -> FlatBufferBuilder<'a> {
let message = &transaction_info.transaction.message;
let account_keys = {
let static_keys = message.static_account_keys();
let atl_keys = &transaction_info.transaction_status_meta.loaded_addresses;
let mut account_keys_fb_vec = Vec::with_capacity(
static_keys.len() + atl_keys.writable.len() + atl_keys.readonly.len(),
);
for key in static_keys.iter() {
account_keys_fb_vec.push(FBPubkey(key.to_bytes()));
}
for i in &atl_keys.writable {
account_keys_fb_vec.push(FBPubkey(i.to_bytes()));
}
for i in &atl_keys.readonly {
account_keys_fb_vec.push(FBPubkey(i.to_bytes()));
}
if !account_keys_fb_vec.is_empty() {
Some(builder.create_vector(&account_keys_fb_vec))
} else {
None
}
};
let log_messages =
if let Some(log_messages) = &transaction_info.transaction_status_meta.log_messages {
let mut log_messages_fb_vec = Vec::with_capacity(log_messages.len());
for message in log_messages {
log_messages_fb_vec.push(builder.create_string(message));
}
Some(builder.create_vector(&log_messages_fb_vec))
} else {
None
};
let inner_instructions = if let Some(inner_instructions_vec) = transaction_info
.transaction_status_meta
.inner_instructions
.as_ref()
{
let mut overall_fb_vec = Vec::with_capacity(inner_instructions_vec.len());
for inner_instructions in inner_instructions_vec.iter() {
let index = inner_instructions.index;
let mut instructions_fb_vec = Vec::with_capacity(inner_instructions.instructions.len());
for compiled_instruction in inner_instructions.instructions.iter() {
let program_id_index = compiled_instruction.instruction.program_id_index;
let accounts =
Some(builder.create_vector(&compiled_instruction.instruction.accounts));
let data = Some(builder.create_vector(&compiled_instruction.instruction.data));
let compiled = CompiledInstruction::create(
&mut builder,
&CompiledInstructionArgs {
program_id_index,
accounts,
data,
},
);
instructions_fb_vec.push(CompiledInnerInstruction::create(
&mut builder,
&CompiledInnerInstructionArgs {
compiled_instruction: Some(compiled),
stack_height: 0, },
));
}
let instructions = Some(builder.create_vector(&instructions_fb_vec));
overall_fb_vec.push(CompiledInnerInstructions::create(
&mut builder,
&CompiledInnerInstructionsArgs {
index,
instructions,
},
))
}
Some(builder.create_vector(&overall_fb_vec))
} else {
None
};
let version = match message {
VersionedMessage::Legacy(_) => TransactionVersion::Legacy,
VersionedMessage::V0(_) => TransactionVersion::V0,
};
let outer_instructions = message.instructions();
let outer_instructions = if !outer_instructions.is_empty() {
let mut instructions_fb_vec = Vec::with_capacity(outer_instructions.len());
for compiled_instruction in outer_instructions.iter() {
let program_id_index = compiled_instruction.program_id_index;
let accounts = Some(builder.create_vector(&compiled_instruction.accounts));
let data = Some(builder.create_vector(&compiled_instruction.data));
instructions_fb_vec.push(CompiledInstruction::create(
&mut builder,
&CompiledInstructionArgs {
program_id_index,
accounts,
data,
},
));
}
Some(builder.create_vector(&instructions_fb_vec))
} else {
None
};
let seen_at = Utc::now();
let txn_sig = transaction_info.signature.to_string();
let signature_offset = builder.create_string(&txn_sig);
let slot_idx = format!("{}_{}", slot, transaction_info.index);
let slot_index_offset = builder.create_string(&slot_idx);
let transaction_info_ser = TransactionInfo::create(
&mut builder,
&TransactionInfoArgs {
is_vote: transaction_info.is_vote,
account_keys,
log_messages,
inner_instructions: None,
outer_instructions,
slot,
slot_index: Some(slot_index_offset),
seen_at: seen_at.timestamp_millis(),
signature: Some(signature_offset),
compiled_inner_instructions: inner_instructions,
version,
},
);
builder.finish(transaction_info_ser, None);
builder
}
pub fn serialize_block<'a>(
mut builder: FlatBufferBuilder<'a>,
block_info: &ReplicaBlockInfoV2,
) -> FlatBufferBuilder<'a> {
let blockhash = Some(builder.create_string(block_info.blockhash));
let rewards = None;
let seen_at = Utc::now();
let block_info = BlockInfo::create(
&mut builder,
&BlockInfoArgs {
slot: block_info.slot,
blockhash,
rewards,
block_time: block_info.block_time,
block_height: block_info.block_height,
seen_at: seen_at.timestamp_millis(),
},
);
builder.finish(block_info, None);
builder
}
pub fn seralize_encoded_transaction_with_status<'a>(
mut builder: FlatBufferBuilder<'a>,
tx: EncodedConfirmedTransactionWithStatusMeta,
) -> Result<FlatBufferBuilder<'a>, PlerkleSerializationError> {
let meta: UiTransactionStatusMeta =
tx.transaction
.meta
.ok_or(PlerkleSerializationError::SerializationError(
"Missing meta data for transaction".to_string(),
))?;
let ui_transaction: VersionedTransaction = tx.transaction.transaction.decode().ok_or(
PlerkleSerializationError::SerializationError("Transaction cannot be decoded".to_string()),
)?;
let msg = ui_transaction.message;
let atl_keys = msg.address_table_lookups();
let account_keys = msg.static_account_keys();
let sig = ui_transaction.signatures[0].to_string();
let account_keys = {
let mut account_keys_fb_vec = vec![];
for key in account_keys.iter() {
account_keys_fb_vec.push(FBPubkey(key.to_bytes()));
}
if atl_keys.is_some() {
if let OptionSerializer::Some(ad) = meta.loaded_addresses {
for i in ad.writable {
let mut output: [u8; 32] = [0; 32];
bs58::decode(i).into(&mut output).map_err(|e| {
PlerkleSerializationError::SerializationError(e.to_string())
})?;
let pubkey = FBPubkey(output);
account_keys_fb_vec.push(pubkey);
}
for i in ad.readonly {
let mut output: [u8; 32] = [0; 32];
bs58::decode(i).into(&mut output).map_err(|e| {
PlerkleSerializationError::SerializationError(e.to_string())
})?;
let pubkey = FBPubkey(output);
account_keys_fb_vec.push(pubkey);
}
}
}
if !account_keys_fb_vec.is_empty() {
Some(builder.create_vector(&account_keys_fb_vec))
} else {
None
}
};
let log_messages = if let OptionSerializer::Some(log_messages) = &meta.log_messages {
let mut log_messages_fb_vec = Vec::with_capacity(log_messages.len());
for message in log_messages {
log_messages_fb_vec.push(builder.create_string(message));
}
Some(builder.create_vector(&log_messages_fb_vec))
} else {
None
};
let inner_instructions = if let OptionSerializer::Some(inner_instructions_vec) =
meta.inner_instructions.as_ref()
{
let mut overall_fb_vec = Vec::with_capacity(inner_instructions_vec.len());
for inner_instructions in inner_instructions_vec.iter() {
let index = inner_instructions.index;
let mut instructions_fb_vec = Vec::with_capacity(inner_instructions.instructions.len());
for ui_instruction in inner_instructions.instructions.iter() {
if let UiInstruction::Compiled(ui_compiled_instruction) = ui_instruction {
let program_id_index = ui_compiled_instruction.program_id_index;
let accounts = Some(builder.create_vector(&ui_compiled_instruction.accounts));
let data = bs58::decode(&ui_compiled_instruction.data)
.into_vec()
.map_err(|e| {
PlerkleSerializationError::SerializationError(e.to_string())
})?;
let data = Some(builder.create_vector(&data));
let compiled = CompiledInstruction::create(
&mut builder,
&CompiledInstructionArgs {
program_id_index,
accounts,
data,
},
);
instructions_fb_vec.push(CompiledInnerInstruction::create(
&mut builder,
&CompiledInnerInstructionArgs {
compiled_instruction: Some(compiled),
stack_height: 0, },
));
}
}
let instructions = Some(builder.create_vector(&instructions_fb_vec));
overall_fb_vec.push(CompiledInnerInstructions::create(
&mut builder,
&CompiledInnerInstructionsArgs {
index,
instructions,
},
));
}
Some(builder.create_vector(&overall_fb_vec))
} else {
let empty: Vec<WIPOffset<CompiledInnerInstructions>> = Vec::new();
Some(builder.create_vector(empty.as_slice()))
};
let outer_instructions = &msg.instructions();
let outer_instructions = if !outer_instructions.is_empty() {
let mut instructions_fb_vec = Vec::with_capacity(outer_instructions.len());
for ui_compiled_instruction in outer_instructions.iter() {
let program_id_index = ui_compiled_instruction.program_id_index;
let accounts = Some(builder.create_vector(&ui_compiled_instruction.accounts));
let data = Some(builder.create_vector(&ui_compiled_instruction.data));
instructions_fb_vec.push(CompiledInstruction::create(
&mut builder,
&CompiledInstructionArgs {
program_id_index,
accounts,
data,
},
));
}
Some(builder.create_vector(&instructions_fb_vec))
} else {
None
};
let version = match msg {
VersionedMessage::Legacy(_) => TransactionVersion::Legacy,
VersionedMessage::V0(_) => TransactionVersion::V0,
};
let sig_db = builder.create_string(&sig);
let transaction_info = TransactionInfo::create(
&mut builder,
&TransactionInfoArgs {
is_vote: false,
account_keys,
log_messages,
inner_instructions: None,
outer_instructions,
slot: tx.slot,
seen_at: 0,
slot_index: None,
signature: Some(sig_db),
compiled_inner_instructions: inner_instructions,
version,
},
);
builder.finish(transaction_info, None);
Ok(builder)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::root_as_transaction_info;
use crate::solana_geyser_plugin_interface_shims::ReplicaTransactionInfoV3;
use solana_message::compiled_instruction::CompiledInstruction as SolanaCompiledInstruction;
use solana_message::v0::LoadedAddresses;
use solana_sdk::{
hash::Hash,
message::{Message, MessageHeader},
pubkey::Pubkey,
signature::Signature,
transaction::VersionedTransaction,
};
use solana_transaction_status::{InnerInstruction, InnerInstructions, TransactionStatusMeta};
fn make_test_transaction() -> (Signature, Hash, VersionedTransaction, TransactionStatusMeta) {
let program_id = Pubkey::new_unique();
let account_a = Pubkey::new_unique();
let account_b = Pubkey::new_unique();
let atl_writable = Pubkey::new_unique();
let atl_readonly = Pubkey::new_unique();
let message = Message {
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 1,
},
account_keys: vec![account_a, account_b, program_id],
recent_blockhash: Hash::new_unique(),
instructions: vec![SolanaCompiledInstruction {
program_id_index: 2,
accounts: vec![0, 1],
data: vec![1, 2, 3, 4],
}],
};
let signature = Signature::new_unique();
let message_hash = Hash::new_unique();
let tx = VersionedTransaction {
signatures: vec![signature],
message: VersionedMessage::Legacy(message),
};
let meta = TransactionStatusMeta {
loaded_addresses: LoadedAddresses {
writable: vec![atl_writable],
readonly: vec![atl_readonly],
},
log_messages: Some(vec![
"Program log: hello".to_string(),
"Program log: world".to_string(),
]),
inner_instructions: Some(vec![InnerInstructions {
index: 0,
instructions: vec![InnerInstruction {
instruction: SolanaCompiledInstruction {
program_id_index: 2,
accounts: vec![0],
data: vec![5, 6],
},
stack_height: Some(2),
}],
}]),
..TransactionStatusMeta::default()
};
(signature, message_hash, tx, meta)
}
#[test]
fn test_serialize_transaction_v3_roundtrip() {
let (signature, message_hash, tx, meta) = make_test_transaction();
let info = ReplicaTransactionInfoV3 {
signature: &signature,
message_hash: &message_hash,
is_vote: false,
transaction: &tx,
transaction_status_meta: &meta,
index: 42,
};
let builder = FlatBufferBuilder::new();
let builder = serialize_transaction_v3(builder, &info, 12345);
let buf = builder.finished_data();
let parsed = root_as_transaction_info(buf).expect("valid flatbuffer");
assert_eq!(parsed.signature().unwrap(), signature.to_string());
assert_eq!(parsed.slot(), 12345);
assert_eq!(parsed.slot_index().unwrap(), "12345_42");
assert!(!parsed.is_vote());
assert_eq!(parsed.version(), TransactionVersion::Legacy);
let keys = parsed.account_keys().unwrap();
assert_eq!(keys.len(), 5);
assert_eq!(
keys.get(0).0,
tx.message.static_account_keys()[0].to_bytes()
);
assert_eq!(keys.get(3).0, meta.loaded_addresses.writable[0].to_bytes());
assert_eq!(keys.get(4).0, meta.loaded_addresses.readonly[0].to_bytes());
let logs = parsed.log_messages().unwrap();
assert_eq!(logs.len(), 2);
assert_eq!(logs.get(0), "Program log: hello");
assert_eq!(logs.get(1), "Program log: world");
let outer = parsed.outer_instructions().unwrap();
assert_eq!(outer.len(), 1);
assert_eq!(outer.get(0).program_id_index(), 2);
assert_eq!(outer.get(0).accounts().unwrap().bytes(), &[0, 1]);
assert_eq!(outer.get(0).data().unwrap().bytes(), &[1, 2, 3, 4]);
let inner = parsed.compiled_inner_instructions().unwrap();
assert_eq!(inner.len(), 1);
assert_eq!(inner.get(0).index(), 0);
let inner_ixs = inner.get(0).instructions().unwrap();
assert_eq!(inner_ixs.len(), 1);
let cix = inner_ixs.get(0).compiled_instruction().unwrap();
assert_eq!(cix.program_id_index(), 2);
assert_eq!(cix.accounts().unwrap().bytes(), &[0]);
assert_eq!(cix.data().unwrap().bytes(), &[5, 6]);
}
#[test]
fn test_serialize_transaction_v3_vote_and_version() {
let (signature, message_hash, tx, meta) = make_test_transaction();
let info = ReplicaTransactionInfoV3 {
signature: &signature,
message_hash: &message_hash,
is_vote: true,
transaction: &tx,
transaction_status_meta: &meta,
index: 0,
};
let builder = FlatBufferBuilder::new();
let builder = serialize_transaction_v3(builder, &info, 999);
let buf = builder.finished_data();
let parsed = root_as_transaction_info(buf).expect("valid flatbuffer");
assert!(parsed.is_vote());
}
#[test]
fn test_serialize_transaction_v3_v0_message() {
let program_id = Pubkey::new_unique();
let account_a = Pubkey::new_unique();
let account_b = Pubkey::new_unique();
let atl_writable = Pubkey::new_unique();
let atl_readonly = Pubkey::new_unique();
let v0_message = solana_message::v0::Message {
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 1,
},
account_keys: vec![account_a.into(), account_b.into(), program_id.into()],
recent_blockhash: Hash::new_unique(),
instructions: vec![SolanaCompiledInstruction {
program_id_index: 2,
accounts: vec![0, 1],
data: vec![10, 20],
}],
address_table_lookups: vec![],
};
let signature = Signature::new_unique();
let message_hash = Hash::new_unique();
let tx = VersionedTransaction {
signatures: vec![signature],
message: VersionedMessage::V0(v0_message),
};
let meta = TransactionStatusMeta {
loaded_addresses: LoadedAddresses {
writable: vec![atl_writable],
readonly: vec![atl_readonly],
},
..TransactionStatusMeta::default()
};
let info = ReplicaTransactionInfoV3 {
signature: &signature,
message_hash: &message_hash,
is_vote: false,
transaction: &tx,
transaction_status_meta: &meta,
index: 7,
};
let builder = FlatBufferBuilder::new();
let builder = serialize_transaction_v3(builder, &info, 500);
let buf = builder.finished_data();
let parsed = root_as_transaction_info(buf).expect("valid flatbuffer");
assert_eq!(parsed.version(), TransactionVersion::V0);
let keys = parsed.account_keys().unwrap();
assert_eq!(keys.len(), 5);
assert_eq!(keys.get(0).0, account_a.to_bytes());
assert_eq!(keys.get(3).0, atl_writable.to_bytes());
assert_eq!(keys.get(4).0, atl_readonly.to_bytes());
let outer = parsed.outer_instructions().unwrap();
assert_eq!(outer.len(), 1);
assert_eq!(outer.get(0).data().unwrap().bytes(), &[10, 20]);
}
}