Skip to content

Commit

Permalink
Merge pull request #540 from TheCharlatan/persistMoneroAddresses
Browse files Browse the repository at this point in the history
Persist monero addresses
  • Loading branch information
zkao authored Jul 11, 2022
2 parents 8bca4a8 + 6df5681 commit 17a5687
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 41 deletions.
6 changes: 3 additions & 3 deletions src/cli/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// along with this software.
// If not, see <https://opensource.org/licenses/MIT>.

use crate::rpc::request::AddressSecretKey;
use crate::rpc::request::{Address, AddressSecretKey};
use crate::syncerd::SweepBitcoinAddress;
use farcaster_core::negotiation::Offer;
use farcaster_core::swap::btcxmr::BtcXmr;
Expand Down Expand Up @@ -261,9 +261,9 @@ impl Exec for Command {
} => {
runtime.request(
ServiceId::Database,
Request::GetAddressSecretKey(source_address.clone()),
Request::GetAddressSecretKey(Address::Bitcoin(source_address.clone())),
)?;
if let Request::AddressSecretKey(AddressSecretKey { secret_key, .. }) =
if let Request::AddressSecretKey(AddressSecretKey::Bitcoin { secret_key, .. }) =
runtime.report_failure()?
{
runtime.request(
Expand Down
173 changes: 150 additions & 23 deletions src/databased/runtime.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::databased::runtime::request::OfferStatus;
use crate::databased::runtime::request::OfferStatusPair;
use crate::databased::runtime::request::OfferStatusSelector;
use crate::databased::runtime::request::{
Address, OfferStatus, OfferStatusPair, OfferStatusSelector,
};
use crate::farcaster_core::consensus::Encodable;
use crate::syncerd::opts::Coin;
use crate::walletd::runtime::{CheckpointWallet, Wallet};
use farcaster_core::negotiation::PublicOffer;
use farcaster_core::swap::btcxmr::BtcXmr;
Expand Down Expand Up @@ -277,19 +278,19 @@ impl Runtime {
})?;
}

Request::AddressSecretKey(request::AddressSecretKey {
Request::SetAddressSecretKey(request::AddressSecretKey::Bitcoin {
address,
secret_key,
}) => {
self.database.set_address(
self.database.set_bitcoin_address(
&address,
&SecretKey::from_slice(&secret_key)
.expect("secret key is not valid secp256k1 secret key"),
)?;
}

Request::GetAddressSecretKey(address) => {
match self.database.get_address_secret_key(&address) {
Request::GetAddressSecretKey(Address::Bitcoin(address)) => {
match self.database.get_bitcoin_address_secret_key(&address) {
Err(_) => endpoints.send_to(
ServiceBus::Ctl,
ServiceId::Database,
Expand All @@ -304,7 +305,7 @@ impl Runtime {
ServiceBus::Ctl,
ServiceId::Database,
source,
Request::AddressSecretKey(request::AddressSecretKey {
Request::AddressSecretKey(request::AddressSecretKey::Bitcoin {
address,
secret_key: secret_key.secret_bytes(),
}),
Expand All @@ -313,13 +314,66 @@ impl Runtime {
}
}

Request::GetAddresses => {
let addresses = self.database.get_all_addresses()?;
Request::GetAddresses(Coin::Bitcoin) => {
let addresses = self.database.get_all_bitcoin_addresses()?;
endpoints.send_to(
ServiceBus::Ctl,
ServiceId::Database,
source,
Request::BitcoinAddressList(addresses.into()),
)?;
}

Request::SetAddressSecretKey(request::AddressSecretKey::Monero {
address,
view,
spend,
}) => {
self.database.set_monero_address(
&monero::Address::from_str(&address)?,
&monero::KeyPair {
view: monero::PrivateKey::from_slice(&view).unwrap(),
spend: monero::PrivateKey::from_slice(&spend).unwrap(),
},
)?;
}

Request::GetAddressSecretKey(Address::Monero(address)) => {
match self
.database
.get_monero_address_secret_key(&monero::Address::from_str(&address).unwrap())
{
Err(_) => endpoints.send_to(
ServiceBus::Ctl,
ServiceId::Database,
source,
Request::Failure(Failure {
code: 1,
info: format!("Could not retrieve secret key for address {}", address),
}),
)?,
Ok(secret_key_pair) => {
endpoints.send_to(
ServiceBus::Ctl,
ServiceId::Database,
source,
Request::AddressSecretKey(request::AddressSecretKey::Monero {
address,
view: secret_key_pair.spend.as_bytes().try_into().unwrap(),
spend: secret_key_pair.view.as_bytes().try_into().unwrap(),
}),
)?;
}
}
}

Request::GetAddresses(Coin::Monero) => {
let addresses = self.database.get_all_monero_addresses()?;
endpoints.send_to(
ServiceBus::Ctl,
ServiceId::Database,
source,
Request::AddressList(addresses.into()),
Request::MoneroAddressList(addresses.into()),
)?;
}

Expand Down Expand Up @@ -495,18 +549,20 @@ impl From<Vec<u8>> for CheckpointKey {
struct Database(lmdb::Environment);

const LMDB_CHECKPOINTS: &str = "checkpoints";
const LMDB_ADDRESSES: &str = "addresses";
const LMDB_BITCOIN_ADDRESSES: &str = "bitcoin_addresses";
const LMDB_MONERO_ADDRESSES: &str = "monero_addresses";
const LMDB_OFFER_HISTORY: &str = "offer_history";

impl Database {
fn new(path: PathBuf) -> Result<Database, lmdb::Error> {
let env = lmdb::Environment::new()
.set_map_size(10485760 * 1024 * 64)
.set_max_dbs(3)
.set_max_dbs(4)
.open(&path)?;
env.create_db(Some(LMDB_CHECKPOINTS), lmdb::DatabaseFlags::empty())?;
env.create_db(Some(LMDB_ADDRESSES), lmdb::DatabaseFlags::empty())?;
env.create_db(Some(LMDB_BITCOIN_ADDRESSES), lmdb::DatabaseFlags::empty())?;
env.create_db(Some(LMDB_OFFER_HISTORY), lmdb::DatabaseFlags::empty())?;
env.create_db(Some(LMDB_MONERO_ADDRESSES), lmdb::DatabaseFlags::empty())?;
Ok(Database(env))
}

Expand Down Expand Up @@ -561,12 +617,12 @@ impl Database {
Ok(res)
}

fn set_address(
fn set_bitcoin_address(
&mut self,
address: &bitcoin::Address,
secret_key: &SecretKey,
) -> Result<(), lmdb::Error> {
let db = self.0.open_db(Some(LMDB_ADDRESSES))?;
let db = self.0.open_db(Some(LMDB_BITCOIN_ADDRESSES))?;
let mut tx = self.0.begin_rw_txn()?;
let mut key = vec![];
let _key_size = address.strict_encode(&mut key);
Expand All @@ -587,11 +643,11 @@ impl Database {
Ok(())
}

fn get_address_secret_key(
fn get_bitcoin_address_secret_key(
&mut self,
address: &bitcoin::Address,
) -> Result<SecretKey, lmdb::Error> {
let db = self.0.open_db(Some(LMDB_ADDRESSES))?;
let db = self.0.open_db(Some(LMDB_BITCOIN_ADDRESSES))?;
let tx = self.0.begin_ro_txn()?;
let mut key = vec![];
let _key_size = address.strict_encode(&mut key);
Expand All @@ -601,8 +657,8 @@ impl Database {
Ok(val)
}

fn get_all_addresses(&mut self) -> Result<Vec<bitcoin::Address>, lmdb::Error> {
let db = self.0.open_db(Some(LMDB_ADDRESSES))?;
fn get_all_bitcoin_addresses(&mut self) -> Result<Vec<bitcoin::Address>, lmdb::Error> {
let db = self.0.open_db(Some(LMDB_BITCOIN_ADDRESSES))?;
let tx = self.0.begin_ro_txn()?;
let mut cursor = tx.open_ro_cursor(db)?;
let res = cursor
Expand All @@ -616,6 +672,60 @@ impl Database {
Ok(res)
}

fn set_monero_address(
&mut self,
address: &monero::Address,
secret_keys: &monero::KeyPair,
) -> Result<(), lmdb::Error> {
let db = self.0.open_db(Some(LMDB_BITCOIN_ADDRESSES))?;
let mut tx = self.0.begin_rw_txn()?;
let key = address.as_bytes();
let mut val = secret_keys.spend.as_bytes().to_vec();
val.append(&mut secret_keys.view.as_bytes().to_vec());
if tx.get(db, &key).is_err() {
tx.put(db, &key, &val, lmdb::WriteFlags::empty())?;
} else {
warn!(
"address {} was already persisted with its secret key",
address
);
}
tx.commit()?;
Ok(())
}

fn get_monero_address_secret_key(
&mut self,
address: &monero::Address,
) -> Result<monero::KeyPair, lmdb::Error> {
let db = self.0.open_db(Some(LMDB_BITCOIN_ADDRESSES))?;
let tx = self.0.begin_ro_txn()?;
let key = address.as_bytes();
let val: [u8; 64] = tx
.get(db, &key)?
.try_into()
.expect("every monero address should have a keypair");
tx.abort();
Ok(monero::KeyPair {
spend: val[0..32].try_into().expect("unable to decode spend key"),
view: val[32..64].try_into().expect("unable to decode view key"),
})
}

fn get_all_monero_addresses(&mut self) -> Result<Vec<String>, lmdb::Error> {
let db = self.0.open_db(Some(LMDB_BITCOIN_ADDRESSES))?;
let tx = self.0.begin_ro_txn()?;
let mut cursor = tx.open_ro_cursor(db)?;
let res = cursor
.iter()
.filter_map(|(key, _)| monero::Address::from_bytes(key).ok())
.map(|addr| addr.to_string())
.collect();
drop(cursor);
tx.abort();
Ok(res)
}

fn set_checkpoint_state(&mut self, key: &CheckpointKey, val: &[u8]) -> Result<(), lmdb::Error> {
let db = self.0.open_db(Some(LMDB_CHECKPOINTS))?;
let mut tx = self.0.begin_rw_txn()?;
Expand Down Expand Up @@ -694,12 +804,29 @@ fn test_lmdb_state() {
bitcoin::PrivateKey::from_slice(&sk.secret_bytes(), bitcoin::Network::Testnet).unwrap();
let pk = bitcoin::PublicKey::from_private_key(bitcoin::secp256k1::SECP256K1, &private_key);
let addr = bitcoin::Address::p2wpkh(&pk, bitcoin::Network::Testnet).unwrap();
database.set_address(&addr, &sk).unwrap();
let val_retrieved = database.get_address_secret_key(&addr).unwrap();
database.set_bitcoin_address(&addr, &sk).unwrap();
let val_retrieved = database.get_bitcoin_address_secret_key(&addr).unwrap();
assert_eq!(sk, val_retrieved);
let addrs = database.get_all_addresses().unwrap();
let addrs = database.get_all_bitcoin_addresses().unwrap();
assert!(addrs.contains(&addr));

let key_pair = monero::KeyPair {
spend: monero::PrivateKey::from_str(
"77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404",
)
.unwrap(),
view: monero::PrivateKey::from_str(
"8163466f1883598e6dd14027b8da727057165da91485834314f5500a65846f09",
)
.unwrap(),
};
let addr = monero::Address::from_keypair(monero::Network::Stagenet, &key_pair);
database.set_monero_address(&addr, &key_pair).unwrap();
let val_retrieved = database.get_monero_address_secret_key(&addr).unwrap();
assert_eq!(key_pair, val_retrieved);
let addrs = database.get_all_monero_addresses().unwrap();
assert!(addrs.contains(&addr.to_string()));

let offer_1 = PublicOffer::<BtcXmr>::from_str("Offer:Cke4ftrP5A71LQM2fvVdFMNR4gmBqNCsR11111uMFuZTAsNgpdK8DiK11111TB9zym113GTvtvqfD1111114A4TUGURtskxM3BUGLBGAdFDhJQVMQmiPUsL5vSTKhyBKw3Lh11111111111111111111111111111111111111111AfZ113XRBuStRU5H").unwrap();
let offer_2 = PublicOffer::<BtcXmr>::from_str("Offer:Cke4ftrP5A71LQM2fvVdFMNR4grq1wi1D11111uMFuZTAsNgpdK8DiK11111TB9zym113GTvtvqfD1111114A4TUGURtskxM3BUGLBGAdFDhJQVMQmiPUsL5vSTKhyBKw3Lh11111111111111111111111111111111111111111AfZ113W5EEpvY61v").unwrap();

Expand Down
48 changes: 35 additions & 13 deletions src/rpc/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ use bitcoin::{
rand::{thread_rng, RngCore},
SecretKey,
},
Address, OutPoint, PublicKey, Transaction,
OutPoint, PublicKey, Transaction,
};
use farcaster_core::{
bitcoin::BitcoinSegwitV0,
Expand Down Expand Up @@ -237,12 +237,12 @@ pub struct NodeId(pub bitcoin::secp256k1::PublicKey);
pub struct PubOffer {
pub public_offer: PublicOffer<BtcXmr>,
pub peer_secret_key: Option<SecretKey>,
pub external_address: Address,
pub external_address: bitcoin::Address,
pub internal_address: String,
}

impl From<(PublicOffer<BtcXmr>, Address, String)> for PubOffer {
fn from(x: (PublicOffer<BtcXmr>, Address, String)) -> Self {
impl From<(PublicOffer<BtcXmr>, bitcoin::Address, String)> for PubOffer {
fn from(x: (PublicOffer<BtcXmr>, bitcoin::Address, String)) -> Self {
let (public_offer, external_address, internal_address) = x;
PubOffer {
public_offer,
Expand Down Expand Up @@ -705,19 +705,23 @@ pub enum Request {

#[api(type = 1311)]
#[display("get_address_secret_key")]
GetAddressSecretKey(bitcoin::Address),
GetAddressSecretKey(Address),

#[api(type = 1312)]
#[display("get_addresses")]
GetAddresses,
GetAddresses(Coin),

#[api(type = 1313)]
#[display("address_list({0})")]
AddressList(List<bitcoin::Address>),
#[display("bitcoin_address_list({0})")]
BitcoinAddressList(List<bitcoin::Address>),

#[api(type = 1318)]
#[display("monero_address_list({0})")]
MoneroAddressList(List<String>),

#[api(type = 1314)]
#[display("address_secret_key")]
AddressSecretKey(AddressSecretKey),
#[display("set_address_secret_key")]
SetAddressSecretKey(AddressSecretKey),

#[api(type = 1315)]
#[display("set_offer_history({0})")]
Expand All @@ -730,6 +734,10 @@ pub enum Request {
#[api(type = 1317)]
#[display("offer_status_list({0})")]
OfferStatusList(List<OfferStatusPair>),

#[api(type = 1319)]
#[display("address_secret_key")]
AddressSecretKey(AddressSecretKey),
}

#[derive(Clone, Debug, Eq, PartialEq, Display, StrictEncode, StrictDecode)]
Expand Down Expand Up @@ -812,6 +820,13 @@ pub enum Outcome {
Abort,
}

#[derive(Eq, PartialEq, Clone, Debug, Display, StrictDecode, StrictEncode)]
#[display("address")]
pub enum Address {
Bitcoin(bitcoin::Address),
Monero(String),
}

#[derive(Clone, Debug, Display, StrictDecode, StrictEncode)]
#[display("funding_info")]
pub enum FundingInfo {
Expand Down Expand Up @@ -866,9 +881,16 @@ pub enum CheckpointState {

#[derive(Clone, Debug, Display, StrictDecode, StrictEncode)]
#[display("address_secret_key")]
pub struct AddressSecretKey {
pub address: bitcoin::Address,
pub secret_key: [u8; 32],
pub enum AddressSecretKey {
Bitcoin {
address: bitcoin::Address,
secret_key: [u8; 32],
},
Monero {
address: String,
view: [u8; 32],
spend: [u8; 32],
},
}

impl FromStr for BitcoinFundingInfo {
Expand Down
Loading

0 comments on commit 17a5687

Please sign in to comment.