mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-13 22:49:25 +00:00
Merge branch 'develop' into HEAD
This commit is contained in:
@@ -6,7 +6,7 @@ license = "MIT"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/coins/bitcoin"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>", "Vrx <vrx00@proton.me>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.74"
|
||||
rust-version = "1.79"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -23,7 +23,7 @@ thiserror = { version = "1", default-features = false, optional = true }
|
||||
zeroize = { version = "^1.5", default-features = false }
|
||||
rand_core = { version = "0.6", default-features = false }
|
||||
|
||||
bitcoin = { version = "0.31", default-features = false, features = ["no-std"] }
|
||||
bitcoin = { version = "0.32", default-features = false }
|
||||
|
||||
k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits"] }
|
||||
|
||||
@@ -36,7 +36,7 @@ serde_json = { version = "1", default-features = false, optional = true }
|
||||
simple-request = { path = "../../common/request", version = "0.1", default-features = false, features = ["tls", "basic-auth"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
secp256k1 = { version = "0.28", default-features = false, features = ["std"] }
|
||||
secp256k1 = { version = "0.29", default-features = false, features = ["std"] }
|
||||
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", features = ["tests"] }
|
||||
|
||||
|
||||
@@ -195,13 +195,13 @@ impl Rpc {
|
||||
// If this was already successfully published, consider this having succeeded
|
||||
if let RpcError::RequestError(Error { code, .. }) = e {
|
||||
if code == RPC_VERIFY_ALREADY_IN_CHAIN {
|
||||
return Ok(tx.txid());
|
||||
return Ok(tx.compute_txid());
|
||||
}
|
||||
}
|
||||
Err(e)?
|
||||
}
|
||||
};
|
||||
if txid != tx.txid() {
|
||||
if txid != tx.compute_txid() {
|
||||
Err(RpcError::InvalidResponse("returned TX ID inequals calculated TX ID"))?;
|
||||
}
|
||||
Ok(txid)
|
||||
@@ -215,7 +215,7 @@ impl Rpc {
|
||||
let tx: Transaction = encode::deserialize(&bytes)
|
||||
.map_err(|_| RpcError::InvalidResponse("node sent an improperly serialized transaction"))?;
|
||||
|
||||
let mut tx_hash = *tx.txid().as_raw_hash().as_byte_array();
|
||||
let mut tx_hash = *tx.compute_txid().as_raw_hash().as_byte_array();
|
||||
tx_hash.reverse();
|
||||
if hash != &tx_hash {
|
||||
Err(RpcError::InvalidResponse("node replied with a different transaction"))?;
|
||||
|
||||
@@ -39,7 +39,7 @@ fn test_algorithm() {
|
||||
.verify_schnorr(
|
||||
&Signature::from_slice(&sig)
|
||||
.expect("couldn't convert produced signature to secp256k1::Signature"),
|
||||
&Message::from(Hash::hash(MESSAGE)),
|
||||
&Message::from_digest_slice(Hash::hash(MESSAGE).as_ref()).unwrap(),
|
||||
&x_only(&keys[&Participant::new(1).unwrap()].group_key()),
|
||||
)
|
||||
.unwrap()
|
||||
|
||||
@@ -4,7 +4,7 @@ use std_shims::{
|
||||
io::{self, Write},
|
||||
};
|
||||
#[cfg(feature = "std")]
|
||||
use std_shims::io::Read;
|
||||
use std::io::{Read, BufReader};
|
||||
|
||||
use k256::{
|
||||
elliptic_curve::sec1::{Tag, ToEncodedPoint},
|
||||
@@ -18,8 +18,8 @@ use frost::{
|
||||
};
|
||||
|
||||
use bitcoin::{
|
||||
consensus::encode::serialize, key::TweakedPublicKey, address::Payload, OutPoint, ScriptBuf,
|
||||
TxOut, Transaction, Block,
|
||||
consensus::encode::serialize, key::TweakedPublicKey, OutPoint, ScriptBuf, TxOut, Transaction,
|
||||
Block,
|
||||
};
|
||||
#[cfg(feature = "std")]
|
||||
use bitcoin::consensus::encode::Decodable;
|
||||
@@ -46,12 +46,12 @@ pub fn tweak_keys(keys: &ThresholdKeys<Secp256k1>) -> ThresholdKeys<Secp256k1> {
|
||||
/// Return the Taproot address payload for a public key.
|
||||
///
|
||||
/// If the key is odd, this will return None.
|
||||
pub fn address_payload(key: ProjectivePoint) -> Option<Payload> {
|
||||
pub fn p2tr_script_buf(key: ProjectivePoint) -> Option<ScriptBuf> {
|
||||
if key.to_encoded_point(true).tag() != Tag::CompressedEvenY {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Payload::p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(x_only(&key))))
|
||||
Some(ScriptBuf::new_p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(x_only(&key))))
|
||||
}
|
||||
|
||||
/// A spendable output.
|
||||
@@ -89,11 +89,17 @@ impl ReceivedOutput {
|
||||
/// Read a ReceivedOutput from a generic satisfying Read.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn read<R: Read>(r: &mut R) -> io::Result<ReceivedOutput> {
|
||||
Ok(ReceivedOutput {
|
||||
offset: Secp256k1::read_F(r)?,
|
||||
output: TxOut::consensus_decode(r).map_err(|_| io::Error::other("invalid TxOut"))?,
|
||||
outpoint: OutPoint::consensus_decode(r).map_err(|_| io::Error::other("invalid OutPoint"))?,
|
||||
})
|
||||
let offset = Secp256k1::read_F(r)?;
|
||||
let output;
|
||||
let outpoint;
|
||||
{
|
||||
let mut buf_r = BufReader::with_capacity(0, r);
|
||||
output =
|
||||
TxOut::consensus_decode(&mut buf_r).map_err(|_| io::Error::other("invalid TxOut"))?;
|
||||
outpoint =
|
||||
OutPoint::consensus_decode(&mut buf_r).map_err(|_| io::Error::other("invalid OutPoint"))?;
|
||||
}
|
||||
Ok(ReceivedOutput { offset, output, outpoint })
|
||||
}
|
||||
|
||||
/// Write a ReceivedOutput to a generic satisfying Write.
|
||||
@@ -124,7 +130,7 @@ impl Scanner {
|
||||
/// Returns None if this key can't be scanned for.
|
||||
pub fn new(key: ProjectivePoint) -> Option<Scanner> {
|
||||
let mut scripts = HashMap::new();
|
||||
scripts.insert(address_payload(key)?.script_pubkey(), Scalar::ZERO);
|
||||
scripts.insert(p2tr_script_buf(key)?, Scalar::ZERO);
|
||||
Some(Scanner { key, scripts })
|
||||
}
|
||||
|
||||
@@ -141,9 +147,8 @@ impl Scanner {
|
||||
// chance of being even
|
||||
// That means this should terminate within a very small amount of iterations
|
||||
loop {
|
||||
match address_payload(self.key + (ProjectivePoint::GENERATOR * offset)) {
|
||||
Some(address) => {
|
||||
let script = address.script_pubkey();
|
||||
match p2tr_script_buf(self.key + (ProjectivePoint::GENERATOR * offset)) {
|
||||
Some(script) => {
|
||||
if self.scripts.contains_key(&script) {
|
||||
None?;
|
||||
}
|
||||
@@ -166,7 +171,7 @@ impl Scanner {
|
||||
res.push(ReceivedOutput {
|
||||
offset: *offset,
|
||||
output: output.clone(),
|
||||
outpoint: OutPoint::new(tx.txid(), vout),
|
||||
outpoint: OutPoint::new(tx.compute_txid(), vout),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@ use bitcoin::{
|
||||
absolute::LockTime,
|
||||
script::{PushBytesBuf, ScriptBuf},
|
||||
transaction::{Version, Transaction},
|
||||
OutPoint, Sequence, Witness, TxIn, Amount, TxOut, Address,
|
||||
OutPoint, Sequence, Witness, TxIn, Amount, TxOut,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
crypto::Schnorr,
|
||||
wallet::{ReceivedOutput, address_payload},
|
||||
wallet::{ReceivedOutput, p2tr_script_buf},
|
||||
};
|
||||
|
||||
#[rustfmt::skip]
|
||||
@@ -61,7 +61,11 @@ pub struct SignableTransaction {
|
||||
}
|
||||
|
||||
impl SignableTransaction {
|
||||
fn calculate_weight(inputs: usize, payments: &[(Address, u64)], change: Option<&Address>) -> u64 {
|
||||
fn calculate_weight(
|
||||
inputs: usize,
|
||||
payments: &[(ScriptBuf, u64)],
|
||||
change: Option<&ScriptBuf>,
|
||||
) -> u64 {
|
||||
// Expand this a full transaction in order to use the bitcoin library's weight function
|
||||
let mut tx = Transaction {
|
||||
version: Version(2),
|
||||
@@ -86,14 +90,14 @@ impl SignableTransaction {
|
||||
// The script pub key is not of a fixed size and does have to be used here
|
||||
.map(|payment| TxOut {
|
||||
value: Amount::from_sat(payment.1),
|
||||
script_pubkey: payment.0.script_pubkey(),
|
||||
script_pubkey: payment.0.clone(),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
if let Some(change) = change {
|
||||
// Use a 0 value since we're currently unsure what the change amount will be, and since
|
||||
// the value is fixed size (so any value could be used here)
|
||||
tx.output.push(TxOut { value: Amount::ZERO, script_pubkey: change.script_pubkey() });
|
||||
tx.output.push(TxOut { value: Amount::ZERO, script_pubkey: change.clone() });
|
||||
}
|
||||
u64::from(tx.weight())
|
||||
}
|
||||
@@ -121,8 +125,8 @@ impl SignableTransaction {
|
||||
/// If data is specified, an OP_RETURN output will be added with it.
|
||||
pub fn new(
|
||||
mut inputs: Vec<ReceivedOutput>,
|
||||
payments: &[(Address, u64)],
|
||||
change: Option<&Address>,
|
||||
payments: &[(ScriptBuf, u64)],
|
||||
change: Option<ScriptBuf>,
|
||||
data: Option<Vec<u8>>,
|
||||
fee_per_weight: u64,
|
||||
) -> Result<SignableTransaction, TransactionError> {
|
||||
@@ -159,10 +163,7 @@ impl SignableTransaction {
|
||||
let payment_sat = payments.iter().map(|payment| payment.1).sum::<u64>();
|
||||
let mut tx_outs = payments
|
||||
.iter()
|
||||
.map(|payment| TxOut {
|
||||
value: Amount::from_sat(payment.1),
|
||||
script_pubkey: payment.0.script_pubkey(),
|
||||
})
|
||||
.map(|payment| TxOut { value: Amount::from_sat(payment.1), script_pubkey: payment.0.clone() })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Add the OP_RETURN output
|
||||
@@ -213,12 +214,11 @@ impl SignableTransaction {
|
||||
|
||||
// If there's a change address, check if there's change to give it
|
||||
if let Some(change) = change {
|
||||
let weight_with_change = Self::calculate_weight(tx_ins.len(), payments, Some(change));
|
||||
let weight_with_change = Self::calculate_weight(tx_ins.len(), payments, Some(&change));
|
||||
let fee_with_change = fee_per_weight * weight_with_change;
|
||||
if let Some(value) = input_sat.checked_sub(payment_sat + fee_with_change) {
|
||||
if value >= DUST {
|
||||
tx_outs
|
||||
.push(TxOut { value: Amount::from_sat(value), script_pubkey: change.script_pubkey() });
|
||||
tx_outs.push(TxOut { value: Amount::from_sat(value), script_pubkey: change });
|
||||
weight = weight_with_change;
|
||||
needed_fee = fee_with_change;
|
||||
}
|
||||
@@ -248,7 +248,7 @@ impl SignableTransaction {
|
||||
|
||||
/// Returns the TX ID of the transaction this will create.
|
||||
pub fn txid(&self) -> [u8; 32] {
|
||||
let mut res = self.tx.txid().to_byte_array();
|
||||
let mut res = self.tx.compute_txid().to_byte_array();
|
||||
res.reverse();
|
||||
res
|
||||
}
|
||||
@@ -288,7 +288,7 @@ impl SignableTransaction {
|
||||
transcript.append_message(b"signing_input", u32::try_from(i).unwrap().to_le_bytes());
|
||||
|
||||
let offset = keys.clone().offset(self.offsets[i]);
|
||||
if address_payload(offset.group_key())?.script_pubkey() != self.prevouts[i].script_pubkey {
|
||||
if p2tr_script_buf(offset.group_key())? != self.prevouts[i].script_pubkey {
|
||||
None?;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,10 @@ use bitcoin_serai::{
|
||||
hashes::Hash as HashTrait,
|
||||
blockdata::opcodes::all::OP_RETURN,
|
||||
script::{PushBytesBuf, Instruction, Instructions, Script},
|
||||
address::NetworkChecked,
|
||||
OutPoint, Amount, TxOut, Transaction, Network, Address,
|
||||
},
|
||||
wallet::{
|
||||
tweak_keys, address_payload, ReceivedOutput, Scanner, TransactionError, SignableTransaction,
|
||||
tweak_keys, p2tr_script_buf, ReceivedOutput, Scanner, TransactionError, SignableTransaction,
|
||||
},
|
||||
rpc::Rpc,
|
||||
};
|
||||
@@ -48,7 +47,7 @@ async fn send_and_get_output(rpc: &Rpc, scanner: &Scanner, key: ProjectivePoint)
|
||||
"generatetoaddress",
|
||||
serde_json::json!([
|
||||
1,
|
||||
Address::<NetworkChecked>::new(Network::Regtest, address_payload(key).unwrap())
|
||||
Address::from_script(&p2tr_script_buf(key).unwrap(), Network::Regtest).unwrap()
|
||||
]),
|
||||
)
|
||||
.await
|
||||
@@ -69,7 +68,7 @@ async fn send_and_get_output(rpc: &Rpc, scanner: &Scanner, key: ProjectivePoint)
|
||||
assert_eq!(outputs, scanner.scan_transaction(&block.txdata[0]));
|
||||
|
||||
assert_eq!(outputs.len(), 1);
|
||||
assert_eq!(outputs[0].outpoint(), &OutPoint::new(block.txdata[0].txid(), 0));
|
||||
assert_eq!(outputs[0].outpoint(), &OutPoint::new(block.txdata[0].compute_txid(), 0));
|
||||
assert_eq!(outputs[0].value(), block.txdata[0].output[0].value.to_sat());
|
||||
|
||||
assert_eq!(
|
||||
@@ -193,7 +192,7 @@ async_sequential! {
|
||||
assert_eq!(output.offset(), Scalar::ZERO);
|
||||
|
||||
let inputs = vec![output];
|
||||
let addr = || Address::<NetworkChecked>::new(Network::Regtest, address_payload(key).unwrap());
|
||||
let addr = || p2tr_script_buf(key).unwrap();
|
||||
let payments = vec![(addr(), 1000)];
|
||||
|
||||
assert!(SignableTransaction::new(inputs.clone(), &payments, None, None, FEE).is_ok());
|
||||
@@ -206,7 +205,7 @@ async_sequential! {
|
||||
// No change
|
||||
assert!(SignableTransaction::new(inputs.clone(), &[(addr(), 1000)], None, None, FEE).is_ok());
|
||||
// Consolidation TX
|
||||
assert!(SignableTransaction::new(inputs.clone(), &[], Some(&addr()), None, FEE).is_ok());
|
||||
assert!(SignableTransaction::new(inputs.clone(), &[], Some(addr()), None, FEE).is_ok());
|
||||
// Data
|
||||
assert!(SignableTransaction::new(inputs.clone(), &[], None, Some(vec![]), FEE).is_ok());
|
||||
// No outputs
|
||||
@@ -229,7 +228,7 @@ async_sequential! {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
SignableTransaction::new(inputs.clone(), &[], Some(&addr()), None, 0),
|
||||
SignableTransaction::new(inputs.clone(), &[], Some(addr()), None, 0),
|
||||
Err(TransactionError::TooLowFee),
|
||||
);
|
||||
|
||||
@@ -261,20 +260,19 @@ async_sequential! {
|
||||
|
||||
// Declare payments, change, fee
|
||||
let payments = [
|
||||
(Address::<NetworkChecked>::new(Network::Regtest, address_payload(key).unwrap()), 1005),
|
||||
(Address::<NetworkChecked>::new(Network::Regtest, address_payload(offset_key).unwrap()), 1007)
|
||||
(p2tr_script_buf(key).unwrap(), 1005),
|
||||
(p2tr_script_buf(offset_key).unwrap(), 1007)
|
||||
];
|
||||
|
||||
let change_offset = scanner.register_offset(Scalar::random(&mut OsRng)).unwrap();
|
||||
let change_key = key + (ProjectivePoint::GENERATOR * change_offset);
|
||||
let change_addr =
|
||||
Address::<NetworkChecked>::new(Network::Regtest, address_payload(change_key).unwrap());
|
||||
let change_addr = p2tr_script_buf(change_key).unwrap();
|
||||
|
||||
// Create and sign the TX
|
||||
let tx = SignableTransaction::new(
|
||||
vec![output.clone(), offset_output.clone()],
|
||||
&payments,
|
||||
Some(&change_addr),
|
||||
Some(change_addr.clone()),
|
||||
None,
|
||||
FEE
|
||||
).unwrap();
|
||||
@@ -287,7 +285,7 @@ async_sequential! {
|
||||
// Ensure we can scan it
|
||||
let outputs = scanner.scan_transaction(&tx);
|
||||
for (o, output) in outputs.iter().enumerate() {
|
||||
assert_eq!(output.outpoint(), &OutPoint::new(tx.txid(), u32::try_from(o).unwrap()));
|
||||
assert_eq!(output.outpoint(), &OutPoint::new(tx.compute_txid(), u32::try_from(o).unwrap()));
|
||||
assert_eq!(&ReceivedOutput::read::<&[u8]>(&mut output.serialize().as_ref()).unwrap(), output);
|
||||
}
|
||||
|
||||
@@ -299,7 +297,7 @@ async_sequential! {
|
||||
for ((output, scanned), payment) in tx.output.iter().zip(outputs.iter()).zip(payments.iter()) {
|
||||
assert_eq!(
|
||||
output,
|
||||
&TxOut { script_pubkey: payment.0.script_pubkey(), value: Amount::from_sat(payment.1) },
|
||||
&TxOut { script_pubkey: payment.0.clone(), value: Amount::from_sat(payment.1) },
|
||||
);
|
||||
assert_eq!(scanned.value(), payment.1 );
|
||||
}
|
||||
@@ -314,13 +312,13 @@ async_sequential! {
|
||||
input_value - payments.iter().map(|payment| payment.1).sum::<u64>() - needed_fee;
|
||||
assert_eq!(
|
||||
tx.output[2],
|
||||
TxOut { script_pubkey: change_addr.script_pubkey(), value: Amount::from_sat(change_amount) },
|
||||
TxOut { script_pubkey: change_addr, value: Amount::from_sat(change_amount) },
|
||||
);
|
||||
|
||||
// This also tests send_raw_transaction and get_transaction, which the RPC test can't
|
||||
// effectively test
|
||||
rpc.send_raw_transaction(&tx).await.unwrap();
|
||||
let mut hash = *tx.txid().as_raw_hash().as_byte_array();
|
||||
let mut hash = *tx.compute_txid().as_raw_hash().as_byte_array();
|
||||
hash.reverse();
|
||||
assert_eq!(tx, rpc.get_transaction(&hash).await.unwrap());
|
||||
assert_eq!(expected_id, hash);
|
||||
@@ -344,7 +342,7 @@ async_sequential! {
|
||||
&SignableTransaction::new(
|
||||
vec![output],
|
||||
&[],
|
||||
Some(&Address::<NetworkChecked>::new(Network::Regtest, address_payload(key).unwrap())),
|
||||
Some(p2tr_script_buf(key).unwrap()),
|
||||
Some(data.clone()),
|
||||
FEE
|
||||
).unwrap()
|
||||
|
||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/coins/ethereum"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>", "Elizabeth Binks <elizabethjbinks@gmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
rust-version = "1.74"
|
||||
rust-version = "1.79"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -29,21 +29,21 @@ frost = { package = "modular-frost", path = "../../crypto/frost", default-featur
|
||||
|
||||
alloy-core = { version = "0.7", default-features = false }
|
||||
alloy-sol-types = { version = "0.7", default-features = false, features = ["json"] }
|
||||
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false, features = ["k256"] }
|
||||
alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
|
||||
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
|
||||
alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
|
||||
alloy-consensus = { version = "0.1", default-features = false, features = ["k256"] }
|
||||
alloy-network = { version = "0.1", default-features = false }
|
||||
alloy-rpc-types-eth = { version = "0.1", default-features = false }
|
||||
alloy-rpc-client = { version = "0.1", default-features = false }
|
||||
alloy-simple-request-transport = { path = "./alloy-simple-request-transport", default-features = false }
|
||||
alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
|
||||
alloy-provider = { version = "0.1", default-features = false }
|
||||
|
||||
alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false, optional = true }
|
||||
alloy-node-bindings = { version = "0.1", default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false, features = ["tests"] }
|
||||
|
||||
tokio = { version = "1", features = ["macros"] }
|
||||
|
||||
alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
|
||||
alloy-node-bindings = { version = "0.1", default-features = false }
|
||||
|
||||
[features]
|
||||
tests = ["alloy-node-bindings"]
|
||||
tests = ["alloy-node-bindings", "frost/tests"]
|
||||
|
||||
@@ -21,8 +21,8 @@ tower = "0.4"
|
||||
serde_json = { version = "1", default-features = false }
|
||||
simple-request = { path = "../../../common/request", default-features = false }
|
||||
|
||||
alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
|
||||
alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
|
||||
alloy-json-rpc = { version = "0.1", default-features = false }
|
||||
alloy-transport = { version = "0.1", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["tls"]
|
||||
|
||||
30
coins/ethereum/relayer/Cargo.toml
Normal file
30
coins/ethereum/relayer/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "serai-ethereum-relayer"
|
||||
version = "0.1.0"
|
||||
description = "A relayer for Serai's Ethereum transactions"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/coins/ethereum/relayer"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = []
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
log = { version = "0.4", default-features = false, features = ["std"] }
|
||||
env_logger = { version = "0.10", default-features = false, features = ["humantime"] }
|
||||
|
||||
tokio = { version = "1", default-features = false, features = ["rt", "time", "io-util", "net", "macros"] }
|
||||
|
||||
serai-env = { path = "../../../common/env" }
|
||||
serai-db = { path = "../../../common/db" }
|
||||
|
||||
[features]
|
||||
parity-db = ["serai-db/parity-db"]
|
||||
rocksdb = ["serai-db/rocksdb"]
|
||||
15
coins/ethereum/relayer/LICENSE
Normal file
15
coins/ethereum/relayer/LICENSE
Normal file
@@ -0,0 +1,15 @@
|
||||
AGPL-3.0-only license
|
||||
|
||||
Copyright (c) 2023-2024 Luke Parker
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License Version 3 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
4
coins/ethereum/relayer/README.md
Normal file
4
coins/ethereum/relayer/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Ethereum Transaction Relayer
|
||||
|
||||
This server collects Ethereum router commands to be published, offering an RPC
|
||||
to fetch them.
|
||||
100
coins/ethereum/relayer/src/main.rs
Normal file
100
coins/ethereum/relayer/src/main.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
pub(crate) use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::TcpListener,
|
||||
};
|
||||
|
||||
use serai_db::{Get, DbTxn, Db as DbTrait};
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
// Override the panic handler with one which will panic if any tokio task panics
|
||||
{
|
||||
let existing = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |panic| {
|
||||
existing(panic);
|
||||
const MSG: &str = "exiting the process due to a task panicking";
|
||||
println!("{MSG}");
|
||||
log::error!("{MSG}");
|
||||
std::process::exit(1);
|
||||
}));
|
||||
}
|
||||
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
std::env::set_var("RUST_LOG", serai_env::var("RUST_LOG").unwrap_or_else(|| "info".to_string()));
|
||||
}
|
||||
env_logger::init();
|
||||
|
||||
log::info!("Starting Ethereum relayer server...");
|
||||
|
||||
// Open the DB
|
||||
#[allow(unused_variables, unreachable_code)]
|
||||
let db = {
|
||||
#[cfg(all(feature = "parity-db", feature = "rocksdb"))]
|
||||
panic!("built with parity-db and rocksdb");
|
||||
#[cfg(all(feature = "parity-db", not(feature = "rocksdb")))]
|
||||
let db =
|
||||
serai_db::new_parity_db(&serai_env::var("DB_PATH").expect("path to DB wasn't specified"));
|
||||
#[cfg(feature = "rocksdb")]
|
||||
let db =
|
||||
serai_db::new_rocksdb(&serai_env::var("DB_PATH").expect("path to DB wasn't specified"));
|
||||
db
|
||||
};
|
||||
|
||||
// Start command recipience server
|
||||
// This should not be publicly exposed
|
||||
// TODO: Add auth
|
||||
tokio::spawn({
|
||||
let db = db.clone();
|
||||
async move {
|
||||
// 5132 ^ ((b'E' << 8) | b'R')
|
||||
let server = TcpListener::bind("0.0.0.0:20830").await.unwrap();
|
||||
loop {
|
||||
let (mut socket, _) = server.accept().await.unwrap();
|
||||
let db = db.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut db = db.clone();
|
||||
loop {
|
||||
let Ok(msg_len) = socket.read_u32_le().await else { break };
|
||||
let mut buf = vec![0; usize::try_from(msg_len).unwrap()];
|
||||
let Ok(_) = socket.read_exact(&mut buf).await else { break };
|
||||
|
||||
if buf.len() < 5 {
|
||||
break;
|
||||
}
|
||||
let nonce = u32::from_le_bytes(buf[.. 4].try_into().unwrap());
|
||||
let mut txn = db.txn();
|
||||
txn.put(nonce.to_le_bytes(), &buf[4 ..]);
|
||||
txn.commit();
|
||||
|
||||
let Ok(()) = socket.write_all(&[1]).await else { break };
|
||||
|
||||
log::info!("received signed command #{nonce}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Start command fetch server
|
||||
// 5132 ^ ((b'E' << 8) | b'R') + 1
|
||||
let server = TcpListener::bind("0.0.0.0:20831").await.unwrap();
|
||||
loop {
|
||||
let (mut socket, _) = server.accept().await.unwrap();
|
||||
let db = db.clone();
|
||||
tokio::spawn(async move {
|
||||
let db = db.clone();
|
||||
loop {
|
||||
// Nonce to get the router comamnd for
|
||||
let mut buf = vec![0; 4];
|
||||
let Ok(_) = socket.read_exact(&mut buf).await else { break };
|
||||
|
||||
let command = db.get(&buf[.. 4]).unwrap_or(vec![]);
|
||||
let Ok(()) = socket.write_all(&u32::try_from(command.len()).unwrap().to_le_bytes()).await
|
||||
else {
|
||||
break;
|
||||
};
|
||||
let Ok(()) = socket.write_all(&command).await else { break };
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ use alloy_consensus::{Signed, TxLegacy};
|
||||
|
||||
use alloy_sol_types::{SolCall, SolEvent};
|
||||
|
||||
use alloy_rpc_types::{BlockNumberOrTag, Filter};
|
||||
use alloy_rpc_types_eth::{BlockNumberOrTag, Filter};
|
||||
use alloy_simple_request_transport::SimpleRequest;
|
||||
use alloy_provider::{Provider, RootProvider};
|
||||
|
||||
@@ -58,14 +58,7 @@ impl Deployer {
|
||||
/// Construct a new view of the `Deployer`.
|
||||
pub async fn new(provider: Arc<RootProvider<SimpleRequest>>) -> Result<Option<Self>, Error> {
|
||||
let address = Self::address();
|
||||
#[cfg(not(test))]
|
||||
let required_block = BlockNumberOrTag::Finalized;
|
||||
#[cfg(test)]
|
||||
let required_block = BlockNumberOrTag::Latest;
|
||||
let code = provider
|
||||
.get_code_at(address.into(), required_block.into())
|
||||
.await
|
||||
.map_err(|_| Error::ConnectionError)?;
|
||||
let code = provider.get_code_at(address.into()).await.map_err(|_| Error::ConnectionError)?;
|
||||
// Contract has yet to be deployed
|
||||
if code.is_empty() {
|
||||
return Ok(None);
|
||||
|
||||
@@ -4,7 +4,7 @@ use alloy_core::primitives::{Address, B256, U256};
|
||||
|
||||
use alloy_sol_types::{SolInterface, SolEvent};
|
||||
|
||||
use alloy_rpc_types::Filter;
|
||||
use alloy_rpc_types_eth::Filter;
|
||||
use alloy_simple_request_transport::SimpleRequest;
|
||||
use alloy_provider::{Provider, RootProvider};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ pub mod alloy {
|
||||
|
||||
pub use alloy_consensus as consensus;
|
||||
pub use alloy_network as network;
|
||||
pub use alloy_rpc_types as rpc_types;
|
||||
pub use alloy_rpc_types_eth as rpc_types;
|
||||
pub use alloy_simple_request_transport as simple_request_transport;
|
||||
pub use alloy_rpc_client as rpc_client;
|
||||
pub use alloy_provider as provider;
|
||||
|
||||
@@ -12,9 +12,9 @@ use alloy_consensus::TxLegacy;
|
||||
|
||||
use alloy_sol_types::{SolValue, SolConstructor, SolCall, SolEvent};
|
||||
|
||||
use alloy_rpc_types::Filter;
|
||||
use alloy_rpc_types_eth::Filter;
|
||||
#[cfg(test)]
|
||||
use alloy_rpc_types::{BlockId, TransactionRequest, TransactionInput};
|
||||
use alloy_rpc_types_eth::{BlockId, TransactionRequest, TransactionInput};
|
||||
use alloy_simple_request_transport::SimpleRequest;
|
||||
use alloy_provider::{Provider, RootProvider};
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use alloy_core::{
|
||||
};
|
||||
use alloy_consensus::{SignableTransaction, TxLegacy};
|
||||
|
||||
use alloy_rpc_types::{BlockNumberOrTag, TransactionReceipt};
|
||||
use alloy_rpc_types_eth::TransactionReceipt;
|
||||
use alloy_simple_request_transport::SimpleRequest;
|
||||
use alloy_provider::{Provider, RootProvider};
|
||||
|
||||
@@ -57,15 +57,14 @@ pub async fn send(
|
||||
// let chain_id = provider.get_chain_id().await.unwrap();
|
||||
// tx.chain_id = Some(chain_id);
|
||||
tx.chain_id = None;
|
||||
tx.nonce =
|
||||
provider.get_transaction_count(address, BlockNumberOrTag::Latest.into()).await.unwrap();
|
||||
tx.nonce = provider.get_transaction_count(address).await.unwrap();
|
||||
// 100 gwei
|
||||
tx.gas_price = 100_000_000_000u128;
|
||||
|
||||
let sig = wallet.sign_prehash_recoverable(tx.signature_hash().as_ref()).unwrap();
|
||||
assert_eq!(address, tx.clone().into_signed(sig.into()).recover_signer().unwrap());
|
||||
assert!(
|
||||
provider.get_balance(address, BlockNumberOrTag::Latest.into()).await.unwrap() >
|
||||
provider.get_balance(address).await.unwrap() >
|
||||
((U256::from(tx.gas_price) * U256::from(tx.gas_limit)) + tx.value)
|
||||
);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ use frost::{
|
||||
use alloy_core::primitives::{Address, U256};
|
||||
|
||||
use alloy_simple_request_transport::SimpleRequest;
|
||||
use alloy_rpc_types_eth::BlockTransactionsKind;
|
||||
use alloy_rpc_client::ClientBuilder;
|
||||
use alloy_provider::{Provider, RootProvider};
|
||||
|
||||
@@ -84,7 +85,7 @@ async fn setup_test() -> (
|
||||
|
||||
async fn latest_block_hash(client: &RootProvider<SimpleRequest>) -> [u8; 32] {
|
||||
client
|
||||
.get_block(client.get_block_number().await.unwrap().into(), false)
|
||||
.get_block(client.get_block_number().await.unwrap().into(), BlockTransactionsKind::Hashes)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
|
||||
@@ -15,7 +15,7 @@ use alloy_core::primitives::Address;
|
||||
|
||||
use alloy_sol_types::SolCall;
|
||||
|
||||
use alloy_rpc_types::{TransactionInput, TransactionRequest};
|
||||
use alloy_rpc_types_eth::{TransactionInput, TransactionRequest};
|
||||
use alloy_simple_request_transport::SimpleRequest;
|
||||
use alloy_rpc_client::ClientBuilder;
|
||||
use alloy_provider::{Provider, RootProvider};
|
||||
|
||||
@@ -6,7 +6,7 @@ license = "MIT"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.74"
|
||||
rust-version = "1.79"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -105,13 +105,13 @@ pub struct Metadata {
|
||||
/// but the payment ID will be returned here anyway:
|
||||
///
|
||||
/// 1) If the payment ID is tied to an output received by a subaddress account
|
||||
/// that spent Monero in the transaction (the received output is considered
|
||||
/// "change" and is not considered a "payment" in this case). If there are multiple
|
||||
/// spending subaddress accounts in a transaction, the highest index spent key image
|
||||
/// is used to determine the spending subaddress account.
|
||||
/// that spent Monero in the transaction (the received output is considered
|
||||
/// "change" and is not considered a "payment" in this case). If there are multiple
|
||||
/// spending subaddress accounts in a transaction, the highest index spent key image
|
||||
/// is used to determine the spending subaddress account.
|
||||
///
|
||||
/// 2) If the payment ID is the unencrypted variant and the block's hf version is
|
||||
/// v12 or higher (https://github.com/serai-dex/serai/issues/512)
|
||||
/// v12 or higher (https://github.com/serai-dex/serai/issues/512)
|
||||
pub payment_id: Option<PaymentId>,
|
||||
/// Arbitrary data encoded in TX extra.
|
||||
pub arbitrary_data: Vec<Vec<u8>>,
|
||||
|
||||
@@ -364,8 +364,8 @@ impl Change {
|
||||
/// 1) The change in the tx is shunted to the fee (fingerprintable fee).
|
||||
///
|
||||
/// 2) If there are 2 outputs in the tx, there would be no payment ID as is the case when the
|
||||
/// reference wallet creates 2 output txs, since monero-serai doesn't know which output
|
||||
/// to tie the dummy payment ID to.
|
||||
/// reference wallet creates 2 output txs, since monero-serai doesn't know which output
|
||||
/// to tie the dummy payment ID to.
|
||||
pub fn fingerprintable(address: Option<MoneroAddress>) -> Change {
|
||||
Change { address, view: None }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user