Implement a proper Monero Timelock type

Transaction scanning now returns the timelock to ensure it's 
acknowledged by wallets.

Fixes https://github.com/serai-dex/serai/issues/16.
This commit is contained in:
Luke Parker 2022-06-02 00:00:26 -04:00
parent 2ae715f899
commit dfd2f624ee
No known key found for this signature in database
GPG key ID: F9F1386DB1E119B6
6 changed files with 62 additions and 23 deletions

View file

@ -9,7 +9,7 @@ use serde_json::json;
use reqwest;
use crate::{transaction::{Input, Transaction}, block::Block};
use crate::{transaction::{Input, Timelock, Transaction}, block::Block};
#[derive(Deserialize, Debug)]
pub struct EmptyResponse {}
@ -267,9 +267,12 @@ impl Rpc {
// get the median time for the given height, yet we do need to in order to be complete
outs.outs.iter().enumerate().map(
|(i, out)| Ok(
if txs[i].prefix.unlock_time <= u64::try_from(height).unwrap() {
Some([rpc_point(&out.key)?, rpc_point(&out.mask)?])
} else { None }
Some([rpc_point(&out.key)?, rpc_point(&out.mask)?]).filter(|_| {
match txs[i].prefix.timelock {
Timelock::Block(t_height) => (t_height <= height),
_ => false
}
})
)
).collect()
}

View file

@ -84,10 +84,40 @@ impl Output {
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Timelock {
None,
Block(usize),
Time(u64)
}
impl Timelock {
fn from_raw(raw: u64) -> Timelock {
if raw == 0 {
Timelock::None
} else if raw < 500_000_000 {
Timelock::Block(usize::try_from(raw).unwrap())
} else {
Timelock::Time(raw)
}
}
fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
write_varint(
&match self {
Timelock::None => 0,
Timelock::Block(block) => (*block).try_into().unwrap(),
Timelock::Time(time) => *time
},
w
)
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct TransactionPrefix {
pub version: u64,
pub unlock_time: u64,
pub timelock: Timelock,
pub inputs: Vec<Input>,
pub outputs: Vec<Output>,
pub extra: Vec<u8>
@ -96,7 +126,7 @@ pub struct TransactionPrefix {
impl TransactionPrefix {
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
write_varint(&self.version, w)?;
write_varint(&self.unlock_time, w)?;
self.timelock.serialize(w)?;
write_vec(Input::serialize, &self.inputs, w)?;
write_vec(Output::serialize, &self.outputs, w)?;
write_varint(&self.extra.len().try_into().unwrap(), w)?;
@ -106,7 +136,7 @@ impl TransactionPrefix {
pub fn deserialize<R: std::io::Read>(r: &mut R) -> std::io::Result<TransactionPrefix> {
let mut prefix = TransactionPrefix {
version: read_varint(r)?,
unlock_time: read_varint(r)?,
timelock: Timelock::from_raw(read_varint(r)?),
inputs: read_vec(Input::deserialize, r)?,
outputs: read_vec(Output::deserialize, r)?,
extra: vec![]

View file

@ -11,7 +11,7 @@ use monero::{consensus::deserialize, blockdata::transaction::ExtraField};
use crate::{
Commitment,
serialize::{write_varint, read_32, read_scalar, read_point},
transaction::Transaction,
transaction::{Timelock, Transaction},
wallet::{uniqueness, shared_key, amount_decryption, commitment_mask}
};
@ -57,13 +57,7 @@ impl Transaction {
&self,
view: Scalar,
spend: EdwardsPoint
) -> Vec<SpendableOutput> {
// Ignore transactions which utilize a timelock. Almost no transactions on Monero do,
// and they're not worth the effort to track given their complexities
if self.prefix.unlock_time != 0 {
return vec![];
}
) -> (Vec<SpendableOutput>, Timelock) {
let mut extra = vec![];
write_varint(&u64::try_from(self.prefix.extra.len()).unwrap(), &mut extra).unwrap();
extra.extend(&self.prefix.extra);
@ -81,7 +75,7 @@ impl Transaction {
pubkeys = m_pubkeys.iter().map(|key| key.point.decompress()).filter_map(|key| key).collect();
} else {
return vec![];
return (vec![], self.prefix.timelock);
};
let mut res = vec![];
@ -136,6 +130,7 @@ impl Transaction {
}
}
}
res
(res, self.prefix.timelock)
}
}

View file

@ -27,7 +27,7 @@ use crate::{
bulletproofs::Bulletproofs,
RctBase, RctPrunable, RctSignatures
},
transaction::{Input, Output, TransactionPrefix, Transaction},
transaction::{Input, Output, Timelock, TransactionPrefix, Transaction},
rpc::{Rpc, RpcError},
wallet::{SpendableOutput, Decoys, key_image_sort, uniqueness, shared_key, commitment_mask, amount_encryption}
};
@ -255,7 +255,7 @@ impl SignableTransaction {
Transaction {
prefix: TransactionPrefix {
version: 2,
unlock_time: 0,
timelock: Timelock::None,
inputs: vec![],
outputs: tx_outputs,
extra

View file

@ -100,7 +100,7 @@ async fn send_core(test: usize, multisig: bool) {
// Grab the largest output available
let output = {
let mut outputs = tx.as_ref().unwrap().scan(view, spend_pub);
let mut outputs = tx.as_ref().unwrap().scan(view, spend_pub).0;
outputs.sort_by(|x, y| x.commitment.amount.cmp(&y.commitment.amount).reverse());
outputs.swap_remove(0)
};
@ -125,7 +125,7 @@ async fn send_core(test: usize, multisig: bool) {
for i in (start + 1) .. (start + 9) {
let tx = rpc.get_block_transactions(i).await.unwrap().swap_remove(0);
let output = tx.scan(view, spend_pub).swap_remove(0);
let output = tx.scan(view, spend_pub).0.swap_remove(0);
amount += output.commitment.amount;
outputs.push(output);
}

View file

@ -9,7 +9,7 @@ use frost::MultisigKeys;
use monero::util::address::Address;
use monero_serai::{
frost::Ed25519,
transaction::Transaction,
transaction::{Timelock, Transaction},
rpc::Rpc,
wallet::{SpendableOutput, SignableTransaction}
};
@ -90,7 +90,18 @@ impl Coin for Monero {
}
async fn get_outputs(&self, block: &Self::Block, key: dfg::EdwardsPoint) -> Vec<Self::Output> {
block.iter().flat_map(|tx| tx.scan(self.view, key.0)).map(Output::from).collect()
block
.iter()
.flat_map(|tx| {
let (outputs, timelock) = tx.scan(self.view, key.0);
if timelock == Timelock::None {
outputs
} else {
vec![]
}
})
.map(Output::from)
.collect()
}
async fn prepare_send<R: RngCore + CryptoRng>(