Skip to content

Commit 4d9c57a

Browse files
committed
Properly handle custom homedir, properly wait for tx confirmation
1 parent 491d137 commit 4d9c57a

File tree

3 files changed

+88
-45
lines changed

3 files changed

+88
-45
lines changed

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ penumbra-transaction = { path = "../penumbra/crates/core/transaction", features
2222

2323
# External dependencies
2424
anyhow = "1"
25+
camino = { version = "1" }
2526
directories = "4.0.1"
2627
lazy_static = "1.4.0"
2728
regex = "1"
2829
tracing = "0.1"
2930
tracing-subscriber = "0.2"
3031
tokio = { version = "1.25", features = ["full"] }
31-
clap = { version = "3", features = ["derive"] }
32+
clap = { version = "3", features = ["derive", "env"] }
3233
serde_json = "1"
3334
futures = "0.3"
3435
derivative = "2"

src/opt/serve.rs

+28-22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use anyhow::Context;
22
use binance::{api::Binance, config::Config, market::*};
3+
use camino::Utf8PathBuf;
34
use clap::Parser;
45
use directories::ProjectDirs;
56
use futures::TryStreamExt;
@@ -11,19 +12,23 @@ use penumbra_proto::{
1112
view::v1::{view_service_client::ViewServiceClient, view_service_server::ViewServiceServer},
1213
};
1314
use penumbra_view::{ViewClient, ViewServer};
14-
use std::path::PathBuf;
1515
use url::Url;
1616

1717
use crate::{BinanceFetcher, Trader, Wallet};
1818

1919
#[derive(Debug, Clone, Parser)]
20+
#[clap(
21+
name = "osiris",
22+
about = "Osiris: An example Penumbra price replication LP bot.",
23+
version
24+
)]
2025
pub struct Serve {
2126
/// The transaction fee for each response (paid in upenumbra).
2227
#[structopt(long, default_value = "0")]
2328
fee: u64,
24-
/// Path to the directory to use to store data [default: platform appdata directory].
25-
#[clap(long, short)]
26-
data_dir: Option<PathBuf>,
29+
/// The home directory used to store configuration and data.
30+
#[clap(long, default_value_t = default_home(), env = "PENUMBRA_PCLI_HOME")]
31+
home: Utf8PathBuf,
2732
/// The URL of the pd gRPC endpoint on the remote node.
2833
#[clap(short, long, default_value = "https://grpc.testnet.penumbra.zone")]
2934
node: Url,
@@ -83,17 +88,9 @@ impl Serve {
8388

8489
let penumbra_config = tracing::debug_span!("penumbra-config").entered();
8590
tracing::debug!("importing wallet material");
86-
// Look up the path to the view state file per platform, creating the directory if needed
87-
let data_dir = self.data_dir.unwrap_or_else(|| {
88-
ProjectDirs::from("zone", "penumbra", "pcli")
89-
.expect("can access penumbra project dir")
90-
.data_dir()
91-
.to_owned()
92-
});
93-
std::fs::create_dir_all(&data_dir).context("can create data dir")?;
9491

9592
// Build a custody service...
96-
let pcli_config_file = data_dir.clone().join("config.toml");
93+
let pcli_config_file = self.home.join("config.toml");
9794
let wallet = Wallet::load(pcli_config_file)
9895
.context("failed to load wallet from local custody file")?;
9996
let soft_kms = SoftKms::new(wallet.spend_key.clone().into());
@@ -104,13 +101,8 @@ impl Serve {
104101
// Wait to synchronize the chain before doing anything else.
105102
tracing::info!(%self.node, "starting initial sync: ");
106103
// Instantiate an in-memory view service.
107-
let view_file = data_dir.join("pcli-view.sqlite");
108-
let view_filepath = Some(
109-
view_file
110-
.to_str()
111-
.ok_or_else(|| anyhow::anyhow!("Non-UTF8 view path"))?
112-
.to_string(),
113-
);
104+
let view_file = self.home.join("pcli-view.sqlite");
105+
let view_filepath = Some(view_file.to_string());
114106
let view_storage =
115107
penumbra_view::Storage::load_or_initialize(view_filepath, &fvk, self.node.clone())
116108
.await?;
@@ -129,8 +121,14 @@ impl Serve {
129121

130122
let trader_config = tracing::debug_span!("trader-config").entered();
131123
// Instantiate the trader (manages the bot's portfolio based on MPSC messages containing price quotes)
132-
let (quotes_sender, trader) =
133-
Trader::new(0, fvk, view, custody, symbols.clone(), self.node);
124+
let (quotes_sender, trader) = Trader::new(
125+
self.source_address,
126+
fvk,
127+
view,
128+
custody,
129+
symbols.clone(),
130+
self.node,
131+
);
134132
trader_config.exit();
135133

136134
let _binance_span = tracing::debug_span!("binance-fetcher").entered();
@@ -148,3 +146,11 @@ impl Serve {
148146
}
149147
}
150148
}
149+
150+
fn default_home() -> Utf8PathBuf {
151+
let path = ProjectDirs::from("zone", "penumbra", "pcli")
152+
.expect("Failed to get platform data dir")
153+
.data_dir()
154+
.to_path_buf();
155+
Utf8PathBuf::from_path_buf(path).expect("Platform default data dir was not UTF-8")
156+
}

src/trader.rs

+58-22
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{collections::BTreeMap, future, str::FromStr};
22

33
use anyhow::Context;
44
use binance::model::BookTickerEvent;
5-
use futures::{StreamExt, TryStreamExt};
5+
use futures::{FutureExt as _, StreamExt, TryStreamExt};
66
use penumbra_asset::asset::Metadata;
77
use penumbra_custody::{AuthorizeRequest, CustodyClient};
88
use penumbra_dex::{
@@ -18,6 +18,8 @@ use penumbra_proto::core::component::dex::v1::query_service_client::QueryService
1818
use penumbra_proto::core::component::dex::v1::{
1919
LiquidityPositionsByPriceRequest, LiquidityPositionsRequest,
2020
};
21+
use penumbra_proto::penumbra::view::v1::broadcast_transaction_response::Status as BroadcastStatus;
22+
use penumbra_transaction::txhash::TransactionId;
2123
use penumbra_view::{Planner, ViewClient};
2224
use rand::rngs::OsRng;
2325
use tokio::sync::watch;
@@ -37,32 +39,32 @@ lazy_static! {
3739
(
3840
// ETH priced in terms of BTC
3941
"ETHBTC".to_string(),
40-
DirectedUnitPair::from_str("test_eth:test_btc").unwrap()
42+
DirectedUnitPair::from_str("test_btc:test_eth").unwrap()
4143
),
4244
(
4345
// ETH priced in terms of USDT
4446
"ETHUSDT".to_string(),
45-
DirectedUnitPair::from_str("test_eth:test_usd").unwrap()
47+
DirectedUnitPair::from_str("test_usd:test_eth").unwrap()
4648
),
4749
(
4850
// BTC priced in terms of USD
4951
"BTCUSDT".to_string(),
50-
DirectedUnitPair::from_str("test_btc:test_usd").unwrap()
52+
DirectedUnitPair::from_str("test_usd:test_btc").unwrap()
5153
),
5254
(
5355
// ATOM priced in terms of BTC
5456
"ATOMBTC".to_string(),
55-
DirectedUnitPair::from_str("test_atom:test_btc").unwrap()
57+
DirectedUnitPair::from_str("test_btc:test_atom").unwrap()
5658
),
5759
(
5860
// ATOM priced in terms of USDT
5961
"ATOMUSDT".to_string(),
60-
DirectedUnitPair::from_str("test_atom:test_usd").unwrap()
62+
DirectedUnitPair::from_str("test_usd:test_atom").unwrap()
6163
),
6264
(
6365
// OSMO priced in terms of USDT
6466
"OSMOUSDT".to_string(),
65-
DirectedUnitPair::from_str("test_osmo:test_usd").unwrap()
67+
DirectedUnitPair::from_str("test_usd:test_osmo").unwrap()
6668
),
6769
]);
6870
}
@@ -79,7 +81,7 @@ where
7981
view: V,
8082
custody: C,
8183
fvk: FullViewingKey,
82-
account: u32,
84+
account: AddressIndex,
8385
pd_url: Url,
8486
}
8587

@@ -90,7 +92,7 @@ where
9092
{
9193
/// Create a new trader.
9294
pub fn new(
93-
account: u32,
95+
account: AddressIndex,
9496
fvk: FullViewingKey,
9597
view: V,
9698
custody: C,
@@ -129,15 +131,11 @@ where
129131
pub async fn run(mut self) -> anyhow::Result<()> {
130132
tracing::info!("starting trader");
131133
let trader_span = tracing::debug_span!("trader");
132-
// TODO figure out why this span doesn't display in logs
133134
let _ = trader_span.enter();
134135
tracing::debug!("running trader functionality");
135-
// Doing this loop without any shutdown signal doesn't exactly
136+
// TODO: Doing this loop without any shutdown signal doesn't exactly
136137
// provide a clean shutdown, but it works for now.
137138
loop {
138-
// TODO: ensure we have some positions from `penumbra` to create a more interesting
139-
// trading environment :)
140-
141139
// Check each pair
142140
let mut actions = self.actions.clone();
143141
for (symbol, rx) in actions.iter_mut() {
@@ -173,8 +171,6 @@ where
173171
.expect("missing symbol -> DirectedUnitPair mapping");
174172

175173
// Create a plan that will contain all LP management operations based on this quote.
176-
// TODO: could move this outside the loop, but it's a little easier to debug
177-
// the plans like this for now
178174
let plan = &mut Planner::new(OsRng);
179175

180176
// Find the spendable balance for each asset in the market.
@@ -223,9 +219,6 @@ where
223219
)
224220
.await?;
225221

226-
// TODO: it's possible to immediately close this position within the same block
227-
// however what if we don't get updates every block?
228-
229222
// Finalize and submit the transaction plan.
230223
match self.finalize_and_submit(plan).await {
231224
Ok(_) => {}
@@ -287,7 +280,50 @@ where
287280
.await?;
288281

289282
// 3. Broadcast the transaction and wait for confirmation.
290-
self.view.broadcast_transaction(tx, true).await?;
283+
let mut rsp = self.view.broadcast_transaction(tx, true).await?;
284+
let id: TransactionId = async move {
285+
while let Some(rsp) = rsp.try_next().await? {
286+
match rsp.status {
287+
Some(status) => match status {
288+
BroadcastStatus::BroadcastSuccess(bs) => {
289+
tracing::debug!(
290+
"transaction broadcast successfully: {}",
291+
TransactionId::try_from(
292+
bs.id.expect("detected transaction missing id")
293+
)?
294+
);
295+
}
296+
BroadcastStatus::Confirmed(c) => {
297+
let id = c.id.expect("detected transaction missing id").try_into()?;
298+
if c.detection_height != 0 {
299+
tracing::debug!(
300+
"transaction confirmed and detected: {} @ height {}",
301+
id,
302+
c.detection_height
303+
);
304+
} else {
305+
tracing::debug!("transaction confirmed and detected: {}", id);
306+
}
307+
return Ok(id);
308+
}
309+
},
310+
None => {
311+
// No status is unexpected behavior
312+
return Err(anyhow::anyhow!(
313+
"empty BroadcastTransactionResponse message"
314+
));
315+
}
316+
}
317+
}
318+
319+
Err(anyhow::anyhow!(
320+
"should have received BroadcastTransaction status or error"
321+
))
322+
}
323+
.boxed()
324+
.await
325+
.context("error broadcasting transaction")?;
326+
tracing::debug!(transaction_id = ?id, "broadcasted transaction");
291327

292328
Ok(())
293329
}
@@ -378,9 +414,9 @@ where
378414
market.into_directed_trading_pair(),
379415
spread as u32,
380416
// p is always the scaling value
381-
(scaling_factor as u128 * denom_scaler).into(),
417+
(scaling_factor as u128 * denom_scaler / 1_000).into(),
382418
// price is expressed in units of asset 2
383-
(mid_price as u128 * numer_scaler).into(),
419+
(mid_price as u128 * numer_scaler / 1_000).into(),
384420
reserves,
385421
);
386422

0 commit comments

Comments
 (0)