mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-01-20 19:11:09 +03:00
Add 'build_output' endpoint to owner api (#641)
* add 'build_output' endpoint to owner api * merge fix
This commit is contained in:
parent
c424a0ed10
commit
2924a0ddb2
6 changed files with 245 additions and 14 deletions
|
@ -19,6 +19,7 @@ use ed25519_dalek::SecretKey as DalekSecretKey;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::config::{TorConfig, WalletConfig};
|
use crate::config::{TorConfig, WalletConfig};
|
||||||
|
use crate::core::core::OutputFeatures;
|
||||||
use crate::core::global;
|
use crate::core::global;
|
||||||
use crate::impls::HttpSlateSender;
|
use crate::impls::HttpSlateSender;
|
||||||
use crate::impls::SlateSender as _;
|
use crate::impls::SlateSender as _;
|
||||||
|
@ -26,9 +27,9 @@ use crate::keychain::{Identifier, Keychain};
|
||||||
use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage};
|
use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage};
|
||||||
use crate::libwallet::api_impl::{owner, owner_updater};
|
use crate::libwallet::api_impl::{owner, owner_updater};
|
||||||
use crate::libwallet::{
|
use crate::libwallet::{
|
||||||
AcctPathMapping, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult,
|
AcctPathMapping, BuiltOutput, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient,
|
||||||
OutputCommitMapping, PaymentProof, Slate, Slatepack, SlatepackAddress, TxLogEntry, ViewWallet,
|
NodeHeightResult, OutputCommitMapping, PaymentProof, Slate, Slatepack, SlatepackAddress,
|
||||||
WalletInfo, WalletInst, WalletLCProvider,
|
TxLogEntry, ViewWallet, WalletInfo, WalletInst, WalletLCProvider,
|
||||||
};
|
};
|
||||||
use crate::util::logger::LoggingConfig;
|
use crate::util::logger::LoggingConfig;
|
||||||
use crate::util::secp::key::SecretKey;
|
use crate::util::secp::key::SecretKey;
|
||||||
|
@ -2404,6 +2405,18 @@ where
|
||||||
) -> Result<(bool, bool), Error> {
|
) -> Result<(bool, bool), Error> {
|
||||||
owner::verify_payment_proof(self.wallet_inst.clone(), keychain_mask, proof)
|
owner::verify_payment_proof(self.wallet_inst.clone(), keychain_mask, proof)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds an output
|
||||||
|
pub fn build_output(
|
||||||
|
&self,
|
||||||
|
keychain_mask: Option<&SecretKey>,
|
||||||
|
features: OutputFeatures,
|
||||||
|
amount: u64,
|
||||||
|
) -> Result<BuiltOutput, Error> {
|
||||||
|
let mut w_lock = self.wallet_inst.lock();
|
||||||
|
let w = w_lock.lc_provider()?.wallet_inst()?;
|
||||||
|
owner::build_output(&mut **w, keychain_mask, features, amount)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// attempt to send slate synchronously with TOR
|
/// attempt to send slate synchronously with TOR
|
||||||
|
|
|
@ -16,12 +16,14 @@
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::config::{TorConfig, WalletConfig};
|
use crate::config::{TorConfig, WalletConfig};
|
||||||
|
use crate::core::core::OutputFeatures;
|
||||||
use crate::core::global;
|
use crate::core::global;
|
||||||
use crate::keychain::{Identifier, Keychain};
|
use crate::keychain::{Identifier, Keychain};
|
||||||
use crate::libwallet::{
|
use crate::libwallet::{
|
||||||
AcctPathMapping, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult,
|
AcctPathMapping, Amount, BuiltOutput, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient,
|
||||||
OutputCommitMapping, PaymentProof, Slate, SlateVersion, Slatepack, SlatepackAddress,
|
NodeHeightResult, OutputCommitMapping, PaymentProof, Slate, SlateVersion, Slatepack,
|
||||||
StatusMessage, TxLogEntry, VersionedSlate, ViewWallet, WalletInfo, WalletLCProvider,
|
SlatepackAddress, StatusMessage, TxLogEntry, VersionedSlate, ViewWallet, WalletInfo,
|
||||||
|
WalletLCProvider,
|
||||||
};
|
};
|
||||||
use crate::util::logger::LoggingConfig;
|
use crate::util::logger::LoggingConfig;
|
||||||
use crate::util::secp::key::{PublicKey, SecretKey};
|
use crate::util::secp::key::{PublicKey, SecretKey};
|
||||||
|
@ -1831,6 +1833,50 @@ pub trait OwnerRpc {
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
fn set_tor_config(&self, tor_config: Option<TorConfig>) -> Result<(), ErrorKind>;
|
fn set_tor_config(&self, tor_config: Option<TorConfig>) -> Result<(), ErrorKind>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Networked version of [Owner::build_output](struct.Owner.html#method.build_output).
|
||||||
|
```
|
||||||
|
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
|
||||||
|
# r#"
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "build_output",
|
||||||
|
"params": {
|
||||||
|
"token": "d202964900000000d302964900000000d402964900000000d502964900000000",
|
||||||
|
"features": "Plain",
|
||||||
|
"amount": "60000000000"
|
||||||
|
},
|
||||||
|
"id": 1
|
||||||
|
}
|
||||||
|
# "#
|
||||||
|
# ,
|
||||||
|
# r#"
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": {
|
||||||
|
"Ok": {
|
||||||
|
"blind": "089705aa74b638ee391e295d227c534a50dd58e603bca97a4404747cf8a5a189",
|
||||||
|
"key_id": "0300000000000000000000000000000000",
|
||||||
|
"output": {
|
||||||
|
"commit": "08e1da9e6dc4d6e808a718b2f110a991dd775d65ce5ae408a4e1f002a4961aa9e7",
|
||||||
|
"features": "Plain",
|
||||||
|
"proof": "4b5d6fb1b4d143fc50c83aef61c5410be760a395ed71f3424f7746bf5ee0539ae299569d99b73ea6583b1057834551faa0ac8cfe34c75431b86d6f37dec1ff070fc01f44babf0d3446781564ff7a143242ea67cb4ff7b11fe399735695c3fe70b40b71f31b04cf73b1d1f3430fb53a8c9f990fae48c09b42f8212d60a2d3ce0b8ea4dc0d37a82c3f328162ab8d50f48c28cb9a721a87a40aa3915bf9fffc0cd820e15b758e8565ad7fbf22d03711dc83f98e7c9f955d9398a1c75bc96df2ee64751592953cced38527b3f68282d2ca2fdf2994fbd93a1642fb9d265d57c3cf7df01501da569f2b4e606a1c3084c807a39947a3e1fd41b0647891e1f64842a2b98e694b93857e30691e0b0bca7bc49dec9d6af1003a40b3431ae0bcae8454a438523d066dcac4f194d8370c5ba6567830f302e1ec2607b8d1720bb6c6c57c549f1a3ef7ad2b54dfdd0178329e0723b8a55b438a1e43a984c072d6505aa5e193042d9703484c8383e78d9553684fad5e399f11f8ae6577e4ac4e3c2478e3fd8df0164600b4816b2167c2bf5b9fd7dd29cc1041fccbf1392240fd7c1dc39dd1ebc86b882a383dfe683e9f029d40b2829e3bf56b9760e1d81b7ad4a9066b1c01ccbea6b196154443cacedaccd5ff4fd25cbd9a8f0d271d5688bbe4b956fd34d3413d0478ac9400f6f1ff3890dea10be072d2d48bfa69a6e1e1b6fffaa9db4663eb1ecc26da331072877eb6d4a05a41584d44ed5d2a96a98727563bf180768940c99a15e9183ae927f47f2c0e13d9c00d7ebf0dacb1b6c139d3e18701d10c9d1ef300eeeab756eaa4584c3f5fb42793f7c2517601ae31d887c177eec8bce35c0aa16ba6991fd885deb9ff7b44ffd489f8e9e9d0717141501143c027d33e8a4baf6d85c859ff8a04d1aafbb3d1a97dc6c8ee3642ec41b8e43a137b43c8e60d69a6f19eb9749e"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# "#
|
||||||
|
# , 0, false, false, false, false);
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
fn build_output(
|
||||||
|
&self,
|
||||||
|
token: Token,
|
||||||
|
features: OutputFeatures,
|
||||||
|
amount: Amount,
|
||||||
|
) -> Result<BuiltOutput, ErrorKind>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<L, C, K> OwnerRpc for Owner<L, C, K>
|
impl<L, C, K> OwnerRpc for Owner<L, C, K>
|
||||||
|
@ -2252,6 +2298,16 @@ where
|
||||||
Owner::set_tor_config(self, tor_config);
|
Owner::set_tor_config(self, tor_config);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_output(
|
||||||
|
&self,
|
||||||
|
token: Token,
|
||||||
|
features: OutputFeatures,
|
||||||
|
amount: Amount,
|
||||||
|
) -> Result<BuiltOutput, ErrorKind> {
|
||||||
|
Owner::build_output(self, (&token.keychain_mask).as_ref(), features, amount.0)
|
||||||
|
.map_err(|e| e.kind())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// helper to set up a real environment to run integrated doctests
|
/// helper to set up a real environment to run integrated doctests
|
||||||
|
|
101
controller/tests/build_output.rs
Normal file
101
controller/tests/build_output.rs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
// Copyright 2021 The Grin Developers
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
extern crate grin_wallet_controller as wallet;
|
||||||
|
extern crate grin_wallet_impls as impls;
|
||||||
|
extern crate grin_wallet_util;
|
||||||
|
|
||||||
|
use grin_wallet_libwallet as libwallet;
|
||||||
|
use grin_wallet_util::grin_core::core::OutputFeatures;
|
||||||
|
use grin_wallet_util::grin_keychain::{
|
||||||
|
mnemonic, BlindingFactor, ExtKeychain, ExtKeychainPath, Keychain, SwitchCommitmentType,
|
||||||
|
};
|
||||||
|
use grin_wallet_util::grin_util::{secp, ZeroingString};
|
||||||
|
use impls::test_framework::LocalWalletClient;
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod common;
|
||||||
|
use common::{clean_output_dir, create_wallet_proxy, setup};
|
||||||
|
|
||||||
|
fn build_output_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||||
|
// Generate seed so we can verify the blinding factor is derived correctly
|
||||||
|
let seed: [u8; 32] = thread_rng().gen();
|
||||||
|
let keychain = ExtKeychain::from_seed(&seed, false).unwrap();
|
||||||
|
let mnemonic = mnemonic::from_entropy(&seed).unwrap();
|
||||||
|
|
||||||
|
// Create a new proxy to simulate server and wallet responses
|
||||||
|
let mut wallet_proxy = create_wallet_proxy(test_dir);
|
||||||
|
let stopper = wallet_proxy.running.clone();
|
||||||
|
|
||||||
|
create_wallet_and_add!(
|
||||||
|
client1,
|
||||||
|
wallet1,
|
||||||
|
mask1_i,
|
||||||
|
test_dir,
|
||||||
|
"wallet1",
|
||||||
|
Some(ZeroingString::from(mnemonic)),
|
||||||
|
&mut wallet_proxy,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
let mask1 = (&mask1_i).as_ref();
|
||||||
|
|
||||||
|
// Set the wallet proxy listener running
|
||||||
|
thread::spawn(move || {
|
||||||
|
if let Err(e) = wallet_proxy.run() {
|
||||||
|
error!("Wallet Proxy error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
|
||||||
|
let features = OutputFeatures::Plain;
|
||||||
|
let amount = 60_000_000_000;
|
||||||
|
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
|
||||||
|
let built_output = sender_api.build_output(m, features, amount)?;
|
||||||
|
|
||||||
|
let key_id = built_output.key_id;
|
||||||
|
assert_eq!(key_id.to_path(), ExtKeychainPath::new(3, 0, 0, 0, 0));
|
||||||
|
|
||||||
|
let blind = built_output.blind;
|
||||||
|
let key = keychain.derive_key(amount, &key_id, SwitchCommitmentType::Regular)?;
|
||||||
|
assert_eq!(blind, BlindingFactor::from_secret_key(key.clone()));
|
||||||
|
|
||||||
|
let output = built_output.output;
|
||||||
|
assert_eq!(output.features(), features);
|
||||||
|
assert_eq!(output.commitment(), secp.commit(amount, key)?);
|
||||||
|
output.verify_proof()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// let logging finish
|
||||||
|
stopper.store(false, Ordering::Relaxed);
|
||||||
|
thread::sleep(Duration::from_millis(200));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_output() {
|
||||||
|
let test_dir = "test_output/build_output";
|
||||||
|
setup(test_dir);
|
||||||
|
if let Err(e) = build_output_test_impl(test_dir) {
|
||||||
|
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
|
||||||
|
}
|
||||||
|
clean_output_dir(test_dir);
|
||||||
|
}
|
|
@ -17,7 +17,8 @@
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::grin_core::core::hash::Hashed;
|
use crate::grin_core::core::hash::Hashed;
|
||||||
use crate::grin_core::core::Transaction;
|
use crate::grin_core::core::{Output, OutputFeatures, Transaction};
|
||||||
|
use crate::grin_core::libtx::proof;
|
||||||
use crate::grin_keychain::ViewKey;
|
use crate::grin_keychain::ViewKey;
|
||||||
use crate::grin_util::secp::key::SecretKey;
|
use crate::grin_util::secp::key::SecretKey;
|
||||||
use crate::grin_util::Mutex;
|
use crate::grin_util::Mutex;
|
||||||
|
@ -25,14 +26,14 @@ use crate::grin_util::ToHex;
|
||||||
use crate::util::{OnionV3Address, OnionV3AddressError};
|
use crate::util::{OnionV3Address, OnionV3AddressError};
|
||||||
|
|
||||||
use crate::api_impl::owner_updater::StatusMessage;
|
use crate::api_impl::owner_updater::StatusMessage;
|
||||||
use crate::grin_keychain::{Identifier, Keychain};
|
use crate::grin_keychain::{BlindingFactor, Identifier, Keychain, SwitchCommitmentType};
|
||||||
use crate::internal::{keys, scan, selection, tx, updater};
|
use crate::internal::{keys, scan, selection, tx, updater};
|
||||||
use crate::slate::{PaymentInfo, Slate, SlateState};
|
use crate::slate::{PaymentInfo, Slate, SlateState};
|
||||||
use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, WalletBackend, WalletInfo};
|
use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, WalletBackend, WalletInfo};
|
||||||
use crate::{
|
use crate::{
|
||||||
address, wallet_lock, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping,
|
address, wallet_lock, BuiltOutput, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult,
|
||||||
PaymentProof, ScannedBlockInfo, Slatepack, SlatepackAddress, Slatepacker, SlatepackerArgs,
|
OutputCommitMapping, PaymentProof, ScannedBlockInfo, Slatepack, SlatepackAddress, Slatepacker,
|
||||||
TxLogEntryType, ViewWallet, WalletInitStatus, WalletInst, WalletLCProvider,
|
SlatepackerArgs, TxLogEntryType, ViewWallet, WalletInitStatus, WalletInst, WalletLCProvider,
|
||||||
};
|
};
|
||||||
use crate::{Error, ErrorKind};
|
use crate::{Error, ErrorKind};
|
||||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
|
@ -1376,3 +1377,42 @@ where
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds an output for the wallet's next available key
|
||||||
|
pub fn build_output<'a, T: ?Sized, C, K>(
|
||||||
|
w: &mut T,
|
||||||
|
keychain_mask: Option<&SecretKey>,
|
||||||
|
features: OutputFeatures,
|
||||||
|
amount: u64,
|
||||||
|
) -> Result<BuiltOutput, Error>
|
||||||
|
where
|
||||||
|
T: WalletBackend<'a, C, K>,
|
||||||
|
C: NodeClient + 'a,
|
||||||
|
K: Keychain + 'a,
|
||||||
|
{
|
||||||
|
let k = w.keychain(keychain_mask)?;
|
||||||
|
|
||||||
|
let key_id = keys::next_available_key(&mut *w, keychain_mask)?;
|
||||||
|
|
||||||
|
let blind = k.derive_key(amount, &key_id, SwitchCommitmentType::Regular)?;
|
||||||
|
let commit = k.secp().commit(amount, blind.clone())?;
|
||||||
|
|
||||||
|
let proof_builder = proof::ProofBuilder::new(&k);
|
||||||
|
let proof = proof::create(
|
||||||
|
&k,
|
||||||
|
&proof_builder,
|
||||||
|
amount,
|
||||||
|
&key_id,
|
||||||
|
SwitchCommitmentType::Regular,
|
||||||
|
commit,
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let output = Output::new(features, commit, proof);
|
||||||
|
|
||||||
|
Ok(BuiltOutput {
|
||||||
|
blind: BlindingFactor::from_secret_key(blind),
|
||||||
|
key_id: key_id,
|
||||||
|
output: output,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -14,8 +14,9 @@
|
||||||
|
|
||||||
//! Types specific to the wallet api, mostly argument serialization
|
//! Types specific to the wallet api, mostly argument serialization
|
||||||
|
|
||||||
|
use crate::grin_core::core::Output;
|
||||||
use crate::grin_core::libtx::secp_ser;
|
use crate::grin_core::libtx::secp_ser;
|
||||||
use crate::grin_keychain::Identifier;
|
use crate::grin_keychain::{BlindingFactor, Identifier};
|
||||||
use crate::grin_util::secp::pedersen;
|
use crate::grin_util::secp::pedersen;
|
||||||
use crate::slate_versions::ser as dalek_ser;
|
use crate::slate_versions::ser as dalek_ser;
|
||||||
use crate::slate_versions::SlateVersion;
|
use crate::slate_versions::SlateVersion;
|
||||||
|
@ -24,6 +25,11 @@ use crate::SlatepackAddress;
|
||||||
|
|
||||||
use ed25519_dalek::Signature as DalekSignature;
|
use ed25519_dalek::Signature as DalekSignature;
|
||||||
|
|
||||||
|
/// Type for storing amounts (in nanogrins).
|
||||||
|
/// Serializes as a string but can deserialize from a string or u64.
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Amount(#[serde(with = "secp_ser::string_or_u64")] pub u64);
|
||||||
|
|
||||||
/// V2 Init / Send TX API Args
|
/// V2 Init / Send TX API Args
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct InitTxArgs {
|
pub struct InitTxArgs {
|
||||||
|
@ -216,3 +222,18 @@ pub struct PaymentProof {
|
||||||
#[serde(with = "dalek_ser::dalek_sig_serde")]
|
#[serde(with = "dalek_ser::dalek_sig_serde")]
|
||||||
pub sender_sig: DalekSignature,
|
pub sender_sig: DalekSignature,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build output result
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct BuiltOutput {
|
||||||
|
/// Blinding Factor
|
||||||
|
#[serde(
|
||||||
|
serialize_with = "secp_ser::as_hex",
|
||||||
|
deserialize_with = "secp_ser::blind_from_hex"
|
||||||
|
)]
|
||||||
|
pub blind: BlindingFactor,
|
||||||
|
/// Key Identifier
|
||||||
|
pub key_id: Identifier,
|
||||||
|
/// Output
|
||||||
|
pub output: Output,
|
||||||
|
}
|
||||||
|
|
|
@ -67,8 +67,8 @@ pub use crate::slatepack::{
|
||||||
};
|
};
|
||||||
pub use api_impl::owner_updater::StatusMessage;
|
pub use api_impl::owner_updater::StatusMessage;
|
||||||
pub use api_impl::types::{
|
pub use api_impl::types::{
|
||||||
BlockFees, InitTxArgs, InitTxSendArgs, IssueInvoiceTxArgs, NodeHeightResult,
|
Amount, BlockFees, BuiltOutput, InitTxArgs, InitTxSendArgs, IssueInvoiceTxArgs,
|
||||||
OutputCommitMapping, PaymentProof, VersionInfo,
|
NodeHeightResult, OutputCommitMapping, PaymentProof, VersionInfo,
|
||||||
};
|
};
|
||||||
pub use internal::scan::scan;
|
pub use internal::scan::scan;
|
||||||
pub use slate_versions::ser as dalek_ser;
|
pub use slate_versions::ser as dalek_ser;
|
||||||
|
|
Loading…
Reference in a new issue