Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions lianad/src/database/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ pub trait DatabaseConnection {
/// Load all receiver session events for a particular session id
fn load_receiver_session_events(&mut self, session_id: &SessionId) -> Vec<Vec<u8>>;

/// Check if input has been seen before and then add it to the input_seen table
fn insert_input_seen_before(&mut self, outpoints: &[bitcoin::OutPoint]) -> bool;

/// Create a payjoin sender
fn save_new_payjoin_sender_session(&mut self, original_txid: &bitcoin::Txid) -> i64;
/// Get a all active payjoin senders
Expand Down Expand Up @@ -484,6 +487,10 @@ impl DatabaseConnection for SqliteConn {
.collect()
}

fn insert_input_seen_before(&mut self, outpoints: &[bitcoin::OutPoint]) -> bool {
self.insert_outpoint_seen_before(outpoints)
}

fn payjoin_get_ohttp_keys(&mut self, ohttp_relay: &str) -> Option<(u32, OhttpKeys)> {
self.payjoin_get_ohttp_keys(ohttp_relay)
}
Expand Down
30 changes: 29 additions & 1 deletion lianad/src/database/sqlite/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::{
payjoin::db::SessionId,
};
use liana::descriptors::LianaDescriptor;
use payjoin::OhttpKeys;
use payjoin::{bitcoin::consensus::Encodable, OhttpKeys};

use std::{
cmp,
Expand Down Expand Up @@ -481,6 +481,34 @@ impl SqliteConn {
.expect("Database must be available")
}

pub fn insert_outpoint_seen_before<'a>(
&mut self,
outpoints: impl IntoIterator<Item = &'a bitcoin::OutPoint>,
) -> bool {
let mut is_duplicate = false;
db_exec(&mut self.conn, |db_tx| {
for outpoint in outpoints {
let mut buf = Vec::new();
outpoint
.consensus_encode(&mut buf)
.expect("Outpoint must encode");
let affected = db_tx.execute(
"INSERT OR IGNORE INTO payjoin_outpoints (outpoint, added_at) \
VALUES (?1, ?2)",
rusqlite::params![buf, curr_timestamp()],
)?;

if affected == 0 {
is_duplicate = true
}
}
Ok(())
})
.expect("database must be available");

is_duplicate
}

/// Remove a set of coins from the database.
pub fn remove_coins(&mut self, outpoints: &[bitcoin::OutPoint]) {
db_exec(&mut self.conn, |db_tx| {
Expand Down
32 changes: 32 additions & 0 deletions lianad/src/database/sqlite/schema.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use bip329::Label;
use liana::descriptors::LianaDescriptor;
use payjoin::bitcoin::{consensus::Decodable, io::Cursor};

use std::{convert::TryFrom, str::FromStr};

Expand Down Expand Up @@ -87,6 +88,16 @@ CREATE TABLE coins (
ON DELETE RESTRICT
);

/* Seen Payjoin outpoints
*
* The 'added_at' field is simply the time that this outpoint is added to the table for
* tracking.
*/
CREATE TABLE payjoin_outpoints (
outpoint BLOB NOT NULL PRIMARY KEY,
added_at INTEGER NOT NULL
);

/* A mapping from descriptor address to derivation index. Necessary until
* we can get the derivation index from the parent descriptor from bitcoind.
*/
Expand Down Expand Up @@ -500,3 +511,24 @@ impl TryFrom<&rusqlite::Row<'_>> for DbWalletTransaction {
})
}
}

/// An outpoint we have seen before in payjoin transactions
#[derive(Clone, Debug, PartialEq)]
pub struct DbPayjoinOutpoint {
pub outpoint: bitcoin::OutPoint,
pub added_at: Option<u32>,
}

impl TryFrom<&rusqlite::Row<'_>> for DbPayjoinOutpoint {
type Error = rusqlite::Error;

fn try_from(row: &rusqlite::Row) -> Result<Self, Self::Error> {
let outpoint: Vec<u8> = row.get(0)?;
let outpoint = bitcoin::OutPoint::consensus_decode(&mut Cursor::new(outpoint))
.expect("Outpoint should be decodable");

let added_at = row.get(1)?;

Ok(DbPayjoinOutpoint { outpoint, added_at })
}
}
19 changes: 14 additions & 5 deletions lianad/src/payjoin/receiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use payjoin::{
},
InputPair,
},
ImplementationError,
};

use crate::{
Expand Down Expand Up @@ -91,7 +92,10 @@ fn check_inputs_not_owned(
) -> Result<(), Box<dyn Error>> {
let proposal = proposal
.check_inputs_not_owned(&mut |script| {
let address = bitcoin::Address::from_script(script, db_conn.network()).unwrap();
let address =
bitcoin::Address::from_script(script, db_conn.network()).map_err(|e| {
ImplementationError::from(Box::new(e) as Box<dyn Error + Send + Sync>)
})?;
Ok(db_conn
.derivation_index_by_address(&address)
.map(|(index, is_change)| AddrInfo { index, is_change })
Expand All @@ -109,9 +113,10 @@ fn check_no_inputs_seen_before(
secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>,
) -> Result<(), Box<dyn Error>> {
let proposal = proposal
// TODO implement check_no_inputs_seen_before callback and add new table to mark relevant
// outpoint as seen for the future
.check_no_inputs_seen_before(&mut |_| Ok(false))
.check_no_inputs_seen_before(&mut |outpoint| {
let seen = db_conn.insert_input_seen_before(&[*outpoint]);
Ok(seen)
})
.save(persister)?;
identify_receiver_outputs(proposal, persister, db_conn, desc, secp)
}
Expand All @@ -123,9 +128,13 @@ fn identify_receiver_outputs(
desc: &descriptors::LianaDescriptor,
secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>,
) -> Result<(), Box<dyn Error>> {
log::debug!("[Payjoin] receiver outputs");
let proposal = proposal
.identify_receiver_outputs(&mut |script| {
let address = bitcoin::Address::from_script(script, db_conn.network()).unwrap();
let address =
bitcoin::Address::from_script(script, db_conn.network()).map_err(|e| {
ImplementationError::from(Box::new(e) as Box<dyn Error + Send + Sync>)
})?;
Ok(db_conn
.derivation_index_by_address(&address)
.map(|(index, is_change)| AddrInfo { index, is_change })
Expand Down
Loading