mirror of
https://github.com/serai-dex/serai.git
synced 2025-03-22 15:19:06 +00:00
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:
parent
2ae715f899
commit
dfd2f624ee
6 changed files with 62 additions and 23 deletions
|
@ -9,7 +9,7 @@ use serde_json::json;
|
||||||
|
|
||||||
use reqwest;
|
use reqwest;
|
||||||
|
|
||||||
use crate::{transaction::{Input, Transaction}, block::Block};
|
use crate::{transaction::{Input, Timelock, Transaction}, block::Block};
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct EmptyResponse {}
|
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
|
// get the median time for the given height, yet we do need to in order to be complete
|
||||||
outs.outs.iter().enumerate().map(
|
outs.outs.iter().enumerate().map(
|
||||||
|(i, out)| Ok(
|
|(i, out)| Ok(
|
||||||
if txs[i].prefix.unlock_time <= u64::try_from(height).unwrap() {
|
Some([rpc_point(&out.key)?, rpc_point(&out.mask)?]).filter(|_| {
|
||||||
Some([rpc_point(&out.key)?, rpc_point(&out.mask)?])
|
match txs[i].prefix.timelock {
|
||||||
} else { None }
|
Timelock::Block(t_height) => (t_height <= height),
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
})
|
||||||
)
|
)
|
||||||
).collect()
|
).collect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct TransactionPrefix {
|
pub struct TransactionPrefix {
|
||||||
pub version: u64,
|
pub version: u64,
|
||||||
pub unlock_time: u64,
|
pub timelock: Timelock,
|
||||||
pub inputs: Vec<Input>,
|
pub inputs: Vec<Input>,
|
||||||
pub outputs: Vec<Output>,
|
pub outputs: Vec<Output>,
|
||||||
pub extra: Vec<u8>
|
pub extra: Vec<u8>
|
||||||
|
@ -96,7 +126,7 @@ pub struct TransactionPrefix {
|
||||||
impl TransactionPrefix {
|
impl TransactionPrefix {
|
||||||
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
|
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
|
||||||
write_varint(&self.version, w)?;
|
write_varint(&self.version, w)?;
|
||||||
write_varint(&self.unlock_time, w)?;
|
self.timelock.serialize(w)?;
|
||||||
write_vec(Input::serialize, &self.inputs, w)?;
|
write_vec(Input::serialize, &self.inputs, w)?;
|
||||||
write_vec(Output::serialize, &self.outputs, w)?;
|
write_vec(Output::serialize, &self.outputs, w)?;
|
||||||
write_varint(&self.extra.len().try_into().unwrap(), 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> {
|
pub fn deserialize<R: std::io::Read>(r: &mut R) -> std::io::Result<TransactionPrefix> {
|
||||||
let mut prefix = TransactionPrefix {
|
let mut prefix = TransactionPrefix {
|
||||||
version: read_varint(r)?,
|
version: read_varint(r)?,
|
||||||
unlock_time: read_varint(r)?,
|
timelock: Timelock::from_raw(read_varint(r)?),
|
||||||
inputs: read_vec(Input::deserialize, r)?,
|
inputs: read_vec(Input::deserialize, r)?,
|
||||||
outputs: read_vec(Output::deserialize, r)?,
|
outputs: read_vec(Output::deserialize, r)?,
|
||||||
extra: vec![]
|
extra: vec![]
|
||||||
|
|
|
@ -11,7 +11,7 @@ use monero::{consensus::deserialize, blockdata::transaction::ExtraField};
|
||||||
use crate::{
|
use crate::{
|
||||||
Commitment,
|
Commitment,
|
||||||
serialize::{write_varint, read_32, read_scalar, read_point},
|
serialize::{write_varint, read_32, read_scalar, read_point},
|
||||||
transaction::Transaction,
|
transaction::{Timelock, Transaction},
|
||||||
wallet::{uniqueness, shared_key, amount_decryption, commitment_mask}
|
wallet::{uniqueness, shared_key, amount_decryption, commitment_mask}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -57,13 +57,7 @@ impl Transaction {
|
||||||
&self,
|
&self,
|
||||||
view: Scalar,
|
view: Scalar,
|
||||||
spend: EdwardsPoint
|
spend: EdwardsPoint
|
||||||
) -> Vec<SpendableOutput> {
|
) -> (Vec<SpendableOutput>, Timelock) {
|
||||||
// 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![];
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut extra = vec![];
|
let mut extra = vec![];
|
||||||
write_varint(&u64::try_from(self.prefix.extra.len()).unwrap(), &mut extra).unwrap();
|
write_varint(&u64::try_from(self.prefix.extra.len()).unwrap(), &mut extra).unwrap();
|
||||||
extra.extend(&self.prefix.extra);
|
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();
|
pubkeys = m_pubkeys.iter().map(|key| key.point.decompress()).filter_map(|key| key).collect();
|
||||||
} else {
|
} else {
|
||||||
return vec![];
|
return (vec![], self.prefix.timelock);
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut res = vec![];
|
let mut res = vec![];
|
||||||
|
@ -136,6 +130,7 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res
|
|
||||||
|
(res, self.prefix.timelock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ use crate::{
|
||||||
bulletproofs::Bulletproofs,
|
bulletproofs::Bulletproofs,
|
||||||
RctBase, RctPrunable, RctSignatures
|
RctBase, RctPrunable, RctSignatures
|
||||||
},
|
},
|
||||||
transaction::{Input, Output, TransactionPrefix, Transaction},
|
transaction::{Input, Output, Timelock, TransactionPrefix, Transaction},
|
||||||
rpc::{Rpc, RpcError},
|
rpc::{Rpc, RpcError},
|
||||||
wallet::{SpendableOutput, Decoys, key_image_sort, uniqueness, shared_key, commitment_mask, amount_encryption}
|
wallet::{SpendableOutput, Decoys, key_image_sort, uniqueness, shared_key, commitment_mask, amount_encryption}
|
||||||
};
|
};
|
||||||
|
@ -255,7 +255,7 @@ impl SignableTransaction {
|
||||||
Transaction {
|
Transaction {
|
||||||
prefix: TransactionPrefix {
|
prefix: TransactionPrefix {
|
||||||
version: 2,
|
version: 2,
|
||||||
unlock_time: 0,
|
timelock: Timelock::None,
|
||||||
inputs: vec![],
|
inputs: vec![],
|
||||||
outputs: tx_outputs,
|
outputs: tx_outputs,
|
||||||
extra
|
extra
|
||||||
|
|
|
@ -100,7 +100,7 @@ async fn send_core(test: usize, multisig: bool) {
|
||||||
|
|
||||||
// Grab the largest output available
|
// Grab the largest output available
|
||||||
let output = {
|
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.sort_by(|x, y| x.commitment.amount.cmp(&y.commitment.amount).reverse());
|
||||||
outputs.swap_remove(0)
|
outputs.swap_remove(0)
|
||||||
};
|
};
|
||||||
|
@ -125,7 +125,7 @@ async fn send_core(test: usize, multisig: bool) {
|
||||||
|
|
||||||
for i in (start + 1) .. (start + 9) {
|
for i in (start + 1) .. (start + 9) {
|
||||||
let tx = rpc.get_block_transactions(i).await.unwrap().swap_remove(0);
|
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;
|
amount += output.commitment.amount;
|
||||||
outputs.push(output);
|
outputs.push(output);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use frost::MultisigKeys;
|
||||||
use monero::util::address::Address;
|
use monero::util::address::Address;
|
||||||
use monero_serai::{
|
use monero_serai::{
|
||||||
frost::Ed25519,
|
frost::Ed25519,
|
||||||
transaction::Transaction,
|
transaction::{Timelock, Transaction},
|
||||||
rpc::Rpc,
|
rpc::Rpc,
|
||||||
wallet::{SpendableOutput, SignableTransaction}
|
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> {
|
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>(
|
async fn prepare_send<R: RngCore + CryptoRng>(
|
||||||
|
|
Loading…
Reference in a new issue