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

pcli: add un-advertised support for importing Prax registry data #4801

Merged
merged 4 commits into from
Aug 12, 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
9 changes: 9 additions & 0 deletions crates/bin/pcli/src/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,17 @@ impl Opt {
let path = self.home.join(crate::VIEW_FILE_NAME);
tracing::info!(%path, "using local view service");

let registry_path = self.home.join("registry.json");
// Check if the path exists or set it to nojne
let registry_path = if registry_path.exists() {
Some(registry_path)
} else {
None
};

let svc = ViewServer::load_or_initialize(
Some(path),
registry_path,
&config.full_viewing_key,
config.grpc_url.clone(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ async fn app_can_sweep_a_collection_of_small_notes() -> anyhow::Result<()> {
// Spawn the client-side view server...
let view_server = {
penumbra_view::ViewServer::load_or_initialize(
None::<&camino::Utf8Path>,
None::<&camino::Utf8Path>,
&*test_keys::FULL_VIEWING_KEY,
grpc_url,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ async fn view_server_can_be_served_on_localhost() -> anyhow::Result<()> {
// Spawn the client-side view server...
let view_server = {
penumbra_view::ViewServer::load_or_initialize(
None::<&camino::Utf8Path>,
None::<&camino::Utf8Path>,
&*test_keys::FULL_VIEWING_KEY,
grpc_url,
Expand Down
16 changes: 10 additions & 6 deletions crates/core/asset/src/asset/denom_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub struct Metadata {
}

// These are constructed by the asset registry.
#[derive(Debug)]
pub(super) struct Inner {
// The Penumbra asset ID
id: Id,
Expand Down Expand Up @@ -317,16 +318,19 @@ impl Metadata {
if amount == 0u64.into() {
return self.default_unit();
}
let mut selected_index = 0;
let mut selected_exponent = 0;
for (unit_index, unit) in self.inner.units.iter().enumerate() {
let unit_amount = Amount::from(10u128.pow(unit.exponent as u32));
if amount >= unit_amount {
return Unit {
unit_index,
inner: self.inner.clone(),
};
if unit_amount <= amount && unit.exponent >= selected_exponent {
selected_index = unit_index;
selected_exponent = unit.exponent;
}
}
self.base_unit()
return Unit {
unit_index: selected_index,
inner: self.inner.clone(),
};
Comment on lines 320 to +333
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bugfix needs a unit test.

It can be triggered by having a Metadata object where the list of units is not sorted. The previous code is incorrect on, for instance, the USDC Metadata currently in the Prax registry. That JSON object could be copied into a unit test, which can create a value like "1.234 USDC", parse it, and check that displaying it round trips (because the correct unit is selected).

}

pub fn starts_with(&self, prefix: &str) -> bool {
Expand Down
21 changes: 11 additions & 10 deletions crates/view/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use tap::{Tap, TapFallible};
use tokio::sync::{watch, RwLock};
use tokio_stream::wrappers::WatchStream;
use tonic::{async_trait, transport::Channel, Request, Response, Status};
use tracing::instrument;
use tracing::{instrument, Instrument};
use url::Url;

use penumbra_asset::{asset, asset::Metadata, Value};
Expand Down Expand Up @@ -91,15 +91,9 @@ pub struct ViewServer {

impl ViewServer {
/// Convenience method that calls [`Storage::load_or_initialize`] and then [`Self::new`].
#[instrument(
skip_all,
fields(
path = ?storage_path.as_ref().map(|p| p.as_ref().as_str()),
url = %node,
)
)]
pub async fn load_or_initialize(
storage_path: Option<impl AsRef<Utf8Path>>,
registry_path: Option<impl AsRef<Utf8Path>>,
fvk: &FullViewingKey,
node: Url,
) -> anyhow::Result<Self> {
Expand All @@ -108,6 +102,10 @@ impl ViewServer {
.await?
.tap(|_| tracing::debug!("storage is ready"));

if let Some(registry_path) = registry_path {
storage.load_asset_metadata(registry_path).await?;
}

Self::new(storage, node)
.tap(|_| tracing::trace!("constructing view server"))
.await
Expand All @@ -121,22 +119,25 @@ impl ViewServer {
/// To create multiple [`ViewService`]s, clone the [`ViewService`] returned
/// by this method, rather than calling it multiple times. That way, each clone
/// will be backed by the same scanning task, rather than each spawning its own.
#[instrument(skip_all)]
pub async fn new(storage: Storage, node: Url) -> anyhow::Result<Self> {
let span = tracing::error_span!(parent: None, "view");
let channel = Channel::from_shared(node.to_string())
.with_context(|| "could not parse node URI")?
.connect()
.instrument(span.clone())
.await
.with_context(|| "could not connect to grpc server")
.tap_err(|error| tracing::error!(?error, "could not connect to grpc server"))?;

let (worker, state_commitment_tree, error_slot, sync_height_rx) =
Worker::new(storage.clone(), channel)
.instrument(span.clone())
.tap(|_| tracing::trace!("constructing view server worker"))
.await?
.tap(|_| tracing::debug!("constructed view server worker"));

tokio::spawn(worker.run()).tap(|_| tracing::debug!("spawned view server worker"));
tokio::spawn(worker.run().instrument(span))
.tap(|_| tracing::debug!("spawned view server worker"));

Ok(Self {
storage,
Expand Down
66 changes: 45 additions & 21 deletions crates/view/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,36 @@ impl Storage {
.await?
}

/// Loads asset metadata from a JSON file and use to update the database.
pub async fn load_asset_metadata(
&self,
registry_path: impl AsRef<Utf8Path>,
) -> anyhow::Result<()> {
tracing::debug!(registry_path = ?registry_path.as_ref(), "loading asset metadata");
let registry_path = registry_path.as_ref();
// Parse into a serde_json::Value first so we can get the bits we care about
let mut registry_json: serde_json::Value = serde_json::from_str(
std::fs::read_to_string(registry_path)
.context("failed to read file")?
.as_str(),
)
.context("failed to parse JSON")?;

let registry: BTreeMap<String, Metadata> = serde_json::value::from_value(
registry_json
.get_mut("assetById")
.ok_or_else(|| anyhow::anyhow!("missing assetById"))?
.take(),
)
.context("could not parse asset registry")?;

for metadata in registry.into_values() {
self.record_asset(metadata).await?;
}

Ok(())
}

/// Query for account balance by address
pub async fn balances(
&self,
Expand Down Expand Up @@ -793,14 +823,10 @@ impl Storage {

spawn_blocking(move || {
pool.get()?
.prepare_cached("SELECT * FROM assets")?
.prepare_cached("SELECT metadata FROM assets")?
.query_and_then([], |row| {
let _asset_id: Vec<u8> = row.get("asset_id")?;
let denom: String = row.get("denom")?;

let denom_metadata = asset::REGISTRY
.parse_denom(&denom)
.ok_or_else(|| anyhow::anyhow!("invalid denomination {}", denom))?;
let metadata_json = row.get::<_, String>("metadata")?;
let denom_metadata = serde_json::from_str(&metadata_json)?;

anyhow::Ok(denom_metadata)
})?
Expand All @@ -816,13 +842,10 @@ impl Storage {

spawn_blocking(move || {
pool.get()?
.prepare_cached("SELECT * FROM assets WHERE asset_id = ?1")?
.prepare_cached("SELECT metadata FROM assets WHERE asset_id = ?1")?
.query_and_then([id], |row| {
let _asset_id: Vec<u8> = row.get("asset_id")?;
let denom: String = row.get("denom")?;
let denom_metadata = asset::REGISTRY
.parse_denom(&denom)
.ok_or_else(|| anyhow::anyhow!("invalid denomination {}", denom))?;
let metadata_json = row.get::<_, String>("metadata")?;
let denom_metadata = serde_json::from_str(&metadata_json)?;
anyhow::Ok(denom_metadata)
})?
.next()
Expand All @@ -840,13 +863,10 @@ impl Storage {

spawn_blocking(move || {
pool.get()?
.prepare_cached("SELECT * FROM assets WHERE denom LIKE ?1 ESCAPE '\\'")?
.prepare_cached("SELECT metadata FROM assets WHERE denom LIKE ?1 ESCAPE '\\'")?
.query_and_then([pattern], |row| {
let _asset_id: Vec<u8> = row.get("asset_id")?;
let denom: String = row.get("denom")?;
let denom_metadata = asset::REGISTRY
.parse_denom(&denom)
.ok_or_else(|| anyhow::anyhow!("invalid denomination {}", denom))?;
let metadata_json = row.get::<_, String>("metadata")?;
let denom_metadata = serde_json::from_str(&metadata_json)?;
anyhow::Ok(denom_metadata)
})?
.collect()
Expand Down Expand Up @@ -1030,17 +1050,21 @@ impl Storage {
}).await?
}

#[tracing::instrument(skip(self))]
pub async fn record_asset(&self, asset: Metadata) -> anyhow::Result<()> {
tracing::debug!(?asset);

let asset_id = asset.id().to_bytes().to_vec();
let denom = asset.base_denom().denom;
let metadata_json = serde_json::to_string(&asset)?;

let pool = self.pool.clone();

spawn_blocking(move || {
pool.get()?
.execute(
"INSERT OR IGNORE INTO assets (asset_id, denom) VALUES (?1, ?2)",
(asset_id, denom),
"INSERT OR REPLACE INTO assets (asset_id, denom, metadata) VALUES (?1, ?2, ?3)",
(asset_id, denom, metadata_json),
)
.map_err(anyhow::Error::from)
})
Expand Down
3 changes: 2 additions & 1 deletion crates/view/src/storage/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ CREATE TABLE sync_height (height BIGINT NOT NULL);
-- used for storing a cache of known assets
CREATE TABLE assets (
asset_id BLOB PRIMARY KEY NOT NULL,
denom TEXT NOT NULL
denom TEXT NOT NULL,
metadata TEXT NOT NULL
);

-- the shape information about the sct
Expand Down
Loading