Skip to content

Commit 5ed9813

Browse files
authored
pcli: add un-advertised support for importing Prax registry data (#4801)
This makes a minimal set of changes to allow `pcli` to make use of the Prax registry. In order to not pre-empt more comprehensive planning about how such an integration should work, this PR just changes `pcli` so that if the registry is placed in a `registry.json` file in the `pcli` home directory, it will be imported into the view database on startup.
1 parent 897b59d commit 5ed9813

File tree

7 files changed

+79
-38
lines changed

7 files changed

+79
-38
lines changed

crates/bin/pcli/src/opt.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,17 @@ impl Opt {
155155
let path = self.home.join(crate::VIEW_FILE_NAME);
156156
tracing::info!(%path, "using local view service");
157157

158+
let registry_path = self.home.join("registry.json");
159+
// Check if the path exists or set it to nojne
160+
let registry_path = if registry_path.exists() {
161+
Some(registry_path)
162+
} else {
163+
None
164+
};
165+
158166
let svc = ViewServer::load_or_initialize(
159167
Some(path),
168+
registry_path,
160169
&config.full_viewing_key,
161170
config.grpc_url.clone(),
162171
)

crates/core/app/tests/app_can_sweep_a_collection_of_small_notes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ async fn app_can_sweep_a_collection_of_small_notes() -> anyhow::Result<()> {
115115
// Spawn the client-side view server...
116116
let view_server = {
117117
penumbra_view::ViewServer::load_or_initialize(
118+
None::<&camino::Utf8Path>,
118119
None::<&camino::Utf8Path>,
119120
&*test_keys::FULL_VIEWING_KEY,
120121
grpc_url,

crates/core/app/tests/view_server_can_be_served_on_localhost.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ async fn view_server_can_be_served_on_localhost() -> anyhow::Result<()> {
9191
// Spawn the client-side view server...
9292
let view_server = {
9393
penumbra_view::ViewServer::load_or_initialize(
94+
None::<&camino::Utf8Path>,
9495
None::<&camino::Utf8Path>,
9596
&*test_keys::FULL_VIEWING_KEY,
9697
grpc_url,

crates/core/asset/src/asset/denom_metadata.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub struct Metadata {
3030
}
3131

3232
// These are constructed by the asset registry.
33+
#[derive(Debug)]
3334
pub(super) struct Inner {
3435
// The Penumbra asset ID
3536
id: Id,
@@ -317,16 +318,19 @@ impl Metadata {
317318
if amount == 0u64.into() {
318319
return self.default_unit();
319320
}
321+
let mut selected_index = 0;
322+
let mut selected_exponent = 0;
320323
for (unit_index, unit) in self.inner.units.iter().enumerate() {
321324
let unit_amount = Amount::from(10u128.pow(unit.exponent as u32));
322-
if amount >= unit_amount {
323-
return Unit {
324-
unit_index,
325-
inner: self.inner.clone(),
326-
};
325+
if unit_amount <= amount && unit.exponent >= selected_exponent {
326+
selected_index = unit_index;
327+
selected_exponent = unit.exponent;
327328
}
328329
}
329-
self.base_unit()
330+
return Unit {
331+
unit_index: selected_index,
332+
inner: self.inner.clone(),
333+
};
330334
}
331335

332336
pub fn starts_with(&self, prefix: &str) -> bool {

crates/view/src/service.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use tap::{Tap, TapFallible};
1616
use tokio::sync::{watch, RwLock};
1717
use tokio_stream::wrappers::WatchStream;
1818
use tonic::{async_trait, transport::Channel, Request, Response, Status};
19-
use tracing::instrument;
19+
use tracing::{instrument, Instrument};
2020
use url::Url;
2121

2222
use penumbra_asset::{asset, asset::Metadata, Value};
@@ -91,15 +91,9 @@ pub struct ViewServer {
9191

9292
impl ViewServer {
9393
/// Convenience method that calls [`Storage::load_or_initialize`] and then [`Self::new`].
94-
#[instrument(
95-
skip_all,
96-
fields(
97-
path = ?storage_path.as_ref().map(|p| p.as_ref().as_str()),
98-
url = %node,
99-
)
100-
)]
10194
pub async fn load_or_initialize(
10295
storage_path: Option<impl AsRef<Utf8Path>>,
96+
registry_path: Option<impl AsRef<Utf8Path>>,
10397
fvk: &FullViewingKey,
10498
node: Url,
10599
) -> anyhow::Result<Self> {
@@ -108,6 +102,10 @@ impl ViewServer {
108102
.await?
109103
.tap(|_| tracing::debug!("storage is ready"));
110104

105+
if let Some(registry_path) = registry_path {
106+
storage.load_asset_metadata(registry_path).await?;
107+
}
108+
111109
Self::new(storage, node)
112110
.tap(|_| tracing::trace!("constructing view server"))
113111
.await
@@ -121,22 +119,25 @@ impl ViewServer {
121119
/// To create multiple [`ViewService`]s, clone the [`ViewService`] returned
122120
/// by this method, rather than calling it multiple times. That way, each clone
123121
/// will be backed by the same scanning task, rather than each spawning its own.
124-
#[instrument(skip_all)]
125122
pub async fn new(storage: Storage, node: Url) -> anyhow::Result<Self> {
123+
let span = tracing::error_span!(parent: None, "view");
126124
let channel = Channel::from_shared(node.to_string())
127125
.with_context(|| "could not parse node URI")?
128126
.connect()
127+
.instrument(span.clone())
129128
.await
130129
.with_context(|| "could not connect to grpc server")
131130
.tap_err(|error| tracing::error!(?error, "could not connect to grpc server"))?;
132131

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

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

141142
Ok(Self {
142143
storage,

crates/view/src/storage.rs

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,36 @@ impl Storage {
255255
.await?
256256
}
257257

258+
/// Loads asset metadata from a JSON file and use to update the database.
259+
pub async fn load_asset_metadata(
260+
&self,
261+
registry_path: impl AsRef<Utf8Path>,
262+
) -> anyhow::Result<()> {
263+
tracing::debug!(registry_path = ?registry_path.as_ref(), "loading asset metadata");
264+
let registry_path = registry_path.as_ref();
265+
// Parse into a serde_json::Value first so we can get the bits we care about
266+
let mut registry_json: serde_json::Value = serde_json::from_str(
267+
std::fs::read_to_string(registry_path)
268+
.context("failed to read file")?
269+
.as_str(),
270+
)
271+
.context("failed to parse JSON")?;
272+
273+
let registry: BTreeMap<String, Metadata> = serde_json::value::from_value(
274+
registry_json
275+
.get_mut("assetById")
276+
.ok_or_else(|| anyhow::anyhow!("missing assetById"))?
277+
.take(),
278+
)
279+
.context("could not parse asset registry")?;
280+
281+
for metadata in registry.into_values() {
282+
self.record_asset(metadata).await?;
283+
}
284+
285+
Ok(())
286+
}
287+
258288
/// Query for account balance by address
259289
pub async fn balances(
260290
&self,
@@ -793,14 +823,10 @@ impl Storage {
793823

794824
spawn_blocking(move || {
795825
pool.get()?
796-
.prepare_cached("SELECT * FROM assets")?
826+
.prepare_cached("SELECT metadata FROM assets")?
797827
.query_and_then([], |row| {
798-
let _asset_id: Vec<u8> = row.get("asset_id")?;
799-
let denom: String = row.get("denom")?;
800-
801-
let denom_metadata = asset::REGISTRY
802-
.parse_denom(&denom)
803-
.ok_or_else(|| anyhow::anyhow!("invalid denomination {}", denom))?;
828+
let metadata_json = row.get::<_, String>("metadata")?;
829+
let denom_metadata = serde_json::from_str(&metadata_json)?;
804830

805831
anyhow::Ok(denom_metadata)
806832
})?
@@ -816,13 +842,10 @@ impl Storage {
816842

817843
spawn_blocking(move || {
818844
pool.get()?
819-
.prepare_cached("SELECT * FROM assets WHERE asset_id = ?1")?
845+
.prepare_cached("SELECT metadata FROM assets WHERE asset_id = ?1")?
820846
.query_and_then([id], |row| {
821-
let _asset_id: Vec<u8> = row.get("asset_id")?;
822-
let denom: String = row.get("denom")?;
823-
let denom_metadata = asset::REGISTRY
824-
.parse_denom(&denom)
825-
.ok_or_else(|| anyhow::anyhow!("invalid denomination {}", denom))?;
847+
let metadata_json = row.get::<_, String>("metadata")?;
848+
let denom_metadata = serde_json::from_str(&metadata_json)?;
826849
anyhow::Ok(denom_metadata)
827850
})?
828851
.next()
@@ -840,13 +863,10 @@ impl Storage {
840863

841864
spawn_blocking(move || {
842865
pool.get()?
843-
.prepare_cached("SELECT * FROM assets WHERE denom LIKE ?1 ESCAPE '\\'")?
866+
.prepare_cached("SELECT metadata FROM assets WHERE denom LIKE ?1 ESCAPE '\\'")?
844867
.query_and_then([pattern], |row| {
845-
let _asset_id: Vec<u8> = row.get("asset_id")?;
846-
let denom: String = row.get("denom")?;
847-
let denom_metadata = asset::REGISTRY
848-
.parse_denom(&denom)
849-
.ok_or_else(|| anyhow::anyhow!("invalid denomination {}", denom))?;
868+
let metadata_json = row.get::<_, String>("metadata")?;
869+
let denom_metadata = serde_json::from_str(&metadata_json)?;
850870
anyhow::Ok(denom_metadata)
851871
})?
852872
.collect()
@@ -1030,17 +1050,21 @@ impl Storage {
10301050
}).await?
10311051
}
10321052

1053+
#[tracing::instrument(skip(self))]
10331054
pub async fn record_asset(&self, asset: Metadata) -> anyhow::Result<()> {
1055+
tracing::debug!(?asset);
1056+
10341057
let asset_id = asset.id().to_bytes().to_vec();
10351058
let denom = asset.base_denom().denom;
1059+
let metadata_json = serde_json::to_string(&asset)?;
10361060

10371061
let pool = self.pool.clone();
10381062

10391063
spawn_blocking(move || {
10401064
pool.get()?
10411065
.execute(
1042-
"INSERT OR IGNORE INTO assets (asset_id, denom) VALUES (?1, ?2)",
1043-
(asset_id, denom),
1066+
"INSERT OR REPLACE INTO assets (asset_id, denom, metadata) VALUES (?1, ?2, ?3)",
1067+
(asset_id, denom, metadata_json),
10441068
)
10451069
.map_err(anyhow::Error::from)
10461070
})

crates/view/src/storage/schema.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ CREATE TABLE sync_height (height BIGINT NOT NULL);
1515
-- used for storing a cache of known assets
1616
CREATE TABLE assets (
1717
asset_id BLOB PRIMARY KEY NOT NULL,
18-
denom TEXT NOT NULL
18+
denom TEXT NOT NULL,
19+
metadata TEXT NOT NULL
1920
);
2021

2122
-- the shape information about the sct

0 commit comments

Comments
 (0)