Skip to content
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
9 changes: 5 additions & 4 deletions Cargo.lock

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

4 changes: 0 additions & 4 deletions crates/backfill/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

# Stellar RPC and parsing - reuse indexer rpc and parser modules by copying minimal logic
base64 = "0.22"
stellar-xdr = { version = "26.0.1", features = ["curr"] }
stellar-strkey = "0.0.16"
hex = "0.4"

# Date/time
chrono = { version = "0.4", features = ["serde"] }
Expand Down
106 changes: 7 additions & 99 deletions crates/backfill/src/parser.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use base64::{engine::general_purpose::STANDARD, Engine};
use serde::Deserialize;
use serde::Deserialize;
use serde_json::Value as Json;
use stellar_strkey::{ed25519, Contract};
use stellar_xdr::curr::{
AccountId, ContractId, Limited, Limits, PublicKey, ReadXdr, ScAddress, ScVal,
};
// ScVal decoding is shared with the live indexer (issue #506): this crate
// previously carried a stale copy that predated the exact U256/I256
// rendering from #415 and lacked the Timepoint/Duration/Error arms, so a
// backfilled event could store different values than the live path stored
// for the same XDR. One decoder makes that divergence impossible.
use trident_common::scval::{decode_scval, scval_to_json, scval_to_string};
use trident_common::{EventType, SorobanEvent, TridentError};

/// Accept a field the RPC sends as either a JSON string or a JSON number.
Expand Down Expand Up @@ -147,96 +148,3 @@ fn parse_event_type(raw: &str) -> Result<EventType, TridentError> {
))),
}
}

fn decode_scval(b64: &str) -> Result<ScVal, TridentError> {
let bytes = STANDARD
.decode(b64)
.map_err(|e| TridentError::parse(anyhow::Error::new(e).context("base64 decode")))?;
let mut cursor = std::io::Cursor::new(bytes);
ScVal::read_xdr(&mut Limited::new(&mut cursor, Limits::none()))
.map_err(|e| TridentError::parse(anyhow::Error::new(e).context("XDR decode ScVal")))
}

pub fn scval_to_string(val: &ScVal) -> String {
match val {
ScVal::Symbol(s) => s.to_utf8_string_lossy(),
ScVal::String(s) => s.to_utf8_string_lossy(),
ScVal::Bool(b) => b.to_string(),
ScVal::Void => "void".into(),
ScVal::U32(n) => n.to_string(),
ScVal::I32(n) => n.to_string(),
ScVal::U64(n) => n.to_string(),
ScVal::I64(n) => n.to_string(),
ScVal::U128(parts) => {
let val = ((parts.hi as u128) << 64) | (parts.lo as u128);
val.to_string()
}
ScVal::I128(parts) => {
let val = ((parts.hi as i128) << 64) | (parts.lo as i128);
val.to_string()
}
ScVal::U256(parts) => format!(
"u256({:x}{:x}{:x}{:x})",
parts.hi_hi, parts.hi_lo, parts.lo_hi, parts.lo_lo
),
ScVal::I256(parts) => format!(
"i256({:x}{:x}{:x}{:x})",
parts.hi_hi, parts.hi_lo, parts.lo_hi, parts.lo_lo
),
ScVal::Bytes(b) => hex::encode(b.as_slice()),
ScVal::Address(addr) => scaddress_to_string(addr),
other => format!("{other:?}"),
}
}

pub fn scval_to_json(val: &ScVal) -> Json {
match val {
ScVal::Void => Json::Null,
ScVal::Bool(b) => Json::Bool(*b),
ScVal::Symbol(s) => Json::String(s.to_utf8_string_lossy()),
ScVal::String(s) => Json::String(s.to_utf8_string_lossy()),
ScVal::U32(n) => Json::from(*n),
ScVal::I32(n) => Json::from(*n),
ScVal::U64(n) => Json::from(*n),
ScVal::I64(n) => Json::from(*n),
ScVal::U128(parts) => {
let v = ((parts.hi as u128) << 64) | (parts.lo as u128);
if v <= u64::MAX as u128 {
Json::from(v as u64)
} else {
Json::String(v.to_string())
}
}
ScVal::I128(parts) => {
let v = ((parts.hi as i128) << 64) | (parts.lo as i128);
if v >= i64::MIN as i128 && v <= i64::MAX as i128 {
Json::from(v as i64)
} else {
Json::String(v.to_string())
}
}
ScVal::Bytes(b) => Json::String(hex::encode(b.as_slice())),
ScVal::Address(addr) => Json::String(scaddress_to_string(addr)),
ScVal::Vec(Some(items)) => Json::Array(items.iter().map(scval_to_json).collect()),
ScVal::Vec(None) => Json::Array(vec![]),
ScVal::Map(Some(entries)) => {
let obj: serde_json::Map<String, Json> = entries
.iter()
.map(|e| (scval_to_string(&e.key), scval_to_json(&e.val)))
.collect();
Json::Object(obj)
}
ScVal::Map(None) => Json::Object(serde_json::Map::new()),
other => Json::String(format!("{other:?}")),
}
}

fn scaddress_to_string(addr: &ScAddress) -> String {
match addr {
ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(bytes))) => {
ed25519::PublicKey(bytes.0).to_string().as_str().to_owned()
}
ScAddress::Contract(ContractId(hash)) => Contract(hash.0).to_string().as_str().to_owned(),
other => format!("{other:?}"),
}
}
7 changes: 7 additions & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,10 @@ serde_json = "1"
chrono = { version = "0.4", features = ["clock"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Shared ScVal decoding (issue #506) — versions pinned to match the indexer
# and backfill crates so the whole workspace resolves one copy of each.
base64 = "0.22"
hex = "0.4"
metrics = "0.24"
stellar-strkey = "0.0.16"
stellar-xdr = { version = "26.0.1", features = ["curr"] }
1 change: 1 addition & 0 deletions crates/common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod errors;
pub mod logging;
pub mod scval;
pub mod types;

pub use errors::{Severity, TridentError};
Expand Down
Loading
Loading