Skip to content

Commit 467becf

Browse files
authored
Merge pull request #14 from DIG-Network/hints_and_sync
Hints and sync
2 parents b6362ec + caa6faf commit 467becf

File tree

4 files changed

+275
-18
lines changed

4 files changed

+275
-18
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ This library offers the following functions:
66

77
- wallet: `selectCoins`, `addFee`, `signCoinSpends`, `sendXch`
88
- drivers: `mintStore`, `adminDelegatedPuzzleFromKey`, `writerDelegatedPuzzleFromKey`, `oracleDelegatedPuzzle`, `oracleSpend`, `updateStoreMetadata`, `updateStoreOwnership`, `meltStore`, `getCost`, `createServerCoin`, `lookupAndSpendServerCoins`
9-
- utils: `getCoinId`, `masterPublicKeyToWalletSyntheticKey`, `masterPublicKeyToFirstPuzzleHash`, `masterSecretKeyToWalletSyntheticSecretKey`, `secretKeyToPublicKey`, `puzzleHashToAddress`, `addressToPuzzleHash`, `newLineageProof`, `newEveProof`, `signMessage`, `verifySignedMessage`, `syntheticKeyToPuzzleHash`, `morphLauncherId`, `getMainnetGenesisChallenge`, `getTestnet11GenesisChallenge`
9+
- utils: `getCoinId`, `masterPublicKeyToWalletSyntheticKey`, `masterPublicKeyToFirstPuzzleHash`, `masterSecretKeyToWalletSyntheticSecretKey`, `secretKeyToPublicKey`, `puzzleHashToAddress`, `addressToPuzzleHash`, `newLineageProof`, `newEveProof`, `signMessage`, `verifySignedMessage`, `syntheticKeyToPuzzleHash`, `morphLauncherId`, `getMainnetGenesisChallenge`, `getTestnet11GenesisChallenge`.
1010

11-
The `Peer` class also exposes the following methods: `getAllUnspentCoins`, `syncStore`, `syncStoreFromLauncherId`, `broadcastSpend`, `isCoinSpent`, `getHeaderHash`, `getFeeEstimate`, `getPeak`, `getHintedCoinStates`, `fetchServerCoin`, `getStoreCreationHeight`.
11+
The `Peer` class also exposes the following methods: `getAllUnspentCoins`, `syncStore`, `syncStoreFromLauncherId`, `broadcastSpend`, `isCoinSpent`, `getHeaderHash`, `getFeeEstimate`, `getPeak`, `getHintedCoinStates`, `fetchServerCoin`, `getStoreCreationHeight`, `lookUpPossibleLaunchers`, `waitForCoinToBeSpent`.
1212

1313
Note that all functions come with detailed JSDoc comments.
1414

index.d.ts

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,18 @@ export interface UnspentCoinsResponse {
178178
lastHeight: number
179179
lastHeaderHash: Buffer
180180
}
181+
/**
182+
* Represents a response containing possible launcher ids for datastores.
183+
*
184+
* @property {Vec<Buffer>} launcher_ids - Launcher ids of coins that might be datastores.
185+
* @property {u32} lastHeight - Last height.
186+
* @property {Buffer} lastHeaderHash - Last header hash.
187+
*/
188+
export interface PossibleLaunchersResponse {
189+
launcherIds: Array<Buffer>
190+
lastHeight: number
191+
lastHeaderHash: Buffer
192+
}
181193
/**
182194
* Selects coins using the knapsack algorithm.
183195
*
@@ -403,17 +415,6 @@ export declare function syntheticKeyToPuzzleHash(syntheticKey: Buffer): Buffer
403415
* @returns {BigInt} The cost of the coin spends.
404416
*/
405417
export declare function getCost(coinSpends: Array<CoinSpend>): bigint
406-
407-
export declare class Tls {
408-
/**
409-
* Creates a new TLS connector.
410-
*
411-
* @param {String} certPath - Path to the certificate file (usually '~/.chia/mainnet/config/ssl/wallet/wallet_node.crt').
412-
* @param {String} keyPath - Path to the key file (usually '~/.chia/mainnet/config/ssl/wallet/wallet_node.key').
413-
*/
414-
constructor(certPath: string, keyPath: string)
415-
}
416-
417418
/**
418419
* Returns the mainnet genesis challenge.
419420
*
@@ -426,7 +427,15 @@ export declare function getMainnetGenesisChallenge(): Buffer
426427
* @returns {Buffer} The testnet11 genesis challenge.
427428
*/
428429
export declare function getTestnet11GenesisChallenge(): Buffer
429-
430+
export declare class Tls {
431+
/**
432+
* Creates a new TLS connector.
433+
*
434+
* @param {String} certPath - Path to the certificate file (usually '~/.chia/mainnet/config/ssl/wallet/wallet_node.crt').
435+
* @param {String} keyPath - Path to the key file (usually '~/.chia/mainnet/config/ssl/wallet/wallet_node.key').
436+
*/
437+
constructor(certPath: string, keyPath: string)
438+
}
430439
export declare class Peer {
431440
/**
432441
* Creates a new Peer instance.
@@ -538,4 +547,21 @@ export declare class Peer {
538547
* @param {bool} forTestnet - True for testnet, false for mainnet.
539548
*/
540549
lookupAndSpendServerCoins(syntheticKey: Buffer, selectedCoins: Array<Coin>, fee: bigint, forTestnet: boolean): Promise<Array<CoinSpend>>
550+
/**
551+
* Looks up possible datastore launchers by searching for singleton launchers created with a DL-specific hint.
552+
*
553+
* @param {Option<u32>} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block.
554+
* @param {Buffer} headerHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain.
555+
* @returns {Promise<PossibleLaunchersResponse>} Possible launcher ids for datastores, as well as a height + header hash combo to use for the next call.
556+
*/
557+
lookUpPossibleLaunchers(lastHeight: number | undefined | null, headerHash: Buffer): Promise<PossibleLaunchersResponse>
558+
/**
559+
* Waits for a coin to be spent on-chain.
560+
*
561+
* @param {Buffer} coin_id - Id of coin to track.
562+
* @param {Option<u32>} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block.
563+
* @param {Buffer} headerHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain.
564+
* @returns {Promise<Buffer>} Promise that resolves when the coin is spent (returning the coin id).
565+
*/
566+
waitForCoinToBeSpent(coinId: Buffer, lastHeight: number | undefined | null, headerHash: Buffer): Promise<Buffer>
541567
}

src/lib.rs

Lines changed: 141 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use chia::bls::{
1111

1212
use chia::protocol::{
1313
Bytes as RustBytes, Bytes32 as RustBytes32, Coin as RustCoin, CoinSpend as RustCoinSpend,
14-
NewPeakWallet, ProtocolMessageTypes, SpendBundle as RustSpendBundle,
14+
CoinStateUpdate, NewPeakWallet, ProtocolMessageTypes, SpendBundle as RustSpendBundle,
1515
};
1616
use chia::puzzles::{standard::StandardArgs, DeriveSynthetic, Proof as RustProof};
1717
use chia::traits::Streamable;
@@ -26,9 +26,14 @@ use js::{Coin, CoinSpend, CoinState, EveProof, Proof, ServerCoin};
2626
use napi::bindgen_prelude::*;
2727
use napi::Result;
2828
use native_tls::TlsConnector;
29+
use std::collections::HashMap;
2930
use std::{net::SocketAddr, sync::Arc};
31+
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
3032
use tokio::sync::Mutex;
31-
use wallet::{SuccessResponse as RustSuccessResponse, SyncStoreResponse as RustSyncStoreResponse};
33+
use wallet::{
34+
PossibleLaunchersResponse as RustPossibleLaunchersResponse,
35+
SuccessResponse as RustSuccessResponse, SyncStoreResponse as RustSyncStoreResponse,
36+
};
3237

3338
pub use wallet::*;
3439

@@ -385,6 +390,46 @@ impl ToJs<UnspentCoinsResponse> for rust::UnspentCoinsResponse {
385390
}
386391
}
387392

393+
#[napi(object)]
394+
/// Represents a response containing possible launcher ids for datastores.
395+
///
396+
/// @property {Vec<Buffer>} launcher_ids - Launcher ids of coins that might be datastores.
397+
/// @property {u32} lastHeight - Last height.
398+
/// @property {Buffer} lastHeaderHash - Last header hash.
399+
pub struct PossibleLaunchersResponse {
400+
pub launcher_ids: Vec<Buffer>,
401+
pub last_height: u32,
402+
pub last_header_hash: Buffer,
403+
}
404+
405+
impl FromJs<PossibleLaunchersResponse> for RustPossibleLaunchersResponse {
406+
fn from_js(value: PossibleLaunchersResponse) -> Result<Self> {
407+
Ok(RustPossibleLaunchersResponse {
408+
last_header_hash: RustBytes32::from_js(value.last_header_hash)?,
409+
last_height: value.last_height,
410+
launcher_ids: value
411+
.launcher_ids
412+
.into_iter()
413+
.map(RustBytes32::from_js)
414+
.collect::<Result<Vec<RustBytes32>>>()?,
415+
})
416+
}
417+
}
418+
419+
impl ToJs<PossibleLaunchersResponse> for RustPossibleLaunchersResponse {
420+
fn to_js(&self) -> Result<PossibleLaunchersResponse> {
421+
Ok(PossibleLaunchersResponse {
422+
last_header_hash: self.last_header_hash.to_js()?,
423+
last_height: self.last_height,
424+
launcher_ids: self
425+
.launcher_ids
426+
.iter()
427+
.map(RustBytes32::to_js)
428+
.collect::<Result<Vec<Buffer>>>()?,
429+
})
430+
}
431+
}
432+
388433
#[napi]
389434
pub struct Tls(TlsConnector);
390435

@@ -406,6 +451,7 @@ impl Tls {
406451
pub struct Peer {
407452
inner: Arc<RustPeer>,
408453
peak: Arc<Mutex<Option<NewPeakWallet>>>,
454+
coin_listeners: Arc<Mutex<HashMap<RustBytes32, UnboundedSender<()>>>>,
409455
}
410456

411457
#[napi]
@@ -436,8 +482,12 @@ impl Peer {
436482

437483
let inner = Arc::new(peer);
438484
let peak = Arc::new(Mutex::new(None));
485+
let coin_listeners = Arc::new(Mutex::new(
486+
HashMap::<RustBytes32, UnboundedSender<()>>::new(),
487+
));
439488

440489
let peak_clone = peak.clone();
490+
let coin_listeners_clone = coin_listeners.clone();
441491
tokio::spawn(async move {
442492
while let Some(message) = receiver.recv().await {
443493
if message.msg_type == ProtocolMessageTypes::NewPeakWallet {
@@ -446,10 +496,33 @@ impl Peer {
446496
*peak_guard = Some(new_peak);
447497
}
448498
}
499+
500+
if message.msg_type == ProtocolMessageTypes::CoinStateUpdate {
501+
if let Ok(coin_state_update) = CoinStateUpdate::from_bytes(&message.data) {
502+
let mut listeners = coin_listeners_clone.lock().await;
503+
504+
for coin_state_update_item in coin_state_update.items {
505+
if coin_state_update_item.spent_height.is_none() {
506+
continue;
507+
}
508+
509+
if let Some(listener) =
510+
listeners.get(&coin_state_update_item.coin.coin_id())
511+
{
512+
let _ = listener.send(());
513+
listeners.remove(&coin_state_update_item.coin.coin_id());
514+
}
515+
}
516+
}
517+
}
449518
}
450519
});
451520

452-
Ok(Self { inner, peak })
521+
Ok(Self {
522+
inner,
523+
peak,
524+
coin_listeners,
525+
})
453526
}
454527

455528
#[napi]
@@ -739,6 +812,71 @@ impl Peer {
739812
.map(|c| c.to_js())
740813
.collect::<Result<Vec<CoinSpend>>>()
741814
}
815+
816+
#[napi]
817+
/// Looks up possible datastore launchers by searching for singleton launchers created with a DL-specific hint.
818+
///
819+
/// @param {Option<u32>} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block.
820+
/// @param {Buffer} headerHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain.
821+
/// @returns {Promise<PossibleLaunchersResponse>} Possible launcher ids for datastores, as well as a height + header hash combo to use for the next call.
822+
pub async fn look_up_possible_launchers(
823+
&self,
824+
last_height: Option<u32>,
825+
header_hash: Buffer,
826+
) -> napi::Result<PossibleLaunchersResponse> {
827+
wallet::look_up_possible_launchers(
828+
&self.inner.clone(),
829+
last_height,
830+
RustBytes32::from_js(header_hash)?,
831+
)
832+
.await
833+
.map_err(js::err)?
834+
.to_js()
835+
}
836+
837+
#[napi]
838+
/// Waits for a coin to be spent on-chain.
839+
///
840+
/// @param {Buffer} coin_id - Id of coin to track.
841+
/// @param {Option<u32>} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block.
842+
/// @param {Buffer} headerHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain.
843+
/// @returns {Promise<Buffer>} Promise that resolves when the coin is spent (returning the coin id).
844+
pub async fn wait_for_coin_to_be_spent(
845+
&self,
846+
coin_id: Buffer,
847+
last_height: Option<u32>,
848+
header_hash: Buffer,
849+
) -> napi::Result<Buffer> {
850+
let rust_coin_id = RustBytes32::from_js(coin_id)?;
851+
let spent_height = wallet::subscribe_to_coin_states(
852+
&self.inner.clone(),
853+
rust_coin_id,
854+
last_height,
855+
RustBytes32::from_js(header_hash)?,
856+
)
857+
.await
858+
.map_err(js::err)?;
859+
860+
if spent_height.is_none() {
861+
let (sender, mut receiver) = unbounded_channel::<()>();
862+
863+
{
864+
let mut listeners = self.coin_listeners.lock().await;
865+
listeners.insert(rust_coin_id, sender);
866+
}
867+
868+
receiver
869+
.recv()
870+
.await
871+
.ok_or_else(|| js::err("Failed to receive spent notification"))?;
872+
}
873+
874+
wallet::unsubscribe_from_coin_states(&self.inner.clone(), rust_coin_id)
875+
.await
876+
.map_err(js::err)?;
877+
878+
rust_coin_id.to_js()
879+
}
742880
}
743881

744882
/// Selects coins using the knapsack algorithm.

0 commit comments

Comments
 (0)